mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
iptables: refactor to make targets extendable
Like matchers, targets should use a module-like register/lookup system. This replaces the brittle switch statements we had before. The only behavior change is supporing IPT_GET_REVISION_TARGET. This makes it much easier to add IPv6 redirect in the next change. Updates #3549. PiperOrigin-RevId: 334469418
This commit is contained in:
committed by
gVisor bot
parent
1d88bce55e
commit
7fbb45e8ed
@@ -265,6 +265,18 @@ type KernelXTEntryMatch struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// XTGetRevision corresponds to xt_get_revision in
|
||||
// include/uapi/linux/netfilter/x_tables.h
|
||||
//
|
||||
// +marshal
|
||||
type XTGetRevision struct {
|
||||
Name ExtensionName
|
||||
Revision uint8
|
||||
}
|
||||
|
||||
// SizeOfXTGetRevision is the size of an XTGetRevision.
|
||||
const SizeOfXTGetRevision = 30
|
||||
|
||||
// XTEntryTarget holds a target for a rule. For example, it can specify that
|
||||
// packets matching the rule should DROP, ACCEPT, or use an extension target.
|
||||
// iptables-extension(8) has a list of possible targets.
|
||||
@@ -285,6 +297,13 @@ type XTEntryTarget struct {
|
||||
// SizeOfXTEntryTarget is the size of an XTEntryTarget.
|
||||
const SizeOfXTEntryTarget = 32
|
||||
|
||||
// KernelXTEntryTarget is identical to XTEntryTarget, but contains a
|
||||
// variable-length Data field.
|
||||
type KernelXTEntryTarget struct {
|
||||
XTEntryTarget
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// XTStandardTarget is a built-in target, one of ACCEPT, DROP, JUMP, QUEUE,
|
||||
// RETURN, or jump. It corresponds to struct xt_standard_target in
|
||||
// include/uapi/linux/netfilter/x_tables.h.
|
||||
@@ -510,6 +529,8 @@ type IPTReplace struct {
|
||||
const SizeOfIPTReplace = 96
|
||||
|
||||
// ExtensionName holds the name of a netfilter extension.
|
||||
//
|
||||
// +marshal
|
||||
type ExtensionName [XT_EXTENSION_MAXNAMELEN]byte
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/binary"
|
||||
"gvisor.dev/gvisor/pkg/syserr"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
@@ -37,7 +39,7 @@ type matchMaker interface {
|
||||
// name is the matcher name as stored in the xt_entry_match struct.
|
||||
name() string
|
||||
|
||||
// marshal converts from an stack.Matcher to an ABI struct.
|
||||
// marshal converts from a stack.Matcher to an ABI struct.
|
||||
marshal(matcher stack.Matcher) []byte
|
||||
|
||||
// unmarshal converts from the ABI matcher struct to an
|
||||
@@ -93,3 +95,71 @@ func unmarshalMatcher(match linux.XTEntryMatch, filter stack.IPHeaderFilter, buf
|
||||
}
|
||||
return matchMaker.unmarshal(buf, filter)
|
||||
}
|
||||
|
||||
// targetMaker knows how to (un)marshal a target. Once registered,
|
||||
// marshalTarget and unmarshalTarget can be used.
|
||||
type targetMaker interface {
|
||||
// id uniquely identifies the target.
|
||||
id() stack.TargetID
|
||||
|
||||
// marshal converts from a stack.Target to an ABI struct.
|
||||
marshal(target stack.Target) []byte
|
||||
|
||||
// unmarshal converts from the ABI matcher struct to a stack.Target.
|
||||
unmarshal(buf []byte, filter stack.IPHeaderFilter) (stack.Target, *syserr.Error)
|
||||
}
|
||||
|
||||
// targetMakers maps the TargetID of supported targets to the targetMaker that
|
||||
// marshals and unmarshals it. It is immutable after package initialization.
|
||||
var targetMakers = map[stack.TargetID]targetMaker{}
|
||||
|
||||
func targetRevision(name string, netProto tcpip.NetworkProtocolNumber, rev uint8) (uint8, bool) {
|
||||
tid := stack.TargetID{
|
||||
Name: name,
|
||||
NetworkProtocol: netProto,
|
||||
Revision: rev,
|
||||
}
|
||||
if _, ok := targetMakers[tid]; !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Return the highest supported revision unless rev is higher.
|
||||
for _, other := range targetMakers {
|
||||
otherID := other.id()
|
||||
if name == otherID.Name && netProto == otherID.NetworkProtocol && otherID.Revision > rev {
|
||||
rev = uint8(otherID.Revision)
|
||||
}
|
||||
}
|
||||
return rev, true
|
||||
}
|
||||
|
||||
// registerTargetMaker should be called by target extensions to register them
|
||||
// with the netfilter package.
|
||||
func registerTargetMaker(tm targetMaker) {
|
||||
if _, ok := targetMakers[tm.id()]; ok {
|
||||
panic(fmt.Sprintf("multiple targets registered with name %q.", tm.id()))
|
||||
}
|
||||
targetMakers[tm.id()] = tm
|
||||
}
|
||||
|
||||
func marshalTarget(target stack.Target) []byte {
|
||||
targetMaker, ok := targetMakers[target.ID()]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown target of type %T with id %+v.", target, target.ID()))
|
||||
}
|
||||
return targetMaker.marshal(target)
|
||||
}
|
||||
|
||||
func unmarshalTarget(target linux.XTEntryTarget, filter stack.IPHeaderFilter, buf []byte) (stack.Target, *syserr.Error) {
|
||||
tid := stack.TargetID{
|
||||
Name: target.Name.String(),
|
||||
NetworkProtocol: filter.NetworkProtocol(),
|
||||
Revision: target.Revision,
|
||||
}
|
||||
targetMaker, ok := targetMakers[tid]
|
||||
if !ok {
|
||||
nflog("unsupported target with name %q", target.Name.String())
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
return targetMaker.unmarshal(buf, filter)
|
||||
}
|
||||
|
||||
@@ -181,18 +181,23 @@ func modifyEntries4(stk *stack.Stack, optVal []byte, replace *linux.IPTReplace,
|
||||
nflog("entry doesn't have enough room for its target (only %d bytes remain)", len(optVal))
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
target, err := parseTarget(filter, optVal[:targetSize])
|
||||
if err != nil {
|
||||
nflog("failed to parse target: %v", err)
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
|
||||
rule := stack.Rule{
|
||||
Filter: filter,
|
||||
Matchers: matchers,
|
||||
}
|
||||
|
||||
{
|
||||
target, err := parseTarget(filter, optVal[:targetSize], false /* ipv6 */)
|
||||
if err != nil {
|
||||
nflog("failed to parse target: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
rule.Target = target
|
||||
}
|
||||
optVal = optVal[targetSize:]
|
||||
|
||||
table.Rules = append(table.Rules, stack.Rule{
|
||||
Filter: filter,
|
||||
Target: target,
|
||||
Matchers: matchers,
|
||||
})
|
||||
table.Rules = append(table.Rules, rule)
|
||||
offsets[offset] = int(entryIdx)
|
||||
offset += uint32(entry.NextOffset)
|
||||
|
||||
|
||||
@@ -184,18 +184,23 @@ func modifyEntries6(stk *stack.Stack, optVal []byte, replace *linux.IPTReplace,
|
||||
nflog("entry doesn't have enough room for its target (only %d bytes remain)", len(optVal))
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
target, err := parseTarget(filter, optVal[:targetSize])
|
||||
if err != nil {
|
||||
nflog("failed to parse target: %v", err)
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
|
||||
rule := stack.Rule{
|
||||
Filter: filter,
|
||||
Matchers: matchers,
|
||||
}
|
||||
|
||||
{
|
||||
target, err := parseTarget(filter, optVal[:targetSize], true /* ipv6 */)
|
||||
if err != nil {
|
||||
nflog("failed to parse target: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
rule.Target = target
|
||||
}
|
||||
optVal = optVal[targetSize:]
|
||||
|
||||
table.Rules = append(table.Rules, stack.Rule{
|
||||
Filter: filter,
|
||||
Target: target,
|
||||
Matchers: matchers,
|
||||
})
|
||||
table.Rules = append(table.Rules, rule)
|
||||
offsets[offset] = int(entryIdx)
|
||||
offset += uint32(entry.NextOffset)
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/syserr"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
@@ -199,7 +200,7 @@ func SetEntries(stk *stack.Stack, optVal []byte, ipv6 bool) *syserr.Error {
|
||||
|
||||
// Check the user chains.
|
||||
for ruleIdx, rule := range table.Rules {
|
||||
if _, ok := rule.Target.(stack.UserChainTarget); !ok {
|
||||
if _, ok := rule.Target.(*stack.UserChainTarget); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -220,7 +221,7 @@ func SetEntries(stk *stack.Stack, optVal []byte, ipv6 bool) *syserr.Error {
|
||||
// Set each jump to point to the appropriate rule. Right now they hold byte
|
||||
// offsets.
|
||||
for ruleIdx, rule := range table.Rules {
|
||||
jump, ok := rule.Target.(JumpTarget)
|
||||
jump, ok := rule.Target.(*JumpTarget)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -311,7 +312,7 @@ func validUnderflow(rule stack.Rule, ipv6 bool) bool {
|
||||
return false
|
||||
}
|
||||
switch rule.Target.(type) {
|
||||
case stack.AcceptTarget, stack.DropTarget:
|
||||
case *stack.AcceptTarget, *stack.DropTarget:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -322,7 +323,7 @@ func isUnconditionalAccept(rule stack.Rule, ipv6 bool) bool {
|
||||
if !validUnderflow(rule, ipv6) {
|
||||
return false
|
||||
}
|
||||
_, ok := rule.Target.(stack.AcceptTarget)
|
||||
_, ok := rule.Target.(*stack.AcceptTarget)
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -341,3 +342,20 @@ func hookFromLinux(hook int) stack.Hook {
|
||||
}
|
||||
panic(fmt.Sprintf("Unknown hook %d does not correspond to a builtin chain", hook))
|
||||
}
|
||||
|
||||
// TargetRevision returns a linux.XTGetRevision for a given target. It sets
|
||||
// Revision to the highest supported value, unless the provided revision number
|
||||
// is larger.
|
||||
func TargetRevision(t *kernel.Task, revPtr usermem.Addr, netProto tcpip.NetworkProtocolNumber) (linux.XTGetRevision, *syserr.Error) {
|
||||
// Read in the target name and version.
|
||||
var rev linux.XTGetRevision
|
||||
if _, err := rev.CopyIn(t, revPtr); err != nil {
|
||||
return linux.XTGetRevision{}, syserr.FromError(err)
|
||||
}
|
||||
maxSupported, ok := targetRevision(rev.Name.String(), netProto, rev.Revision)
|
||||
if !ok {
|
||||
return linux.XTGetRevision{}, syserr.ErrProtocolNotSupported
|
||||
}
|
||||
rev.Revision = maxSupported
|
||||
return rev, nil
|
||||
}
|
||||
|
||||
@@ -15,255 +15,276 @@
|
||||
package netfilter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/binary"
|
||||
"gvisor.dev/gvisor/pkg/syserr"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// errorTargetName is used to mark targets as error targets. Error targets
|
||||
// shouldn't be reached - an error has occurred if we fall through to one.
|
||||
const errorTargetName = "ERROR"
|
||||
func init() {
|
||||
// Standard targets include ACCEPT, DROP, RETURN, and JUMP.
|
||||
registerTargetMaker(&standardTargetMaker{
|
||||
NetworkProtocol: header.IPv4ProtocolNumber,
|
||||
})
|
||||
registerTargetMaker(&standardTargetMaker{
|
||||
NetworkProtocol: header.IPv6ProtocolNumber,
|
||||
})
|
||||
|
||||
// redirectTargetName is used to mark targets as redirect targets. Redirect
|
||||
// targets should be reached for only NAT and Mangle tables. These targets will
|
||||
// change the destination port/destination IP for packets.
|
||||
const redirectTargetName = "REDIRECT"
|
||||
// Both user chains and actual errors are represented in iptables by
|
||||
// error targets.
|
||||
registerTargetMaker(&errorTargetMaker{
|
||||
NetworkProtocol: header.IPv4ProtocolNumber,
|
||||
})
|
||||
registerTargetMaker(&errorTargetMaker{
|
||||
NetworkProtocol: header.IPv6ProtocolNumber,
|
||||
})
|
||||
|
||||
func marshalTarget(target stack.Target) []byte {
|
||||
registerTargetMaker(&redirectTargetMaker{
|
||||
NetworkProtocol: header.IPv4ProtocolNumber,
|
||||
})
|
||||
}
|
||||
|
||||
type standardTargetMaker struct {
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
func (sm *standardTargetMaker) id() stack.TargetID {
|
||||
// Standard targets have the empty string as a name and no revisions.
|
||||
return stack.TargetID{
|
||||
NetworkProtocol: sm.NetworkProtocol,
|
||||
}
|
||||
}
|
||||
func (*standardTargetMaker) marshal(target stack.Target) []byte {
|
||||
// Translate verdicts the same way as the iptables tool.
|
||||
var verdict int32
|
||||
switch tg := target.(type) {
|
||||
case stack.AcceptTarget:
|
||||
return marshalStandardTarget(stack.RuleAccept)
|
||||
case stack.DropTarget:
|
||||
return marshalStandardTarget(stack.RuleDrop)
|
||||
case stack.ErrorTarget:
|
||||
return marshalErrorTarget(errorTargetName)
|
||||
case stack.UserChainTarget:
|
||||
return marshalErrorTarget(tg.Name)
|
||||
case stack.ReturnTarget:
|
||||
return marshalStandardTarget(stack.RuleReturn)
|
||||
case stack.RedirectTarget:
|
||||
return marshalRedirectTarget(tg)
|
||||
case JumpTarget:
|
||||
return marshalJumpTarget(tg)
|
||||
case *stack.AcceptTarget:
|
||||
verdict = -linux.NF_ACCEPT - 1
|
||||
case *stack.DropTarget:
|
||||
verdict = -linux.NF_DROP - 1
|
||||
case *stack.ReturnTarget:
|
||||
verdict = linux.NF_RETURN
|
||||
case *JumpTarget:
|
||||
verdict = int32(tg.Offset)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown target of type %T", target))
|
||||
}
|
||||
}
|
||||
|
||||
func marshalStandardTarget(verdict stack.RuleVerdict) []byte {
|
||||
nflog("convert to binary: marshalling standard target")
|
||||
|
||||
// The target's name will be the empty string.
|
||||
target := linux.XTStandardTarget{
|
||||
xt := linux.XTStandardTarget{
|
||||
Target: linux.XTEntryTarget{
|
||||
TargetSize: linux.SizeOfXTStandardTarget,
|
||||
},
|
||||
Verdict: translateFromStandardVerdict(verdict),
|
||||
Verdict: verdict,
|
||||
}
|
||||
|
||||
ret := make([]byte, 0, linux.SizeOfXTStandardTarget)
|
||||
return binary.Marshal(ret, usermem.ByteOrder, target)
|
||||
return binary.Marshal(ret, usermem.ByteOrder, xt)
|
||||
}
|
||||
|
||||
func marshalErrorTarget(errorName string) []byte {
|
||||
func (*standardTargetMaker) unmarshal(buf []byte, filter stack.IPHeaderFilter) (stack.Target, *syserr.Error) {
|
||||
if len(buf) != linux.SizeOfXTStandardTarget {
|
||||
nflog("buf has wrong size for standard target %d", len(buf))
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
var standardTarget linux.XTStandardTarget
|
||||
buf = buf[:linux.SizeOfXTStandardTarget]
|
||||
binary.Unmarshal(buf, usermem.ByteOrder, &standardTarget)
|
||||
|
||||
if standardTarget.Verdict < 0 {
|
||||
// A Verdict < 0 indicates a non-jump verdict.
|
||||
return translateToStandardTarget(standardTarget.Verdict, filter.NetworkProtocol())
|
||||
}
|
||||
// A verdict >= 0 indicates a jump.
|
||||
return &JumpTarget{
|
||||
Offset: uint32(standardTarget.Verdict),
|
||||
NetworkProtocol: filter.NetworkProtocol(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type errorTargetMaker struct {
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
func (em *errorTargetMaker) id() stack.TargetID {
|
||||
// Error targets have no revision.
|
||||
return stack.TargetID{
|
||||
Name: stack.ErrorTargetName,
|
||||
NetworkProtocol: em.NetworkProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
func (*errorTargetMaker) marshal(target stack.Target) []byte {
|
||||
var errorName string
|
||||
switch tg := target.(type) {
|
||||
case *stack.ErrorTarget:
|
||||
errorName = stack.ErrorTargetName
|
||||
case *stack.UserChainTarget:
|
||||
errorName = tg.Name
|
||||
default:
|
||||
panic(fmt.Sprintf("errorMakerTarget cannot marshal unknown type %T", target))
|
||||
}
|
||||
|
||||
// This is an error target named error
|
||||
target := linux.XTErrorTarget{
|
||||
xt := linux.XTErrorTarget{
|
||||
Target: linux.XTEntryTarget{
|
||||
TargetSize: linux.SizeOfXTErrorTarget,
|
||||
},
|
||||
}
|
||||
copy(target.Name[:], errorName)
|
||||
copy(target.Target.Name[:], errorTargetName)
|
||||
copy(xt.Name[:], errorName)
|
||||
copy(xt.Target.Name[:], stack.ErrorTargetName)
|
||||
|
||||
ret := make([]byte, 0, linux.SizeOfXTErrorTarget)
|
||||
return binary.Marshal(ret, usermem.ByteOrder, target)
|
||||
return binary.Marshal(ret, usermem.ByteOrder, xt)
|
||||
}
|
||||
|
||||
func marshalRedirectTarget(rt stack.RedirectTarget) []byte {
|
||||
func (*errorTargetMaker) unmarshal(buf []byte, filter stack.IPHeaderFilter) (stack.Target, *syserr.Error) {
|
||||
if len(buf) != linux.SizeOfXTErrorTarget {
|
||||
nflog("buf has insufficient size for error target %d", len(buf))
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
var errorTarget linux.XTErrorTarget
|
||||
buf = buf[:linux.SizeOfXTErrorTarget]
|
||||
binary.Unmarshal(buf, usermem.ByteOrder, &errorTarget)
|
||||
|
||||
// Error targets are used in 2 cases:
|
||||
// * An actual error case. These rules have an error
|
||||
// named stack.ErrorTargetName. The last entry of the table
|
||||
// is usually an error case to catch any packets that
|
||||
// somehow fall through every rule.
|
||||
// * To mark the start of a user defined chain. These
|
||||
// rules have an error with the name of the chain.
|
||||
switch name := errorTarget.Name.String(); name {
|
||||
case stack.ErrorTargetName:
|
||||
return &stack.ErrorTarget{NetworkProtocol: filter.NetworkProtocol()}, nil
|
||||
default:
|
||||
// User defined chain.
|
||||
return &stack.UserChainTarget{
|
||||
Name: name,
|
||||
NetworkProtocol: filter.NetworkProtocol(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type redirectTargetMaker struct {
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
func (rm *redirectTargetMaker) id() stack.TargetID {
|
||||
return stack.TargetID{
|
||||
Name: stack.RedirectTargetName,
|
||||
NetworkProtocol: rm.NetworkProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
func (*redirectTargetMaker) marshal(target stack.Target) []byte {
|
||||
rt := target.(*stack.RedirectTarget)
|
||||
// This is a redirect target named redirect
|
||||
target := linux.XTRedirectTarget{
|
||||
xt := linux.XTRedirectTarget{
|
||||
Target: linux.XTEntryTarget{
|
||||
TargetSize: linux.SizeOfXTRedirectTarget,
|
||||
},
|
||||
}
|
||||
copy(target.Target.Name[:], redirectTargetName)
|
||||
copy(xt.Target.Name[:], stack.RedirectTargetName)
|
||||
|
||||
ret := make([]byte, 0, linux.SizeOfXTRedirectTarget)
|
||||
target.NfRange.RangeSize = 1
|
||||
xt.NfRange.RangeSize = 1
|
||||
if rt.RangeProtoSpecified {
|
||||
target.NfRange.RangeIPV4.Flags |= linux.NF_NAT_RANGE_PROTO_SPECIFIED
|
||||
xt.NfRange.RangeIPV4.Flags |= linux.NF_NAT_RANGE_PROTO_SPECIFIED
|
||||
}
|
||||
// Convert port from little endian to big endian.
|
||||
port := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(port, rt.MinPort)
|
||||
target.NfRange.RangeIPV4.MinPort = binary.BigEndian.Uint16(port)
|
||||
binary.LittleEndian.PutUint16(port, rt.MaxPort)
|
||||
target.NfRange.RangeIPV4.MaxPort = binary.BigEndian.Uint16(port)
|
||||
return binary.Marshal(ret, usermem.ByteOrder, target)
|
||||
xt.NfRange.RangeIPV4.MinPort = htons(rt.MinPort)
|
||||
xt.NfRange.RangeIPV4.MaxPort = htons(rt.MaxPort)
|
||||
return binary.Marshal(ret, usermem.ByteOrder, xt)
|
||||
}
|
||||
|
||||
func marshalJumpTarget(jt JumpTarget) []byte {
|
||||
nflog("convert to binary: marshalling jump target")
|
||||
|
||||
// The target's name will be the empty string.
|
||||
target := linux.XTStandardTarget{
|
||||
Target: linux.XTEntryTarget{
|
||||
TargetSize: linux.SizeOfXTStandardTarget,
|
||||
},
|
||||
// Verdict is overloaded by the ABI. When positive, it holds
|
||||
// the jump offset from the start of the table.
|
||||
Verdict: int32(jt.Offset),
|
||||
func (*redirectTargetMaker) unmarshal(buf []byte, filter stack.IPHeaderFilter) (stack.Target, *syserr.Error) {
|
||||
if len(buf) < linux.SizeOfXTRedirectTarget {
|
||||
nflog("redirectTargetMaker: buf has insufficient size for redirect target %d", len(buf))
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
|
||||
ret := make([]byte, 0, linux.SizeOfXTStandardTarget)
|
||||
return binary.Marshal(ret, usermem.ByteOrder, target)
|
||||
}
|
||||
|
||||
// translateFromStandardVerdict translates verdicts the same way as the iptables
|
||||
// tool.
|
||||
func translateFromStandardVerdict(verdict stack.RuleVerdict) int32 {
|
||||
switch verdict {
|
||||
case stack.RuleAccept:
|
||||
return -linux.NF_ACCEPT - 1
|
||||
case stack.RuleDrop:
|
||||
return -linux.NF_DROP - 1
|
||||
case stack.RuleReturn:
|
||||
return linux.NF_RETURN
|
||||
default:
|
||||
// TODO(gvisor.dev/issue/170): Support Jump.
|
||||
panic(fmt.Sprintf("unknown standard verdict: %d", verdict))
|
||||
if p := filter.Protocol; p != header.TCPProtocolNumber && p != header.UDPProtocolNumber {
|
||||
nflog("redirectTargetMaker: bad proto %d", p)
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
|
||||
var redirectTarget linux.XTRedirectTarget
|
||||
buf = buf[:linux.SizeOfXTRedirectTarget]
|
||||
binary.Unmarshal(buf, usermem.ByteOrder, &redirectTarget)
|
||||
|
||||
// Copy linux.XTRedirectTarget to stack.RedirectTarget.
|
||||
target := stack.RedirectTarget{NetworkProtocol: filter.NetworkProtocol()}
|
||||
|
||||
// RangeSize should be 1.
|
||||
nfRange := redirectTarget.NfRange
|
||||
if nfRange.RangeSize != 1 {
|
||||
nflog("redirectTargetMaker: bad rangesize %d", nfRange.RangeSize)
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
|
||||
// TODO(gvisor.dev/issue/170): Check if the flags are valid.
|
||||
// Also check if we need to map ports or IP.
|
||||
// For now, redirect target only supports destination port change.
|
||||
// Port range and IP range are not supported yet.
|
||||
if nfRange.RangeIPV4.Flags&linux.NF_NAT_RANGE_PROTO_SPECIFIED == 0 {
|
||||
nflog("redirectTargetMaker: invalid range flags %d", nfRange.RangeIPV4.Flags)
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
target.RangeProtoSpecified = true
|
||||
|
||||
target.MinIP = tcpip.Address(nfRange.RangeIPV4.MinIP[:])
|
||||
target.MaxIP = tcpip.Address(nfRange.RangeIPV4.MaxIP[:])
|
||||
|
||||
// TODO(gvisor.dev/issue/170): Port range is not supported yet.
|
||||
if nfRange.RangeIPV4.MinPort != nfRange.RangeIPV4.MaxPort {
|
||||
nflog("redirectTargetMaker: MinPort != MaxPort (%d, %d)", nfRange.RangeIPV4.MinPort, nfRange.RangeIPV4.MaxPort)
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
|
||||
target.MinPort = ntohs(nfRange.RangeIPV4.MinPort)
|
||||
target.MaxPort = ntohs(nfRange.RangeIPV4.MaxPort)
|
||||
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
// translateToStandardTarget translates from the value in a
|
||||
// linux.XTStandardTarget to an stack.Verdict.
|
||||
func translateToStandardTarget(val int32) (stack.Target, error) {
|
||||
func translateToStandardTarget(val int32, netProto tcpip.NetworkProtocolNumber) (stack.Target, *syserr.Error) {
|
||||
// TODO(gvisor.dev/issue/170): Support other verdicts.
|
||||
switch val {
|
||||
case -linux.NF_ACCEPT - 1:
|
||||
return stack.AcceptTarget{}, nil
|
||||
return &stack.AcceptTarget{NetworkProtocol: netProto}, nil
|
||||
case -linux.NF_DROP - 1:
|
||||
return stack.DropTarget{}, nil
|
||||
return &stack.DropTarget{NetworkProtocol: netProto}, nil
|
||||
case -linux.NF_QUEUE - 1:
|
||||
return nil, errors.New("unsupported iptables verdict QUEUE")
|
||||
nflog("unsupported iptables verdict QUEUE")
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
case linux.NF_RETURN:
|
||||
return stack.ReturnTarget{}, nil
|
||||
return &stack.ReturnTarget{NetworkProtocol: netProto}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown iptables verdict %d", val)
|
||||
nflog("unknown iptables verdict %d", val)
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
}
|
||||
|
||||
// parseTarget parses a target from optVal. optVal should contain only the
|
||||
// target.
|
||||
func parseTarget(filter stack.IPHeaderFilter, optVal []byte) (stack.Target, error) {
|
||||
func parseTarget(filter stack.IPHeaderFilter, optVal []byte, ipv6 bool) (stack.Target, *syserr.Error) {
|
||||
nflog("set entries: parsing target of size %d", len(optVal))
|
||||
if len(optVal) < linux.SizeOfXTEntryTarget {
|
||||
return nil, fmt.Errorf("optVal has insufficient size for entry target %d", len(optVal))
|
||||
nflog("optVal has insufficient size for entry target %d", len(optVal))
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
var target linux.XTEntryTarget
|
||||
buf := optVal[:linux.SizeOfXTEntryTarget]
|
||||
binary.Unmarshal(buf, usermem.ByteOrder, &target)
|
||||
switch target.Name.String() {
|
||||
case "":
|
||||
// Standard target.
|
||||
if len(optVal) != linux.SizeOfXTStandardTarget {
|
||||
return nil, fmt.Errorf("optVal has wrong size for standard target %d", len(optVal))
|
||||
}
|
||||
var standardTarget linux.XTStandardTarget
|
||||
buf = optVal[:linux.SizeOfXTStandardTarget]
|
||||
binary.Unmarshal(buf, usermem.ByteOrder, &standardTarget)
|
||||
|
||||
if standardTarget.Verdict < 0 {
|
||||
// A Verdict < 0 indicates a non-jump verdict.
|
||||
return translateToStandardTarget(standardTarget.Verdict)
|
||||
}
|
||||
// A verdict >= 0 indicates a jump.
|
||||
return JumpTarget{Offset: uint32(standardTarget.Verdict)}, nil
|
||||
|
||||
case errorTargetName:
|
||||
// Error target.
|
||||
if len(optVal) != linux.SizeOfXTErrorTarget {
|
||||
return nil, fmt.Errorf("optVal has insufficient size for error target %d", len(optVal))
|
||||
}
|
||||
var errorTarget linux.XTErrorTarget
|
||||
buf = optVal[:linux.SizeOfXTErrorTarget]
|
||||
binary.Unmarshal(buf, usermem.ByteOrder, &errorTarget)
|
||||
|
||||
// Error targets are used in 2 cases:
|
||||
// * An actual error case. These rules have an error
|
||||
// named errorTargetName. The last entry of the table
|
||||
// is usually an error case to catch any packets that
|
||||
// somehow fall through every rule.
|
||||
// * To mark the start of a user defined chain. These
|
||||
// rules have an error with the name of the chain.
|
||||
switch name := errorTarget.Name.String(); name {
|
||||
case errorTargetName:
|
||||
nflog("set entries: error target")
|
||||
return stack.ErrorTarget{}, nil
|
||||
default:
|
||||
// User defined chain.
|
||||
nflog("set entries: user-defined target %q", name)
|
||||
return stack.UserChainTarget{Name: name}, nil
|
||||
}
|
||||
|
||||
case redirectTargetName:
|
||||
// Redirect target.
|
||||
if len(optVal) < linux.SizeOfXTRedirectTarget {
|
||||
return nil, fmt.Errorf("netfilter.SetEntries: optVal has insufficient size for redirect target %d", len(optVal))
|
||||
}
|
||||
|
||||
if p := filter.Protocol; p != header.TCPProtocolNumber && p != header.UDPProtocolNumber {
|
||||
return nil, fmt.Errorf("netfilter.SetEntries: bad proto %d", p)
|
||||
}
|
||||
|
||||
var redirectTarget linux.XTRedirectTarget
|
||||
buf = optVal[:linux.SizeOfXTRedirectTarget]
|
||||
binary.Unmarshal(buf, usermem.ByteOrder, &redirectTarget)
|
||||
|
||||
// Copy linux.XTRedirectTarget to stack.RedirectTarget.
|
||||
var target stack.RedirectTarget
|
||||
nfRange := redirectTarget.NfRange
|
||||
|
||||
// RangeSize should be 1.
|
||||
if nfRange.RangeSize != 1 {
|
||||
return nil, fmt.Errorf("netfilter.SetEntries: bad rangesize %d", nfRange.RangeSize)
|
||||
}
|
||||
|
||||
// TODO(gvisor.dev/issue/170): Check if the flags are valid.
|
||||
// Also check if we need to map ports or IP.
|
||||
// For now, redirect target only supports destination port change.
|
||||
// Port range and IP range are not supported yet.
|
||||
if nfRange.RangeIPV4.Flags&linux.NF_NAT_RANGE_PROTO_SPECIFIED == 0 {
|
||||
return nil, fmt.Errorf("netfilter.SetEntries: invalid range flags %d", nfRange.RangeIPV4.Flags)
|
||||
}
|
||||
target.RangeProtoSpecified = true
|
||||
|
||||
target.MinIP = tcpip.Address(nfRange.RangeIPV4.MinIP[:])
|
||||
target.MaxIP = tcpip.Address(nfRange.RangeIPV4.MaxIP[:])
|
||||
|
||||
// TODO(gvisor.dev/issue/170): Port range is not supported yet.
|
||||
if nfRange.RangeIPV4.MinPort != nfRange.RangeIPV4.MaxPort {
|
||||
return nil, fmt.Errorf("netfilter.SetEntries: minport != maxport (%d, %d)", nfRange.RangeIPV4.MinPort, nfRange.RangeIPV4.MaxPort)
|
||||
}
|
||||
|
||||
// Convert port from big endian to little endian.
|
||||
port := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(port, nfRange.RangeIPV4.MinPort)
|
||||
target.MinPort = binary.LittleEndian.Uint16(port)
|
||||
|
||||
binary.BigEndian.PutUint16(port, nfRange.RangeIPV4.MaxPort)
|
||||
target.MaxPort = binary.LittleEndian.Uint16(port)
|
||||
return target, nil
|
||||
}
|
||||
|
||||
// Unknown target.
|
||||
return nil, fmt.Errorf("unknown target %q doesn't exist or isn't supported yet", target.Name.String())
|
||||
return unmarshalTarget(target, filter, optVal)
|
||||
}
|
||||
|
||||
// JumpTarget implements stack.Target.
|
||||
@@ -274,9 +295,31 @@ type JumpTarget struct {
|
||||
|
||||
// RuleNum is the rule to jump to.
|
||||
RuleNum int
|
||||
|
||||
// NetworkProtocol is the network protocol the target is used with.
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
// ID implements Target.ID.
|
||||
func (jt *JumpTarget) ID() stack.TargetID {
|
||||
return stack.TargetID{
|
||||
NetworkProtocol: jt.NetworkProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
// Action implements stack.Target.Action.
|
||||
func (jt JumpTarget) Action(*stack.PacketBuffer, *stack.ConnTrack, stack.Hook, *stack.GSO, *stack.Route, tcpip.Address) (stack.RuleVerdict, int) {
|
||||
return stack.RuleJump, jt.RuleNum
|
||||
}
|
||||
|
||||
func ntohs(port uint16) uint16 {
|
||||
buf := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(buf, port)
|
||||
return usermem.ByteOrder.Uint16(buf)
|
||||
}
|
||||
|
||||
func htons(port uint16) uint16 {
|
||||
buf := make([]byte, 2)
|
||||
usermem.ByteOrder.PutUint16(buf, port)
|
||||
return binary.BigEndian.Uint16(buf)
|
||||
}
|
||||
|
||||
@@ -1719,6 +1719,26 @@ func getSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name in
|
||||
}
|
||||
return &entries, nil
|
||||
|
||||
case linux.IPT_SO_GET_REVISION_TARGET:
|
||||
if outLen < linux.SizeOfXTGetRevision {
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
|
||||
// Only valid for raw IPv4 sockets.
|
||||
if family, skType, _ := s.Type(); family != linux.AF_INET || skType != linux.SOCK_RAW {
|
||||
return nil, syserr.ErrProtocolNotAvailable
|
||||
}
|
||||
|
||||
stack := inet.StackFromContext(t)
|
||||
if stack == nil {
|
||||
return nil, syserr.ErrNoDevice
|
||||
}
|
||||
ret, err := netfilter.TargetRevision(t, outPtr, header.IPv4ProtocolNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ret, nil
|
||||
|
||||
default:
|
||||
emitUnimplementedEventIP(t, name)
|
||||
}
|
||||
|
||||
@@ -1040,7 +1040,7 @@ func TestWriteStats(t *testing.T) {
|
||||
t.Fatalf("failed to find filter table")
|
||||
}
|
||||
ruleIdx := filter.BuiltinChains[stack.Output]
|
||||
filter.Rules[ruleIdx].Target = stack.DropTarget{}
|
||||
filter.Rules[ruleIdx].Target = &stack.DropTarget{}
|
||||
if err := ipt.ReplaceTable(stack.FilterTable, filter, false /* ipv6 */); err != nil {
|
||||
t.Fatalf("failed to replace table: %s", err)
|
||||
}
|
||||
@@ -1062,10 +1062,10 @@ func TestWriteStats(t *testing.T) {
|
||||
}
|
||||
// We'll match and DROP the last packet.
|
||||
ruleIdx := filter.BuiltinChains[stack.Output]
|
||||
filter.Rules[ruleIdx].Target = stack.DropTarget{}
|
||||
filter.Rules[ruleIdx].Target = &stack.DropTarget{}
|
||||
filter.Rules[ruleIdx].Matchers = []stack.Matcher{&limitedMatcher{nPackets - 1}}
|
||||
// Make sure the next rule is ACCEPT.
|
||||
filter.Rules[ruleIdx+1].Target = stack.AcceptTarget{}
|
||||
filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
|
||||
if err := ipt.ReplaceTable(stack.FilterTable, filter, false /* ipv6 */); err != nil {
|
||||
t.Fatalf("failed to replace table: %s", err)
|
||||
}
|
||||
|
||||
@@ -1748,7 +1748,7 @@ func TestWriteStats(t *testing.T) {
|
||||
t.Fatalf("failed to find filter table")
|
||||
}
|
||||
ruleIdx := filter.BuiltinChains[stack.Output]
|
||||
filter.Rules[ruleIdx].Target = stack.DropTarget{}
|
||||
filter.Rules[ruleIdx].Target = &stack.DropTarget{}
|
||||
if err := ipt.ReplaceTable(stack.FilterTable, filter, true /* ipv6 */); err != nil {
|
||||
t.Fatalf("failed to replace table: %v", err)
|
||||
}
|
||||
@@ -1770,10 +1770,10 @@ func TestWriteStats(t *testing.T) {
|
||||
}
|
||||
// We'll match and DROP the last packet.
|
||||
ruleIdx := filter.BuiltinChains[stack.Output]
|
||||
filter.Rules[ruleIdx].Target = stack.DropTarget{}
|
||||
filter.Rules[ruleIdx].Target = &stack.DropTarget{}
|
||||
filter.Rules[ruleIdx].Matchers = []stack.Matcher{&limitedMatcher{nPackets - 1}}
|
||||
// Make sure the next rule is ACCEPT.
|
||||
filter.Rules[ruleIdx+1].Target = stack.AcceptTarget{}
|
||||
filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
|
||||
if err := ipt.ReplaceTable(stack.FilterTable, filter, true /* ipv6 */); err != nil {
|
||||
t.Fatalf("failed to replace table: %v", err)
|
||||
}
|
||||
|
||||
+24
-24
@@ -60,11 +60,11 @@ func DefaultTables() *IPTables {
|
||||
v4Tables: [numTables]Table{
|
||||
natID: Table{
|
||||
Rules: []Rule{
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: ErrorTarget{}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
Rule{Target: &ErrorTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
},
|
||||
BuiltinChains: [NumHooks]int{
|
||||
Prerouting: 0,
|
||||
@@ -83,9 +83,9 @@ func DefaultTables() *IPTables {
|
||||
},
|
||||
mangleID: Table{
|
||||
Rules: []Rule{
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: ErrorTarget{}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
Rule{Target: &ErrorTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
},
|
||||
BuiltinChains: [NumHooks]int{
|
||||
Prerouting: 0,
|
||||
@@ -101,10 +101,10 @@ func DefaultTables() *IPTables {
|
||||
},
|
||||
filterID: Table{
|
||||
Rules: []Rule{
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: ErrorTarget{}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
Rule{Target: &ErrorTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
},
|
||||
BuiltinChains: [NumHooks]int{
|
||||
Prerouting: HookUnset,
|
||||
@@ -125,11 +125,11 @@ func DefaultTables() *IPTables {
|
||||
v6Tables: [numTables]Table{
|
||||
natID: Table{
|
||||
Rules: []Rule{
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: ErrorTarget{}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
Rule{Target: &ErrorTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
},
|
||||
BuiltinChains: [NumHooks]int{
|
||||
Prerouting: 0,
|
||||
@@ -148,9 +148,9 @@ func DefaultTables() *IPTables {
|
||||
},
|
||||
mangleID: Table{
|
||||
Rules: []Rule{
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: ErrorTarget{}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
Rule{Target: &ErrorTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
},
|
||||
BuiltinChains: [NumHooks]int{
|
||||
Prerouting: 0,
|
||||
@@ -166,10 +166,10 @@ func DefaultTables() *IPTables {
|
||||
},
|
||||
filterID: Table{
|
||||
Rules: []Rule{
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: AcceptTarget{}},
|
||||
Rule{Target: ErrorTarget{}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
Rule{Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
Rule{Target: &ErrorTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
},
|
||||
BuiltinChains: [NumHooks]int{
|
||||
Prerouting: HookUnset,
|
||||
|
||||
@@ -21,7 +21,17 @@ import (
|
||||
)
|
||||
|
||||
// AcceptTarget accepts packets.
|
||||
type AcceptTarget struct{}
|
||||
type AcceptTarget struct {
|
||||
// NetworkProtocol is the network protocol the target is used with.
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
// ID implements Target.ID.
|
||||
func (at *AcceptTarget) ID() TargetID {
|
||||
return TargetID{
|
||||
NetworkProtocol: at.NetworkProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (AcceptTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.Address) (RuleVerdict, int) {
|
||||
@@ -29,16 +39,41 @@ func (AcceptTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.
|
||||
}
|
||||
|
||||
// DropTarget drops packets.
|
||||
type DropTarget struct{}
|
||||
type DropTarget struct {
|
||||
// NetworkProtocol is the network protocol the target is used with.
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
// ID implements Target.ID.
|
||||
func (dt *DropTarget) ID() TargetID {
|
||||
return TargetID{
|
||||
NetworkProtocol: dt.NetworkProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (DropTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.Address) (RuleVerdict, int) {
|
||||
return RuleDrop, 0
|
||||
}
|
||||
|
||||
// ErrorTargetName is used to mark targets as error targets. Error targets
|
||||
// shouldn't be reached - an error has occurred if we fall through to one.
|
||||
const ErrorTargetName = "ERROR"
|
||||
|
||||
// ErrorTarget logs an error and drops the packet. It represents a target that
|
||||
// should be unreachable.
|
||||
type ErrorTarget struct{}
|
||||
type ErrorTarget struct {
|
||||
// NetworkProtocol is the network protocol the target is used with.
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
// ID implements Target.ID.
|
||||
func (et *ErrorTarget) ID() TargetID {
|
||||
return TargetID{
|
||||
Name: ErrorTargetName,
|
||||
NetworkProtocol: et.NetworkProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (ErrorTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.Address) (RuleVerdict, int) {
|
||||
@@ -48,7 +83,19 @@ func (ErrorTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.A
|
||||
|
||||
// UserChainTarget marks a rule as the beginning of a user chain.
|
||||
type UserChainTarget struct {
|
||||
// Name is the chain name.
|
||||
Name string
|
||||
|
||||
// NetworkProtocol is the network protocol the target is used with.
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
// ID implements Target.ID.
|
||||
func (uc *UserChainTarget) ID() TargetID {
|
||||
return TargetID{
|
||||
Name: ErrorTargetName,
|
||||
NetworkProtocol: uc.NetworkProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
@@ -58,13 +105,28 @@ func (UserChainTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcp
|
||||
|
||||
// ReturnTarget returns from the current chain. If the chain is a built-in, the
|
||||
// hook's underflow should be called.
|
||||
type ReturnTarget struct{}
|
||||
type ReturnTarget struct {
|
||||
// NetworkProtocol is the network protocol the target is used with.
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
// ID implements Target.ID.
|
||||
func (rt *ReturnTarget) ID() TargetID {
|
||||
return TargetID{
|
||||
NetworkProtocol: rt.NetworkProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (ReturnTarget) Action(*PacketBuffer, *ConnTrack, Hook, *GSO, *Route, tcpip.Address) (RuleVerdict, int) {
|
||||
return RuleReturn, 0
|
||||
}
|
||||
|
||||
// RedirectTargetName is used to mark targets as redirect targets. Redirect
|
||||
// targets should be reached for only NAT and Mangle tables. These targets will
|
||||
// change the destination port/destination IP for packets.
|
||||
const RedirectTargetName = "REDIRECT"
|
||||
|
||||
// RedirectTarget redirects the packet by modifying the destination port/IP.
|
||||
// Min and Max values for IP and Ports in the struct indicate the range of
|
||||
// values which can be used to redirect.
|
||||
@@ -86,6 +148,17 @@ type RedirectTarget struct {
|
||||
|
||||
// MaxPort indicates port used to redirect.
|
||||
MaxPort uint16
|
||||
|
||||
// NetworkProtocol is the network protocol the target is used with.
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
// ID implements Target.ID.
|
||||
func (rt *RedirectTarget) ID() TargetID {
|
||||
return TargetID{
|
||||
Name: RedirectTargetName,
|
||||
NetworkProtocol: rt.NetworkProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
|
||||
@@ -104,8 +104,20 @@ type IPTables struct {
|
||||
reaperDone chan struct{}
|
||||
}
|
||||
|
||||
// A Table defines a set of chains and hooks into the network stack. It is
|
||||
// really just a list of rules.
|
||||
// A Table defines a set of chains and hooks into the network stack.
|
||||
//
|
||||
// It is a list of Rules, entry points (BuiltinChains), and error handlers
|
||||
// (Underflows). As packets traverse netstack, they hit hooks. When a packet
|
||||
// hits a hook, iptables compares it to Rules starting from that hook's entry
|
||||
// point. So if a packet hits the Input hook, we look up the corresponding
|
||||
// entry point in BuiltinChains and jump to that point.
|
||||
//
|
||||
// If the Rule doesn't match the packet, iptables continues to the next Rule.
|
||||
// If a Rule does match, it can issue a verdict on the packet (e.g. RuleAccept
|
||||
// or RuleDrop) that causes the packet to stop traversing iptables. It can also
|
||||
// jump to other rules or perform custom actions based on Rule.Target.
|
||||
//
|
||||
// Underflow Rules are invoked when a chain returns without reaching a verdict.
|
||||
//
|
||||
// +stateify savable
|
||||
type Table struct {
|
||||
@@ -260,6 +272,18 @@ func (fl IPHeaderFilter) match(pkt *PacketBuffer, hook Hook, nicName string) boo
|
||||
return true
|
||||
}
|
||||
|
||||
// NetworkProtocol returns the protocol (IPv4 or IPv6) on to which the header
|
||||
// applies.
|
||||
func (fl IPHeaderFilter) NetworkProtocol() tcpip.NetworkProtocolNumber {
|
||||
switch len(fl.Src) {
|
||||
case header.IPv4AddressSize:
|
||||
return header.IPv4ProtocolNumber
|
||||
case header.IPv6AddressSize:
|
||||
return header.IPv6ProtocolNumber
|
||||
}
|
||||
panic(fmt.Sprintf("invalid address in IPHeaderFilter: %s", fl.Src))
|
||||
}
|
||||
|
||||
// filterAddress returns whether addr matches the filter.
|
||||
func filterAddress(addr, mask, filterAddr tcpip.Address, invert bool) bool {
|
||||
matches := true
|
||||
@@ -285,8 +309,23 @@ type Matcher interface {
|
||||
Match(hook Hook, packet *PacketBuffer, interfaceName string) (matches bool, hotdrop bool)
|
||||
}
|
||||
|
||||
// A TargetID uniquely identifies a target.
|
||||
type TargetID struct {
|
||||
// Name is the target name as stored in the xt_entry_target struct.
|
||||
Name string
|
||||
|
||||
// NetworkProtocol is the protocol to which the target applies.
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
|
||||
// Revision is the version of the target.
|
||||
Revision uint8
|
||||
}
|
||||
|
||||
// A Target is the interface for taking an action for a packet.
|
||||
type Target interface {
|
||||
// ID uniquely identifies the Target.
|
||||
ID() TargetID
|
||||
|
||||
// Action takes an action on the packet and returns a verdict on how
|
||||
// traversal should (or should not) continue. If the return value is
|
||||
// Jump, it also returns the index of the rule to jump to.
|
||||
|
||||
@@ -82,6 +82,38 @@ TEST(IP6TablesBasic, GetEntriesErrorPrecedence) {
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
TEST(IP6TablesBasic, GetRevision) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
|
||||
|
||||
int sock;
|
||||
ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW),
|
||||
SyscallSucceeds());
|
||||
|
||||
struct xt_get_revision rev = {
|
||||
.name = "REDIRECT",
|
||||
.revision = 0,
|
||||
};
|
||||
socklen_t rev_len = sizeof(rev);
|
||||
|
||||
// TODO(gvisor.dev/issue/3549): IPv6 redirect support.
|
||||
const int retval =
|
||||
getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len);
|
||||
if (IsRunningOnGvisor()) {
|
||||
EXPECT_THAT(retval, SyscallFailsWithErrno(ENOPROTOOPT));
|
||||
return;
|
||||
}
|
||||
|
||||
// Revision 0 exists.
|
||||
EXPECT_THAT(retval, SyscallSucceeds());
|
||||
EXPECT_EQ(rev.revision, 0);
|
||||
|
||||
// Revisions > 0 don't exist.
|
||||
rev.revision = 1;
|
||||
EXPECT_THAT(
|
||||
getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len),
|
||||
SyscallFailsWithErrno(EPROTONOSUPPORT));
|
||||
}
|
||||
|
||||
// This tests the initial state of a machine with empty ip6tables via
|
||||
// getsockopt(IP6T_SO_GET_INFO). We don't have a guarantee that the iptables are
|
||||
// empty when running in native, but we can test that gVisor has the same
|
||||
|
||||
@@ -117,6 +117,32 @@ TEST(IPTablesBasic, OriginalDstErrors) {
|
||||
SyscallFailsWithErrno(ENOTCONN));
|
||||
}
|
||||
|
||||
TEST(IPTablesBasic, GetRevision) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
|
||||
|
||||
int sock;
|
||||
ASSERT_THAT(sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP),
|
||||
SyscallSucceeds());
|
||||
|
||||
struct xt_get_revision rev = {
|
||||
.name = "REDIRECT",
|
||||
.revision = 0,
|
||||
};
|
||||
socklen_t rev_len = sizeof(rev);
|
||||
|
||||
// Revision 0 exists.
|
||||
EXPECT_THAT(
|
||||
getsockopt(sock, SOL_IP, IPT_SO_GET_REVISION_TARGET, &rev, &rev_len),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(rev.revision, 0);
|
||||
|
||||
// Revisions > 0 don't exist.
|
||||
rev.revision = 1;
|
||||
EXPECT_THAT(
|
||||
getsockopt(sock, SOL_IP, IPT_SO_GET_REVISION_TARGET, &rev, &rev_len),
|
||||
SyscallFailsWithErrno(EPROTONOSUPPORT));
|
||||
}
|
||||
|
||||
// Fixture for iptables tests.
|
||||
class IPTablesTest : public ::testing::Test {
|
||||
protected:
|
||||
|
||||
Reference in New Issue
Block a user