2020-01-27 21:22:50 +00:00
|
|
|
// Copyright 2019 The gVisor Authors.
|
2019-05-31 23:14:04 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2020-03-24 16:05:06 +00:00
|
|
|
package stack
|
2019-05-31 23:14:04 +00:00
|
|
|
|
2020-01-08 23:57:25 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2020-01-11 02:07:15 +00:00
|
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
2020-01-08 23:57:25 +00:00
|
|
|
)
|
2020-01-08 22:48:47 +00:00
|
|
|
|
2020-01-13 19:26:26 +00:00
|
|
|
// Table names.
|
2019-05-31 23:14:04 +00:00
|
|
|
const (
|
2019-12-12 23:48:24 +00:00
|
|
|
TablenameNat = "nat"
|
|
|
|
TablenameMangle = "mangle"
|
|
|
|
TablenameFilter = "filter"
|
2019-05-31 23:14:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Chain names as defined by net/ipv4/netfilter/ip_tables.c.
|
|
|
|
const (
|
2019-12-12 23:48:24 +00:00
|
|
|
ChainNamePrerouting = "PREROUTING"
|
|
|
|
ChainNameInput = "INPUT"
|
|
|
|
ChainNameForward = "FORWARD"
|
|
|
|
ChainNameOutput = "OUTPUT"
|
|
|
|
ChainNamePostrouting = "POSTROUTING"
|
2019-05-31 23:14:04 +00:00
|
|
|
)
|
|
|
|
|
2020-01-13 19:26:26 +00:00
|
|
|
// HookUnset indicates that there is no hook set for an entrypoint or
|
|
|
|
// underflow.
|
2019-12-12 23:48:24 +00:00
|
|
|
const HookUnset = -1
|
|
|
|
|
2019-05-31 23:14:04 +00:00
|
|
|
// DefaultTables returns a default set of tables. Each chain is set to accept
|
|
|
|
// all packets.
|
2019-08-02 23:25:34 +00:00
|
|
|
func DefaultTables() IPTables {
|
2020-01-13 19:26:26 +00:00
|
|
|
// TODO(gvisor.dev/issue/170): We may be able to swap out some strings for
|
|
|
|
// iotas.
|
2019-08-02 23:25:34 +00:00
|
|
|
return IPTables{
|
2019-06-07 19:54:53 +00:00
|
|
|
Tables: map[string]Table{
|
2019-12-12 23:48:24 +00:00
|
|
|
TablenameNat: Table{
|
|
|
|
Rules: []Rule{
|
2020-02-07 19:21:07 +00:00
|
|
|
Rule{Target: AcceptTarget{}},
|
|
|
|
Rule{Target: AcceptTarget{}},
|
|
|
|
Rule{Target: AcceptTarget{}},
|
|
|
|
Rule{Target: AcceptTarget{}},
|
2020-01-09 01:30:08 +00:00
|
|
|
Rule{Target: ErrorTarget{}},
|
2019-12-12 23:48:24 +00:00
|
|
|
},
|
|
|
|
BuiltinChains: map[Hook]int{
|
|
|
|
Prerouting: 0,
|
|
|
|
Input: 1,
|
|
|
|
Output: 2,
|
|
|
|
Postrouting: 3,
|
2019-05-31 23:14:04 +00:00
|
|
|
},
|
2019-12-12 23:48:24 +00:00
|
|
|
Underflows: map[Hook]int{
|
|
|
|
Prerouting: 0,
|
|
|
|
Input: 1,
|
|
|
|
Output: 2,
|
|
|
|
Postrouting: 3,
|
2019-05-31 23:14:04 +00:00
|
|
|
},
|
2019-12-12 23:48:24 +00:00
|
|
|
UserChains: map[string]int{},
|
2019-05-31 23:14:04 +00:00
|
|
|
},
|
2019-12-12 23:48:24 +00:00
|
|
|
TablenameMangle: Table{
|
|
|
|
Rules: []Rule{
|
2020-02-07 19:21:07 +00:00
|
|
|
Rule{Target: AcceptTarget{}},
|
|
|
|
Rule{Target: AcceptTarget{}},
|
2020-01-09 01:30:08 +00:00
|
|
|
Rule{Target: ErrorTarget{}},
|
2019-12-12 23:48:24 +00:00
|
|
|
},
|
|
|
|
BuiltinChains: map[Hook]int{
|
|
|
|
Prerouting: 0,
|
|
|
|
Output: 1,
|
2019-05-31 23:14:04 +00:00
|
|
|
},
|
2019-12-12 23:48:24 +00:00
|
|
|
Underflows: map[Hook]int{
|
|
|
|
Prerouting: 0,
|
|
|
|
Output: 1,
|
2019-05-31 23:14:04 +00:00
|
|
|
},
|
2019-12-12 23:48:24 +00:00
|
|
|
UserChains: map[string]int{},
|
|
|
|
},
|
|
|
|
TablenameFilter: Table{
|
|
|
|
Rules: []Rule{
|
2020-02-07 19:21:07 +00:00
|
|
|
Rule{Target: AcceptTarget{}},
|
|
|
|
Rule{Target: AcceptTarget{}},
|
|
|
|
Rule{Target: AcceptTarget{}},
|
2020-01-09 01:30:08 +00:00
|
|
|
Rule{Target: ErrorTarget{}},
|
2019-12-12 23:48:24 +00:00
|
|
|
},
|
|
|
|
BuiltinChains: map[Hook]int{
|
|
|
|
Input: 0,
|
|
|
|
Forward: 1,
|
|
|
|
Output: 2,
|
|
|
|
},
|
|
|
|
Underflows: map[Hook]int{
|
|
|
|
Input: 0,
|
|
|
|
Forward: 1,
|
|
|
|
Output: 2,
|
|
|
|
},
|
|
|
|
UserChains: map[string]int{},
|
2019-05-31 23:14:04 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Priorities: map[Hook][]string{
|
2019-12-12 23:48:24 +00:00
|
|
|
Input: []string{TablenameNat, TablenameFilter},
|
|
|
|
Prerouting: []string{TablenameMangle, TablenameNat},
|
|
|
|
Output: []string{TablenameMangle, TablenameNat, TablenameFilter},
|
2019-05-31 23:14:04 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-13 19:26:26 +00:00
|
|
|
// EmptyFilterTable returns a Table with no rules and the filter table chains
|
|
|
|
// mapped to HookUnset.
|
2019-12-12 23:48:24 +00:00
|
|
|
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,
|
2019-05-31 23:14:04 +00:00
|
|
|
},
|
2019-12-12 23:48:24 +00:00
|
|
|
UserChains: map[string]int{},
|
2020-02-18 19:30:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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{},
|
2019-05-31 23:14:04 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-08 22:48:47 +00:00
|
|
|
|
2020-02-13 00:19:06 +00:00
|
|
|
// 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
|
|
|
|
)
|
|
|
|
|
2020-01-08 22:48:47 +00:00
|
|
|
// 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.
|
2020-01-21 22:47:17 +00:00
|
|
|
//
|
|
|
|
// Precondition: pkt.NetworkHeader is set.
|
2020-03-24 16:05:06 +00:00
|
|
|
func (it *IPTables) Check(hook Hook, pkt PacketBuffer) bool {
|
2020-01-08 22:48:47 +00:00
|
|
|
// Go through each table containing the hook.
|
|
|
|
for _, tablename := range it.Priorities[hook] {
|
2020-02-13 00:19:06 +00:00
|
|
|
table := it.Tables[tablename]
|
|
|
|
ruleIdx := table.BuiltinChains[hook]
|
|
|
|
switch verdict := it.checkChain(hook, pkt, table, ruleIdx); verdict {
|
2020-01-08 23:57:25 +00:00
|
|
|
// If the table returns Accept, move on to the next table.
|
2020-02-13 00:19:06 +00:00
|
|
|
case chainAccept:
|
2020-01-08 23:57:25 +00:00
|
|
|
continue
|
|
|
|
// The Drop verdict is final.
|
2020-02-13 00:19:06 +00:00
|
|
|
case chainDrop:
|
2020-01-08 23:57:25 +00:00
|
|
|
return false
|
2020-02-13 00:19:06 +00:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2020-01-15 01:54:02 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("Unknown verdict %v.", verdict))
|
2020-01-08 22:48:47 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-08 23:57:25 +00:00
|
|
|
|
|
|
|
// Every table returned Accept.
|
|
|
|
return true
|
2020-01-08 22:48:47 +00:00
|
|
|
}
|
|
|
|
|
2020-01-21 22:47:17 +00:00
|
|
|
// Precondition: pkt.NetworkHeader is set.
|
2020-03-24 16:05:06 +00:00
|
|
|
func (it *IPTables) checkChain(hook Hook, pkt PacketBuffer, table Table, ruleIdx int) chainVerdict {
|
2020-01-10 21:58:46 +00:00
|
|
|
// Start from ruleIdx and walk the list of rules until a rule gives us
|
|
|
|
// a verdict.
|
2020-02-13 00:19:06 +00:00
|
|
|
for ruleIdx < len(table.Rules) {
|
|
|
|
switch verdict, jumpTo := it.checkRule(hook, pkt, table, ruleIdx); verdict {
|
2020-02-07 19:21:07 +00:00
|
|
|
case RuleAccept:
|
2020-02-13 00:19:06 +00:00
|
|
|
return chainAccept
|
2020-02-07 19:21:07 +00:00
|
|
|
|
|
|
|
case RuleDrop:
|
2020-02-13 00:19:06 +00:00
|
|
|
return chainDrop
|
2020-02-07 19:21:07 +00:00
|
|
|
|
|
|
|
case RuleReturn:
|
2020-02-13 00:19:06 +00:00
|
|
|
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++
|
2020-02-26 21:18:35 +00:00
|
|
|
continue
|
2020-02-07 19:21:07 +00:00
|
|
|
default:
|
2020-02-13 00:19:06 +00:00
|
|
|
panic(fmt.Sprintf("Unknown verdict: %d", verdict))
|
2020-02-07 19:21:07 +00:00
|
|
|
}
|
|
|
|
|
2020-01-15 01:54:02 +00:00
|
|
|
default:
|
2020-02-07 19:21:07 +00:00
|
|
|
panic(fmt.Sprintf("Unknown verdict: %d", verdict))
|
2020-01-08 22:48:47 +00:00
|
|
|
}
|
2020-02-07 19:21:07 +00:00
|
|
|
|
2020-01-08 22:48:47 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 19:21:07 +00:00
|
|
|
// We got through the entire table without a decision. Default to DROP
|
|
|
|
// for safety.
|
2020-02-13 00:19:06 +00:00
|
|
|
return chainDrop
|
2020-01-08 22:48:47 +00:00
|
|
|
}
|
|
|
|
|
2020-01-11 02:07:15 +00:00
|
|
|
// Precondition: pk.NetworkHeader is set.
|
2020-03-24 16:05:06 +00:00
|
|
|
func (it *IPTables) checkRule(hook Hook, pkt PacketBuffer, table Table, ruleIdx int) (RuleVerdict, int) {
|
2020-01-08 22:48:47 +00:00
|
|
|
rule := table.Rules[ruleIdx]
|
2020-01-09 21:41:52 +00:00
|
|
|
|
2020-02-25 23:03:51 +00:00
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
|
2020-02-15 01:19:32 +00:00
|
|
|
// Check whether the packet matches the IP header filter.
|
|
|
|
if !filterMatch(rule.Filter, header.IPv4(pkt.NetworkHeader)) {
|
2020-02-13 00:19:06 +00:00
|
|
|
// Continue on to the next rule.
|
|
|
|
return RuleJump, ruleIdx + 1
|
2020-01-09 23:38:21 +00:00
|
|
|
}
|
2020-01-09 21:41:52 +00:00
|
|
|
|
2020-01-08 22:48:47 +00:00
|
|
|
// 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 {
|
2020-02-13 00:19:06 +00:00
|
|
|
return RuleDrop, 0
|
2020-01-08 22:48:47 +00:00
|
|
|
}
|
|
|
|
if !matches {
|
2020-02-13 00:19:06 +00:00
|
|
|
// Continue on to the next rule.
|
|
|
|
return RuleJump, ruleIdx + 1
|
2020-01-08 22:48:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// All the matchers matched, so run the target.
|
2020-02-26 21:18:35 +00:00
|
|
|
return rule.Target.Action(pkt)
|
2020-01-08 22:48:47 +00:00
|
|
|
}
|
2020-02-15 01:19:32 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|