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.
|
|
|
|
|
|
|
|
// Package iptables supports packet filtering and manipulation via the iptables
|
|
|
|
// tool.
|
|
|
|
package iptables
|
|
|
|
|
2020-01-08 23:57:25 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
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{
|
|
|
|
Rule{Target: UnconditionalAcceptTarget{}},
|
|
|
|
Rule{Target: UnconditionalAcceptTarget{}},
|
|
|
|
Rule{Target: UnconditionalAcceptTarget{}},
|
|
|
|
Rule{Target: UnconditionalAcceptTarget{}},
|
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{
|
|
|
|
Rule{Target: UnconditionalAcceptTarget{}},
|
|
|
|
Rule{Target: UnconditionalAcceptTarget{}},
|
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{
|
|
|
|
Rule{Target: UnconditionalAcceptTarget{}},
|
|
|
|
Rule{Target: UnconditionalAcceptTarget{}},
|
|
|
|
Rule{Target: UnconditionalAcceptTarget{}},
|
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{},
|
2019-05-31 23:14:04 +00:00
|
|
|
}
|
|
|
|
}
|
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.
|
|
|
|
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] {
|
2020-01-10 21:58:46 +00:00
|
|
|
switch verdict := it.checkTable(hook, pkt, tablename); verdict {
|
2020-01-08 23:57:25 +00:00
|
|
|
// If the table returns Accept, move on to the next table.
|
|
|
|
case Accept:
|
|
|
|
continue
|
|
|
|
// The Drop verdict is final.
|
|
|
|
case Drop:
|
|
|
|
return false
|
|
|
|
case Stolen, Queue, Repeat, None, Jump, Return, Continue:
|
|
|
|
panic(fmt.Sprintf("Unimplemented verdict %v.", verdict))
|
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-08 23:57:25 +00:00
|
|
|
func (it *IPTables) checkTable(hook Hook, pkt tcpip.PacketBuffer, tablename string) Verdict {
|
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-01-08 22:48:47 +00:00
|
|
|
table := it.Tables[tablename]
|
|
|
|
for ruleIdx := table.BuiltinChains[hook]; ruleIdx < len(table.Rules); ruleIdx++ {
|
2020-01-10 21:58:46 +00:00
|
|
|
switch verdict := it.checkRule(hook, pkt, table, ruleIdx); verdict {
|
|
|
|
// In either of these cases, this table is done with the packet.
|
2020-01-08 22:48:47 +00:00
|
|
|
case Accept, Drop:
|
|
|
|
return verdict
|
2020-01-08 23:57:25 +00:00
|
|
|
// Continue traversing the rules of the table.
|
2020-01-08 22:48:47 +00:00
|
|
|
case Continue:
|
|
|
|
continue
|
|
|
|
case Stolen, Queue, Repeat, None, Jump, Return:
|
2020-01-08 23:57:25 +00:00
|
|
|
panic(fmt.Sprintf("Unimplemented verdict %v.", verdict))
|
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
|
|
|
panic(fmt.Sprintf("Traversed past the entire list of iptables rules in table %q.", tablename))
|
2020-01-08 22:48:47 +00:00
|
|
|
}
|
|
|
|
|
2020-01-11 02:07:15 +00:00
|
|
|
// Precondition: pk.NetworkHeader is set.
|
2020-01-08 22:48:47 +00:00
|
|
|
func (it *IPTables) checkRule(hook Hook, pkt tcpip.PacketBuffer, table Table, ruleIdx int) Verdict {
|
|
|
|
rule := table.Rules[ruleIdx]
|
2020-01-09 21:41:52 +00:00
|
|
|
|
|
|
|
// First check whether the packet matches the IP header filter.
|
|
|
|
// TODO(gvisor.dev/issue/170): Support other fields of the filter.
|
2020-01-14 00:10:00 +00:00
|
|
|
if rule.Filter.Protocol != 0 && rule.Filter.Protocol != header.IPv4(pkt.NetworkHeader).TransportProtocol() {
|
2020-01-09 23:38:21 +00:00
|
|
|
return Continue
|
|
|
|
}
|
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 {
|
|
|
|
return Drop
|
|
|
|
}
|
|
|
|
if !matches {
|
|
|
|
return Continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// All the matchers matched, so run the target.
|
|
|
|
verdict, _ := rule.Target.Action(pkt)
|
|
|
|
return verdict
|
|
|
|
}
|