diff options
Diffstat (limited to 'test/iptables')
-rw-r--r-- | test/iptables/BUILD | 41 | ||||
-rw-r--r-- | test/iptables/README.md | 84 | ||||
-rw-r--r-- | test/iptables/filter_input.go | 990 | ||||
-rw-r--r-- | test/iptables/filter_output.go | 714 | ||||
-rw-r--r-- | test/iptables/iptables.go | 115 | ||||
-rw-r--r-- | test/iptables/iptables_test.go | 458 | ||||
-rw-r--r-- | test/iptables/iptables_unsafe.go | 64 | ||||
-rw-r--r-- | test/iptables/iptables_util.go | 298 | ||||
-rw-r--r-- | test/iptables/nat.go | 917 | ||||
-rw-r--r-- | test/iptables/runner/BUILD | 12 | ||||
-rw-r--r-- | test/iptables/runner/main.go | 79 |
11 files changed, 0 insertions, 3772 deletions
diff --git a/test/iptables/BUILD b/test/iptables/BUILD deleted file mode 100644 index 94d4ca2d4..000000000 --- a/test/iptables/BUILD +++ /dev/null @@ -1,41 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "iptables", - testonly = 1, - srcs = [ - "filter_input.go", - "filter_output.go", - "iptables.go", - "iptables_unsafe.go", - "iptables_util.go", - "nat.go", - ], - visibility = ["//test/iptables:__subpackages__"], - deps = [ - "//pkg/binary", - "//pkg/test/testutil", - "//pkg/usermem", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "iptables_test", - size = "large", - srcs = [ - "iptables_test.go", - ], - data = ["//test/iptables/runner"], - library = ":iptables", - tags = [ - "local", - "manual", - ], - deps = [ - "//pkg/test/dockerutil", - "//pkg/test/testutil", - ], -) diff --git a/test/iptables/README.md b/test/iptables/README.md deleted file mode 100644 index 1196f8eb5..000000000 --- a/test/iptables/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# iptables Tests - -iptables tests are run via `make iptables-tests`. - -iptables require some extra Docker configuration to work. Enable IPv6 in -`/etc/docker/daemon.json` (make sure to restart Docker if you change this file): - -```json -{ - "experimental": true, - "fixed-cidr-v6": "2001:db8:1::/64", - "ipv6": true, - // Runtimes and other Docker config... -} -``` - -And if you're running manually (i.e. not using the `make` target), you'll need -to: - -* Enable iptables via `modprobe iptables_filter && modprobe ip6table_filter`. -* Enable `--net-raw` in your chosen runtime in `/etc/docker/daemon.json` (make - sure to restart Docker if you change this file). - -The resulting runtime should look something like this: - -```json -"runsc": { - "path": "/tmp/iptables/runsc", - "runtimeArgs": [ - "--debug-log", - "/tmp/iptables/logs/runsc.log.%TEST%.%TIMESTAMP%.%COMMAND%", - "--net-raw" - ] -}, -// ... -``` - -## Test Structure - -Each test implements `TestCase`, providing (1) a function to run inside the -container and (2) a function to run locally. Those processes are given each -others' IP addresses. The test succeeds when both functions succeed. - -The function inside the container (`ContainerAction`) typically sets some -iptables rules and then tries to send or receive packets. The local function -(`LocalAction`) will typically just send or receive packets. - -### Adding Tests - -1) Add your test to the `iptables` package. - -2) Register the test in an `init` function via `RegisterTestCase` (see -`filter_input.go` as an example). - -3) Add it to `iptables_test.go` (see the other tests in that file). - -Your test is now runnable with bazel! - -## Run individual tests - -Build and install `runsc`. Re-run this when you modify gVisor: - -```bash -$ bazel build //runsc && sudo cp bazel-bin/runsc/linux_amd64_pure_stripped/runsc $(which runsc) -``` - -Build the testing Docker container. Re-run this when you modify the test code in -this directory: - -```bash -$ make load-iptables -``` - -Run an individual test via: - -```bash -$ bazel test //test/iptables:iptables_test --test_filter=<TESTNAME> -``` - -To run an individual test with `runc`: - -```bash -$ bazel test //test/iptables:iptables_test --test_filter=<TESTNAME> --test_arg=--runtime=runc -``` diff --git a/test/iptables/filter_input.go b/test/iptables/filter_input.go deleted file mode 100644 index 4739bc06f..000000000 --- a/test/iptables/filter_input.go +++ /dev/null @@ -1,990 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iptables - -import ( - "context" - "errors" - "fmt" - "net" - "time" -) - -const ( - dropPort = 2401 - acceptPort = 2402 - sendloopDuration = 2 * time.Second - chainName = "foochain" -) - -func init() { - RegisterTestCase(&FilterInputDropAll{}) - RegisterTestCase(&FilterInputDropDifferentUDPPort{}) - RegisterTestCase(&FilterInputDropOnlyUDP{}) - RegisterTestCase(&FilterInputDropTCPDestPort{}) - RegisterTestCase(&FilterInputDropTCPSrcPort{}) - RegisterTestCase(&FilterInputDropUDPPort{}) - RegisterTestCase(&FilterInputDropUDP{}) - RegisterTestCase(&FilterInputCreateUserChain{}) - RegisterTestCase(&FilterInputDefaultPolicyAccept{}) - RegisterTestCase(&FilterInputDefaultPolicyDrop{}) - RegisterTestCase(&FilterInputReturnUnderflow{}) - RegisterTestCase(&FilterInputSerializeJump{}) - RegisterTestCase(&FilterInputJumpBasic{}) - RegisterTestCase(&FilterInputJumpReturn{}) - RegisterTestCase(&FilterInputJumpReturnDrop{}) - RegisterTestCase(&FilterInputJumpBuiltin{}) - RegisterTestCase(&FilterInputJumpTwice{}) - RegisterTestCase(&FilterInputDestination{}) - RegisterTestCase(&FilterInputInvertDestination{}) - RegisterTestCase(&FilterInputSource{}) - RegisterTestCase(&FilterInputInvertSource{}) - RegisterTestCase(&FilterInputInterfaceAccept{}) - RegisterTestCase(&FilterInputInterfaceDrop{}) - RegisterTestCase(&FilterInputInterface{}) - RegisterTestCase(&FilterInputInterfaceBeginsWith{}) - RegisterTestCase(&FilterInputInterfaceInvertDrop{}) - RegisterTestCase(&FilterInputInterfaceInvertAccept{}) -} - -// FilterInputDropUDP tests that we can drop UDP traffic. -type FilterInputDropUDP struct{ containerCase } - -var _ TestCase = (*FilterInputDropUDP)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropUDP) Name() string { - return "FilterInputDropUDP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil { - return err - } - - // Listen for UDP packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - // At this point we know that reading timed out and never received a - // packet. - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// FilterInputDropOnlyUDP tests that "-p udp -j DROP" only affects UDP traffic. -type FilterInputDropOnlyUDP struct{ baseCase } - -var _ TestCase = (*FilterInputDropOnlyUDP)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropOnlyUDP) Name() string { - return "FilterInputDropOnlyUDP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropOnlyUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil { - return err - } - - // Listen for a TCP connection, which should be allowed. - if err := listenTCP(ctx, acceptPort, ipv6); err != nil { - return fmt.Errorf("failed to establish a connection %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropOnlyUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Try to establish a TCP connection with the container, which should - // succeed. - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// FilterInputDropUDPPort tests that we can drop UDP traffic by port. -type FilterInputDropUDPPort struct{ containerCase } - -var _ TestCase = (*FilterInputDropUDPPort)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropUDPPort) Name() string { - return "FilterInputDropUDPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { - return err - } - - // Listen for UDP packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - // At this point we know that reading timed out and never received a - // packet. - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// FilterInputDropDifferentUDPPort tests that dropping traffic for a single UDP port -// doesn't drop packets on other ports. -type FilterInputDropDifferentUDPPort struct{ containerCase } - -var _ TestCase = (*FilterInputDropDifferentUDPPort)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropDifferentUDPPort) Name() string { - return "FilterInputDropDifferentUDPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropDifferentUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { - return err - } - - // Listen for UDP packets on another port. - if err := listenUDP(ctx, acceptPort, ipv6); err != nil { - return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", acceptPort, err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropDifferentUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputDropTCPDestPort tests that connections are not accepted on specified source ports. -type FilterInputDropTCPDestPort struct{ baseCase } - -var _ TestCase = (*FilterInputDropTCPDestPort)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropTCPDestPort) Name() string { - return "FilterInputDropTCPDestPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropTCPDestPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on drop port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropTCPDestPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Ensure we cannot connect to the container. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { - return fmt.Errorf("expected not to connect, but was able to connect on port %d", dropPort) - } - return nil -} - -// FilterInputDropTCPSrcPort tests that connections are not accepted on specified source ports. -type FilterInputDropTCPSrcPort struct{ baseCase } - -var _ TestCase = (*FilterInputDropTCPSrcPort)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropTCPSrcPort) Name() string { - return "FilterInputDropTCPSrcPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropTCPSrcPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Drop anything from an ephemeral port. - if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "-m", "tcp", "--sport", "1024:65535", "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but was", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropTCPSrcPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Ensure we cannot connect to the container. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { - return fmt.Errorf("expected not to connect, but was able to connect on port %d", acceptPort) - } - return nil -} - -// FilterInputDropAll tests that we can drop all traffic to the INPUT chain. -type FilterInputDropAll struct{ containerCase } - -var _ TestCase = (*FilterInputDropAll)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDropAll) Name() string { - return "FilterInputDropAll" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDropAll) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-j", "DROP"); err != nil { - return err - } - - // Listen for all packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets should have been dropped, but got a packet") - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - // At this point we know that reading timed out and never received a - // packet. - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDropAll) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// FilterInputMultiUDPRules verifies that multiple UDP rules are applied -// correctly. This has the added benefit of testing whether we're serializing -// rules correctly -- if we do it incorrectly, the iptables tool will -// misunderstand and save the wrong tables. -type FilterInputMultiUDPRules struct{ baseCase } - -var _ TestCase = (*FilterInputMultiUDPRules)(nil) - -// Name implements TestCase.Name. -func (*FilterInputMultiUDPRules) Name() string { - return "FilterInputMultiUDPRules" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputMultiUDPRules) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"}, - {"-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", acceptPort), "-j", "ACCEPT"}, - {"-L"}, - } - return filterTableRules(ipv6, rules) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputMultiUDPRules) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// FilterInputRequireProtocolUDP checks that "-m udp" requires "-p udp" to be -// specified. -type FilterInputRequireProtocolUDP struct{ baseCase } - -var _ TestCase = (*FilterInputRequireProtocolUDP)(nil) - -// Name implements TestCase.Name. -func (*FilterInputRequireProtocolUDP) Name() string { - return "FilterInputRequireProtocolUDP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputRequireProtocolUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err == nil { - return errors.New("expected iptables to fail with out \"-p udp\", but succeeded") - } - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputRequireProtocolUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// FilterInputCreateUserChain tests chain creation. -type FilterInputCreateUserChain struct{ baseCase } - -var _ TestCase = (*FilterInputCreateUserChain)(nil) - -// Name implements TestCase.Name. -func (*FilterInputCreateUserChain) Name() string { - return "FilterInputCreateUserChain" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputCreateUserChain) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - // Create a chain. - {"-N", chainName}, - // Add a simple rule to the chain. - {"-A", chainName, "-j", "DROP"}, - } - return filterTableRules(ipv6, rules) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputCreateUserChain) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// FilterInputDefaultPolicyAccept tests the default ACCEPT policy. -type FilterInputDefaultPolicyAccept struct{ containerCase } - -var _ TestCase = (*FilterInputDefaultPolicyAccept)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDefaultPolicyAccept) Name() string { - return "FilterInputDefaultPolicyAccept" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDefaultPolicyAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Set the default policy to accept, then receive a packet. - if err := filterTable(ipv6, "-P", "INPUT", "ACCEPT"); err != nil { - return err - } - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDefaultPolicyAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputDefaultPolicyDrop tests the default DROP policy. -type FilterInputDefaultPolicyDrop struct{ containerCase } - -var _ TestCase = (*FilterInputDefaultPolicyDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDefaultPolicyDrop) Name() string { - return "FilterInputDefaultPolicyDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDefaultPolicyDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-P", "INPUT", "DROP"); err != nil { - return err - } - - // Listen for UDP packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - // At this point we know that reading timed out and never received a - // packet. - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDefaultPolicyDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputReturnUnderflow tests that -j RETURN in a built-in chain causes -// the underflow rule (i.e. default policy) to be executed. -type FilterInputReturnUnderflow struct{ containerCase } - -var _ TestCase = (*FilterInputReturnUnderflow)(nil) - -// Name implements TestCase.Name. -func (*FilterInputReturnUnderflow) Name() string { - return "FilterInputReturnUnderflow" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputReturnUnderflow) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Add a RETURN rule followed by an unconditional accept, and set the - // default policy to DROP. - rules := [][]string{ - {"-A", "INPUT", "-j", "RETURN"}, - {"-A", "INPUT", "-j", "DROP"}, - {"-P", "INPUT", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // We should receive packets, as the RETURN rule will trigger the default - // ACCEPT policy. - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputReturnUnderflow) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputSerializeJump verifies that we can serialize jumps. -type FilterInputSerializeJump struct{ baseCase } - -var _ TestCase = (*FilterInputSerializeJump)(nil) - -// Name implements TestCase.Name. -func (*FilterInputSerializeJump) Name() string { - return "FilterInputSerializeJump" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputSerializeJump) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Write a JUMP rule, the serialize it with `-L`. - rules := [][]string{ - {"-N", chainName}, - {"-A", "INPUT", "-j", chainName}, - {"-L"}, - } - return filterTableRules(ipv6, rules) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputSerializeJump) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// FilterInputJumpBasic jumps to a chain and executes a rule there. -type FilterInputJumpBasic struct{ containerCase } - -var _ TestCase = (*FilterInputJumpBasic)(nil) - -// Name implements TestCase.Name. -func (*FilterInputJumpBasic) Name() string { - return "FilterInputJumpBasic" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputJumpBasic) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-P", "INPUT", "DROP"}, - {"-N", chainName}, - {"-A", "INPUT", "-j", chainName}, - {"-A", chainName, "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for UDP packets on acceptPort. - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputJumpBasic) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputJumpReturn jumps, returns, and executes a rule. -type FilterInputJumpReturn struct{ containerCase } - -var _ TestCase = (*FilterInputJumpReturn)(nil) - -// Name implements TestCase.Name. -func (*FilterInputJumpReturn) Name() string { - return "FilterInputJumpReturn" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputJumpReturn) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-N", chainName}, - {"-P", "INPUT", "ACCEPT"}, - {"-A", "INPUT", "-j", chainName}, - {"-A", chainName, "-j", "RETURN"}, - {"-A", chainName, "-j", "DROP"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for UDP packets on acceptPort. - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputJumpReturn) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputJumpReturnDrop jumps to a chain, returns, and DROPs packets. -type FilterInputJumpReturnDrop struct{ containerCase } - -var _ TestCase = (*FilterInputJumpReturnDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterInputJumpReturnDrop) Name() string { - return "FilterInputJumpReturnDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputJumpReturnDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-N", chainName}, - {"-A", "INPUT", "-j", chainName}, - {"-A", "INPUT", "-j", "DROP"}, - {"-A", chainName, "-j", "RETURN"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for UDP packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - // At this point we know that reading timed out and never received a - // packet. - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputJumpReturnDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// FilterInputJumpBuiltin verifies that jumping to a top-levl chain is illegal. -type FilterInputJumpBuiltin struct{ baseCase } - -var _ TestCase = (*FilterInputJumpBuiltin)(nil) - -// Name implements TestCase.Name. -func (*FilterInputJumpBuiltin) Name() string { - return "FilterInputJumpBuiltin" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputJumpBuiltin) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-j", "OUTPUT"); err == nil { - return fmt.Errorf("iptables should be unable to jump to a built-in chain") - } - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputJumpBuiltin) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// FilterInputJumpTwice jumps twice, then returns twice and executes a rule. -type FilterInputJumpTwice struct{ containerCase } - -var _ TestCase = (*FilterInputJumpTwice)(nil) - -// Name implements TestCase.Name. -func (*FilterInputJumpTwice) Name() string { - return "FilterInputJumpTwice" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputJumpTwice) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - const chainName2 = chainName + "2" - rules := [][]string{ - {"-P", "INPUT", "DROP"}, - {"-N", chainName}, - {"-N", chainName2}, - {"-A", "INPUT", "-j", chainName}, - {"-A", chainName, "-j", chainName2}, - {"-A", "INPUT", "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // UDP packets should jump and return twice, eventually hitting the - // ACCEPT rule. - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputJumpTwice) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputDestination verifies that we can filter packets via `-d -// <ipaddr>`. -type FilterInputDestination struct{ containerCase } - -var _ TestCase = (*FilterInputDestination)(nil) - -// Name implements TestCase.Name. -func (*FilterInputDestination) Name() string { - return "FilterInputDestination" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - addrs, err := localAddrs(ipv6) - if err != nil { - return err - } - - // Make INPUT's default action DROP, then ACCEPT all packets bound for - // this machine. - rules := [][]string{{"-P", "INPUT", "DROP"}} - for _, addr := range addrs { - rules = append(rules, []string{"-A", "INPUT", "-d", addr, "-j", "ACCEPT"}) - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInvertDestination verifies that we can filter packets via `! -d -// <ipaddr>`. -type FilterInputInvertDestination struct{ containerCase } - -var _ TestCase = (*FilterInputInvertDestination)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInvertDestination) Name() string { - return "FilterInputInvertDestination" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInvertDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Make INPUT's default action DROP, then ACCEPT all packets not bound - // for 127.0.0.1. - rules := [][]string{ - {"-P", "INPUT", "DROP"}, - {"-A", "INPUT", "!", "-d", localIP(ipv6), "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInvertDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputSource verifies that we can filter packets via `-s -// <ipaddr>`. -type FilterInputSource struct{ containerCase } - -var _ TestCase = (*FilterInputSource)(nil) - -// Name implements TestCase.Name. -func (*FilterInputSource) Name() string { - return "FilterInputSource" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputSource) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Make INPUT's default action DROP, then ACCEPT all packets from this - // machine. - rules := [][]string{ - {"-P", "INPUT", "DROP"}, - {"-A", "INPUT", "-s", fmt.Sprintf("%v", ip), "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputSource) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInvertSource verifies that we can filter packets via `! -s -// <ipaddr>`. -type FilterInputInvertSource struct{ containerCase } - -var _ TestCase = (*FilterInputInvertSource)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInvertSource) Name() string { - return "FilterInputInvertSource" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInvertSource) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Make INPUT's default action DROP, then ACCEPT all packets not bound - // for 127.0.0.1. - rules := [][]string{ - {"-P", "INPUT", "DROP"}, - {"-A", "INPUT", "!", "-s", localIP(ipv6), "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInvertSource) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInterfaceAccept tests that packets are accepted from interface -// matching the iptables rule. -type FilterInputInterfaceAccept struct{ localCase } - -var _ TestCase = (*FilterInputInterfaceAccept)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterfaceAccept) Name() string { - return "FilterInputInterfaceAccept" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterfaceAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - ifname, ok := getInterfaceName() - if !ok { - return fmt.Errorf("no interface is present, except loopback") - } - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", ifname, "-j", "ACCEPT"); err != nil { - return err - } - if err := listenUDP(ctx, acceptPort, ipv6); err != nil { - return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %w", acceptPort, err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterfaceAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInterfaceDrop tests that packets are dropped from interface -// matching the iptables rule. -type FilterInputInterfaceDrop struct{ localCase } - -var _ TestCase = (*FilterInputInterfaceDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterfaceDrop) Name() string { - return "FilterInputInterfaceDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterfaceDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - ifname, ok := getInterfaceName() - if !ok { - return fmt.Errorf("no interface is present, except loopback") - } - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", ifname, "-j", "DROP"); err != nil { - return err - } - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, acceptPort, ipv6); err != nil { - if errors.Is(err, context.DeadlineExceeded) { - return nil - } - return fmt.Errorf("error reading: %w", err) - } - return fmt.Errorf("packets should have been dropped, but got a packet") -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterfaceDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInterface tests that packets are not dropped from interface which -// is not matching the interface name in the iptables rule. -type FilterInputInterface struct{ localCase } - -var _ TestCase = (*FilterInputInterface)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterface) Name() string { - return "FilterInputInterface" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterface) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", "lo", "-j", "DROP"); err != nil { - return err - } - if err := listenUDP(ctx, acceptPort, ipv6); err != nil { - return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %w", acceptPort, err) - } - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterface) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInterfaceBeginsWith tests that packets are dropped from an -// interface which begins with the given interface name. -type FilterInputInterfaceBeginsWith struct{ localCase } - -var _ TestCase = (*FilterInputInterfaceBeginsWith)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterfaceBeginsWith) Name() string { - return "FilterInputInterfaceBeginsWith" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterfaceBeginsWith) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", "e+", "-j", "DROP"); err != nil { - return err - } - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, acceptPort, ipv6); err != nil { - if errors.Is(err, context.DeadlineExceeded) { - return nil - } - return fmt.Errorf("error reading: %w", err) - } - return fmt.Errorf("packets should have been dropped, but got a packet") -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterfaceBeginsWith) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// FilterInputInterfaceInvertDrop tests that we selectively drop packets from -// interface not matching the interface name. -type FilterInputInterfaceInvertDrop struct{ baseCase } - -var _ TestCase = (*FilterInputInterfaceInvertDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterfaceInvertDrop) Name() string { - return "FilterInputInterfaceInvertDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterfaceInvertDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "-i", "lo", "-j", "DROP"); err != nil { - return err - } - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err != nil { - if errors.Is(err, context.DeadlineExceeded) { - return nil - } - return fmt.Errorf("error reading: %w", err) - } - return fmt.Errorf("connection on port %d should not be accepted, but was accepted", acceptPort) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterfaceInvertDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err != nil { - var operr *net.OpError - if errors.As(err, &operr) && operr.Timeout() { - return nil - } - return fmt.Errorf("error connecting: %w", err) - } - return fmt.Errorf("connection destined to port %d should not be accepted, but was accepted", acceptPort) -} - -// FilterInputInterfaceInvertAccept tests that we can selectively accept packets -// not matching the specific incoming interface. -type FilterInputInterfaceInvertAccept struct{ baseCase } - -var _ TestCase = (*FilterInputInterfaceInvertAccept)(nil) - -// Name implements TestCase.Name. -func (*FilterInputInterfaceInvertAccept) Name() string { - return "FilterInputInterfaceInvertAccept" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterInputInterfaceInvertAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "-i", "lo", "-j", "ACCEPT"); err != nil { - return err - } - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterInputInterfaceInvertAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} diff --git a/test/iptables/filter_output.go b/test/iptables/filter_output.go deleted file mode 100644 index bcb2a3b70..000000000 --- a/test/iptables/filter_output.go +++ /dev/null @@ -1,714 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iptables - -import ( - "context" - "errors" - "fmt" - "net" -) - -func init() { - RegisterTestCase(&FilterOutputDropTCPDestPort{}) - RegisterTestCase(&FilterOutputDropTCPSrcPort{}) - RegisterTestCase(&FilterOutputDestination{}) - RegisterTestCase(&FilterOutputInvertDestination{}) - RegisterTestCase(&FilterOutputAcceptTCPOwner{}) - RegisterTestCase(&FilterOutputDropTCPOwner{}) - RegisterTestCase(&FilterOutputAcceptUDPOwner{}) - RegisterTestCase(&FilterOutputDropUDPOwner{}) - RegisterTestCase(&FilterOutputOwnerFail{}) - RegisterTestCase(&FilterOutputAcceptGIDOwner{}) - RegisterTestCase(&FilterOutputDropGIDOwner{}) - RegisterTestCase(&FilterOutputInvertGIDOwner{}) - RegisterTestCase(&FilterOutputInvertUIDOwner{}) - RegisterTestCase(&FilterOutputInvertUIDAndGIDOwner{}) - RegisterTestCase(&FilterOutputInterfaceAccept{}) - RegisterTestCase(&FilterOutputInterfaceDrop{}) - RegisterTestCase(&FilterOutputInterface{}) - RegisterTestCase(&FilterOutputInterfaceBeginsWith{}) - RegisterTestCase(&FilterOutputInterfaceInvertDrop{}) - RegisterTestCase(&FilterOutputInterfaceInvertAccept{}) -} - -// FilterOutputDropTCPDestPort tests that connections are not accepted on -// specified source ports. -type FilterOutputDropTCPDestPort struct{ baseCase } - -var _ TestCase = (*FilterOutputDropTCPDestPort)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDropTCPDestPort) Name() string { - return "FilterOutputDropTCPDestPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDropTCPDestPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", "1024:65535", "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDropTCPDestPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", dropPort) - } - - return nil -} - -// FilterOutputDropTCPSrcPort tests that connections are not accepted on -// specified source ports. -type FilterOutputDropTCPSrcPort struct{ baseCase } - -var _ TestCase = (*FilterOutputDropTCPSrcPort)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDropTCPSrcPort) Name() string { - return "FilterOutputDropTCPSrcPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDropTCPSrcPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--sport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on drop port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", dropPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDropTCPSrcPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, dropPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", dropPort) - } - - return nil -} - -// FilterOutputAcceptTCPOwner tests that TCP connections from uid owner are accepted. -type FilterOutputAcceptTCPOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputAcceptTCPOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputAcceptTCPOwner) Name() string { - return "FilterOutputAcceptTCPOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputAcceptTCPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--uid-owner", "root", "-j", "ACCEPT"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputAcceptTCPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// FilterOutputDropTCPOwner tests that TCP connections from uid owner are dropped. -type FilterOutputDropTCPOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputDropTCPOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDropTCPOwner) Name() string { - return "FilterOutputDropTCPOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDropTCPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--uid-owner", "root", "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should be dropped, but got accepted", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDropTCPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should be dropped, but got accepted", acceptPort) - } - - return nil -} - -// FilterOutputAcceptUDPOwner tests that UDP packets from uid owner are accepted. -type FilterOutputAcceptUDPOwner struct{ localCase } - -var _ TestCase = (*FilterOutputAcceptUDPOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputAcceptUDPOwner) Name() string { - return "FilterOutputAcceptUDPOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputAcceptUDPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "--uid-owner", "root", "-j", "ACCEPT"); err != nil { - return err - } - - // Send UDP packets on acceptPort. - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputAcceptUDPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Listen for UDP packets on acceptPort. - return listenUDP(ctx, acceptPort, ipv6) -} - -// FilterOutputDropUDPOwner tests that UDP packets from uid owner are dropped. -type FilterOutputDropUDPOwner struct{ localCase } - -var _ TestCase = (*FilterOutputDropUDPOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDropUDPOwner) Name() string { - return "FilterOutputDropUDPOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDropUDPOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "--uid-owner", "root", "-j", "DROP"); err != nil { - return err - } - - // Send UDP packets on dropPort. - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDropUDPOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Listen for UDP packets on dropPort. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, dropPort, ipv6); err == nil { - return fmt.Errorf("packets should not be received") - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// FilterOutputOwnerFail tests that without uid/gid option, owner rule -// will fail. -type FilterOutputOwnerFail struct{ baseCase } - -var _ TestCase = (*FilterOutputOwnerFail)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputOwnerFail) Name() string { - return "FilterOutputOwnerFail" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputOwnerFail) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "-j", "ACCEPT"); err == nil { - return fmt.Errorf("invalid argument") - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputOwnerFail) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // no-op. - return nil -} - -// FilterOutputAcceptGIDOwner tests that TCP connections from gid owner are accepted. -type FilterOutputAcceptGIDOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputAcceptGIDOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputAcceptGIDOwner) Name() string { - return "FilterOutputAcceptGIDOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputAcceptGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--gid-owner", "root", "-j", "ACCEPT"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputAcceptGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// FilterOutputDropGIDOwner tests that TCP connections from gid owner are dropped. -type FilterOutputDropGIDOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputDropGIDOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDropGIDOwner) Name() string { - return "FilterOutputDropGIDOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDropGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--gid-owner", "root", "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDropGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) - } - - return nil -} - -// FilterOutputInvertGIDOwner tests that TCP connections from gid owner are dropped. -type FilterOutputInvertGIDOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputInvertGIDOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInvertGIDOwner) Name() string { - return "FilterOutputInvertGIDOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInvertGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--gid-owner", "root", "-j", "ACCEPT"}, - {"-A", "OUTPUT", "-p", "tcp", "-j", "DROP"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInvertGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) - } - - return nil -} - -// FilterOutputInvertUIDOwner tests that TCP connections from gid owner are dropped. -type FilterOutputInvertUIDOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputInvertUIDOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInvertUIDOwner) Name() string { - return "FilterOutputInvertUIDOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInvertUIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--uid-owner", "root", "-j", "DROP"}, - {"-A", "OUTPUT", "-p", "tcp", "-j", "ACCEPT"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for TCP packets on accept port. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInvertUIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// FilterOutputInvertUIDAndGIDOwner tests that TCP connections from uid and gid -// owner are dropped. -type FilterOutputInvertUIDAndGIDOwner struct{ baseCase } - -var _ TestCase = (*FilterOutputInvertUIDAndGIDOwner)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInvertUIDAndGIDOwner) Name() string { - return "FilterOutputInvertUIDAndGIDOwner" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInvertUIDAndGIDOwner) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--uid-owner", "root", "!", "--gid-owner", "root", "-j", "ACCEPT"}, - {"-A", "OUTPUT", "-p", "tcp", "-j", "DROP"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInvertUIDAndGIDOwner) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) - } - - return nil -} - -// FilterOutputDestination tests that we can selectively allow packets to -// certain destinations. -type FilterOutputDestination struct{ localCase } - -var _ TestCase = (*FilterOutputDestination)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputDestination) Name() string { - return "FilterOutputDestination" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - var rules [][]string - if ipv6 { - rules = [][]string{ - {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"}, - // Allow solicited node multicast addresses so we can send neighbor - // solicitations. - {"-A", "OUTPUT", "-d", "ff02::1:ff00:0/104", "-j", "ACCEPT"}, - {"-P", "OUTPUT", "DROP"}, - } - } else { - rules = [][]string{ - {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"}, - {"-P", "OUTPUT", "DROP"}, - } - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenUDP(ctx, acceptPort, ipv6) -} - -// FilterOutputInvertDestination tests that we can selectively allow packets -// not headed for a particular destination. -type FilterOutputInvertDestination struct{ localCase } - -var _ TestCase = (*FilterOutputInvertDestination)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInvertDestination) Name() string { - return "FilterOutputInvertDestination" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInvertDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - rules := [][]string{ - {"-A", "OUTPUT", "!", "-d", localIP(ipv6), "-j", "ACCEPT"}, - {"-P", "OUTPUT", "DROP"}, - } - if err := filterTableRules(ipv6, rules); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInvertDestination) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenUDP(ctx, acceptPort, ipv6) -} - -// FilterOutputInterfaceAccept tests that packets are sent via interface -// matching the iptables rule. -type FilterOutputInterfaceAccept struct{ localCase } - -var _ TestCase = (*FilterOutputInterfaceAccept)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterfaceAccept) Name() string { - return "FilterOutputInterfaceAccept" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterfaceAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - ifname, ok := getInterfaceName() - if !ok { - return fmt.Errorf("no interface is present, except loopback") - } - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", ifname, "-j", "ACCEPT"); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterfaceAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenUDP(ctx, acceptPort, ipv6) -} - -// FilterOutputInterfaceDrop tests that packets are not sent via interface -// matching the iptables rule. -type FilterOutputInterfaceDrop struct{ localCase } - -var _ TestCase = (*FilterOutputInterfaceDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterfaceDrop) Name() string { - return "FilterOutputInterfaceDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterfaceDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - ifname, ok := getInterfaceName() - if !ok { - return fmt.Errorf("no interface is present, except loopback") - } - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", ifname, "-j", "DROP"); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterfaceDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("packets should not be received on port %v, but are received", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// FilterOutputInterface tests that packets are sent via interface which is -// not matching the interface name in the iptables rule. -type FilterOutputInterface struct{ localCase } - -var _ TestCase = (*FilterOutputInterface)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterface) Name() string { - return "FilterOutputInterface" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterface) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", "lo", "-j", "DROP"); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterface) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenUDP(ctx, acceptPort, ipv6) -} - -// FilterOutputInterfaceBeginsWith tests that packets are not sent via an -// interface which begins with the given interface name. -type FilterOutputInterfaceBeginsWith struct{ localCase } - -var _ TestCase = (*FilterOutputInterfaceBeginsWith)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterfaceBeginsWith) Name() string { - return "FilterOutputInterfaceBeginsWith" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterfaceBeginsWith) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", "e+", "-j", "DROP"); err != nil { - return err - } - - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterfaceBeginsWith) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("packets should not be received on port %v, but are received", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// FilterOutputInterfaceInvertDrop tests that we selectively do not send -// packets via interface not matching the interface name. -type FilterOutputInterfaceInvertDrop struct{ baseCase } - -var _ TestCase = (*FilterOutputInterfaceInvertDrop)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterfaceInvertDrop) Name() string { - return "FilterOutputInterfaceInvertDrop" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterfaceInvertDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "-o", "lo", "-j", "DROP"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenTCP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection on port %d should not be accepted, but got accepted", acceptPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterfaceInvertDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := connectTCP(timedCtx, ip, acceptPort, ipv6); err == nil { - return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort) - } - - return nil -} - -// FilterOutputInterfaceInvertAccept tests that we can selectively send packets -// not matching the specific outgoing interface. -type FilterOutputInterfaceInvertAccept struct{ baseCase } - -var _ TestCase = (*FilterOutputInterfaceInvertAccept)(nil) - -// Name implements TestCase.Name. -func (*FilterOutputInterfaceInvertAccept) Name() string { - return "FilterOutputInterfaceInvertAccept" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*FilterOutputInterfaceInvertAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "-o", "lo", "-j", "ACCEPT"); err != nil { - return err - } - - // Listen for TCP packets on accept port. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*FilterOutputInterfaceInvertAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} diff --git a/test/iptables/iptables.go b/test/iptables/iptables.go deleted file mode 100644 index 970587a02..000000000 --- a/test/iptables/iptables.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package iptables contains a set of iptables tests implemented as TestCases -package iptables - -import ( - "context" - "fmt" - "net" - "time" -) - -// IPExchangePort is the port the container listens on to receive the IP -// address of the local process. -const IPExchangePort = 2349 - -// TerminalStatement is the last statement in the test runner. -const TerminalStatement = "Finished!" - -// TestTimeout is the timeout used for all tests. -const TestTimeout = 10 * time.Second - -// NegativeTimeout is the time tests should wait to establish the negative -// case, i.e. that connections are not made. -const NegativeTimeout = 2 * time.Second - -// A TestCase contains one action to run in the container and one to run -// locally. The actions run concurrently and each must succeed for the test -// pass. -type TestCase interface { - // Name returns the name of the test. - Name() string - - // ContainerAction runs inside the container. It receives the IP of the - // local process. - ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error - - // LocalAction runs locally. It receives the IP of the container. - LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error - - // ContainerSufficient indicates whether ContainerAction's return value - // alone indicates whether the test succeeded. - ContainerSufficient() bool - - // LocalSufficient indicates whether LocalAction's return value alone - // indicates whether the test succeeded. - LocalSufficient() bool -} - -// baseCase provides defaults for ContainerSufficient and LocalSufficient when -// both actions are required to finish. -type baseCase struct{} - -// ContainerSufficient implements TestCase.ContainerSufficient. -func (*baseCase) ContainerSufficient() bool { - return false -} - -// LocalSufficient implements TestCase.LocalSufficient. -func (*baseCase) LocalSufficient() bool { - return false -} - -// localCase provides defaults for ContainerSufficient and LocalSufficient when -// only the local action is required to finish. -type localCase struct{} - -// ContainerSufficient implements TestCase.ContainerSufficient. -func (*localCase) ContainerSufficient() bool { - return false -} - -// LocalSufficient implements TestCase.LocalSufficient. -func (*localCase) LocalSufficient() bool { - return true -} - -// containerCase provides defaults for ContainerSufficient and LocalSufficient -// when only the container action is required to finish. -type containerCase struct{} - -// ContainerSufficient implements TestCase.ContainerSufficient. -func (*containerCase) ContainerSufficient() bool { - return true -} - -// LocalSufficient implements TestCase.LocalSufficient. -func (*containerCase) LocalSufficient() bool { - return false -} - -// Tests maps test names to TestCase. -// -// New TestCases are added by calling RegisterTestCase in an init function. -var Tests = map[string]TestCase{} - -// RegisterTestCase registers tc so it can be run. -func RegisterTestCase(tc TestCase) { - if _, ok := Tests[tc.Name()]; ok { - panic(fmt.Sprintf("TestCase %s already registered.", tc.Name())) - } - Tests[tc.Name()] = tc -} diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go deleted file mode 100644 index d6c69a319..000000000 --- a/test/iptables/iptables_test.go +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iptables - -import ( - "context" - "errors" - "fmt" - "net" - "reflect" - "sync" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/pkg/test/testutil" -) - -// singleTest runs a TestCase. Each test follows a pattern: -// - Create a container. -// - Get the container's IP. -// - Send the container our IP. -// - Start a new goroutine running the local action of the test. -// - Wait for both the container and local actions to finish. -// -// Container output is logged to $TEST_UNDECLARED_OUTPUTS_DIR if it exists, or -// to stderr. -func singleTest(t *testing.T, test TestCase) { - for _, tc := range []bool{false, true} { - subtest := "IPv4" - if tc { - subtest = "IPv6" - } - t.Run(subtest, func(t *testing.T) { - iptablesTest(t, test, tc) - }) - } -} - -func iptablesTest(t *testing.T, test TestCase, ipv6 bool) { - if _, ok := Tests[test.Name()]; !ok { - t.Fatalf("no test found with name %q. Has it been registered?", test.Name()) - } - - // Wait for the local and container goroutines to finish. - var wg sync.WaitGroup - defer wg.Wait() - - ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) - defer cancel() - - d := dockerutil.MakeContainer(ctx, t) - defer func() { - if logs, err := d.Logs(context.Background()); err != nil { - t.Logf("Failed to retrieve container logs.") - } else { - t.Logf("=== Container logs: ===\n%s", logs) - } - // Use a new context, as cleanup should run even when we - // timeout. - d.CleanUp(context.Background()) - }() - - // Create and start the container. - opts := dockerutil.RunOpts{ - Image: "iptables", - CapAdd: []string{"NET_ADMIN"}, - } - d.CopyFiles(&opts, "/runner", "test/iptables/runner/runner") - args := []string{"/runner/runner", "-name", test.Name()} - if ipv6 { - args = append(args, "-ipv6") - } - if err := d.Spawn(ctx, opts, args...); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Get the container IP. - ip, err := d.FindIP(ctx, ipv6) - if err != nil { - // If ipv6 is not configured, don't fail. - if ipv6 && err == dockerutil.ErrNoIP { - t.Skipf("No ipv6 address is available.") - } - t.Fatalf("failed to get container IP: %v", err) - } - - // Give the container our IP. - if err := sendIP(ip); err != nil { - t.Fatalf("failed to send IP to container: %v", err) - } - - // Run our side of the test. - errCh := make(chan error, 2) - wg.Add(1) - go func() { - defer wg.Done() - if err := test.LocalAction(ctx, ip, ipv6); err != nil && !errors.Is(err, context.Canceled) { - errCh <- fmt.Errorf("LocalAction failed: %v", err) - } else { - errCh <- nil - } - if test.LocalSufficient() { - errCh <- nil - } - }() - - // Run the container side. - wg.Add(1) - go func() { - defer wg.Done() - // Wait for the final statement. This structure has the side - // effect that all container logs will appear within the - // individual test context. - if _, err := d.WaitForOutput(ctx, TerminalStatement, TestTimeout); err != nil && !errors.Is(err, context.Canceled) { - errCh <- fmt.Errorf("ContainerAction failed: %v", err) - } else { - errCh <- nil - } - if test.ContainerSufficient() { - errCh <- nil - } - }() - - for i := 0; i < 2; i++ { - select { - case err := <-errCh: - if err != nil { - t.Fatal(err) - } - } - } -} - -func sendIP(ip net.IP) error { - contAddr := net.TCPAddr{ - IP: ip, - Port: IPExchangePort, - } - var conn *net.TCPConn - // The container may not be listening when we first connect, so retry - // upon error. - cb := func() error { - c, err := net.DialTCP("tcp", nil, &contAddr) - conn = c - return err - } - if err := testutil.Poll(cb, TestTimeout); err != nil { - return fmt.Errorf("timed out waiting to send IP, most recent error: %v", err) - } - if _, err := conn.Write([]byte{0}); err != nil { - return fmt.Errorf("error writing to container: %v", err) - } - return nil -} - -func TestFilterInputDropUDP(t *testing.T) { - singleTest(t, &FilterInputDropUDP{}) -} - -func TestFilterInputDropUDPPort(t *testing.T) { - singleTest(t, &FilterInputDropUDPPort{}) -} - -func TestFilterInputDropDifferentUDPPort(t *testing.T) { - singleTest(t, &FilterInputDropDifferentUDPPort{}) -} - -func TestFilterInputDropAll(t *testing.T) { - singleTest(t, &FilterInputDropAll{}) -} - -func TestFilterInputDropOnlyUDP(t *testing.T) { - singleTest(t, &FilterInputDropOnlyUDP{}) -} - -func TestFilterInputDropTCPDestPort(t *testing.T) { - singleTest(t, &FilterInputDropTCPDestPort{}) -} - -func TestFilterInputDropTCPSrcPort(t *testing.T) { - singleTest(t, &FilterInputDropTCPSrcPort{}) -} - -func TestFilterInputCreateUserChain(t *testing.T) { - singleTest(t, &FilterInputCreateUserChain{}) -} - -func TestFilterInputDefaultPolicyAccept(t *testing.T) { - singleTest(t, &FilterInputDefaultPolicyAccept{}) -} - -func TestFilterInputDefaultPolicyDrop(t *testing.T) { - singleTest(t, &FilterInputDefaultPolicyDrop{}) -} - -func TestFilterInputReturnUnderflow(t *testing.T) { - singleTest(t, &FilterInputReturnUnderflow{}) -} - -func TestFilterOutputDropTCPDestPort(t *testing.T) { - singleTest(t, &FilterOutputDropTCPDestPort{}) -} - -func TestFilterOutputDropTCPSrcPort(t *testing.T) { - singleTest(t, &FilterOutputDropTCPSrcPort{}) -} - -func TestFilterOutputAcceptTCPOwner(t *testing.T) { - singleTest(t, &FilterOutputAcceptTCPOwner{}) -} - -func TestFilterOutputDropTCPOwner(t *testing.T) { - singleTest(t, &FilterOutputDropTCPOwner{}) -} - -func TestFilterOutputAcceptUDPOwner(t *testing.T) { - singleTest(t, &FilterOutputAcceptUDPOwner{}) -} - -func TestFilterOutputDropUDPOwner(t *testing.T) { - singleTest(t, &FilterOutputDropUDPOwner{}) -} - -func TestFilterOutputOwnerFail(t *testing.T) { - singleTest(t, &FilterOutputOwnerFail{}) -} - -func TestFilterOutputAcceptGIDOwner(t *testing.T) { - singleTest(t, &FilterOutputAcceptGIDOwner{}) -} - -func TestFilterOutputDropGIDOwner(t *testing.T) { - singleTest(t, &FilterOutputDropGIDOwner{}) -} - -func TestFilterOutputInvertGIDOwner(t *testing.T) { - singleTest(t, &FilterOutputInvertGIDOwner{}) -} - -func TestFilterOutputInvertUIDOwner(t *testing.T) { - singleTest(t, &FilterOutputInvertUIDOwner{}) -} - -func TestFilterOutputInvertUIDAndGIDOwner(t *testing.T) { - singleTest(t, &FilterOutputInvertUIDAndGIDOwner{}) -} - -func TestFilterOutputInterfaceAccept(t *testing.T) { - singleTest(t, &FilterOutputInterfaceAccept{}) -} - -func TestFilterOutputInterfaceDrop(t *testing.T) { - singleTest(t, &FilterOutputInterfaceDrop{}) -} - -func TestFilterOutputInterface(t *testing.T) { - singleTest(t, &FilterOutputInterface{}) -} - -func TestFilterOutputInterfaceBeginsWith(t *testing.T) { - singleTest(t, &FilterOutputInterfaceBeginsWith{}) -} - -func TestFilterOutputInterfaceInvertDrop(t *testing.T) { - singleTest(t, &FilterOutputInterfaceInvertDrop{}) -} - -func TestFilterOutputInterfaceInvertAccept(t *testing.T) { - singleTest(t, &FilterOutputInterfaceInvertAccept{}) -} - -func TestJumpSerialize(t *testing.T) { - singleTest(t, &FilterInputSerializeJump{}) -} - -func TestJumpBasic(t *testing.T) { - singleTest(t, &FilterInputJumpBasic{}) -} - -func TestJumpReturn(t *testing.T) { - singleTest(t, &FilterInputJumpReturn{}) -} - -func TestJumpReturnDrop(t *testing.T) { - singleTest(t, &FilterInputJumpReturnDrop{}) -} - -func TestJumpBuiltin(t *testing.T) { - singleTest(t, &FilterInputJumpBuiltin{}) -} - -func TestJumpTwice(t *testing.T) { - singleTest(t, &FilterInputJumpTwice{}) -} - -func TestInputDestination(t *testing.T) { - singleTest(t, &FilterInputDestination{}) -} - -func TestInputInvertDestination(t *testing.T) { - singleTest(t, &FilterInputInvertDestination{}) -} - -func TestFilterOutputDestination(t *testing.T) { - singleTest(t, &FilterOutputDestination{}) -} - -func TestFilterOutputInvertDestination(t *testing.T) { - singleTest(t, &FilterOutputInvertDestination{}) -} - -func TestNATPreRedirectUDPPort(t *testing.T) { - singleTest(t, &NATPreRedirectUDPPort{}) -} - -func TestNATPreRedirectTCPPort(t *testing.T) { - singleTest(t, &NATPreRedirectTCPPort{}) -} - -func TestNATPreRedirectTCPOutgoing(t *testing.T) { - singleTest(t, &NATPreRedirectTCPOutgoing{}) -} - -func TestNATOutRedirectTCPIncoming(t *testing.T) { - singleTest(t, &NATOutRedirectTCPIncoming{}) -} -func TestNATOutRedirectUDPPort(t *testing.T) { - singleTest(t, &NATOutRedirectUDPPort{}) -} - -func TestNATOutRedirectTCPPort(t *testing.T) { - singleTest(t, &NATOutRedirectTCPPort{}) -} - -func TestNATDropUDP(t *testing.T) { - singleTest(t, &NATDropUDP{}) -} - -func TestNATAcceptAll(t *testing.T) { - singleTest(t, &NATAcceptAll{}) -} - -func TestNATOutRedirectIP(t *testing.T) { - singleTest(t, &NATOutRedirectIP{}) -} - -func TestNATOutDontRedirectIP(t *testing.T) { - singleTest(t, &NATOutDontRedirectIP{}) -} - -func TestNATOutRedirectInvert(t *testing.T) { - singleTest(t, &NATOutRedirectInvert{}) -} - -func TestNATPreRedirectIP(t *testing.T) { - singleTest(t, &NATPreRedirectIP{}) -} - -func TestNATPreDontRedirectIP(t *testing.T) { - singleTest(t, &NATPreDontRedirectIP{}) -} - -func TestNATPreRedirectInvert(t *testing.T) { - singleTest(t, &NATPreRedirectInvert{}) -} - -func TestNATRedirectRequiresProtocol(t *testing.T) { - singleTest(t, &NATRedirectRequiresProtocol{}) -} - -func TestNATLoopbackSkipsPrerouting(t *testing.T) { - singleTest(t, &NATLoopbackSkipsPrerouting{}) -} - -func TestInputSource(t *testing.T) { - singleTest(t, &FilterInputSource{}) -} - -func TestInputInvertSource(t *testing.T) { - singleTest(t, &FilterInputInvertSource{}) -} - -func TestInputInterfaceAccept(t *testing.T) { - singleTest(t, &FilterInputInterfaceAccept{}) -} - -func TestInputInterfaceDrop(t *testing.T) { - singleTest(t, &FilterInputInterfaceDrop{}) -} - -func TestInputInterface(t *testing.T) { - singleTest(t, &FilterInputInterface{}) -} - -func TestInputInterfaceBeginsWith(t *testing.T) { - singleTest(t, &FilterInputInterfaceBeginsWith{}) -} - -func TestInputInterfaceInvertDrop(t *testing.T) { - singleTest(t, &FilterInputInterfaceInvertDrop{}) -} - -func TestInputInterfaceInvertAccept(t *testing.T) { - singleTest(t, &FilterInputInterfaceInvertAccept{}) -} - -func TestFilterAddrs(t *testing.T) { - tcs := []struct { - ipv6 bool - addrs []string - want []string - }{ - { - ipv6: false, - addrs: []string{"192.168.0.1", "192.168.0.2/24", "::1", "::2/128"}, - want: []string{"192.168.0.1", "192.168.0.2"}, - }, - { - ipv6: true, - addrs: []string{"192.168.0.1", "192.168.0.2/24", "::1", "::2/128"}, - want: []string{"::1", "::2"}, - }, - } - - for _, tc := range tcs { - if got := filterAddrs(tc.addrs, tc.ipv6); !reflect.DeepEqual(got, tc.want) { - t.Errorf("%v with IPv6 %t: got %v, but wanted %v", tc.addrs, tc.ipv6, got, tc.want) - } - } -} - -func TestNATPreOriginalDst(t *testing.T) { - singleTest(t, &NATPreOriginalDst{}) -} - -func TestNATOutOriginalDst(t *testing.T) { - singleTest(t, &NATOutOriginalDst{}) -} - -func TestNATPreRECVORIGDSTADDR(t *testing.T) { - singleTest(t, &NATPreRECVORIGDSTADDR{}) -} - -func TestNATOutRECVORIGDSTADDR(t *testing.T) { - singleTest(t, &NATOutRECVORIGDSTADDR{}) -} diff --git a/test/iptables/iptables_unsafe.go b/test/iptables/iptables_unsafe.go deleted file mode 100644 index dd1a1c082..000000000 --- a/test/iptables/iptables_unsafe.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iptables - -import ( - "fmt" - "unsafe" - - "golang.org/x/sys/unix" -) - -type originalDstError struct { - errno unix.Errno -} - -func (e originalDstError) Error() string { - return fmt.Sprintf("errno (%d) when calling getsockopt(SO_ORIGINAL_DST): %v", int(e.errno), e.errno.Error()) -} - -// SO_ORIGINAL_DST gets the original destination of a redirected packet via -// getsockopt. -const SO_ORIGINAL_DST = 80 - -func originalDestination4(connfd int) (unix.RawSockaddrInet4, error) { - var addr unix.RawSockaddrInet4 - var addrLen uint32 = unix.SizeofSockaddrInet4 - if errno := originalDestination(connfd, unix.SOL_IP, unsafe.Pointer(&addr), &addrLen); errno != 0 { - return unix.RawSockaddrInet4{}, originalDstError{errno} - } - return addr, nil -} - -func originalDestination6(connfd int) (unix.RawSockaddrInet6, error) { - var addr unix.RawSockaddrInet6 - var addrLen uint32 = unix.SizeofSockaddrInet6 - if errno := originalDestination(connfd, unix.SOL_IPV6, unsafe.Pointer(&addr), &addrLen); errno != 0 { - return unix.RawSockaddrInet6{}, originalDstError{errno} - } - return addr, nil -} - -func originalDestination(connfd int, level uintptr, optval unsafe.Pointer, optlen *uint32) unix.Errno { - _, _, errno := unix.Syscall6( - unix.SYS_GETSOCKOPT, - uintptr(connfd), - level, - SO_ORIGINAL_DST, - uintptr(optval), - uintptr(unsafe.Pointer(optlen)), - 0) - return errno -} diff --git a/test/iptables/iptables_util.go b/test/iptables/iptables_util.go deleted file mode 100644 index bba17b894..000000000 --- a/test/iptables/iptables_util.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iptables - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "net" - "os/exec" - "strings" - "time" - - "gvisor.dev/gvisor/pkg/test/testutil" -) - -// filterTable calls `ip{6}tables -t filter` with the given args. -func filterTable(ipv6 bool, args ...string) error { - return tableCmd(ipv6, "filter", args) -} - -// natTable calls `ip{6}tables -t nat` with the given args. -func natTable(ipv6 bool, args ...string) error { - return tableCmd(ipv6, "nat", args) -} - -func tableCmd(ipv6 bool, table string, args []string) error { - args = append([]string{"-t", table}, args...) - binary := "iptables" - if ipv6 { - binary = "ip6tables" - } - cmd := exec.Command(binary, args...) - if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("error running iptables with args %v\nerror: %v\noutput: %s", args, err, string(out)) - } - return nil -} - -// filterTableRules is like filterTable, but runs multiple iptables commands. -func filterTableRules(ipv6 bool, argsList [][]string) error { - return tableRules(ipv6, "filter", argsList) -} - -// natTableRules is like natTable, but runs multiple iptables commands. -func natTableRules(ipv6 bool, argsList [][]string) error { - return tableRules(ipv6, "nat", argsList) -} - -func tableRules(ipv6 bool, table string, argsList [][]string) error { - for _, args := range argsList { - if err := tableCmd(ipv6, table, args); err != nil { - return err - } - } - return nil -} - -// listenUDP listens on a UDP port and returns the value of net.Conn.Read() for -// the first read on that port. -func listenUDP(ctx context.Context, port int, ipv6 bool) error { - localAddr := net.UDPAddr{ - Port: port, - } - conn, err := net.ListenUDP(udpNetwork(ipv6), &localAddr) - if err != nil { - return err - } - defer conn.Close() - - ch := make(chan error) - go func() { - _, err = conn.Read([]byte{0}) - ch <- err - }() - - select { - case err := <-ch: - return err - case <-ctx.Done(): - return ctx.Err() - } -} - -// sendUDPLoop sends 1 byte UDP packets repeatedly to the IP and port specified -// over a duration. -func sendUDPLoop(ctx context.Context, ip net.IP, port int, ipv6 bool) error { - remote := net.UDPAddr{ - IP: ip, - Port: port, - } - conn, err := net.DialUDP(udpNetwork(ipv6), nil, &remote) - if err != nil { - return err - } - defer conn.Close() - - for { - // This may return an error (connection refused) if the remote - // hasn't started listening yet or they're dropping our - // packets. So we ignore Write errors and depend on the remote - // to report a failure if it doesn't get a packet it needs. - conn.Write([]byte{0}) - select { - case <-ctx.Done(): - // Being cancelled or timing out isn't an error, as we - // cannot tell with UDP whether we succeeded. - return nil - // Continue looping. - case <-time.After(200 * time.Millisecond): - } - } -} - -// listenTCP listens for connections on a TCP port. -func listenTCP(ctx context.Context, port int, ipv6 bool) error { - localAddr := net.TCPAddr{ - Port: port, - } - - // Starts listening on port. - lConn, err := net.ListenTCP(tcpNetwork(ipv6), &localAddr) - if err != nil { - return err - } - defer lConn.Close() - - // Accept connections on port. - ch := make(chan error) - go func() { - conn, err := lConn.AcceptTCP() - ch <- err - conn.Close() - }() - - select { - case err := <-ch: - return err - case <-ctx.Done(): - return fmt.Errorf("timed out waiting for a connection at %#v: %w", localAddr, ctx.Err()) - } -} - -// connectTCP connects to the given IP and port from an ephemeral local address. -func connectTCP(ctx context.Context, ip net.IP, port int, ipv6 bool) error { - contAddr := net.TCPAddr{ - IP: ip, - Port: port, - } - // The container may not be listening when we first connect, so retry - // upon error. - callback := func() error { - var d net.Dialer - conn, err := d.DialContext(ctx, tcpNetwork(ipv6), contAddr.String()) - if conn != nil { - conn.Close() - } - return err - } - if err := testutil.PollContext(ctx, callback); err != nil { - return fmt.Errorf("timed out waiting to connect IP on port %v, most recent error: %w", port, err) - } - - return nil -} - -// localAddrs returns a list of local network interface addresses. When ipv6 is -// true, only IPv6 addresses are returned. Otherwise only IPv4 addresses are -// returned. -func localAddrs(ipv6 bool) ([]string, error) { - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil, err - } - addrStrs := make([]string, 0, len(addrs)) - for _, addr := range addrs { - // Add only IPv4 or only IPv6 addresses. - parts := strings.Split(addr.String(), "/") - if len(parts) != 2 { - return nil, fmt.Errorf("bad interface address: %q", addr.String()) - } - if isIPv6 := net.ParseIP(parts[0]).To4() == nil; isIPv6 == ipv6 { - addrStrs = append(addrStrs, addr.String()) - } - } - return filterAddrs(addrStrs, ipv6), nil -} - -func filterAddrs(addrs []string, ipv6 bool) []string { - addrStrs := make([]string, 0, len(addrs)) - for _, addr := range addrs { - // Add only IPv4 or only IPv6 addresses. - parts := strings.Split(addr, "/") - if isIPv6 := net.ParseIP(parts[0]).To4() == nil; isIPv6 == ipv6 { - addrStrs = append(addrStrs, parts[0]) - } - } - return addrStrs -} - -// getInterfaceName returns the name of the interface other than loopback. -func getInterfaceName() (string, bool) { - iface, ok := getNonLoopbackInterface() - if !ok { - return "", false - } - return iface.Name, true -} - -func getInterfaceAddrs(ipv6 bool) ([]net.IP, error) { - iface, ok := getNonLoopbackInterface() - if !ok { - return nil, errors.New("no non-loopback interface found") - } - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - - // Get only IPv4 or IPv6 addresses. - ips := make([]net.IP, 0, len(addrs)) - for _, addr := range addrs { - parts := strings.Split(addr.String(), "/") - var ip net.IP - // To16() returns IPv4 addresses as IPv4-mapped IPv6 addresses. - // So we check whether To4() returns nil to test whether the - // address is v4 or v6. - if v4 := net.ParseIP(parts[0]).To4(); ipv6 && v4 == nil { - ip = net.ParseIP(parts[0]).To16() - } else { - ip = v4 - } - if ip != nil { - ips = append(ips, ip) - } - } - return ips, nil -} - -func getNonLoopbackInterface() (net.Interface, bool) { - if interfaces, err := net.Interfaces(); err == nil { - for _, intf := range interfaces { - if intf.Name != "lo" { - return intf, true - } - } - } - return net.Interface{}, false -} - -func htons(x uint16) uint16 { - buf := make([]byte, 2) - binary.BigEndian.PutUint16(buf, x) - return binary.LittleEndian.Uint16(buf) -} - -func localIP(ipv6 bool) string { - if ipv6 { - return "::1" - } - return "127.0.0.1" -} - -func nowhereIP(ipv6 bool) string { - if ipv6 { - return "2001:db8::1" - } - return "192.0.2.1" -} - -// udpNetwork returns an IPv6 or IPv6 UDP network argument to net.Dial. -func udpNetwork(ipv6 bool) string { - if ipv6 { - return "udp6" - } - return "udp4" -} - -// tcpNetwork returns an IPv6 or IPv6 TCP network argument to net.Dial. -func tcpNetwork(ipv6 bool) string { - if ipv6 { - return "tcp6" - } - return "tcp4" -} diff --git a/test/iptables/nat.go b/test/iptables/nat.go deleted file mode 100644 index 70d8a1832..000000000 --- a/test/iptables/nat.go +++ /dev/null @@ -1,917 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iptables - -import ( - "context" - "errors" - "fmt" - "net" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/binary" - "gvisor.dev/gvisor/pkg/usermem" -) - -const redirectPort = 42 - -func init() { - RegisterTestCase(&NATPreRedirectUDPPort{}) - RegisterTestCase(&NATPreRedirectTCPPort{}) - RegisterTestCase(&NATPreRedirectTCPOutgoing{}) - RegisterTestCase(&NATOutRedirectTCPIncoming{}) - RegisterTestCase(&NATOutRedirectUDPPort{}) - RegisterTestCase(&NATOutRedirectTCPPort{}) - RegisterTestCase(&NATDropUDP{}) - RegisterTestCase(&NATAcceptAll{}) - RegisterTestCase(&NATPreRedirectIP{}) - RegisterTestCase(&NATPreDontRedirectIP{}) - RegisterTestCase(&NATPreRedirectInvert{}) - RegisterTestCase(&NATOutRedirectIP{}) - RegisterTestCase(&NATOutDontRedirectIP{}) - RegisterTestCase(&NATOutRedirectInvert{}) - RegisterTestCase(&NATRedirectRequiresProtocol{}) - RegisterTestCase(&NATLoopbackSkipsPrerouting{}) - RegisterTestCase(&NATPreOriginalDst{}) - RegisterTestCase(&NATOutOriginalDst{}) - RegisterTestCase(&NATPreRECVORIGDSTADDR{}) - RegisterTestCase(&NATOutRECVORIGDSTADDR{}) -} - -// NATPreRedirectUDPPort tests that packets are redirected to different port. -type NATPreRedirectUDPPort struct{ containerCase } - -var _ TestCase = (*NATPreRedirectUDPPort)(nil) - -// Name implements TestCase.Name. -func (*NATPreRedirectUDPPort) Name() string { - return "NATPreRedirectUDPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRedirectUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { - return err - } - - if err := listenUDP(ctx, redirectPort, ipv6); err != nil { - return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", redirectPort, err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRedirectUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// NATPreRedirectTCPPort tests that connections are redirected on specified ports. -type NATPreRedirectTCPPort struct{ baseCase } - -var _ TestCase = (*NATPreRedirectTCPPort)(nil) - -// Name implements TestCase.Name. -func (*NATPreRedirectTCPPort) Name() string { - return "NATPreRedirectTCPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRedirectTCPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil { - return err - } - - // Listen for TCP packets on redirect port. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRedirectTCPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, dropPort, ipv6) -} - -// NATPreRedirectTCPOutgoing verifies that outgoing TCP connections aren't -// affected by PREROUTING connection tracking. -type NATPreRedirectTCPOutgoing struct{ baseCase } - -var _ TestCase = (*NATPreRedirectTCPOutgoing)(nil) - -// Name implements TestCase.Name. -func (*NATPreRedirectTCPOutgoing) Name() string { - return "NATPreRedirectTCPOutgoing" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRedirectTCPOutgoing) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect all incoming TCP traffic to a closed port. - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil { - return err - } - - // Establish a connection to the host process. - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRedirectTCPOutgoing) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenTCP(ctx, acceptPort, ipv6) -} - -// NATOutRedirectTCPIncoming verifies that incoming TCP connections aren't -// affected by OUTPUT connection tracking. -type NATOutRedirectTCPIncoming struct{ baseCase } - -var _ TestCase = (*NATOutRedirectTCPIncoming)(nil) - -// Name implements TestCase.Name. -func (*NATOutRedirectTCPIncoming) Name() string { - return "NATOutRedirectTCPIncoming" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRedirectTCPIncoming) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect all outgoing TCP traffic to a closed port. - if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil { - return err - } - - // Establish a connection to the host process. - return listenTCP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRedirectTCPIncoming) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, acceptPort, ipv6) -} - -// NATOutRedirectUDPPort tests that packets are redirected to different port. -type NATOutRedirectUDPPort struct{ containerCase } - -var _ TestCase = (*NATOutRedirectUDPPort)(nil) - -// Name implements TestCase.Name. -func (*NATOutRedirectUDPPort) Name() string { - return "NATOutRedirectUDPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRedirectUDPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)), "-A", "OUTPUT", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRedirectUDPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// NATDropUDP tests that packets are not received in ports other than redirect -// port. -type NATDropUDP struct{ containerCase } - -var _ TestCase = (*NATDropUDP)(nil) - -// Name implements TestCase.Name. -func (*NATDropUDP) Name() string { - return "NATDropUDP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATDropUDP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { - return err - } - - timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout) - defer cancel() - if err := listenUDP(timedCtx, acceptPort, ipv6); err == nil { - return fmt.Errorf("packets on port %d should have been redirected to port %d", acceptPort, redirectPort) - } else if !errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("error reading: %v", err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATDropUDP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// NATAcceptAll tests that all UDP packets are accepted. -type NATAcceptAll struct{ containerCase } - -var _ TestCase = (*NATAcceptAll)(nil) - -// Name implements TestCase.Name. -func (*NATAcceptAll) Name() string { - return "NATAcceptAll" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATAcceptAll) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "ACCEPT"); err != nil { - return err - } - - if err := listenUDP(ctx, acceptPort, ipv6); err != nil { - return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %v", acceptPort, err) - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATAcceptAll) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// NATOutRedirectIP uses iptables to select packets based on destination IP and -// redirects them. -type NATOutRedirectIP struct{ baseCase } - -var _ TestCase = (*NATOutRedirectIP)(nil) - -// Name implements TestCase.Name. -func (*NATOutRedirectIP) Name() string { - return "NATOutRedirectIP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect OUTPUT packets to a listening localhost port. - return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)), - "-A", "OUTPUT", - "-d", nowhereIP(ipv6), - "-p", "udp", - "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// NATOutDontRedirectIP tests that iptables matching with "-d" does not match -// packets it shouldn't. -type NATOutDontRedirectIP struct{ localCase } - -var _ TestCase = (*NATOutDontRedirectIP)(nil) - -// Name implements TestCase.Name. -func (*NATOutDontRedirectIP) Name() string { - return "NATOutDontRedirectIP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutDontRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "OUTPUT", "-d", localIP(ipv6), "-p", "udp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil { - return err - } - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutDontRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return listenUDP(ctx, acceptPort, ipv6) -} - -// NATOutRedirectInvert tests that iptables can match with "! -d". -type NATOutRedirectInvert struct{ baseCase } - -var _ TestCase = (*NATOutRedirectInvert)(nil) - -// Name implements TestCase.Name. -func (*NATOutRedirectInvert) Name() string { - return "NATOutRedirectInvert" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRedirectInvert) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect OUTPUT packets to a listening localhost port. - dest := "192.0.2.2" - if ipv6 { - dest = "2001:db8::2" - } - return loopbackTest(ctx, ipv6, net.ParseIP(nowhereIP(ipv6)), - "-A", "OUTPUT", - "!", "-d", dest, - "-p", "udp", - "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRedirectInvert) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// NATPreRedirectIP tests that we can use iptables to select packets based on -// destination IP and redirect them. -type NATPreRedirectIP struct{ containerCase } - -var _ TestCase = (*NATPreRedirectIP)(nil) - -// Name implements TestCase.Name. -func (*NATPreRedirectIP) Name() string { - return "NATPreRedirectIP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - addrs, err := localAddrs(ipv6) - if err != nil { - return err - } - - var rules [][]string - for _, addr := range addrs { - rules = append(rules, []string{"-A", "PREROUTING", "-p", "udp", "-d", addr, "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)}) - } - if err := natTableRules(ipv6, rules); err != nil { - return err - } - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// NATPreDontRedirectIP tests that iptables matching with "-d" does not match -// packets it shouldn't. -type NATPreDontRedirectIP struct{ containerCase } - -var _ TestCase = (*NATPreDontRedirectIP)(nil) - -// Name implements TestCase.Name. -func (*NATPreDontRedirectIP) Name() string { - return "NATPreDontRedirectIP" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreDontRedirectIP) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil { - return err - } - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreDontRedirectIP) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// NATPreRedirectInvert tests that iptables can match with "! -d". -type NATPreRedirectInvert struct{ containerCase } - -var _ TestCase = (*NATPreRedirectInvert)(nil) - -// Name implements TestCase.Name. -func (*NATPreRedirectInvert) Name() string { - return "NATPreRedirectInvert" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRedirectInvert) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "!", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil { - return err - } - return listenUDP(ctx, acceptPort, ipv6) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRedirectInvert) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, dropPort, ipv6) -} - -// NATRedirectRequiresProtocol tests that use of the --to-ports flag requires a -// protocol to be specified with -p. -type NATRedirectRequiresProtocol struct{ baseCase } - -var _ TestCase = (*NATRedirectRequiresProtocol)(nil) - -// Name implements TestCase.Name. -func (*NATRedirectRequiresProtocol) Name() string { - return "NATRedirectRequiresProtocol" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATRedirectRequiresProtocol) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err == nil { - return errors.New("expected an error using REDIRECT --to-ports without a protocol") - } - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATRedirectRequiresProtocol) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// NATOutRedirectTCPPort tests that connections are redirected on specified ports. -type NATOutRedirectTCPPort struct{ baseCase } - -var _ TestCase = (*NATOutRedirectTCPPort)(nil) - -// Name implements TestCase.Name. -func (*NATOutRedirectTCPPort) Name() string { - return "NATOutRedirectTCPPort" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRedirectTCPPort) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil { - return err - } - - localAddr := net.TCPAddr{ - IP: net.ParseIP(localIP(ipv6)), - Port: acceptPort, - } - - // Starts listening on port. - lConn, err := net.ListenTCP("tcp", &localAddr) - if err != nil { - return err - } - defer lConn.Close() - - // Accept connections on port. - if err := connectTCP(ctx, ip, dropPort, ipv6); err != nil { - return err - } - - conn, err := lConn.AcceptTCP() - if err != nil { - return err - } - conn.Close() - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRedirectTCPPort) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return nil -} - -// NATLoopbackSkipsPrerouting tests that packets sent via loopback aren't -// affected by PREROUTING rules. -type NATLoopbackSkipsPrerouting struct{ baseCase } - -var _ TestCase = (*NATLoopbackSkipsPrerouting)(nil) - -// Name implements TestCase.Name. -func (*NATLoopbackSkipsPrerouting) Name() string { - return "NATLoopbackSkipsPrerouting" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATLoopbackSkipsPrerouting) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect anything sent to localhost to an unused port. - dest := []byte{127, 0, 0, 1} - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil { - return err - } - - // Establish a connection via localhost. If the PREROUTING rule did apply to - // loopback traffic, the connection would fail. - sendCh := make(chan error) - go func() { - sendCh <- connectTCP(ctx, dest, acceptPort, ipv6) - }() - - if err := listenTCP(ctx, acceptPort, ipv6); err != nil { - return err - } - return <-sendCh -} - -// LocalAction implements TestCase.LocalAction. -func (*NATLoopbackSkipsPrerouting) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -// NATPreOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination -// of PREROUTING NATted packets. -type NATPreOriginalDst struct{ baseCase } - -var _ TestCase = (*NATPreOriginalDst)(nil) - -// Name implements TestCase.Name. -func (*NATPreOriginalDst) Name() string { - return "NATPreOriginalDst" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreOriginalDst) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect incoming TCP connections to acceptPort. - if err := natTable(ipv6, "-A", "PREROUTING", - "-p", "tcp", - "--destination-port", fmt.Sprintf("%d", dropPort), - "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil { - return err - } - - addrs, err := getInterfaceAddrs(ipv6) - if err != nil { - return err - } - return listenForRedirectedConn(ctx, ipv6, addrs) -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreOriginalDst) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return connectTCP(ctx, ip, dropPort, ipv6) -} - -// NATOutOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination -// of OUTBOUND NATted packets. -type NATOutOriginalDst struct{ baseCase } - -var _ TestCase = (*NATOutOriginalDst)(nil) - -// Name implements TestCase.Name. -func (*NATOutOriginalDst) Name() string { - return "NATOutOriginalDst" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutOriginalDst) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // Redirect incoming TCP connections to acceptPort. - if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil { - return err - } - - connCh := make(chan error) - go func() { - connCh <- connectTCP(ctx, ip, dropPort, ipv6) - }() - - if err := listenForRedirectedConn(ctx, ipv6, []net.IP{ip}); err != nil { - return err - } - return <-connCh -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutOriginalDst) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -func listenForRedirectedConn(ctx context.Context, ipv6 bool, originalDsts []net.IP) error { - // The net package doesn't give guaranteed access to the connection's - // underlying FD, and thus we cannot call getsockopt. We have to use - // traditional syscalls. - - // Create the listening socket, bind, listen, and accept. - family := unix.AF_INET - if ipv6 { - family = unix.AF_INET6 - } - sockfd, err := unix.Socket(family, unix.SOCK_STREAM, 0) - if err != nil { - return err - } - defer unix.Close(sockfd) - - var bindAddr unix.Sockaddr - if ipv6 { - bindAddr = &unix.SockaddrInet6{ - Port: acceptPort, - Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any - } - } else { - bindAddr = &unix.SockaddrInet4{ - Port: acceptPort, - Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY - } - } - if err := unix.Bind(sockfd, bindAddr); err != nil { - return err - } - - if err := unix.Listen(sockfd, 1); err != nil { - return err - } - - // Block on accept() in another goroutine. - connCh := make(chan int) - errCh := make(chan error) - go func() { - for { - connFD, _, err := unix.Accept(sockfd) - if errors.Is(err, unix.EINTR) { - continue - } - if err != nil { - errCh <- err - return - } - connCh <- connFD - return - } - }() - - // Wait for accept() to return or for the context to finish. - var connFD int - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - return err - case connFD = <-connCh: - } - defer unix.Close(connFD) - - // Verify that, despite listening on acceptPort, SO_ORIGINAL_DST - // indicates the packet was sent to originalDst:dropPort. - if ipv6 { - got, err := originalDestination6(connFD) - if err != nil { - return err - } - return addrMatches6(got, originalDsts, dropPort) - } - - got, err := originalDestination4(connFD) - if err != nil { - return err - } - return addrMatches4(got, originalDsts, dropPort) -} - -// loopbackTests runs an iptables rule and ensures that packets sent to -// dest:dropPort are received by localhost:acceptPort. -func loopbackTest(ctx context.Context, ipv6 bool, dest net.IP, args ...string) error { - if err := natTable(ipv6, args...); err != nil { - return err - } - sendCh := make(chan error, 1) - listenCh := make(chan error, 1) - go func() { - sendCh <- sendUDPLoop(ctx, dest, dropPort, ipv6) - }() - go func() { - listenCh <- listenUDP(ctx, acceptPort, ipv6) - }() - select { - case err := <-listenCh: - return err - case err := <-sendCh: - return err - } -} - -// NATPreRECVORIGDSTADDR tests that IP{V6}_RECVORIGDSTADDR gets the post-NAT -// address on the PREROUTING chain. -type NATPreRECVORIGDSTADDR struct{ containerCase } - -var _ TestCase = (*NATPreRECVORIGDSTADDR)(nil) - -// Name implements TestCase.Name. -func (*NATPreRECVORIGDSTADDR) Name() string { - return "NATPreRECVORIGDSTADDR" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATPreRECVORIGDSTADDR) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { - return err - } - - if err := recvWithRECVORIGDSTADDR(ctx, ipv6, nil, redirectPort); err != nil { - return err - } - - return nil -} - -// LocalAction implements TestCase.LocalAction. -func (*NATPreRECVORIGDSTADDR) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - return sendUDPLoop(ctx, ip, acceptPort, ipv6) -} - -// NATOutRECVORIGDSTADDR tests that IP{V6}_RECVORIGDSTADDR gets the post-NAT -// address on the OUTPUT chain. -type NATOutRECVORIGDSTADDR struct{ containerCase } - -var _ TestCase = (*NATOutRECVORIGDSTADDR)(nil) - -// Name implements TestCase.Name. -func (*NATOutRECVORIGDSTADDR) Name() string { - return "NATOutRECVORIGDSTADDR" -} - -// ContainerAction implements TestCase.ContainerAction. -func (*NATOutRECVORIGDSTADDR) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { - if err := natTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil { - return err - } - - sendCh := make(chan error) - go func() { - // Packets will be sent to a non-container IP and redirected - // back to the container. - sendCh <- sendUDPLoop(ctx, ip, acceptPort, ipv6) - }() - - expectedIP := &net.IP{127, 0, 0, 1} - if ipv6 { - expectedIP = &net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - } - if err := recvWithRECVORIGDSTADDR(ctx, ipv6, expectedIP, redirectPort); err != nil { - return err - } - - select { - case err := <-sendCh: - return err - default: - return nil - } -} - -// LocalAction implements TestCase.LocalAction. -func (*NATOutRECVORIGDSTADDR) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error { - // No-op. - return nil -} - -func recvWithRECVORIGDSTADDR(ctx context.Context, ipv6 bool, expectedDst *net.IP, port uint16) error { - // The net package doesn't give guaranteed access to a connection's - // underlying FD, and thus we cannot call getsockopt. We have to use - // traditional syscalls for IP_RECVORIGDSTADDR. - - // Create the listening socket. - var ( - family = unix.AF_INET - level = unix.SOL_IP - option = unix.IP_RECVORIGDSTADDR - bindAddr unix.Sockaddr = &unix.SockaddrInet4{ - Port: int(port), - Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY - } - ) - if ipv6 { - family = unix.AF_INET6 - level = unix.SOL_IPV6 - option = 74 // IPV6_RECVORIGDSTADDR, which is missing from the syscall package. - bindAddr = &unix.SockaddrInet6{ - Port: int(port), - Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any - } - } - sockfd, err := unix.Socket(family, unix.SOCK_DGRAM, 0) - if err != nil { - return fmt.Errorf("failed Socket(%d, %d, 0): %w", family, unix.SOCK_DGRAM, err) - } - defer unix.Close(sockfd) - - if err := unix.Bind(sockfd, bindAddr); err != nil { - return fmt.Errorf("failed Bind(%d, %+v): %v", sockfd, bindAddr, err) - } - - // Enable IP_RECVORIGDSTADDR. - if err := unix.SetsockoptInt(sockfd, level, option, 1); err != nil { - return fmt.Errorf("failed SetsockoptByte(%d, %d, %d, 1): %v", sockfd, level, option, err) - } - - addrCh := make(chan interface{}) - errCh := make(chan error) - go func() { - var addr interface{} - var err error - if ipv6 { - addr, err = recvOrigDstAddr6(sockfd) - } else { - addr, err = recvOrigDstAddr4(sockfd) - } - if err != nil { - errCh <- err - } else { - addrCh <- addr - } - }() - - // Wait to receive a packet. - var addr interface{} - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-errCh: - return err - case addr = <-addrCh: - } - - // Get a list of local IPs to verify that the packet now appears to have - // been sent to us. - var localAddrs []net.IP - if expectedDst != nil { - localAddrs = []net.IP{*expectedDst} - } else { - localAddrs, err = getInterfaceAddrs(ipv6) - if err != nil { - return fmt.Errorf("failed to get local interfaces: %w", err) - } - } - - // Verify that the address has the post-NAT port and address. - if ipv6 { - return addrMatches6(addr.(unix.RawSockaddrInet6), localAddrs, redirectPort) - } - return addrMatches4(addr.(unix.RawSockaddrInet4), localAddrs, redirectPort) -} - -func recvOrigDstAddr4(sockfd int) (unix.RawSockaddrInet4, error) { - buf, err := recvOrigDstAddr(sockfd, unix.SOL_IP, unix.SizeofSockaddrInet4) - if err != nil { - return unix.RawSockaddrInet4{}, err - } - var addr unix.RawSockaddrInet4 - binary.Unmarshal(buf, usermem.ByteOrder, &addr) - return addr, nil -} - -func recvOrigDstAddr6(sockfd int) (unix.RawSockaddrInet6, error) { - buf, err := recvOrigDstAddr(sockfd, unix.SOL_IP, unix.SizeofSockaddrInet6) - if err != nil { - return unix.RawSockaddrInet6{}, err - } - var addr unix.RawSockaddrInet6 - binary.Unmarshal(buf, usermem.ByteOrder, &addr) - return addr, nil -} - -func recvOrigDstAddr(sockfd int, level uintptr, addrSize int) ([]byte, error) { - buf := make([]byte, 64) - oob := make([]byte, unix.CmsgSpace(addrSize)) - for { - _, oobn, _, _, err := unix.Recvmsg( - sockfd, - buf, // Message buffer. - oob, // Out-of-band buffer. - 0) // Flags. - if errors.Is(err, unix.EINTR) { - continue - } - if err != nil { - return nil, fmt.Errorf("failed when calling Recvmsg: %w", err) - } - oob = oob[:oobn] - - // Parse out the control message. - msgs, err := unix.ParseSocketControlMessage(oob) - if err != nil { - return nil, fmt.Errorf("failed to parse control message: %w", err) - } - return msgs[0].Data, nil - } -} - -func addrMatches4(got unix.RawSockaddrInet4, wantAddrs []net.IP, port uint16) error { - for _, wantAddr := range wantAddrs { - want := unix.RawSockaddrInet4{ - Family: unix.AF_INET, - Port: htons(port), - } - copy(want.Addr[:], wantAddr.To4()) - if got == want { - return nil - } - } - return fmt.Errorf("got %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, wantAddrs) -} - -func addrMatches6(got unix.RawSockaddrInet6, wantAddrs []net.IP, port uint16) error { - for _, wantAddr := range wantAddrs { - want := unix.RawSockaddrInet6{ - Family: unix.AF_INET6, - Port: htons(port), - } - copy(want.Addr[:], wantAddr.To16()) - if got == want { - return nil - } - } - return fmt.Errorf("got %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, wantAddrs) -} diff --git a/test/iptables/runner/BUILD b/test/iptables/runner/BUILD deleted file mode 100644 index 24504a1b9..000000000 --- a/test/iptables/runner/BUILD +++ /dev/null @@ -1,12 +0,0 @@ -load("//tools:defs.bzl", "go_binary") - -package(licenses = ["notice"]) - -go_binary( - name = "runner", - testonly = 1, - srcs = ["main.go"], - pure = True, - visibility = ["//test/iptables:__subpackages__"], - deps = ["//test/iptables"], -) diff --git a/test/iptables/runner/main.go b/test/iptables/runner/main.go deleted file mode 100644 index 9ae2d1b4d..000000000 --- a/test/iptables/runner/main.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package main runs iptables tests from within a docker container. -package main - -import ( - "context" - "flag" - "fmt" - "log" - "net" - - "gvisor.dev/gvisor/test/iptables" -) - -var ( - name = flag.String("name", "", "name of the test to run") - ipv6 = flag.Bool("ipv6", false, "whether the test utilizes ip6tables") -) - -func main() { - flag.Parse() - - // Find out which test we're running. - test, ok := iptables.Tests[*name] - if !ok { - log.Fatalf("No test found named %q", *name) - } - log.Printf("Running test %q", *name) - - // Get the IP of the local process. - ip, err := getIP() - if err != nil { - log.Fatal(err) - } - - // Run the test. - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - if err := test.ContainerAction(ctx, ip, *ipv6); err != nil { - log.Fatalf("Failed running test %q: %v", *name, err) - } - - // Emit the final line. - log.Printf("%s", iptables.TerminalStatement) -} - -// getIP listens for a connection from the local process and returns the source -// IP of that connection. -func getIP() (net.IP, error) { - localAddr := net.TCPAddr{ - Port: iptables.IPExchangePort, - } - listener, err := net.ListenTCP("tcp", &localAddr) - if err != nil { - return net.IP{}, fmt.Errorf("failed listening for IP: %v", err) - } - defer listener.Close() - conn, err := listener.AcceptTCP() - if err != nil { - return net.IP{}, fmt.Errorf("failed accepting IP: %v", err) - } - defer conn.Close() - log.Printf("Connected to %v", conn.RemoteAddr()) - - return conn.RemoteAddr().(*net.TCPAddr).IP, nil -} |