Enable iptables source filtering (-s/--source)

This commit is contained in:
Kevin Krakauer 2020-05-28 16:44:15 -07:00
parent f7418e2159
commit c55b84e16a
5 changed files with 147 additions and 51 deletions

View File

@ -64,6 +64,8 @@ const enableLogging = false
var emptyFilter = stack.IPHeaderFilter{
Dst: "\x00\x00\x00\x00",
DstMask: "\x00\x00\x00\x00",
Src: "\x00\x00\x00\x00",
SrcMask: "\x00\x00\x00\x00",
}
// nflog logs messages related to the writing and reading of iptables.
@ -214,11 +216,16 @@ func convertNetstackToBinary(tablename string, table stack.Table) (linux.KernelI
}
copy(entry.IPTEntry.IP.Dst[:], rule.Filter.Dst)
copy(entry.IPTEntry.IP.DstMask[:], rule.Filter.DstMask)
copy(entry.IPTEntry.IP.Src[:], rule.Filter.Src)
copy(entry.IPTEntry.IP.SrcMask[:], rule.Filter.SrcMask)
copy(entry.IPTEntry.IP.OutputInterface[:], rule.Filter.OutputInterface)
copy(entry.IPTEntry.IP.OutputInterfaceMask[:], rule.Filter.OutputInterfaceMask)
if rule.Filter.DstInvert {
entry.IPTEntry.IP.InverseFlags |= linux.IPT_INV_DSTIP
}
if rule.Filter.SrcInvert {
entry.IPTEntry.IP.InverseFlags |= linux.IPT_INV_SRCIP
}
if rule.Filter.OutputInterfaceInvert {
entry.IPTEntry.IP.InverseFlags |= linux.IPT_INV_VIA_OUT
}
@ -737,6 +744,9 @@ func filterFromIPTIP(iptip linux.IPTIP) (stack.IPHeaderFilter, error) {
if len(iptip.Dst) != header.IPv4AddressSize || len(iptip.DstMask) != header.IPv4AddressSize {
return stack.IPHeaderFilter{}, fmt.Errorf("incorrect length of destination (%d) and/or destination mask (%d) fields", len(iptip.Dst), len(iptip.DstMask))
}
if len(iptip.Src) != header.IPv4AddressSize || len(iptip.SrcMask) != header.IPv4AddressSize {
return stack.IPHeaderFilter{}, fmt.Errorf("incorrect length of source (%d) and/or source mask (%d) fields", len(iptip.Src), len(iptip.SrcMask))
}
n := bytes.IndexByte([]byte(iptip.OutputInterface[:]), 0)
if n == -1 {
@ -755,6 +765,9 @@ func filterFromIPTIP(iptip linux.IPTIP) (stack.IPHeaderFilter, error) {
Dst: tcpip.Address(iptip.Dst[:]),
DstMask: tcpip.Address(iptip.DstMask[:]),
DstInvert: iptip.InverseFlags&linux.IPT_INV_DSTIP != 0,
Src: tcpip.Address(iptip.Src[:]),
SrcMask: tcpip.Address(iptip.SrcMask[:]),
SrcInvert: iptip.InverseFlags&linux.IPT_INV_SRCIP != 0,
OutputInterface: ifname,
OutputInterfaceMask: ifnameMask,
OutputInterfaceInvert: iptip.InverseFlags&linux.IPT_INV_VIA_OUT != 0,
@ -765,15 +778,13 @@ func containsUnsupportedFields(iptip linux.IPTIP) bool {
// The following features are supported:
// - Protocol
// - Dst and DstMask
// - Src and SrcMask
// - The inverse destination IP check flag
// - OutputInterface, OutputInterfaceMask and its inverse.
var emptyInetAddr = linux.InetAddr{}
var emptyInterface = [linux.IFNAMSIZ]byte{}
// Disable any supported inverse flags.
inverseMask := uint8(linux.IPT_INV_DSTIP) | uint8(linux.IPT_INV_VIA_OUT)
return iptip.Src != emptyInetAddr ||
iptip.SrcMask != emptyInetAddr ||
iptip.InputInterface != emptyInterface ||
inverseMask := uint8(linux.IPT_INV_DSTIP) | uint8(linux.IPT_INV_SRCIP) | uint8(linux.IPT_INV_VIA_OUT)
return iptip.InputInterface != emptyInterface ||
iptip.InputInterfaceMask != emptyInterface ||
iptip.Flags != 0 ||
iptip.InverseFlags&^inverseMask != 0

View File

@ -16,7 +16,6 @@ package stack
import (
"fmt"
"strings"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
@ -314,7 +313,7 @@ func (it *IPTables) checkRule(hook Hook, pkt *PacketBuffer, table Table, ruleIdx
}
// Check whether the packet matches the IP header filter.
if !filterMatch(rule.Filter, header.IPv4(pkt.NetworkHeader), hook, nicName) {
if !rule.Filter.match(header.IPv4(pkt.NetworkHeader), hook, nicName) {
// Continue on to the next rule.
return RuleJump, ruleIdx + 1
}
@ -335,47 +334,3 @@ func (it *IPTables) checkRule(hook Hook, pkt *PacketBuffer, table Table, ruleIdx
// All the matchers matched, so run the target.
return rule.Target.Action(pkt, &it.connections, hook, gso, r, address)
}
func filterMatch(filter IPHeaderFilter, hdr header.IPv4, hook Hook, nicName string) bool {
// TODO(gvisor.dev/issue/170): Support other fields of the filter.
// Check the transport protocol.
if filter.Protocol != 0 && filter.Protocol != hdr.TransportProtocol() {
return false
}
// Check the destination IP.
dest := hdr.DestinationAddress()
matches := true
for i := range filter.Dst {
if dest[i]&filter.DstMask[i] != filter.Dst[i] {
matches = false
break
}
}
if matches == filter.DstInvert {
return false
}
// Check the output interface.
// TODO(gvisor.dev/issue/170): Add the check for FORWARD and POSTROUTING
// hooks after supported.
if hook == Output {
n := len(filter.OutputInterface)
if n == 0 {
return true
}
// If the interface name ends with '+', any interface which begins
// with the name should be matched.
ifName := filter.OutputInterface
matches = true
if strings.HasSuffix(ifName, "+") {
matches = strings.HasPrefix(nicName, ifName[:n-1])
} else {
matches = nicName == ifName
}
return filter.OutputInterfaceInvert != matches
}
return true
}

View File

@ -15,7 +15,10 @@
package stack
import (
"strings"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
// A Hook specifies one of the hooks built into the network stack.
@ -159,6 +162,16 @@ type IPHeaderFilter struct {
// comparison.
DstInvert bool
// Src matches the source IP address.
Src tcpip.Address
// SrcMask masks bits of the source IP address when comparing with Src.
SrcMask tcpip.Address
// SrcInvert inverts the meaning of the source IP check, i.e. when true the
// filter will match packets that fail the source comparison.
SrcInvert bool
// OutputInterface matches the name of the outgoing interface for the
// packet.
OutputInterface string
@ -173,6 +186,55 @@ type IPHeaderFilter struct {
OutputInterfaceInvert bool
}
// match returns whether hdr matches the filter.
func (fl IPHeaderFilter) match(hdr header.IPv4, hook Hook, nicName string) bool {
// TODO(gvisor.dev/issue/170): Support other fields of the filter.
// Check the transport protocol.
if fl.Protocol != 0 && fl.Protocol != hdr.TransportProtocol() {
return false
}
// Check the source and destination IPs.
if !filterAddress(hdr.DestinationAddress(), fl.DstMask, fl.Dst, fl.DstInvert) || !filterAddress(hdr.SourceAddress(), fl.SrcMask, fl.Src, fl.SrcInvert) {
return false
}
// Check the output interface.
// TODO(gvisor.dev/issue/170): Add the check for FORWARD and POSTROUTING
// hooks after supported.
if hook == Output {
n := len(fl.OutputInterface)
if n == 0 {
return true
}
// If the interface name ends with '+', any interface which begins
// with the name should be matched.
ifName := fl.OutputInterface
matches := true
if strings.HasSuffix(ifName, "+") {
matches = strings.HasPrefix(nicName, ifName[:n-1])
} else {
matches = nicName == ifName
}
return fl.OutputInterfaceInvert != matches
}
return true
}
// filterAddress returns whether addr matches the filter.
func filterAddress(addr, mask, filterAddr tcpip.Address, invert bool) bool {
matches := true
for i := range filterAddr {
if addr[i]&mask[i] != filterAddr[i] {
matches = false
break
}
}
return matches != invert
}
// A Matcher is the interface for matching packets.
type Matcher interface {
// Name returns the name of the Matcher.

View File

@ -49,6 +49,8 @@ func init() {
RegisterTestCase(FilterInputJumpTwice{})
RegisterTestCase(FilterInputDestination{})
RegisterTestCase(FilterInputInvertDestination{})
RegisterTestCase(FilterInputSource{})
RegisterTestCase(FilterInputInvertSource{})
}
// FilterInputDropUDP tests that we can drop UDP traffic.
@ -667,3 +669,61 @@ func (FilterInputInvertDestination) ContainerAction(ip net.IP) error {
func (FilterInputInvertDestination) LocalAction(ip net.IP) error {
return sendUDPLoop(ip, acceptPort, sendloopDuration)
}
// FilterInputSource verifies that we can filter packets via `-d
// <ipaddr>`.
type FilterInputSource struct{}
// Name implements TestCase.Name.
func (FilterInputSource) Name() string {
return "FilterInputSource"
}
// ContainerAction implements TestCase.ContainerAction.
func (FilterInputSource) ContainerAction(ip net.IP) 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(rules); err != nil {
return err
}
return listenUDP(acceptPort, sendloopDuration)
}
// LocalAction implements TestCase.LocalAction.
func (FilterInputSource) LocalAction(ip net.IP) error {
return sendUDPLoop(ip, acceptPort, sendloopDuration)
}
// FilterInputInvertSource verifies that we can filter packets via `! -d
// <ipaddr>`.
type FilterInputInvertSource struct{}
// Name implements TestCase.Name.
func (FilterInputInvertSource) Name() string {
return "FilterInputInvertSource"
}
// ContainerAction implements TestCase.ContainerAction.
func (FilterInputInvertSource) ContainerAction(ip net.IP) 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, "-j", "ACCEPT"},
}
if err := filterTableRules(rules); err != nil {
return err
}
return listenUDP(acceptPort, sendloopDuration)
}
// LocalAction implements TestCase.LocalAction.
func (FilterInputInvertSource) LocalAction(ip net.IP) error {
return sendUDPLoop(ip, acceptPort, sendloopDuration)
}

View File

@ -302,3 +302,11 @@ func TestNATPreRedirectInvert(t *testing.T) {
func TestNATRedirectRequiresProtocol(t *testing.T) {
singleTest(t, NATRedirectRequiresProtocol{})
}
func TestInputSource(t *testing.T) {
singleTest(t, FilterInputSource{})
}
func TestInputInvertSource(t *testing.T) {
singleTest(t, FilterInputInvertSource{})
}