mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Merge pull request #1617 from kevinGC:iptables-write-filter-proto
PiperOrigin-RevId: 291249314
This commit is contained in:
@@ -18,6 +18,7 @@ go_library(
|
||||
"//pkg/sentry/kernel",
|
||||
"//pkg/sentry/usermem",
|
||||
"//pkg/syserr",
|
||||
"//pkg/tcpip",
|
||||
"//pkg/tcpip/iptables",
|
||||
"//pkg/tcpip/stack",
|
||||
],
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -14,5 +14,6 @@ go_library(
|
||||
deps = [
|
||||
"//pkg/log",
|
||||
"//pkg/tcpip",
|
||||
"//pkg/tcpip/header",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,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{}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ container_image(
|
||||
|
||||
go_image(
|
||||
name = "runner",
|
||||
testonly = 1,
|
||||
srcs = ["main.go"],
|
||||
base = ":iptables-base",
|
||||
deps = ["//test/iptables"],
|
||||
|
||||
Reference in New Issue
Block a user