summaryrefslogtreecommitdiffhomepage
path: root/test/iptables
diff options
context:
space:
mode:
authorAndrei Vagin <avagin@google.com>2020-03-06 21:12:32 -0800
committerGitHub <noreply@github.com>2020-03-06 21:12:32 -0800
commitbf87da89d3c43555fd57e8f1d7aed21b6da78de4 (patch)
tree744ba15a2f663d64d56bf1c70bdfe4096f6a1af9 /test/iptables
parent89957c6c87b5ad5c7bac68f93d9472388db57702 (diff)
parentddfc7239be94fa9711df877a66a9718aabff8b96 (diff)
Merge branch 'master' into pr_lazy_fpsimd_2
Diffstat (limited to 'test/iptables')
-rw-r--r--test/iptables/README.md14
-rw-r--r--test/iptables/filter_input.go347
-rw-r--r--test/iptables/iptables_test.go62
-rw-r--r--test/iptables/iptables_util.go21
-rw-r--r--test/iptables/nat.go4
5 files changed, 442 insertions, 6 deletions
diff --git a/test/iptables/README.md b/test/iptables/README.md
index 9f8e34420..cc8a2fcac 100644
--- a/test/iptables/README.md
+++ b/test/iptables/README.md
@@ -2,6 +2,9 @@
iptables tests are run via `scripts/iptables_test.sh`.
+iptables requires raw socket support, so you must add the `--net-raw=true` flag
+to `/etc/docker/daemon.json` in order to use it.
+
## Test Structure
Each test implements `TestCase`, providing (1) a function to run inside the
@@ -25,10 +28,17 @@ Your test is now runnable with bazel!
## Run individual tests
-Build the testing Docker container:
+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
-$ bazel run //test/iptables/runner -- --norun
+$ bazel run //test/iptables/runner:runner-image -- --norun
```
Run an individual test via:
diff --git a/test/iptables/filter_input.go b/test/iptables/filter_input.go
index fd02ff2ff..b2fb6401a 100644
--- a/test/iptables/filter_input.go
+++ b/test/iptables/filter_input.go
@@ -15,6 +15,7 @@
package iptables
import (
+ "errors"
"fmt"
"net"
"time"
@@ -25,6 +26,7 @@ const (
acceptPort = 2402
sendloopDuration = 2 * time.Second
network = "udp4"
+ chainName = "foochain"
)
func init() {
@@ -35,6 +37,16 @@ func init() {
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{})
}
// FilterInputDropUDP tests that we can drop UDP traffic.
@@ -248,3 +260,338 @@ func (FilterInputDropAll) ContainerAction(ip net.IP) error {
func (FilterInputDropAll) LocalAction(ip net.IP) error {
return sendUDPLoop(ip, dropPort, sendloopDuration)
}
+
+// 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{}
+
+// Name implements TestCase.Name.
+func (FilterInputMultiUDPRules) Name() string {
+ return "FilterInputMultiUDPRules"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputMultiUDPRules) ContainerAction(ip net.IP) 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(rules)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputMultiUDPRules) LocalAction(ip net.IP) error {
+ // No-op.
+ return nil
+}
+
+// FilterInputRequireProtocolUDP checks that "-m udp" requires "-p udp" to be
+// specified.
+type FilterInputRequireProtocolUDP struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputRequireProtocolUDP) Name() string {
+ return "FilterInputRequireProtocolUDP"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputRequireProtocolUDP) ContainerAction(ip net.IP) error {
+ if err := filterTable("-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
+}
+
+func (FilterInputRequireProtocolUDP) LocalAction(ip net.IP) error {
+ // No-op.
+ return nil
+}
+
+// FilterInputCreateUserChain tests chain creation.
+type FilterInputCreateUserChain struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputCreateUserChain) Name() string {
+ return "FilterInputCreateUserChain"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputCreateUserChain) ContainerAction(ip net.IP) error {
+ rules := [][]string{
+ // Create a chain.
+ {"-N", chainName},
+ // Add a simple rule to the chain.
+ {"-A", chainName, "-j", "DROP"},
+ }
+ return filterTableRules(rules)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputCreateUserChain) LocalAction(ip net.IP) error {
+ // No-op.
+ return nil
+}
+
+// FilterInputDefaultPolicyAccept tests the default ACCEPT policy.
+type FilterInputDefaultPolicyAccept struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputDefaultPolicyAccept) Name() string {
+ return "FilterInputDefaultPolicyAccept"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputDefaultPolicyAccept) ContainerAction(ip net.IP) error {
+ // Set the default policy to accept, then receive a packet.
+ if err := filterTable("-P", "INPUT", "ACCEPT"); err != nil {
+ return err
+ }
+ return listenUDP(acceptPort, sendloopDuration)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputDefaultPolicyAccept) LocalAction(ip net.IP) error {
+ return sendUDPLoop(ip, acceptPort, sendloopDuration)
+}
+
+// FilterInputDefaultPolicyDrop tests the default DROP policy.
+type FilterInputDefaultPolicyDrop struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputDefaultPolicyDrop) Name() string {
+ return "FilterInputDefaultPolicyDrop"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputDefaultPolicyDrop) ContainerAction(ip net.IP) error {
+ if err := filterTable("-P", "INPUT", "DROP"); err != nil {
+ return err
+ }
+
+ // Listen for UDP packets on dropPort.
+ if err := listenUDP(dropPort, sendloopDuration); err == nil {
+ return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort)
+ } else if netErr, ok := err.(net.Error); !ok || !netErr.Timeout() {
+ 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(ip net.IP) error {
+ return sendUDPLoop(ip, acceptPort, sendloopDuration)
+}
+
+// FilterInputReturnUnderflow tests that -j RETURN in a built-in chain causes
+// the underflow rule (i.e. default policy) to be executed.
+type FilterInputReturnUnderflow struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputReturnUnderflow) Name() string {
+ return "FilterInputReturnUnderflow"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputReturnUnderflow) ContainerAction(ip net.IP) 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(rules); err != nil {
+ return err
+ }
+
+ // We should receive packets, as the RETURN rule will trigger the default
+ // ACCEPT policy.
+ return listenUDP(acceptPort, sendloopDuration)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputReturnUnderflow) LocalAction(ip net.IP) error {
+ return sendUDPLoop(ip, acceptPort, sendloopDuration)
+}
+
+// FilterInputSerializeJump verifies that we can serialize jumps.
+type FilterInputSerializeJump struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputSerializeJump) Name() string {
+ return "FilterInputSerializeJump"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputSerializeJump) ContainerAction(ip net.IP) error {
+ // Write a JUMP rule, the serialize it with `-L`.
+ rules := [][]string{
+ {"-N", chainName},
+ {"-A", "INPUT", "-j", chainName},
+ {"-L"},
+ }
+ return filterTableRules(rules)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputSerializeJump) LocalAction(ip net.IP) error {
+ // No-op.
+ return nil
+}
+
+// FilterInputJumpBasic jumps to a chain and executes a rule there.
+type FilterInputJumpBasic struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputJumpBasic) Name() string {
+ return "FilterInputJumpBasic"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputJumpBasic) ContainerAction(ip net.IP) error {
+ rules := [][]string{
+ {"-P", "INPUT", "DROP"},
+ {"-N", chainName},
+ {"-A", "INPUT", "-j", chainName},
+ {"-A", chainName, "-j", "ACCEPT"},
+ }
+ if err := filterTableRules(rules); err != nil {
+ return err
+ }
+
+ // Listen for UDP packets on acceptPort.
+ return listenUDP(acceptPort, sendloopDuration)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputJumpBasic) LocalAction(ip net.IP) error {
+ return sendUDPLoop(ip, acceptPort, sendloopDuration)
+}
+
+// FilterInputJumpReturn jumps, returns, and executes a rule.
+type FilterInputJumpReturn struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputJumpReturn) Name() string {
+ return "FilterInputJumpReturn"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputJumpReturn) ContainerAction(ip net.IP) error {
+ rules := [][]string{
+ {"-N", chainName},
+ {"-P", "INPUT", "ACCEPT"},
+ {"-A", "INPUT", "-j", chainName},
+ {"-A", chainName, "-j", "RETURN"},
+ {"-A", chainName, "-j", "DROP"},
+ }
+ if err := filterTableRules(rules); err != nil {
+ return err
+ }
+
+ // Listen for UDP packets on acceptPort.
+ return listenUDP(acceptPort, sendloopDuration)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputJumpReturn) LocalAction(ip net.IP) error {
+ return sendUDPLoop(ip, acceptPort, sendloopDuration)
+}
+
+// FilterInputJumpReturnDrop jumps to a chain, returns, and DROPs packets.
+type FilterInputJumpReturnDrop struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputJumpReturnDrop) Name() string {
+ return "FilterInputJumpReturnDrop"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputJumpReturnDrop) ContainerAction(ip net.IP) error {
+ rules := [][]string{
+ {"-N", chainName},
+ {"-A", "INPUT", "-j", chainName},
+ {"-A", "INPUT", "-j", "DROP"},
+ {"-A", chainName, "-j", "RETURN"},
+ }
+ if err := filterTableRules(rules); err != nil {
+ return err
+ }
+
+ // Listen for UDP packets on dropPort.
+ if err := listenUDP(dropPort, sendloopDuration); err == nil {
+ return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort)
+ } else if netErr, ok := err.(net.Error); !ok || !netErr.Timeout() {
+ 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(ip net.IP) error {
+ return sendUDPLoop(ip, dropPort, sendloopDuration)
+}
+
+// FilterInputJumpBuiltin verifies that jumping to a top-levl chain is illegal.
+type FilterInputJumpBuiltin struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputJumpBuiltin) Name() string {
+ return "FilterInputJumpBuiltin"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputJumpBuiltin) ContainerAction(ip net.IP) error {
+ if err := filterTable("-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(ip net.IP) error {
+ // No-op.
+ return nil
+}
+
+// FilterInputJumpTwice jumps twice, then returns twice and executes a rule.
+type FilterInputJumpTwice struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputJumpTwice) Name() string {
+ return "FilterInputJumpTwice"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputJumpTwice) ContainerAction(ip net.IP) 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(rules); err != nil {
+ return err
+ }
+
+ // UDP packets should jump and return twice, eventually hitting the
+ // ACCEPT rule.
+ return listenUDP(acceptPort, sendloopDuration)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputJumpTwice) LocalAction(ip net.IP) error {
+ return sendUDPLoop(ip, acceptPort, sendloopDuration)
+}
diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go
index 679a29bef..0621861eb 100644
--- a/test/iptables/iptables_test.go
+++ b/test/iptables/iptables_test.go
@@ -30,7 +30,7 @@ import (
const timeout = 18 * time.Second
-var image = flag.String("image", "bazel/test/iptables/runner:runner", "image to run tests in")
+var image = flag.String("image", "bazel/test/iptables/runner:runner-image", "image to run tests in")
type result struct {
output string
@@ -214,6 +214,30 @@ func TestFilterInputDropTCPSrcPort(t *testing.T) {
}
}
+func TestFilterInputCreateUserChain(t *testing.T) {
+ if err := singleTest(FilterInputCreateUserChain{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestFilterInputDefaultPolicyAccept(t *testing.T) {
+ if err := singleTest(FilterInputDefaultPolicyAccept{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestFilterInputDefaultPolicyDrop(t *testing.T) {
+ if err := singleTest(FilterInputDefaultPolicyDrop{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestFilterInputReturnUnderflow(t *testing.T) {
+ if err := singleTest(FilterInputReturnUnderflow{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
func TestFilterOutputDropTCPDestPort(t *testing.T) {
if err := singleTest(FilterOutputDropTCPDestPort{}); err != nil {
t.Fatal(err)
@@ -225,3 +249,39 @@ func TestFilterOutputDropTCPSrcPort(t *testing.T) {
t.Fatal(err)
}
}
+
+func TestJumpSerialize(t *testing.T) {
+ if err := singleTest(FilterInputSerializeJump{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestJumpBasic(t *testing.T) {
+ if err := singleTest(FilterInputJumpBasic{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestJumpReturn(t *testing.T) {
+ if err := singleTest(FilterInputJumpReturn{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestJumpReturnDrop(t *testing.T) {
+ if err := singleTest(FilterInputJumpReturnDrop{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestJumpBuiltin(t *testing.T) {
+ if err := singleTest(FilterInputJumpBuiltin{}); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestJumpTwice(t *testing.T) {
+ if err := singleTest(FilterInputJumpTwice{}); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/test/iptables/iptables_util.go b/test/iptables/iptables_util.go
index 043114c78..32cf5a417 100644
--- a/test/iptables/iptables_util.go
+++ b/test/iptables/iptables_util.go
@@ -27,7 +27,16 @@ const iptablesBinary = "iptables"
// filterTable calls `iptables -t filter` with the given args.
func filterTable(args ...string) error {
- args = append([]string{"-t", "filter"}, args...)
+ return tableCmd("filter", args)
+}
+
+// natTable calls `iptables -t nat` with the given args.
+func natTable(args ...string) error {
+ return tableCmd("nat", args)
+}
+
+func tableCmd(table string, args []string) error {
+ args = append([]string{"-t", table}, args...)
cmd := exec.Command(iptablesBinary, 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))
@@ -35,6 +44,16 @@ func filterTable(args ...string) error {
return nil
}
+// filterTableRules is like filterTable, but runs multiple iptables commands.
+func filterTableRules(argsList [][]string) error {
+ for _, args := range argsList {
+ if err := filterTable(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(port int, timeout time.Duration) error {
diff --git a/test/iptables/nat.go b/test/iptables/nat.go
index b5c6f927e..a01117ec8 100644
--- a/test/iptables/nat.go
+++ b/test/iptables/nat.go
@@ -38,7 +38,7 @@ func (NATRedirectUDPPort) Name() string {
// ContainerAction implements TestCase.ContainerAction.
func (NATRedirectUDPPort) ContainerAction(ip net.IP) error {
- if err := filterTable("-t", "nat", "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
+ if err := natTable("-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
return err
}
@@ -63,7 +63,7 @@ func (NATDropUDP) Name() string {
// ContainerAction implements TestCase.ContainerAction.
func (NATDropUDP) ContainerAction(ip net.IP) error {
- if err := filterTable("-t", "nat", "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
+ if err := natTable("-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
return err
}