diff options
author | gVisor bot <gvisor-bot@google.com> | 2020-01-23 14:48:39 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-01-23 14:48:39 -0800 |
commit | 3d10edc9423789342047f8fcf3b6054bb71ea392 (patch) | |
tree | 20cdcf677ae33aa7bd5447734d586103bb6aad57 | |
parent | 14d2ed1ad7785a54b35ef7ee949d3cf89a87e66d (diff) | |
parent | 747137c120bca27aeb259817d30ef60e01521621 (diff) |
Merge pull request #1617 from kevinGC:iptables-write-filter-proto
PiperOrigin-RevId: 291249314
-rw-r--r-- | pkg/sentry/socket/netfilter/BUILD | 1 | ||||
-rw-r--r-- | pkg/sentry/socket/netfilter/netfilter.go | 46 | ||||
-rw-r--r-- | pkg/tcpip/iptables/BUILD | 1 | ||||
-rw-r--r-- | pkg/tcpip/iptables/iptables.go | 9 | ||||
-rw-r--r-- | pkg/tcpip/iptables/types.go | 13 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv4/ipv4.go | 3 | ||||
-rw-r--r-- | test/iptables/BUILD | 4 | ||||
-rw-r--r-- | test/iptables/filter_input.go | 30 | ||||
-rw-r--r-- | test/iptables/iptables_test.go | 8 | ||||
-rw-r--r-- | test/iptables/iptables_util.go | 39 | ||||
-rw-r--r-- | test/iptables/runner/BUILD | 1 |
11 files changed, 124 insertions, 31 deletions
diff --git a/pkg/sentry/socket/netfilter/BUILD b/pkg/sentry/socket/netfilter/BUILD index 2e581e9d2..b70047d81 100644 --- a/pkg/sentry/socket/netfilter/BUILD +++ b/pkg/sentry/socket/netfilter/BUILD @@ -18,6 +18,7 @@ go_library( "//pkg/sentry/kernel", "//pkg/sentry/usermem", "//pkg/syserr", + "//pkg/tcpip", "//pkg/tcpip/iptables", "//pkg/tcpip/stack", ], diff --git a/pkg/sentry/socket/netfilter/netfilter.go b/pkg/sentry/socket/netfilter/netfilter.go index 507a77483..c65c36081 100644 --- a/pkg/sentry/socket/netfilter/netfilter.go +++ b/pkg/sentry/socket/netfilter/netfilter.go @@ -25,6 +25,7 @@ import ( "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/usermem" "gvisor.dev/gvisor/pkg/syserr" + "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/iptables" "gvisor.dev/gvisor/pkg/tcpip/stack" ) @@ -163,6 +164,9 @@ func convertNetstackToBinary(tablename string, table iptables.Table) (linux.Kern // Each rule corresponds to an entry. entry := linux.KernelIPTEntry{ IPTEntry: linux.IPTEntry{ + IP: linux.IPTIP{ + Protocol: uint16(rule.Filter.Protocol), + }, NextOffset: linux.SizeOfIPTEntry, TargetOffset: linux.SizeOfIPTEntry, }, @@ -321,12 +325,11 @@ func SetEntries(stack *stack.Stack, optVal []byte) *syserr.Error { return syserr.ErrInvalidArgument } - // TODO(gvisor.dev/issue/170): We should support IPTIP - // filtering. We reject any nonzero IPTIP values for now. - emptyIPTIP := linux.IPTIP{} - if entry.IP != emptyIPTIP { - log.Warningf("netfilter: non-empty struct iptip found") - return syserr.ErrInvalidArgument + // TODO(gvisor.dev/issue/170): We should support more IPTIP + // filtering fields. + filter, err := filterFromIPTIP(entry.IP) + if err != nil { + return err } // Get the target of the rule. @@ -336,7 +339,10 @@ func SetEntries(stack *stack.Stack, optVal []byte) *syserr.Error { } optVal = optVal[consumed:] - table.Rules = append(table.Rules, iptables.Rule{Target: target}) + table.Rules = append(table.Rules, iptables.Rule{ + Filter: filter, + Target: target, + }) offsets = append(offsets, offset) offset += linux.SizeOfIPTEntry + consumed } @@ -460,6 +466,32 @@ func parseTarget(optVal []byte) (iptables.Target, uint32, *syserr.Error) { return nil, 0, syserr.ErrInvalidArgument } +func filterFromIPTIP(iptip linux.IPTIP) (iptables.IPHeaderFilter, *syserr.Error) { + if containsUnsupportedFields(iptip) { + log.Warningf("netfilter: unsupported fields in struct iptip: %+v", iptip) + return iptables.IPHeaderFilter{}, syserr.ErrInvalidArgument + } + return iptables.IPHeaderFilter{ + Protocol: tcpip.TransportProtocolNumber(iptip.Protocol), + }, nil +} + +func containsUnsupportedFields(iptip linux.IPTIP) bool { + // Currently we check that everything except protocol is zeroed. + var emptyInetAddr = linux.InetAddr{} + var emptyInterface = [linux.IFNAMSIZ]byte{} + return iptip.Dst != emptyInetAddr || + iptip.Src != emptyInetAddr || + iptip.SrcMask != emptyInetAddr || + iptip.DstMask != emptyInetAddr || + iptip.InputInterface != emptyInterface || + iptip.OutputInterface != emptyInterface || + iptip.InputInterfaceMask != emptyInterface || + iptip.OutputInterfaceMask != emptyInterface || + iptip.Flags != 0 || + iptip.InverseFlags != 0 +} + func hookFromLinux(hook int) iptables.Hook { switch hook { case linux.NF_INET_PRE_ROUTING: diff --git a/pkg/tcpip/iptables/BUILD b/pkg/tcpip/iptables/BUILD index 2893c80cd..297eaccaf 100644 --- a/pkg/tcpip/iptables/BUILD +++ b/pkg/tcpip/iptables/BUILD @@ -14,5 +14,6 @@ go_library( deps = [ "//pkg/log", "//pkg/tcpip", + "//pkg/tcpip/header", ], ) diff --git a/pkg/tcpip/iptables/iptables.go b/pkg/tcpip/iptables/iptables.go index 605a71679..fc06b5b87 100644 --- a/pkg/tcpip/iptables/iptables.go +++ b/pkg/tcpip/iptables/iptables.go @@ -20,6 +20,7 @@ import ( "fmt" "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" ) // Table names. @@ -184,8 +185,16 @@ func (it *IPTables) checkTable(hook Hook, pkt tcpip.PacketBuffer, tablename stri panic(fmt.Sprintf("Traversed past the entire list of iptables rules in table %q.", tablename)) } +// Precondition: pk.NetworkHeader is set. func (it *IPTables) checkRule(hook Hook, pkt tcpip.PacketBuffer, table Table, ruleIdx int) Verdict { rule := table.Rules[ruleIdx] + + // First check whether the packet matches the IP header filter. + // TODO(gvisor.dev/issue/170): Support other fields of the filter. + if rule.Filter.Protocol != 0 && rule.Filter.Protocol != header.IPv4(pkt.NetworkHeader).TransportProtocol() { + return Continue + } + // Go through each rule matcher. If they all match, run // the rule target. for _, matcher := range rule.Matchers { diff --git a/pkg/tcpip/iptables/types.go b/pkg/tcpip/iptables/types.go index 9f6906100..a8b972f1b 100644 --- a/pkg/tcpip/iptables/types.go +++ b/pkg/tcpip/iptables/types.go @@ -14,7 +14,9 @@ package iptables -import "gvisor.dev/gvisor/pkg/tcpip" +import ( + "gvisor.dev/gvisor/pkg/tcpip" +) // A Hook specifies one of the hooks built into the network stack. // @@ -151,6 +153,9 @@ func (table *Table) SetMetadata(metadata interface{}) { // packets this rule applies to. If there are no matchers in the rule, it // applies to any packet. type Rule struct { + // Filter holds basic IP filtering fields common to every rule. + Filter IPHeaderFilter + // Matchers is the list of matchers for this rule. Matchers []Matcher @@ -158,6 +163,12 @@ type Rule struct { Target Target } +// IPHeaderFilter holds basic IP filtering data common to every rule. +type IPHeaderFilter struct { + // Protocol matches the transport protocol. + Protocol tcpip.TransportProtocolNumber +} + // A Matcher is the interface for matching packets. type Matcher interface { // Match returns whether the packet matches and whether the packet diff --git a/pkg/tcpip/network/ipv4/ipv4.go b/pkg/tcpip/network/ipv4/ipv4.go index 0a1453b31..85512f9b2 100644 --- a/pkg/tcpip/network/ipv4/ipv4.go +++ b/pkg/tcpip/network/ipv4/ipv4.go @@ -353,7 +353,8 @@ func (e *endpoint) HandlePacket(r *stack.Route, pkt tcpip.PacketBuffer) { } pkt.NetworkHeader = headerView[:h.HeaderLength()] - // iptables filtering. + // iptables filtering. All packets that reach here are intended for + // this machine and will not be forwarded. ipt := e.stack.IPTables() if ok := ipt.Check(iptables.Input, pkt); !ok { // iptables is telling us to drop the packet. diff --git a/test/iptables/BUILD b/test/iptables/BUILD index 372ba7abf..22f470092 100644 --- a/test/iptables/BUILD +++ b/test/iptables/BUILD @@ -4,6 +4,7 @@ package(licenses = ["notice"]) go_library( name = "iptables", + testonly = 1, srcs = [ "filter_input.go", "filter_output.go", @@ -13,6 +14,9 @@ go_library( ], importpath = "gvisor.dev/gvisor/test/iptables", visibility = ["//test/iptables:__subpackages__"], + deps = [ + "//runsc/testutil", + ], ) go_test( diff --git a/test/iptables/filter_input.go b/test/iptables/filter_input.go index 03e4a1d72..fd02ff2ff 100644 --- a/test/iptables/filter_input.go +++ b/test/iptables/filter_input.go @@ -30,6 +30,7 @@ const ( func init() { RegisterTestCase(FilterInputDropAll{}) RegisterTestCase(FilterInputDropDifferentUDPPort{}) + RegisterTestCase(FilterInputDropOnlyUDP{}) RegisterTestCase(FilterInputDropTCPDestPort{}) RegisterTestCase(FilterInputDropTCPSrcPort{}) RegisterTestCase(FilterInputDropUDPPort{}) @@ -67,6 +68,35 @@ func (FilterInputDropUDP) LocalAction(ip net.IP) error { return sendUDPLoop(ip, dropPort, sendloopDuration) } +// FilterInputDropOnlyUDP tests that "-p udp -j DROP" only affects UDP traffic. +type FilterInputDropOnlyUDP struct{} + +// Name implements TestCase.Name. +func (FilterInputDropOnlyUDP) Name() string { + return "FilterInputDropOnlyUDP" +} + +// ContainerAction implements TestCase.ContainerAction. +func (FilterInputDropOnlyUDP) ContainerAction(ip net.IP) error { + if err := filterTable("-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil { + return err + } + + // Listen for a TCP connection, which should be allowed. + if err := listenTCP(acceptPort, sendloopDuration); err != nil { + return fmt.Errorf("failed to establish a connection %v", err) + } + + return nil +} + +// LocalAction implements TestCase.LocalAction. +func (FilterInputDropOnlyUDP) LocalAction(ip net.IP) error { + // Try to establish a TCP connection with the container, which should + // succeed. + return connectTCP(ip, acceptPort, dropPort, sendloopDuration) +} + // FilterInputDropUDPPort tests that we can drop UDP traffic by port. type FilterInputDropUDPPort struct{} diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go index 1cda10365..679a29bef 100644 --- a/test/iptables/iptables_test.go +++ b/test/iptables/iptables_test.go @@ -15,6 +15,7 @@ package iptables import ( + "flag" "fmt" "net" "os" @@ -22,7 +23,6 @@ import ( "testing" "time" - "flag" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/runsc/dockerutil" "gvisor.dev/gvisor/runsc/testutil" @@ -184,6 +184,12 @@ func TestFilterInputDropAll(t *testing.T) { } } +func TestFilterInputDropOnlyUDP(t *testing.T) { + if err := singleTest(FilterInputDropOnlyUDP{}); err != nil { + t.Fatal(err) + } +} + func TestNATRedirectUDPPort(t *testing.T) { if err := singleTest(NATRedirectUDPPort{}); err != nil { t.Fatal(err) diff --git a/test/iptables/iptables_util.go b/test/iptables/iptables_util.go index 1c4f4f665..043114c78 100644 --- a/test/iptables/iptables_util.go +++ b/test/iptables/iptables_util.go @@ -19,6 +19,8 @@ import ( "net" "os/exec" "time" + + "gvisor.dev/gvisor/runsc/testutil" ) const iptablesBinary = "iptables" @@ -105,31 +107,26 @@ func listenTCP(port int, timeout time.Duration) error { } // connectTCP connects the TCP server over specified local port, server IP and remote/server port. -func connectTCP(ip net.IP, remotePort, localPort int, duration time.Duration) error { - remote := net.TCPAddr{ +func connectTCP(ip net.IP, remotePort, localPort int, timeout time.Duration) error { + contAddr := net.TCPAddr{ IP: ip, Port: remotePort, } - - local := net.TCPAddr{ - Port: localPort, - } - - // Container may not be up. Retry DialTCP over a duration. - to := time.After(duration) - for { - conn, err := net.DialTCP("tcp4", &local, &remote) - if err == nil { - conn.Close() - return nil + // The container may not be listening when we first connect, so retry + // upon error. + callback := func() error { + localAddr := net.TCPAddr{ + Port: localPort, } - select { - // Timed out waiting for connection to be accepted. - case <-to: - return err - default: - time.Sleep(200 * time.Millisecond) + conn, err := net.DialTCP("tcp4", &localAddr, &contAddr) + if conn != nil { + conn.Close() } + return err } - return fmt.Errorf("Failed to establish connection on port %d", localPort) + if err := testutil.Poll(callback, timeout); err != nil { + return fmt.Errorf("timed out waiting to send IP, most recent error: %v", err) + } + + return nil } diff --git a/test/iptables/runner/BUILD b/test/iptables/runner/BUILD index c6c42d870..a5b6f082c 100644 --- a/test/iptables/runner/BUILD +++ b/test/iptables/runner/BUILD @@ -10,6 +10,7 @@ container_image( go_image( name = "runner", + testonly = 1, srcs = ["main.go"], base = ":iptables-base", deps = ["//test/iptables"], |