diff options
Diffstat (limited to 'pkg/tcpip/tests/integration')
-rw-r--r-- | pkg/tcpip/tests/integration/iptables_test.go | 469 |
1 files changed, 325 insertions, 144 deletions
diff --git a/pkg/tcpip/tests/integration/iptables_test.go b/pkg/tcpip/tests/integration/iptables_test.go index bdf4a64b9..7f872c271 100644 --- a/pkg/tcpip/tests/integration/iptables_test.go +++ b/pkg/tcpip/tests/integration/iptables_test.go @@ -1162,19 +1162,19 @@ func TestInputHookWithLocalForwarding(t *testing.T) { } } -func TestSNAT(t *testing.T) { - const listenPort = 8080 +func TestNAT(t *testing.T) { + const listenPort uint16 = 8080 type endpointAndAddresses struct { - serverEP tcpip.Endpoint - serverAddr tcpip.Address - serverReadableCH chan struct{} - - clientEP tcpip.Endpoint - clientAddr tcpip.Address - clientReadableCH chan struct{} - - nattedClientAddr tcpip.Address + serverEP tcpip.Endpoint + serverAddr tcpip.FullAddress + serverReadableCH chan struct{} + serverConnectAddr tcpip.Address + + clientEP tcpip.Endpoint + clientAddr tcpip.Address + clientReadableCH chan struct{} + clientConnectAddr tcpip.FullAddress } newEP := func(t *testing.T, s *stack.Stack, transProto tcpip.TransportProtocolNumber, netProto tcpip.NetworkProtocolNumber) (tcpip.Endpoint, chan struct{}) { @@ -1195,71 +1195,247 @@ func TestSNAT(t *testing.T) { return ep, ch } + setupNAT := func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, hook stack.Hook, filter stack.IPHeaderFilter, target stack.Target) { + t.Helper() + + ipv6 := netProto == ipv6.ProtocolNumber + ipt := s.IPTables() + table := ipt.GetTable(stack.NATID, ipv6) + ruleIdx := table.BuiltinChains[hook] + table.Rules[ruleIdx].Filter = filter + table.Rules[ruleIdx].Target = target + // Make sure the packet is not dropped by the next rule. + table.Rules[ruleIdx+1].Target = &stack.AcceptTarget{} + if err := ipt.ReplaceTable(stack.NATID, table, ipv6); err != nil { + t.Fatalf("ipt.ReplaceTable(%d, _, %t): %s", stack.NATID, ipv6, err) + } + } + + setupDNAT := func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, target stack.Target) { + t.Helper() + + setupNAT( + t, + s, + netProto, + stack.Prerouting, + stack.IPHeaderFilter{ + Protocol: transProto, + CheckProtocol: true, + InputInterface: utils.RouterNIC2Name, + }, + target) + } + + setupSNAT := func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, target stack.Target) { + t.Helper() + + setupNAT( + t, + s, + netProto, + stack.Postrouting, + stack.IPHeaderFilter{ + Protocol: transProto, + CheckProtocol: true, + OutputInterface: utils.RouterNIC1Name, + }, + target) + } + + type natType struct { + name string + setupNAT func(_ *testing.T, _ *stack.Stack, _ tcpip.NetworkProtocolNumber, _ tcpip.TransportProtocolNumber, snatAddr, dnatAddr tcpip.Address) + } + + snatTypes := []natType{ + { + name: "SNAT", + setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, snatAddr, _ tcpip.Address) { + t.Helper() + + setupSNAT(t, s, netProto, transProto, &stack.SNATTarget{NetworkProtocol: netProto, Addr: snatAddr}) + }, + }, + { + name: "Masquerade", + setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, _, _ tcpip.Address) { + t.Helper() + + setupSNAT(t, s, netProto, transProto, &stack.MasqueradeTarget{NetworkProtocol: netProto}) + }, + }, + } + dnatTypes := []natType{ + { + name: "Redirect", + setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, _, _ tcpip.Address) { + t.Helper() + + setupDNAT(t, s, netProto, transProto, &stack.RedirectTarget{NetworkProtocol: netProto, Port: listenPort}) + }, + }, + { + name: "DNAT", + setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, _, dnatAddr tcpip.Address) { + t.Helper() + + setupDNAT(t, s, netProto, transProto, &stack.DNATTarget{NetworkProtocol: netProto, Addr: dnatAddr, Port: listenPort}) + }, + }, + } + tests := []struct { - name string + name string + netProto tcpip.NetworkProtocolNumber + // Setups up the stacks in such a way that: + // + // - Host2 is the client for all tests. + // - Host1 is the server when performing SNAT + // + NAT will transform client-originating packets' source addresses to + // the router's NIC1's address before reaching Host1. + // - Router is the server when performing DNAT (client will still attempt to + // send packets to Host1). + // + NAT will transform client-originating packets' destination addresses + // to the router's NIC2's address. epAndAddrs func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses + natTypes []natType }{ { - name: "IPv4 host1 server with host2 client", + name: "IPv4 SNAT", + 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) + listenerStack := host1Stack + serverAddr := tcpip.FullAddress{ + Addr: utils.Host1IPv4Addr.AddressWithPrefix.Address, + Port: listenPort, } - - ep1, ep1WECH := newEP(t, host1Stack, proto, ipv4.ProtocolNumber) + serverConnectAddr := utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address + clientConnectPort := serverAddr.Port + ep1, ep1WECH := newEP(t, listenerStack, proto, ipv4.ProtocolNumber) ep2, ep2WECH := newEP(t, host2Stack, proto, ipv4.ProtocolNumber) return endpointAndAddresses{ - serverEP: ep1, - serverAddr: utils.Host1IPv4Addr.AddressWithPrefix.Address, - serverReadableCH: ep1WECH, + serverEP: ep1, + serverAddr: serverAddr, + serverReadableCH: ep1WECH, + serverConnectAddr: serverConnectAddr, clientEP: ep2, clientAddr: utils.Host2IPv4Addr.AddressWithPrefix.Address, clientReadableCH: ep2WECH, - - nattedClientAddr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address, + clientConnectAddr: tcpip.FullAddress{ + Addr: utils.Host1IPv4Addr.AddressWithPrefix.Address, + Port: clientConnectPort, + }, } }, + natTypes: snatTypes, }, { - name: "IPv6 host1 server with host2 client", + name: "IPv4 DNAT", + 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, 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) + // If we are performing DNAT, then the packet will be redirected + // to the router. + listenerStack := routerStack + serverAddr := tcpip.FullAddress{ + Addr: utils.RouterNIC2IPv4Addr.AddressWithPrefix.Address, + Port: listenPort, } + serverConnectAddr := utils.Host2IPv4Addr.AddressWithPrefix.Address + // DNAT will update the destination port to what the server is + // bound to. + clientConnectPort := serverAddr.Port + 1 + ep1, ep1WECH := newEP(t, listenerStack, proto, ipv4.ProtocolNumber) + ep2, ep2WECH := newEP(t, host2Stack, proto, ipv4.ProtocolNumber) + return endpointAndAddresses{ + serverEP: ep1, + serverAddr: serverAddr, + serverReadableCH: ep1WECH, + serverConnectAddr: serverConnectAddr, - ep1, ep1WECH := newEP(t, host1Stack, proto, ipv6.ProtocolNumber) + clientEP: ep2, + clientAddr: utils.Host2IPv4Addr.AddressWithPrefix.Address, + clientReadableCH: ep2WECH, + clientConnectAddr: tcpip.FullAddress{ + Addr: utils.Host1IPv4Addr.AddressWithPrefix.Address, + Port: clientConnectPort, + }, + } + }, + natTypes: dnatTypes, + }, + { + name: "IPv6 SNAT", + netProto: ipv6.ProtocolNumber, + epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses { + t.Helper() + + listenerStack := host1Stack + serverAddr := tcpip.FullAddress{ + Addr: utils.Host1IPv6Addr.AddressWithPrefix.Address, + Port: listenPort, + } + serverConnectAddr := utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address + clientConnectPort := serverAddr.Port + ep1, ep1WECH := newEP(t, listenerStack, proto, ipv6.ProtocolNumber) ep2, ep2WECH := newEP(t, host2Stack, proto, ipv6.ProtocolNumber) return endpointAndAddresses{ - serverEP: ep1, - serverAddr: utils.Host1IPv6Addr.AddressWithPrefix.Address, - serverReadableCH: ep1WECH, + serverEP: ep1, + serverAddr: serverAddr, + serverReadableCH: ep1WECH, + serverConnectAddr: serverConnectAddr, clientEP: ep2, clientAddr: utils.Host2IPv6Addr.AddressWithPrefix.Address, clientReadableCH: ep2WECH, + clientConnectAddr: tcpip.FullAddress{ + Addr: utils.Host1IPv6Addr.AddressWithPrefix.Address, + Port: clientConnectPort, + }, + } + }, + natTypes: snatTypes, + }, + { + name: "IPv6 DNAT", + netProto: ipv6.ProtocolNumber, + epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses { + t.Helper() + + // If we are performing DNAT, then the packet will be redirected + // to the router. + listenerStack := routerStack + serverAddr := tcpip.FullAddress{ + Addr: utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address, + Port: listenPort, + } + serverConnectAddr := utils.Host2IPv6Addr.AddressWithPrefix.Address + // DNAT will update the destination port to what the server is + // bound to. + clientConnectPort := serverAddr.Port + 1 + ep1, ep1WECH := newEP(t, listenerStack, proto, ipv6.ProtocolNumber) + ep2, ep2WECH := newEP(t, host2Stack, proto, ipv6.ProtocolNumber) + return endpointAndAddresses{ + serverEP: ep1, + serverAddr: serverAddr, + serverReadableCH: ep1WECH, + serverConnectAddr: serverConnectAddr, - nattedClientAddr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address, + clientEP: ep2, + clientAddr: utils.Host2IPv6Addr.AddressWithPrefix.Address, + clientReadableCH: ep2WECH, + clientConnectAddr: tcpip.FullAddress{ + Addr: utils.Host1IPv6Addr.AddressWithPrefix.Address, + Port: clientConnectPort, + }, } }, + natTypes: dnatTypes, }, } @@ -1328,116 +1504,121 @@ func TestSNAT(t *testing.T) { 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}, - } + for _, natType := range test.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}, + } - host1Stack := stack.New(stackOpts) - routerStack := stack.New(stackOpts) - host2Stack := stack.New(stackOpts) - utils.SetupRoutedStacks(t, host1Stack, routerStack, host2Stack) + 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) - } + epsAndAddrs := test.epAndAddrs(t, host1Stack, routerStack, host2Stack, subTest.proto) + natType.setupNAT(t, routerStack, test.netProto, subTest.proto, epsAndAddrs.serverConnectAddr, epsAndAddrs.serverAddr.Addr) - 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 err := epsAndAddrs.serverEP.Bind(epsAndAddrs.serverAddr); err != nil { + t.Fatalf("epsAndAddrs.serverEP.Bind(%#v): %s", epsAndAddrs.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) + } - serverEP := epsAndAddrs.serverEP - serverCH := epsAndAddrs.serverReadableCH - if ep, ch := subTest.setupServerConn(t, serverEP, serverCH, nattedClientAddr); ep != nil { - defer ep.Close() - serverEP = ep - serverCH = ch - } + if subTest.setupServer != nil { + subTest.setupServer(t, epsAndAddrs.serverEP) + } + { + err := epsAndAddrs.clientEP.Connect(epsAndAddrs.clientConnectAddr) + if diff := cmp.Diff(subTest.expectedConnectErr, err); diff != "" { + t.Fatalf("unexpected error from epsAndAddrs.clientEP.Connect(%#v), (-want, +got):\n%s", epsAndAddrs.clientConnectAddr, diff) + } + } + serverConnectAddr := tcpip.FullAddress{Addr: epsAndAddrs.serverConnectAddr} + if addr, err := epsAndAddrs.clientEP.GetLocalAddress(); err != nil { + t.Fatalf("epsAndAddrs.clientEP.GetLocalAddress(): %s", err) + } else { + serverConnectAddr.Port = addr.Port + } - 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) - } - } + serverEP := epsAndAddrs.serverEP + serverCH := epsAndAddrs.serverReadableCH + if ep, ch := subTest.setupServerConn(t, serverEP, serverCH, serverConnectAddr); ep != nil { + defer ep.Close() + serverEP = ep + serverCH = ch + } - 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 + 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) + } } - if err != nil { - t.Fatalf("ep.Read(_, %d, %#v): %s", len(data), opts, err) + + 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() + } } - 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{1, 2, 3, 4} + write(epsAndAddrs.clientEP, data) + read(serverCH, serverEP, data, serverConnectAddr) + } - { - data := []byte{5, 6, 7, 8, 9, 10, 11, 12} - write(serverEP, data) - read(epsAndAddrs.clientReadableCH, epsAndAddrs.clientEP, data, serverAddr) + { + data := []byte{5, 6, 7, 8, 9, 10, 11, 12} + write(serverEP, data) + read(epsAndAddrs.clientReadableCH, epsAndAddrs.clientEP, data, epsAndAddrs.clientConnectAddr) + } + }) } }) } |