Enable iptables source filtering (-s/--source)
This commit is contained in:
parent
f7418e2159
commit
c55b84e16a
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue