mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Merge pull request #1850 from kevinGC:jump2
PiperOrigin-RevId: 295785052
This commit is contained in:
@@ -225,11 +225,14 @@ type XTEntryTarget struct {
|
||||
// SizeOfXTEntryTarget is the size of an XTEntryTarget.
|
||||
const SizeOfXTEntryTarget = 32
|
||||
|
||||
// XTStandardTarget is a builtin target, one of ACCEPT, DROP, JUMP, QUEUE, or
|
||||
// RETURN. It corresponds to struct xt_standard_target in
|
||||
// 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.
|
||||
type XTStandardTarget struct {
|
||||
Target XTEntryTarget
|
||||
Target XTEntryTarget
|
||||
// A positive verdict indicates a jump, and is the offset from the
|
||||
// start of the table to jump to. A negative value means one of the
|
||||
// other built-in targets.
|
||||
Verdict int32
|
||||
_ [4]byte
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ go_library(
|
||||
srcs = [
|
||||
"extensions.go",
|
||||
"netfilter.go",
|
||||
"targets.go",
|
||||
"tcp_matcher.go",
|
||||
"udp_matcher.go",
|
||||
],
|
||||
|
||||
@@ -240,13 +240,15 @@ func marshalTarget(target iptables.Target) []byte {
|
||||
return marshalErrorTarget(tg.Name)
|
||||
case iptables.ReturnTarget:
|
||||
return marshalStandardTarget(iptables.RuleReturn)
|
||||
case JumpTarget:
|
||||
return marshalJumpTarget(tg)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown target of type %T", target))
|
||||
}
|
||||
}
|
||||
|
||||
func marshalStandardTarget(verdict iptables.RuleVerdict) []byte {
|
||||
nflog("convert to binary: marshalling standard target with size %d", linux.SizeOfXTStandardTarget)
|
||||
nflog("convert to binary: marshalling standard target")
|
||||
|
||||
// The target's name will be the empty string.
|
||||
target := linux.XTStandardTarget{
|
||||
@@ -274,6 +276,23 @@ func marshalErrorTarget(errorName string) []byte {
|
||||
return binary.Marshal(ret, usermem.ByteOrder, target)
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
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 iptables.RuleVerdict) int32 {
|
||||
@@ -335,7 +354,8 @@ func SetEntries(stack *stack.Stack, optVal []byte) *syserr.Error {
|
||||
|
||||
// Convert input into a list of rules and their offsets.
|
||||
var offset uint32
|
||||
var offsets []uint32
|
||||
// offsets maps rule byte offsets to their position in table.Rules.
|
||||
offsets := map[uint32]int{}
|
||||
for entryIdx := uint32(0); entryIdx < replace.NumEntries; entryIdx++ {
|
||||
nflog("set entries: processing entry at offset %d", offset)
|
||||
|
||||
@@ -396,11 +416,12 @@ func SetEntries(stack *stack.Stack, optVal []byte) *syserr.Error {
|
||||
Target: target,
|
||||
Matchers: matchers,
|
||||
})
|
||||
offsets = append(offsets, offset)
|
||||
offsets[offset] = int(entryIdx)
|
||||
offset += uint32(entry.NextOffset)
|
||||
|
||||
if initialOptValLen-len(optVal) != int(entry.NextOffset) {
|
||||
nflog("entry NextOffset is %d, but entry took up %d bytes", entry.NextOffset, initialOptValLen-len(optVal))
|
||||
return syserr.ErrInvalidArgument
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,13 +430,13 @@ func SetEntries(stack *stack.Stack, optVal []byte) *syserr.Error {
|
||||
for hook, _ := range replace.HookEntry {
|
||||
if table.ValidHooks()&(1<<hook) != 0 {
|
||||
hk := hookFromLinux(hook)
|
||||
for ruleIdx, offset := range offsets {
|
||||
for offset, ruleIdx := range offsets {
|
||||
if offset == replace.HookEntry[hook] {
|
||||
table.BuiltinChains[hk] = ruleIdx
|
||||
}
|
||||
if offset == replace.Underflow[hook] {
|
||||
if !validUnderflow(table.Rules[ruleIdx]) {
|
||||
nflog("underflow for hook %d isn't an unconditional ACCEPT or DROP.")
|
||||
nflog("underflow for hook %d isn't an unconditional ACCEPT or DROP")
|
||||
return syserr.ErrInvalidArgument
|
||||
}
|
||||
table.Underflows[hk] = ruleIdx
|
||||
@@ -444,16 +465,35 @@ func SetEntries(stack *stack.Stack, optVal []byte) *syserr.Error {
|
||||
// - There's some other rule after it.
|
||||
// - There are no matchers.
|
||||
if ruleIdx == len(table.Rules)-1 {
|
||||
nflog("user chain must have a rule or default policy.")
|
||||
nflog("user chain must have a rule or default policy")
|
||||
return syserr.ErrInvalidArgument
|
||||
}
|
||||
if len(table.Rules[ruleIdx].Matchers) != 0 {
|
||||
nflog("user chain's first node must have no matcheres.")
|
||||
nflog("user chain's first node must have no matchers")
|
||||
return syserr.ErrInvalidArgument
|
||||
}
|
||||
table.UserChains[target.Name] = ruleIdx + 1
|
||||
}
|
||||
|
||||
// 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)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the rule corresponding to the jump rule offset.
|
||||
jumpTo, ok := offsets[jump.Offset]
|
||||
if !ok {
|
||||
nflog("failed to find a rule to jump to")
|
||||
return syserr.ErrInvalidArgument
|
||||
}
|
||||
jump.RuleNum = jumpTo
|
||||
rule.Target = jump
|
||||
table.Rules[ruleIdx] = rule
|
||||
}
|
||||
|
||||
// TODO(gvisor.dev/issue/170): Support other chains.
|
||||
// Since we only support modifying the INPUT chain right now, make sure
|
||||
// all other chains point to ACCEPT rules.
|
||||
@@ -548,7 +588,12 @@ func parseTarget(optVal []byte) (iptables.Target, error) {
|
||||
buf = optVal[:linux.SizeOfXTStandardTarget]
|
||||
binary.Unmarshal(buf, usermem.ByteOrder, &standardTarget)
|
||||
|
||||
return translateToStandardTarget(standardTarget.Verdict)
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package netfilter
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/iptables"
|
||||
)
|
||||
|
||||
// JumpTarget implements iptables.Target.
|
||||
type JumpTarget struct {
|
||||
// Offset is the byte offset of the rule to jump to. It is used for
|
||||
// marshaling and unmarshaling.
|
||||
Offset uint32
|
||||
|
||||
// RuleNum is the rule to jump to.
|
||||
RuleNum int
|
||||
}
|
||||
|
||||
// Action implements iptables.Target.Action.
|
||||
func (jt JumpTarget) Action(tcpip.PacketBuffer) (iptables.RuleVerdict, int) {
|
||||
return iptables.RuleJump, jt.RuleNum
|
||||
}
|
||||
@@ -135,25 +135,53 @@ func EmptyFilterTable() Table {
|
||||
}
|
||||
}
|
||||
|
||||
// A chainVerdict is what a table decides should be done with a packet.
|
||||
type chainVerdict int
|
||||
|
||||
const (
|
||||
// chainAccept indicates the packet should continue through netstack.
|
||||
chainAccept chainVerdict = iota
|
||||
|
||||
// chainAccept indicates the packet should be dropped.
|
||||
chainDrop
|
||||
|
||||
// chainReturn indicates the packet should return to the calling chain
|
||||
// or the underflow rule of a builtin chain.
|
||||
chainReturn
|
||||
)
|
||||
|
||||
// Check runs pkt through the rules for hook. It returns true when the packet
|
||||
// should continue traversing the network stack and false when it should be
|
||||
// dropped.
|
||||
//
|
||||
// Precondition: pkt.NetworkHeader is set.
|
||||
func (it *IPTables) Check(hook Hook, pkt tcpip.PacketBuffer) bool {
|
||||
// TODO(gvisor.dev/issue/170): A lot of this is uncomplicated because
|
||||
// we're missing features. Jumps, the call stack, etc. aren't checked
|
||||
// for yet because we're yet to support them.
|
||||
|
||||
// Go through each table containing the hook.
|
||||
for _, tablename := range it.Priorities[hook] {
|
||||
switch verdict := it.checkTable(hook, pkt, tablename); verdict {
|
||||
table := it.Tables[tablename]
|
||||
ruleIdx := table.BuiltinChains[hook]
|
||||
switch verdict := it.checkChain(hook, pkt, table, ruleIdx); verdict {
|
||||
// If the table returns Accept, move on to the next table.
|
||||
case TableAccept:
|
||||
case chainAccept:
|
||||
continue
|
||||
// The Drop verdict is final.
|
||||
case TableDrop:
|
||||
case chainDrop:
|
||||
return false
|
||||
case chainReturn:
|
||||
// Any Return from a built-in chain means we have to
|
||||
// call the underflow.
|
||||
underflow := table.Rules[table.Underflows[hook]]
|
||||
switch v, _ := underflow.Target.Action(pkt); v {
|
||||
case RuleAccept:
|
||||
continue
|
||||
case RuleDrop:
|
||||
return false
|
||||
case RuleJump, RuleReturn:
|
||||
panic("Underflows should only return RuleAccept or RuleDrop.")
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown verdict: %d", v))
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown verdict %v.", verdict))
|
||||
}
|
||||
@@ -164,37 +192,37 @@ func (it *IPTables) Check(hook Hook, pkt tcpip.PacketBuffer) bool {
|
||||
}
|
||||
|
||||
// Precondition: pkt.NetworkHeader is set.
|
||||
func (it *IPTables) checkTable(hook Hook, pkt tcpip.PacketBuffer, tablename string) TableVerdict {
|
||||
func (it *IPTables) checkChain(hook Hook, pkt tcpip.PacketBuffer, table Table, ruleIdx int) chainVerdict {
|
||||
// Start from ruleIdx and walk the list of rules until a rule gives us
|
||||
// a verdict.
|
||||
table := it.Tables[tablename]
|
||||
for ruleIdx := table.BuiltinChains[hook]; ruleIdx < len(table.Rules); ruleIdx++ {
|
||||
switch verdict := it.checkRule(hook, pkt, table, ruleIdx); verdict {
|
||||
for ruleIdx < len(table.Rules) {
|
||||
switch verdict, jumpTo := it.checkRule(hook, pkt, table, ruleIdx); verdict {
|
||||
case RuleAccept:
|
||||
return TableAccept
|
||||
return chainAccept
|
||||
|
||||
case RuleDrop:
|
||||
return TableDrop
|
||||
|
||||
case RuleContinue:
|
||||
continue
|
||||
return chainDrop
|
||||
|
||||
case RuleReturn:
|
||||
// TODO(gvisor.dev/issue/170): We don't implement jump
|
||||
// yet, so any Return is from a built-in chain. That
|
||||
// means we have to to call the underflow.
|
||||
underflow := table.Rules[table.Underflows[hook]]
|
||||
// Underflow is guaranteed to be an unconditional
|
||||
// ACCEPT or DROP.
|
||||
switch v, _ := underflow.Target.Action(pkt); v {
|
||||
case RuleAccept:
|
||||
return TableAccept
|
||||
case RuleDrop:
|
||||
return TableDrop
|
||||
case RuleContinue, RuleReturn:
|
||||
panic("Underflows should only return RuleAccept or RuleDrop.")
|
||||
return chainReturn
|
||||
|
||||
case RuleJump:
|
||||
// "Jumping" to the next rule just means we're
|
||||
// continuing on down the list.
|
||||
if jumpTo == ruleIdx+1 {
|
||||
ruleIdx++
|
||||
continue
|
||||
}
|
||||
switch verdict := it.checkChain(hook, pkt, table, jumpTo); verdict {
|
||||
case chainAccept:
|
||||
return chainAccept
|
||||
case chainDrop:
|
||||
return chainDrop
|
||||
case chainReturn:
|
||||
ruleIdx++
|
||||
continue
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown verdict: %d", v))
|
||||
panic(fmt.Sprintf("Unknown verdict: %d", verdict))
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -205,17 +233,18 @@ func (it *IPTables) checkTable(hook Hook, pkt tcpip.PacketBuffer, tablename stri
|
||||
|
||||
// We got through the entire table without a decision. Default to DROP
|
||||
// for safety.
|
||||
return TableDrop
|
||||
return chainDrop
|
||||
}
|
||||
|
||||
// Precondition: pk.NetworkHeader is set.
|
||||
func (it *IPTables) checkRule(hook Hook, pkt tcpip.PacketBuffer, table Table, ruleIdx int) RuleVerdict {
|
||||
func (it *IPTables) checkRule(hook Hook, pkt tcpip.PacketBuffer, table Table, ruleIdx int) (RuleVerdict, int) {
|
||||
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 RuleContinue
|
||||
// Continue on to the next rule.
|
||||
return RuleJump, ruleIdx + 1
|
||||
}
|
||||
|
||||
// Go through each rule matcher. If they all match, run
|
||||
@@ -223,14 +252,14 @@ func (it *IPTables) checkRule(hook Hook, pkt tcpip.PacketBuffer, table Table, ru
|
||||
for _, matcher := range rule.Matchers {
|
||||
matches, hotdrop := matcher.Match(hook, pkt, "")
|
||||
if hotdrop {
|
||||
return RuleDrop
|
||||
return RuleDrop, 0
|
||||
}
|
||||
if !matches {
|
||||
return RuleContinue
|
||||
// Continue on to the next rule.
|
||||
return RuleJump, ruleIdx + 1
|
||||
}
|
||||
}
|
||||
|
||||
// All the matchers matched, so run the target.
|
||||
verdict, _ := rule.Target.Action(pkt)
|
||||
return verdict
|
||||
return rule.Target.Action(pkt)
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file contains various Targets.
|
||||
|
||||
package iptables
|
||||
|
||||
import (
|
||||
@@ -25,16 +23,16 @@ import (
|
||||
type AcceptTarget struct{}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (AcceptTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
return RuleAccept, ""
|
||||
func (AcceptTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, int) {
|
||||
return RuleAccept, 0
|
||||
}
|
||||
|
||||
// DropTarget drops packets.
|
||||
type DropTarget struct{}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (DropTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
return RuleDrop, ""
|
||||
func (DropTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, int) {
|
||||
return RuleDrop, 0
|
||||
}
|
||||
|
||||
// ErrorTarget logs an error and drops the packet. It represents a target that
|
||||
@@ -42,9 +40,9 @@ func (DropTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
type ErrorTarget struct{}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (ErrorTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
func (ErrorTarget) Action(packet tcpip.PacketBuffer) (RuleVerdict, int) {
|
||||
log.Debugf("ErrorTarget triggered.")
|
||||
return RuleDrop, ""
|
||||
return RuleDrop, 0
|
||||
}
|
||||
|
||||
// UserChainTarget marks a rule as the beginning of a user chain.
|
||||
@@ -53,7 +51,7 @@ type UserChainTarget struct {
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (UserChainTarget) Action(tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
func (UserChainTarget) Action(tcpip.PacketBuffer) (RuleVerdict, int) {
|
||||
panic("UserChainTarget should never be called.")
|
||||
}
|
||||
|
||||
@@ -62,6 +60,6 @@ func (UserChainTarget) Action(tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
type ReturnTarget struct{}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (ReturnTarget) Action(tcpip.PacketBuffer) (RuleVerdict, string) {
|
||||
return RuleReturn, ""
|
||||
func (ReturnTarget) Action(tcpip.PacketBuffer) (RuleVerdict, int) {
|
||||
return RuleReturn, 0
|
||||
}
|
||||
|
||||
@@ -56,17 +56,6 @@ const (
|
||||
NumHooks
|
||||
)
|
||||
|
||||
// A TableVerdict is what a table decides should be done with a packet.
|
||||
type TableVerdict int
|
||||
|
||||
const (
|
||||
// TableAccept indicates the packet should continue through netstack.
|
||||
TableAccept TableVerdict = iota
|
||||
|
||||
// TableAccept indicates the packet should be dropped.
|
||||
TableDrop
|
||||
)
|
||||
|
||||
// A RuleVerdict is what a rule decides should be done with a packet.
|
||||
type RuleVerdict int
|
||||
|
||||
@@ -74,12 +63,12 @@ const (
|
||||
// RuleAccept indicates the packet should continue through netstack.
|
||||
RuleAccept RuleVerdict = iota
|
||||
|
||||
// RuleContinue indicates the packet should continue to the next rule.
|
||||
RuleContinue
|
||||
|
||||
// RuleDrop indicates the packet should be dropped.
|
||||
RuleDrop
|
||||
|
||||
// RuleJump indicates the packet should jump to another chain.
|
||||
RuleJump
|
||||
|
||||
// RuleReturn indicates the packet should return to the previous chain.
|
||||
RuleReturn
|
||||
)
|
||||
@@ -174,6 +163,6 @@ type Matcher interface {
|
||||
type Target interface {
|
||||
// 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 name of the chain to jump to.
|
||||
Action(packet tcpip.PacketBuffer) (RuleVerdict, string)
|
||||
// Jump, it also returns the index of the rule to jump to.
|
||||
Action(packet tcpip.PacketBuffer) (RuleVerdict, int)
|
||||
}
|
||||
|
||||
+199
-19
@@ -26,6 +26,7 @@ const (
|
||||
acceptPort = 2402
|
||||
sendloopDuration = 2 * time.Second
|
||||
network = "udp4"
|
||||
chainName = "foochain"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -40,6 +41,12 @@ func init() {
|
||||
RegisterTestCase(FilterInputDefaultPolicyAccept{})
|
||||
RegisterTestCase(FilterInputDefaultPolicyDrop{})
|
||||
RegisterTestCase(FilterInputReturnUnderflow{})
|
||||
RegisterTestCase(FilterInputSerializeJump{})
|
||||
RegisterTestCase(FilterInputJumpBasic{})
|
||||
RegisterTestCase(FilterInputJumpReturn{})
|
||||
RegisterTestCase(FilterInputJumpReturnDrop{})
|
||||
RegisterTestCase(FilterInputJumpBuiltin{})
|
||||
RegisterTestCase(FilterInputJumpTwice{})
|
||||
}
|
||||
|
||||
// FilterInputDropUDP tests that we can drop UDP traffic.
|
||||
@@ -267,13 +274,12 @@ func (FilterInputMultiUDPRules) Name() string {
|
||||
|
||||
// ContainerAction implements TestCase.ContainerAction.
|
||||
func (FilterInputMultiUDPRules) ContainerAction(ip net.IP) error {
|
||||
if err := filterTable("-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil {
|
||||
return err
|
||||
rules := [][]string{
|
||||
{"-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"},
|
||||
{"-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", acceptPort), "-j", "ACCEPT"},
|
||||
{"-L"},
|
||||
}
|
||||
if err := filterTable("-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", acceptPort), "-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
return filterTable("-L")
|
||||
return filterTableRules(rules)
|
||||
}
|
||||
|
||||
// LocalAction implements TestCase.LocalAction.
|
||||
@@ -314,14 +320,13 @@ func (FilterInputCreateUserChain) Name() string {
|
||||
|
||||
// ContainerAction implements TestCase.ContainerAction.
|
||||
func (FilterInputCreateUserChain) ContainerAction(ip net.IP) error {
|
||||
// Create a chain.
|
||||
const chainName = "foochain"
|
||||
if err := filterTable("-N", chainName); err != nil {
|
||||
return err
|
||||
rules := [][]string{
|
||||
// Create a chain.
|
||||
{"-N", chainName},
|
||||
// Add a simple rule to the chain.
|
||||
{"-A", chainName, "-j", "DROP"},
|
||||
}
|
||||
|
||||
// Add a simple rule to the chain.
|
||||
return filterTable("-A", chainName, "-j", "DROP")
|
||||
return filterTableRules(rules)
|
||||
}
|
||||
|
||||
// LocalAction implements TestCase.LocalAction.
|
||||
@@ -396,13 +401,12 @@ func (FilterInputReturnUnderflow) Name() string {
|
||||
func (FilterInputReturnUnderflow) ContainerAction(ip net.IP) error {
|
||||
// Add a RETURN rule followed by an unconditional accept, and set the
|
||||
// default policy to DROP.
|
||||
if err := filterTable("-A", "INPUT", "-j", "RETURN"); err != nil {
|
||||
return err
|
||||
rules := [][]string{
|
||||
{"-A", "INPUT", "-j", "RETURN"},
|
||||
{"-A", "INPUT", "-j", "DROP"},
|
||||
{"-P", "INPUT", "ACCEPT"},
|
||||
}
|
||||
if err := filterTable("-A", "INPUT", "-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := filterTable("-P", "INPUT", "ACCEPT"); err != nil {
|
||||
if err := filterTableRules(rules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -415,3 +419,179 @@ func (FilterInputReturnUnderflow) ContainerAction(ip net.IP) error {
|
||||
func (FilterInputReturnUnderflow) LocalAction(ip net.IP) error {
|
||||
return sendUDPLoop(ip, acceptPort, sendloopDuration)
|
||||
}
|
||||
|
||||
// FilterInputSerializeJump verifies that we can serialize jumps.
|
||||
type FilterInputSerializeJump struct{}
|
||||
|
||||
// Name implements TestCase.Name.
|
||||
func (FilterInputSerializeJump) Name() string {
|
||||
return "FilterInputSerializeJump"
|
||||
}
|
||||
|
||||
// ContainerAction implements TestCase.ContainerAction.
|
||||
func (FilterInputSerializeJump) ContainerAction(ip net.IP) error {
|
||||
// Write a JUMP rule, the serialize it with `-L`.
|
||||
rules := [][]string{
|
||||
{"-N", chainName},
|
||||
{"-A", "INPUT", "-j", chainName},
|
||||
{"-L"},
|
||||
}
|
||||
return filterTableRules(rules)
|
||||
}
|
||||
|
||||
// LocalAction implements TestCase.LocalAction.
|
||||
func (FilterInputSerializeJump) LocalAction(ip net.IP) error {
|
||||
// No-op.
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterInputJumpBasic jumps to a chain and executes a rule there.
|
||||
type FilterInputJumpBasic struct{}
|
||||
|
||||
// Name implements TestCase.Name.
|
||||
func (FilterInputJumpBasic) Name() string {
|
||||
return "FilterInputJumpBasic"
|
||||
}
|
||||
|
||||
// ContainerAction implements TestCase.ContainerAction.
|
||||
func (FilterInputJumpBasic) ContainerAction(ip net.IP) error {
|
||||
rules := [][]string{
|
||||
{"-P", "INPUT", "DROP"},
|
||||
{"-N", chainName},
|
||||
{"-A", "INPUT", "-j", chainName},
|
||||
{"-A", chainName, "-j", "ACCEPT"},
|
||||
}
|
||||
if err := filterTableRules(rules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Listen for UDP packets on acceptPort.
|
||||
return listenUDP(acceptPort, sendloopDuration)
|
||||
}
|
||||
|
||||
// LocalAction implements TestCase.LocalAction.
|
||||
func (FilterInputJumpBasic) LocalAction(ip net.IP) error {
|
||||
return sendUDPLoop(ip, acceptPort, sendloopDuration)
|
||||
}
|
||||
|
||||
// FilterInputJumpReturn jumps, returns, and executes a rule.
|
||||
type FilterInputJumpReturn struct{}
|
||||
|
||||
// Name implements TestCase.Name.
|
||||
func (FilterInputJumpReturn) Name() string {
|
||||
return "FilterInputJumpReturn"
|
||||
}
|
||||
|
||||
// ContainerAction implements TestCase.ContainerAction.
|
||||
func (FilterInputJumpReturn) ContainerAction(ip net.IP) error {
|
||||
rules := [][]string{
|
||||
{"-N", chainName},
|
||||
{"-P", "INPUT", "ACCEPT"},
|
||||
{"-A", "INPUT", "-j", chainName},
|
||||
{"-A", chainName, "-j", "RETURN"},
|
||||
{"-A", chainName, "-j", "DROP"},
|
||||
}
|
||||
if err := filterTableRules(rules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Listen for UDP packets on acceptPort.
|
||||
return listenUDP(acceptPort, sendloopDuration)
|
||||
}
|
||||
|
||||
// LocalAction implements TestCase.LocalAction.
|
||||
func (FilterInputJumpReturn) LocalAction(ip net.IP) error {
|
||||
return sendUDPLoop(ip, acceptPort, sendloopDuration)
|
||||
}
|
||||
|
||||
// FilterInputJumpReturnDrop jumps to a chain, returns, and DROPs packets.
|
||||
type FilterInputJumpReturnDrop struct{}
|
||||
|
||||
// Name implements TestCase.Name.
|
||||
func (FilterInputJumpReturnDrop) Name() string {
|
||||
return "FilterInputJumpReturnDrop"
|
||||
}
|
||||
|
||||
// ContainerAction implements TestCase.ContainerAction.
|
||||
func (FilterInputJumpReturnDrop) ContainerAction(ip net.IP) error {
|
||||
rules := [][]string{
|
||||
{"-N", chainName},
|
||||
{"-A", "INPUT", "-j", chainName},
|
||||
{"-A", "INPUT", "-j", "DROP"},
|
||||
{"-A", chainName, "-j", "RETURN"},
|
||||
}
|
||||
if err := filterTableRules(rules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Listen for UDP packets on dropPort.
|
||||
if err := listenUDP(dropPort, sendloopDuration); err == nil {
|
||||
return fmt.Errorf("packets on port %d should have been dropped, but got a packet", dropPort)
|
||||
} else if netErr, ok := err.(net.Error); !ok || !netErr.Timeout() {
|
||||
return fmt.Errorf("error reading: %v", err)
|
||||
}
|
||||
|
||||
// At this point we know that reading timed out and never received a
|
||||
// packet.
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAction implements TestCase.LocalAction.
|
||||
func (FilterInputJumpReturnDrop) LocalAction(ip net.IP) error {
|
||||
return sendUDPLoop(ip, dropPort, sendloopDuration)
|
||||
}
|
||||
|
||||
// FilterInputJumpBuiltin verifies that jumping to a top-levl chain is illegal.
|
||||
type FilterInputJumpBuiltin struct{}
|
||||
|
||||
// Name implements TestCase.Name.
|
||||
func (FilterInputJumpBuiltin) Name() string {
|
||||
return "FilterInputJumpBuiltin"
|
||||
}
|
||||
|
||||
// ContainerAction implements TestCase.ContainerAction.
|
||||
func (FilterInputJumpBuiltin) ContainerAction(ip net.IP) error {
|
||||
if err := filterTable("-A", "INPUT", "-j", "OUTPUT"); err == nil {
|
||||
return fmt.Errorf("iptables should be unable to jump to a built-in chain")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAction implements TestCase.LocalAction.
|
||||
func (FilterInputJumpBuiltin) LocalAction(ip net.IP) error {
|
||||
// No-op.
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterInputJumpTwice jumps twice, then returns twice and executes a rule.
|
||||
type FilterInputJumpTwice struct{}
|
||||
|
||||
// Name implements TestCase.Name.
|
||||
func (FilterInputJumpTwice) Name() string {
|
||||
return "FilterInputJumpTwice"
|
||||
}
|
||||
|
||||
// ContainerAction implements TestCase.ContainerAction.
|
||||
func (FilterInputJumpTwice) ContainerAction(ip net.IP) error {
|
||||
const chainName2 = chainName + "2"
|
||||
rules := [][]string{
|
||||
{"-P", "INPUT", "DROP"},
|
||||
{"-N", chainName},
|
||||
{"-N", chainName2},
|
||||
{"-A", "INPUT", "-j", chainName},
|
||||
{"-A", chainName, "-j", chainName2},
|
||||
{"-A", "INPUT", "-j", "ACCEPT"},
|
||||
}
|
||||
if err := filterTableRules(rules); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// UDP packets should jump and return twice, eventually hitting the
|
||||
// ACCEPT rule.
|
||||
return listenUDP(acceptPort, sendloopDuration)
|
||||
}
|
||||
|
||||
// LocalAction implements TestCase.LocalAction.
|
||||
func (FilterInputJumpTwice) LocalAction(ip net.IP) error {
|
||||
return sendUDPLoop(ip, acceptPort, sendloopDuration)
|
||||
}
|
||||
|
||||
@@ -249,3 +249,39 @@ func TestFilterOutputDropTCPSrcPort(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJumpSerialize(t *testing.T) {
|
||||
if err := singleTest(FilterInputSerializeJump{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJumpBasic(t *testing.T) {
|
||||
if err := singleTest(FilterInputJumpBasic{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJumpReturn(t *testing.T) {
|
||||
if err := singleTest(FilterInputJumpReturn{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJumpReturnDrop(t *testing.T) {
|
||||
if err := singleTest(FilterInputJumpReturnDrop{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJumpBuiltin(t *testing.T) {
|
||||
if err := singleTest(FilterInputJumpBuiltin{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJumpTwice(t *testing.T) {
|
||||
if err := singleTest(FilterInputJumpTwice{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,16 @@ func filterTable(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// filterTableRules is like filterTable, but runs multiple iptables commands.
|
||||
func filterTableRules(argsList [][]string) error {
|
||||
for _, args := range argsList {
|
||||
if err := filterTable(args...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenUDP listens on a UDP port and returns the value of net.Conn.Read() for
|
||||
// the first read on that port.
|
||||
func listenUDP(port int, timeout time.Duration) error {
|
||||
|
||||
Reference in New Issue
Block a user