blob: 6c0a4b24d91de951f7fabd8d2283bb4a2655497e [file] [log] [blame]
// Copyright 2019 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 stack
import (
"fmt"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
// Table names.
const (
TablenameNat = "nat"
TablenameMangle = "mangle"
TablenameFilter = "filter"
)
// Chain names as defined by net/ipv4/netfilter/ip_tables.c.
const (
ChainNamePrerouting = "PREROUTING"
ChainNameInput = "INPUT"
ChainNameForward = "FORWARD"
ChainNameOutput = "OUTPUT"
ChainNamePostrouting = "POSTROUTING"
)
// HookUnset indicates that there is no hook set for an entrypoint or
// underflow.
const HookUnset = -1
// DefaultTables returns a default set of tables. Each chain is set to accept
// all packets.
func DefaultTables() IPTables {
// TODO(gvisor.dev/issue/170): We may be able to swap out some strings for
// iotas.
return IPTables{
Tables: map[string]Table{
TablenameNat: Table{
Rules: []Rule{
Rule{Target: AcceptTarget{}},
Rule{Target: AcceptTarget{}},
Rule{Target: AcceptTarget{}},
Rule{Target: AcceptTarget{}},
Rule{Target: ErrorTarget{}},
},
BuiltinChains: map[Hook]int{
Prerouting: 0,
Input: 1,
Output: 2,
Postrouting: 3,
},
Underflows: map[Hook]int{
Prerouting: 0,
Input: 1,
Output: 2,
Postrouting: 3,
},
UserChains: map[string]int{},
},
TablenameMangle: Table{
Rules: []Rule{
Rule{Target: AcceptTarget{}},
Rule{Target: AcceptTarget{}},
Rule{Target: ErrorTarget{}},
},
BuiltinChains: map[Hook]int{
Prerouting: 0,
Output: 1,
},
Underflows: map[Hook]int{
Prerouting: 0,
Output: 1,
},
UserChains: map[string]int{},
},
TablenameFilter: Table{
Rules: []Rule{
Rule{Target: AcceptTarget{}},
Rule{Target: AcceptTarget{}},
Rule{Target: AcceptTarget{}},
Rule{Target: ErrorTarget{}},
},
BuiltinChains: map[Hook]int{
Input: 0,
Forward: 1,
Output: 2,
},
Underflows: map[Hook]int{
Input: 0,
Forward: 1,
Output: 2,
},
UserChains: map[string]int{},
},
},
Priorities: map[Hook][]string{
Input: []string{TablenameNat, TablenameFilter},
Prerouting: []string{TablenameMangle, TablenameNat},
Output: []string{TablenameMangle, TablenameNat, TablenameFilter},
},
}
}
// EmptyFilterTable returns a Table with no rules and the filter table chains
// mapped to HookUnset.
func EmptyFilterTable() Table {
return Table{
Rules: []Rule{},
BuiltinChains: map[Hook]int{
Input: HookUnset,
Forward: HookUnset,
Output: HookUnset,
},
Underflows: map[Hook]int{
Input: HookUnset,
Forward: HookUnset,
Output: HookUnset,
},
UserChains: map[string]int{},
}
}
// EmptyNatTable returns a Table with no rules and the filter table chains
// mapped to HookUnset.
func EmptyNatTable() Table {
return Table{
Rules: []Rule{},
BuiltinChains: map[Hook]int{
Prerouting: HookUnset,
Input: HookUnset,
Output: HookUnset,
Postrouting: HookUnset,
},
Underflows: map[Hook]int{
Prerouting: HookUnset,
Input: HookUnset,
Output: HookUnset,
Postrouting: HookUnset,
},
UserChains: map[string]int{},
}
}
// 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 PacketBuffer) bool {
// Go through each table containing the hook.
for _, tablename := range it.Priorities[hook] {
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 chainAccept:
continue
// The Drop verdict is final.
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))
}
}
// Every table returned Accept.
return true
}
// CheckPackets runs pkts through the rules for hook and returns a map of packets that
// should not go forward.
//
// NOTE: unlike the Check API the returned map contains packets that should be
// dropped.
func (it *IPTables) CheckPackets(hook Hook, pkts PacketBufferList) (drop map[*PacketBuffer]struct{}) {
for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
if ok := it.Check(hook, *pkt); !ok {
if drop == nil {
drop = make(map[*PacketBuffer]struct{})
}
drop[pkt] = struct{}{}
}
}
return drop
}
// Precondition: pkt.NetworkHeader is set.
func (it *IPTables) checkChain(hook Hook, pkt PacketBuffer, table Table, ruleIdx int) chainVerdict {
// Start from ruleIdx and walk the list of rules until a rule gives us
// a verdict.
for ruleIdx < len(table.Rules) {
switch verdict, jumpTo := it.checkRule(hook, pkt, table, ruleIdx); verdict {
case RuleAccept:
return chainAccept
case RuleDrop:
return chainDrop
case RuleReturn:
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", verdict))
}
default:
panic(fmt.Sprintf("Unknown verdict: %d", verdict))
}
}
// We got through the entire table without a decision. Default to DROP
// for safety.
return chainDrop
}
// Precondition: pk.NetworkHeader is set.
func (it *IPTables) checkRule(hook Hook, pkt PacketBuffer, table Table, ruleIdx int) (RuleVerdict, int) {
rule := table.Rules[ruleIdx]
// If pkt.NetworkHeader hasn't been set yet, it will be contained in
// pkt.Data.First().
if pkt.NetworkHeader == nil {
pkt.NetworkHeader = pkt.Data.First()
}
// Check whether the packet matches the IP header filter.
if !filterMatch(rule.Filter, header.IPv4(pkt.NetworkHeader)) {
// Continue on to the next rule.
return RuleJump, ruleIdx + 1
}
// Go through each rule matcher. If they all match, run
// the rule target.
for _, matcher := range rule.Matchers {
matches, hotdrop := matcher.Match(hook, pkt, "")
if hotdrop {
return RuleDrop, 0
}
if !matches {
// Continue on to the next rule.
return RuleJump, ruleIdx + 1
}
}
// All the matchers matched, so run the target.
return rule.Target.Action(pkt)
}
func filterMatch(filter IPHeaderFilter, hdr header.IPv4) 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
}
return true
}