Merge pull request #1617 from kevinGC:iptables-write-filter-proto

PiperOrigin-RevId: 291249314
This commit is contained in:
gVisor bot
2020-01-23 14:48:39 -08:00
11 changed files with 124 additions and 31 deletions
+1
View File
@@ -18,6 +18,7 @@ go_library(
"//pkg/sentry/kernel",
"//pkg/sentry/usermem",
"//pkg/syserr",
"//pkg/tcpip",
"//pkg/tcpip/iptables",
"//pkg/tcpip/stack",
],
+39 -7
View File
@@ -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:
+1
View File
@@ -14,5 +14,6 @@ go_library(
deps = [
"//pkg/log",
"//pkg/tcpip",
"//pkg/tcpip/header",
],
)
+9
View File
@@ -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 {
+12 -1
View File
@@ -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
+2 -1
View File
@@ -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.
+4
View File
@@ -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(
+30
View File
@@ -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{}
+7 -1
View File
@@ -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)
+18 -21
View File
@@ -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 {
// The container may not be listening when we first connect, so retry
// upon error.
callback := func() error {
localAddr := net.TCPAddr{
Port: localPort,
}
conn, err := net.DialTCP("tcp4", &localAddr, &contAddr)
if conn != nil {
conn.Close()
return nil
}
select {
// Timed out waiting for connection to be accepted.
case <-to:
return err
default:
time.Sleep(200 * time.Millisecond)
}
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
}
+1
View File
@@ -10,6 +10,7 @@ container_image(
go_image(
name = "runner",
testonly = 1,
srcs = ["main.go"],
base = ":iptables-base",
deps = ["//test/iptables"],