Support naive Masquerade NAT target

* Does not accept a port range (Issue #5772).
* Does not support checking for tuple conflits (Issue #5773).

PiperOrigin-RevId: 399524088
This commit is contained in:
Ghanan Gowripalan
2021-09-28 14:07:35 -07:00
committed by gVisor bot
parent e251f6cc5c
commit ed083bac40
5 changed files with 225 additions and 159 deletions
+2 -2
View File
@@ -466,7 +466,7 @@ func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer, headerIn
// Postrouting NAT can only change the source address, and does not alter the
// route or outgoing interface of the packet.
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckPostrouting(pkt, r, outNicName); !ok {
if ok := e.protocol.stack.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
// iptables is telling us to drop the packet.
e.stats.ip.IPTablesPostroutingDropped.Increment()
return nil
@@ -576,7 +576,7 @@ func (e *endpoint) WritePackets(r *stack.Route, pkts stack.PacketBufferList, par
// We ignore the list of NAT-ed packets here because Postrouting NAT can only
// change the source address, and does not alter the route or outgoing
// interface of the packet.
postroutingDropped, _ := e.protocol.stack.IPTables().CheckPostroutingPackets(pkts, r, outNicName)
postroutingDropped, _ := e.protocol.stack.IPTables().CheckPostroutingPackets(pkts, r, e, outNicName)
stats.IPTablesPostroutingDropped.IncrementBy(uint64(len(postroutingDropped)))
for pkt := range postroutingDropped {
pkts.Remove(pkt)
+2 -2
View File
@@ -788,7 +788,7 @@ func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer, protocol
// Postrouting NAT can only change the source address, and does not alter the
// route or outgoing interface of the packet.
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
if ok := e.protocol.stack.IPTables().CheckPostrouting(pkt, r, outNicName); !ok {
if ok := e.protocol.stack.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
// iptables is telling us to drop the packet.
e.stats.ip.IPTablesPostroutingDropped.Increment()
return nil
@@ -897,7 +897,7 @@ func (e *endpoint) WritePackets(r *stack.Route, pkts stack.PacketBufferList, par
// We ignore the list of NAT-ed packets here because Postrouting NAT can only
// change the source address, and does not alter the route or outgoing
// interface of the packet.
postroutingDropped, _ := e.protocol.stack.IPTables().CheckPostroutingPackets(pkts, r, outNicName)
postroutingDropped, _ := e.protocol.stack.IPTables().CheckPostroutingPackets(pkts, r, e, outNicName)
stats.IPTablesPostroutingDropped.IncrementBy(uint64(len(postroutingDropped)))
for pkt := range postroutingDropped {
pkts.Remove(pkt)
+7 -7
View File
@@ -310,8 +310,8 @@ func (it *IPTables) CheckOutput(pkt *PacketBuffer, r *Route, outNicName string)
// must be dropped if false is returned.
//
// Precondition: The packet's network and transport header must be set.
func (it *IPTables) CheckPostrouting(pkt *PacketBuffer, r *Route, outNicName string) bool {
return it.check(Postrouting, pkt, r, nil /* addressEP */, "" /* inNicName */, outNicName)
func (it *IPTables) CheckPostrouting(pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, outNicName string) bool {
return it.check(Postrouting, pkt, r, addressEP, "" /* inNicName */, outNicName)
}
// check runs pkt through the rules for hook. It returns true when the packet
@@ -431,7 +431,7 @@ func (it *IPTables) startReaper(interval time.Duration) {
//
// Precondition: The packets' network and transport header must be set.
func (it *IPTables) CheckOutputPackets(pkts PacketBufferList, r *Route, outNicName string) (drop map[*PacketBuffer]struct{}, natPkts map[*PacketBuffer]struct{}) {
return it.checkPackets(Output, pkts, r, outNicName)
return it.checkPackets(Output, pkts, r, nil /* addressEP */, outNicName)
}
// CheckPostroutingPackets performs the postrouting hook on the packets.
@@ -439,8 +439,8 @@ func (it *IPTables) CheckOutputPackets(pkts PacketBufferList, r *Route, outNicNa
// Returns a map of packets that must be dropped.
//
// Precondition: The packets' network and transport header must be set.
func (it *IPTables) CheckPostroutingPackets(pkts PacketBufferList, r *Route, outNicName string) (drop map[*PacketBuffer]struct{}, natPkts map[*PacketBuffer]struct{}) {
return it.checkPackets(Postrouting, pkts, r, outNicName)
func (it *IPTables) CheckPostroutingPackets(pkts PacketBufferList, r *Route, addressEP AddressableEndpoint, outNicName string) (drop map[*PacketBuffer]struct{}, natPkts map[*PacketBuffer]struct{}) {
return it.checkPackets(Postrouting, pkts, r, addressEP, outNicName)
}
// checkPackets runs pkts through the rules for hook and returns a map of
@@ -450,10 +450,10 @@ func (it *IPTables) CheckPostroutingPackets(pkts PacketBufferList, r *Route, out
// dropped.
//
// Precondition: The packets' network and transport header must be set.
func (it *IPTables) checkPackets(hook Hook, pkts PacketBufferList, r *Route, outNicName string) (drop map[*PacketBuffer]struct{}, natPkts map[*PacketBuffer]struct{}) {
func (it *IPTables) checkPackets(hook Hook, pkts PacketBufferList, r *Route, addressEP AddressableEndpoint, outNicName string) (drop map[*PacketBuffer]struct{}, natPkts map[*PacketBuffer]struct{}) {
for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
if !pkt.NatDone {
if ok := it.check(hook, pkt, r, nil /* addressEP */, "" /* inNicName */, outNicName); !ok {
if ok := it.check(hook, pkt, r, addressEP, "" /* inNicName */, outNicName); !ok {
if drop == nil {
drop = make(map[*PacketBuffer]struct{})
}
+60 -20
View File
@@ -181,15 +181,7 @@ type SNATTarget struct {
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (st *SNATTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Route, _ AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if st.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
"SNATTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
st.NetworkProtocol, pkt.NetworkProtocolNumber))
}
func snatAction(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Route, port uint16, address tcpip.Address) (RuleVerdict, int) {
// Packet is already manipulated.
if pkt.NatDone {
return RuleAccept, 0
@@ -200,16 +192,8 @@ func (st *SNATTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Rou
return RuleDrop, 0
}
switch hook {
case Postrouting, Input:
case Prerouting, Output, Forward:
panic(fmt.Sprintf("%s not supported", hook))
default:
panic(fmt.Sprintf("%s unrecognized", hook))
}
port := st.Port
// TODO(https://gvisor.dev/issue/5773): If the port is in use, pick a
// different port.
if port == 0 {
switch protocol := pkt.TransportProtocolNumber; protocol {
case header.UDPProtocolNumber:
@@ -228,13 +212,69 @@ func (st *SNATTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Rou
// tracking.
//
// Does nothing if the protocol does not support connection tracking.
if conn := ct.insertSNATConn(pkt, hook, port, st.Addr); conn != nil {
if conn := ct.insertSNATConn(pkt, hook, port, address); conn != nil {
ct.handlePacket(pkt, hook, r)
}
return RuleAccept, 0
}
// Action implements Target.Action.
func (st *SNATTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Route, _ AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if st.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
"SNATTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
st.NetworkProtocol, pkt.NetworkProtocolNumber))
}
switch hook {
case Postrouting, Input:
case Prerouting, Output, Forward:
panic(fmt.Sprintf("%s not supported", hook))
default:
panic(fmt.Sprintf("%s unrecognized", hook))
}
return snatAction(pkt, ct, hook, r, st.Port, st.Addr)
}
// MasqueradeTarget modifies the source port/IP in the outgoing packets.
type MasqueradeTarget struct {
// NetworkProtocol is the network protocol the target is used with. It
// is immutable.
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (mt *MasqueradeTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if mt.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
"MasqueradeTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
mt.NetworkProtocol, pkt.NetworkProtocolNumber))
}
switch hook {
case Postrouting:
case Prerouting, Input, Forward, Output:
panic(fmt.Sprintf("masquerade target is supported only on postrouting hook; hook = %d", hook))
default:
panic(fmt.Sprintf("%s unrecognized", hook))
}
// addressEP is expected to be set for the postrouting hook.
ep := addressEP.AcquireOutgoingPrimaryAddress(pkt.Network().DestinationAddress(), false /* allowExpired */)
if ep == nil {
// No address exists that we can use as a source address.
return RuleDrop, 0
}
address := ep.AddressWithPrefix().Address
ep.DecRef()
return snatAction(pkt, ct, hook, r, 0 /* port */, address)
}
func rewritePacket(n header.Network, t header.ChecksummableTransport, updateSRCFields, fullChecksum, updatePseudoHeader bool, newPort uint16, newAddr tcpip.Address) {
if updateSRCFields {
if fullChecksum {
+154 -128
View File
@@ -1197,24 +1197,15 @@ func TestSNAT(t *testing.T) {
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
epAndAddrs func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses
}{
{
name: "IPv4 host1 server with host2 client",
name: "IPv4 host1 server with host2 client",
netProto: ipv4.ProtocolNumber,
epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
t.Helper()
ipt := routerStack.IPTables()
filter := ipt.GetTable(stack.NATID, false /* ipv6 */)
ruleIdx := filter.BuiltinChains[stack.Postrouting]
filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{OutputInterface: utils.RouterNIC1Name}
filter.Rules[ruleIdx].Target = &stack.SNATTarget{NetworkProtocol: ipv4.ProtocolNumber, Addr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address}
// Make sure the packet is not dropped by the next rule.
filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
if err := ipt.ReplaceTable(stack.NATID, filter, false /* ipv6 */); err != nil {
t.Fatalf("ipt.ReplaceTable(%d, _, %t): %s", stack.NATID, false, err)
}
ep1, ep1WECH := newEP(t, host1Stack, proto, ipv4.ProtocolNumber)
ep2, ep2WECH := newEP(t, host2Stack, proto, ipv4.ProtocolNumber)
return endpointAndAddresses{
@@ -1231,21 +1222,11 @@ func TestSNAT(t *testing.T) {
},
},
{
name: "IPv6 host1 server with host2 client",
name: "IPv6 host1 server with host2 client",
netProto: ipv6.ProtocolNumber,
epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
t.Helper()
ipt := routerStack.IPTables()
filter := ipt.GetTable(stack.NATID, true /* ipv6 */)
ruleIdx := filter.BuiltinChains[stack.Postrouting]
filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{OutputInterface: utils.RouterNIC1Name}
filter.Rules[ruleIdx].Target = &stack.SNATTarget{NetworkProtocol: ipv6.ProtocolNumber, Addr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address}
// Make sure the packet is not dropped by the next rule.
filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
if err := ipt.ReplaceTable(stack.NATID, filter, true /* ipv6 */); err != nil {
t.Fatalf("ipt.ReplaceTable(%d, _, %t): %s", stack.NATID, true, err)
}
ep1, ep1WECH := newEP(t, host1Stack, proto, ipv6.ProtocolNumber)
ep2, ep2WECH := newEP(t, host2Stack, proto, ipv6.ProtocolNumber)
return endpointAndAddresses{
@@ -1324,120 +1305,165 @@ func TestSNAT(t *testing.T) {
},
}
setupNAT := func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, target stack.Target) {
t.Helper()
ipv6 := netProto == ipv6.ProtocolNumber
ipt := s.IPTables()
filter := ipt.GetTable(stack.NATID, ipv6)
ruleIdx := filter.BuiltinChains[stack.Postrouting]
filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{OutputInterface: utils.RouterNIC1Name}
filter.Rules[ruleIdx].Target = target
// Make sure the packet is not dropped by the next rule.
filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
if err := ipt.ReplaceTable(stack.NATID, filter, ipv6); err != nil {
t.Fatalf("ipt.ReplaceTable(%d, _, %t): %s", stack.NATID, ipv6, err)
}
}
natTypes := []struct {
name string
setupNAT func(*testing.T, *stack.Stack, tcpip.NetworkProtocolNumber, tcpip.Address)
}{
{
name: "SNAT",
setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, natToAddr tcpip.Address) {
t.Helper()
setupNAT(t, s, netProto, &stack.SNATTarget{NetworkProtocol: netProto, Addr: natToAddr})
},
},
{
name: "Masquerade",
setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, natToAddr tcpip.Address) {
t.Helper()
setupNAT(t, s, netProto, &stack.MasqueradeTarget{NetworkProtocol: netProto})
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
for _, subTest := range subTests {
t.Run(subTest.name, func(t *testing.T) {
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
}
host1Stack := stack.New(stackOpts)
routerStack := stack.New(stackOpts)
host2Stack := stack.New(stackOpts)
utils.SetupRoutedStacks(t, host1Stack, routerStack, host2Stack)
epsAndAddrs := test.epAndAddrs(t, host1Stack, routerStack, host2Stack, subTest.proto)
serverAddr := tcpip.FullAddress{Addr: epsAndAddrs.serverAddr, Port: listenPort}
if err := epsAndAddrs.serverEP.Bind(serverAddr); err != nil {
t.Fatalf("epsAndAddrs.serverEP.Bind(%#v): %s", serverAddr, err)
}
clientAddr := tcpip.FullAddress{Addr: epsAndAddrs.clientAddr}
if err := epsAndAddrs.clientEP.Bind(clientAddr); err != nil {
t.Fatalf("epsAndAddrs.clientEP.Bind(%#v): %s", clientAddr, err)
}
if subTest.setupServer != nil {
subTest.setupServer(t, epsAndAddrs.serverEP)
}
{
err := epsAndAddrs.clientEP.Connect(serverAddr)
if diff := cmp.Diff(subTest.expectedConnectErr, err); diff != "" {
t.Fatalf("unexpected error from epsAndAddrs.clientEP.Connect(%#v), (-want, +got):\n%s", serverAddr, diff)
}
}
nattedClientAddr := tcpip.FullAddress{Addr: epsAndAddrs.nattedClientAddr}
if addr, err := epsAndAddrs.clientEP.GetLocalAddress(); err != nil {
t.Fatalf("epsAndAddrs.clientEP.GetLocalAddress(): %s", err)
} else {
nattedClientAddr.Port = addr.Port
}
serverEP := epsAndAddrs.serverEP
serverCH := epsAndAddrs.serverReadableCH
if ep, ch := subTest.setupServerConn(t, serverEP, serverCH, nattedClientAddr); ep != nil {
defer ep.Close()
serverEP = ep
serverCH = ch
}
write := func(ep tcpip.Endpoint, data []byte) {
t.Helper()
var r bytes.Reader
r.Reset(data)
var wOpts tcpip.WriteOptions
n, err := ep.Write(&r, wOpts)
if err != nil {
t.Fatalf("ep.Write(_, %#v): %s", wOpts, err)
}
if want := int64(len(data)); n != want {
t.Fatalf("got ep.Write(_, %#v) = (%d, _), want = (%d, _)", wOpts, n, want)
}
}
read := func(ch chan struct{}, ep tcpip.Endpoint, data []byte, expectedFrom tcpip.FullAddress) {
t.Helper()
var buf bytes.Buffer
var res tcpip.ReadResult
for {
var err tcpip.Error
opts := tcpip.ReadOptions{NeedRemoteAddr: subTest.needRemoteAddr}
res, err = ep.Read(&buf, opts)
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
<-ch
continue
for _, natType := range natTypes {
t.Run(natType.name, func(t *testing.T) {
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
}
if err != nil {
t.Fatalf("ep.Read(_, %d, %#v): %s", len(data), opts, err)
host1Stack := stack.New(stackOpts)
routerStack := stack.New(stackOpts)
host2Stack := stack.New(stackOpts)
utils.SetupRoutedStacks(t, host1Stack, routerStack, host2Stack)
epsAndAddrs := test.epAndAddrs(t, host1Stack, routerStack, host2Stack, subTest.proto)
natType.setupNAT(t, routerStack, test.netProto, epsAndAddrs.nattedClientAddr)
serverAddr := tcpip.FullAddress{Addr: epsAndAddrs.serverAddr, Port: listenPort}
if err := epsAndAddrs.serverEP.Bind(serverAddr); err != nil {
t.Fatalf("epsAndAddrs.serverEP.Bind(%#v): %s", serverAddr, err)
}
clientAddr := tcpip.FullAddress{Addr: epsAndAddrs.clientAddr}
if err := epsAndAddrs.clientEP.Bind(clientAddr); err != nil {
t.Fatalf("epsAndAddrs.clientEP.Bind(%#v): %s", clientAddr, err)
}
break
}
readResult := tcpip.ReadResult{
Count: len(data),
Total: len(data),
}
if subTest.needRemoteAddr {
readResult.RemoteAddr = expectedFrom
}
if diff := cmp.Diff(readResult, res, checker.IgnoreCmpPath(
"ControlMessages",
"RemoteAddr.NIC",
)); diff != "" {
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
}
if diff := cmp.Diff(buf.Bytes(), data); diff != "" {
t.Errorf("received data mismatch (-want +got):\n%s", diff)
}
if subTest.setupServer != nil {
subTest.setupServer(t, epsAndAddrs.serverEP)
}
{
err := epsAndAddrs.clientEP.Connect(serverAddr)
if diff := cmp.Diff(subTest.expectedConnectErr, err); diff != "" {
t.Fatalf("unexpected error from epsAndAddrs.clientEP.Connect(%#v), (-want, +got):\n%s", serverAddr, diff)
}
}
nattedClientAddr := tcpip.FullAddress{Addr: epsAndAddrs.nattedClientAddr}
if addr, err := epsAndAddrs.clientEP.GetLocalAddress(); err != nil {
t.Fatalf("epsAndAddrs.clientEP.GetLocalAddress(): %s", err)
} else {
nattedClientAddr.Port = addr.Port
}
if t.Failed() {
t.FailNow()
}
}
serverEP := epsAndAddrs.serverEP
serverCH := epsAndAddrs.serverReadableCH
if ep, ch := subTest.setupServerConn(t, serverEP, serverCH, nattedClientAddr); ep != nil {
defer ep.Close()
serverEP = ep
serverCH = ch
}
{
data := []byte{1, 2, 3, 4}
write(epsAndAddrs.clientEP, data)
read(serverCH, serverEP, data, nattedClientAddr)
}
write := func(ep tcpip.Endpoint, data []byte) {
t.Helper()
{
data := []byte{5, 6, 7, 8, 9, 10, 11, 12}
write(serverEP, data)
read(epsAndAddrs.clientReadableCH, epsAndAddrs.clientEP, data, serverAddr)
var r bytes.Reader
r.Reset(data)
var wOpts tcpip.WriteOptions
n, err := ep.Write(&r, wOpts)
if err != nil {
t.Fatalf("ep.Write(_, %#v): %s", wOpts, err)
}
if want := int64(len(data)); n != want {
t.Fatalf("got ep.Write(_, %#v) = (%d, _), want = (%d, _)", wOpts, n, want)
}
}
read := func(ch chan struct{}, ep tcpip.Endpoint, data []byte, expectedFrom tcpip.FullAddress) {
t.Helper()
var buf bytes.Buffer
var res tcpip.ReadResult
for {
var err tcpip.Error
opts := tcpip.ReadOptions{NeedRemoteAddr: subTest.needRemoteAddr}
res, err = ep.Read(&buf, opts)
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
<-ch
continue
}
if err != nil {
t.Fatalf("ep.Read(_, %d, %#v): %s", len(data), opts, err)
}
break
}
readResult := tcpip.ReadResult{
Count: len(data),
Total: len(data),
}
if subTest.needRemoteAddr {
readResult.RemoteAddr = expectedFrom
}
if diff := cmp.Diff(readResult, res, checker.IgnoreCmpPath(
"ControlMessages",
"RemoteAddr.NIC",
)); diff != "" {
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
}
if diff := cmp.Diff(buf.Bytes(), data); diff != "" {
t.Errorf("received data mismatch (-want +got):\n%s", diff)
}
if t.Failed() {
t.FailNow()
}
}
{
data := []byte{1, 2, 3, 4}
write(epsAndAddrs.clientEP, data)
read(serverCH, serverEP, data, nattedClientAddr)
}
{
data := []byte{5, 6, 7, 8, 9, 10, 11, 12}
write(serverEP, data)
read(epsAndAddrs.clientReadableCH, epsAndAddrs.clientEP, data, serverAddr)
}
})
}
})
}