mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-20 21:51:42 +00:00
wgengine/router: set up basic IPv6 routing/firewalling.
Part of #19. Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
parent
f0ef561049
commit
0d80904fc2
@ -6,7 +6,6 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/go-iptables/iptables"
|
"github.com/coreos/go-iptables/iptables"
|
||||||
@ -90,6 +89,7 @@ type linuxRouter struct {
|
|||||||
dns *dns.Manager
|
dns *dns.Manager
|
||||||
|
|
||||||
ipt4 netfilterRunner
|
ipt4 netfilterRunner
|
||||||
|
ipt6 netfilterRunner
|
||||||
cmd commandRunner
|
cmd commandRunner
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,12 +104,16 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return newUserspaceRouterAdvanced(logf, tunname, ipt4, osCommandRunner{})
|
ipt6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUserspaceRouterAdvanced(logf, tunname, ipt4, ipt6, osCommandRunner{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netfilter netfilterRunner, cmd commandRunner) (Router, error) {
|
func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netfilter4, netfilter6 netfilterRunner, cmd commandRunner) (Router, error) {
|
||||||
_, err := exec.Command("ip", "rule").Output()
|
ipRuleAvailable := (cmd.run("ip", "rule") == nil)
|
||||||
ipRuleAvailable := (err == nil)
|
|
||||||
|
|
||||||
mconfig := dns.ManagerConfig{
|
mconfig := dns.ManagerConfig{
|
||||||
Logf: logf,
|
Logf: logf,
|
||||||
@ -121,7 +125,8 @@ func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netfilter netf
|
|||||||
ipRuleAvailable: ipRuleAvailable,
|
ipRuleAvailable: ipRuleAvailable,
|
||||||
tunname: tunname,
|
tunname: tunname,
|
||||||
netfilterMode: NetfilterOff,
|
netfilterMode: NetfilterOff,
|
||||||
ipt4: netfilter,
|
ipt4: netfilter4,
|
||||||
|
ipt6: netfilter6,
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
dns: dns.NewManager(mconfig),
|
dns: dns.NewManager(mconfig),
|
||||||
}, nil
|
}, nil
|
||||||
@ -434,6 +439,7 @@ func (r *linuxRouter) addIPRules() error {
|
|||||||
|
|
||||||
rg := newRunGroup(nil, r.cmd)
|
rg := newRunGroup(nil, r.cmd)
|
||||||
|
|
||||||
|
for _, family := range []string{"-4", "-6"} {
|
||||||
// NOTE(apenwarr): We leave spaces between each pref number.
|
// NOTE(apenwarr): We leave spaces between each pref number.
|
||||||
// This is so the sysadmin can override by inserting rules in
|
// This is so the sysadmin can override by inserting rules in
|
||||||
// between if they want.
|
// between if they want.
|
||||||
@ -449,7 +455,7 @@ func (r *linuxRouter) addIPRules() error {
|
|||||||
// Packets from us, tagged with our fwmark, first try the kernel's
|
// Packets from us, tagged with our fwmark, first try the kernel's
|
||||||
// main routing table.
|
// main routing table.
|
||||||
rg.Run(
|
rg.Run(
|
||||||
"ip", "rule", "add",
|
"ip", family, "rule", "add",
|
||||||
"pref", tailscaleRouteTable+"10",
|
"pref", tailscaleRouteTable+"10",
|
||||||
"fwmark", tailscaleBypassMark,
|
"fwmark", tailscaleBypassMark,
|
||||||
"table", "main",
|
"table", "main",
|
||||||
@ -457,7 +463,7 @@ func (r *linuxRouter) addIPRules() error {
|
|||||||
// ...and then we try the 'default' table, for correctness,
|
// ...and then we try the 'default' table, for correctness,
|
||||||
// even though it's been empty on every Linux system I've ever seen.
|
// even though it's been empty on every Linux system I've ever seen.
|
||||||
rg.Run(
|
rg.Run(
|
||||||
"ip", "rule", "add",
|
"ip", family, "rule", "add",
|
||||||
"pref", tailscaleRouteTable+"30",
|
"pref", tailscaleRouteTable+"30",
|
||||||
"fwmark", tailscaleBypassMark,
|
"fwmark", tailscaleBypassMark,
|
||||||
"table", "default",
|
"table", "default",
|
||||||
@ -466,7 +472,7 @@ func (r *linuxRouter) addIPRules() error {
|
|||||||
// then packets from us should be aborted rather than falling through
|
// then packets from us should be aborted rather than falling through
|
||||||
// to the tailscale routes, because that would create routing loops.
|
// to the tailscale routes, because that would create routing loops.
|
||||||
rg.Run(
|
rg.Run(
|
||||||
"ip", "rule", "add",
|
"ip", family, "rule", "add",
|
||||||
"pref", tailscaleRouteTable+"50",
|
"pref", tailscaleRouteTable+"50",
|
||||||
"fwmark", tailscaleBypassMark,
|
"fwmark", tailscaleBypassMark,
|
||||||
"type", "unreachable",
|
"type", "unreachable",
|
||||||
@ -480,12 +486,13 @@ func (r *linuxRouter) addIPRules() error {
|
|||||||
// NOTE(apenwarr): tables >255 are not supported in busybox, so we
|
// NOTE(apenwarr): tables >255 are not supported in busybox, so we
|
||||||
// can't use a table number that aligns with the rule preferences.
|
// can't use a table number that aligns with the rule preferences.
|
||||||
rg.Run(
|
rg.Run(
|
||||||
"ip", "rule", "add",
|
"ip", family, "rule", "add",
|
||||||
"pref", tailscaleRouteTable+"70",
|
"pref", tailscaleRouteTable+"70",
|
||||||
"table", tailscaleRouteTable,
|
"table", tailscaleRouteTable,
|
||||||
)
|
)
|
||||||
// If that didn't match, then non-fwmark packets fall through to the
|
// If that didn't match, then non-fwmark packets fall through to the
|
||||||
// usual rules (pref 32766 and 32767, ie. main and default).
|
// usual rules (pref 32766 and 32767, ie. main and default).
|
||||||
|
}
|
||||||
|
|
||||||
return rg.ErrAcc
|
return rg.ErrAcc
|
||||||
}
|
}
|
||||||
@ -505,6 +512,7 @@ func (r *linuxRouter) delIPRules() error {
|
|||||||
// unknown rules during deletion.
|
// unknown rules during deletion.
|
||||||
rg := newRunGroup([]int{2, 254}, r.cmd)
|
rg := newRunGroup([]int{2, 254}, r.cmd)
|
||||||
|
|
||||||
|
for _, family := range []string{"-4", "-6"} {
|
||||||
// When deleting rules, we want to be a bit specific (mention which
|
// When deleting rules, we want to be a bit specific (mention which
|
||||||
// table we were routing to) but not *too* specific (fwmarks, etc).
|
// table we were routing to) but not *too* specific (fwmarks, etc).
|
||||||
// That leaves us some flexibility to change these values in later
|
// That leaves us some flexibility to change these values in later
|
||||||
@ -515,63 +523,80 @@ func (r *linuxRouter) delIPRules() error {
|
|||||||
// (never released in a stable version, so we can drop this
|
// (never released in a stable version, so we can drop this
|
||||||
// support eventually).
|
// support eventually).
|
||||||
rg.Run(
|
rg.Run(
|
||||||
"ip", "rule", "del",
|
"ip", family, "rule", "del",
|
||||||
"pref", "10000",
|
"pref", "10000",
|
||||||
"table", "main",
|
"table", "main",
|
||||||
)
|
)
|
||||||
|
|
||||||
// Delete new-style tailscale rules.
|
// Delete new-style tailscale rules.
|
||||||
rg.Run(
|
rg.Run(
|
||||||
"ip", "rule", "del",
|
"ip", family, "rule", "del",
|
||||||
"pref", tailscaleRouteTable+"10",
|
"pref", tailscaleRouteTable+"10",
|
||||||
"table", "main",
|
"table", "main",
|
||||||
)
|
)
|
||||||
rg.Run(
|
rg.Run(
|
||||||
"ip", "rule", "del",
|
"ip", family, "rule", "del",
|
||||||
"pref", tailscaleRouteTable+"30",
|
"pref", tailscaleRouteTable+"30",
|
||||||
"table", "default",
|
"table", "default",
|
||||||
)
|
)
|
||||||
rg.Run(
|
rg.Run(
|
||||||
"ip", "rule", "del",
|
"ip", family, "rule", "del",
|
||||||
"pref", tailscaleRouteTable+"50",
|
"pref", tailscaleRouteTable+"50",
|
||||||
"type", "unreachable",
|
"type", "unreachable",
|
||||||
)
|
)
|
||||||
rg.Run(
|
rg.Run(
|
||||||
"ip", "rule", "del",
|
"ip", family, "rule", "del",
|
||||||
"pref", tailscaleRouteTable+"70",
|
"pref", tailscaleRouteTable+"70",
|
||||||
"table", tailscaleRouteTable,
|
"table", tailscaleRouteTable,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return rg.ErrAcc
|
return rg.ErrAcc
|
||||||
}
|
}
|
||||||
|
|
||||||
// addNetfilterChains creates custom Tailscale chains in netfilter.
|
// addNetfilterChains creates custom Tailscale chains in netfilter.
|
||||||
func (r *linuxRouter) addNetfilterChains() error {
|
func (r *linuxRouter) addNetfilterChains() error {
|
||||||
create := func(table, chain string) error {
|
create := func(ipt netfilterRunner, table, chain string) error {
|
||||||
err := r.ipt4.ClearChain(table, chain)
|
err := ipt.ClearChain(table, chain)
|
||||||
if errCode(err) == 1 {
|
if errCode(err) == 1 {
|
||||||
// nonexistent chain. let's create it!
|
// nonexistent chain. let's create it!
|
||||||
return r.ipt4.NewChain(table, chain)
|
return ipt.NewChain(table, chain)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("setting up %s/%s: %w", table, chain, err)
|
return fmt.Errorf("setting up %s/%s: %w", table, chain, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := create("filter", "ts-input"); err != nil {
|
|
||||||
|
for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} {
|
||||||
|
if err := create(ipt, "filter", "ts-input"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := create("filter", "ts-forward"); err != nil {
|
if err := create(ipt, "filter", "ts-forward"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := create("nat", "ts-postrouting"); err != nil {
|
if err := create(ipt, "nat", "ts-postrouting"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addNetfilterBase adds some basic processing rules to be
|
||||||
|
// supplemented by later calls to other helpers.
|
||||||
|
func (r *linuxRouter) addNetfilterBase() error {
|
||||||
|
if err := r.addNetfilterBase4(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.addNetfilterBase6(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addNetfilterBase adds with some basic processing rules to be supplemented
|
// addNetfilterBase4 adds some basic IPv4 processing rules to be
|
||||||
// by later calls to other helpers.
|
// supplemented by later calls to other helpers.
|
||||||
func (r *linuxRouter) addNetfilterBase() error {
|
func (r *linuxRouter) addNetfilterBase4() error {
|
||||||
// Only allow CGNAT range traffic to come from tailscale0. There
|
// Only allow CGNAT range traffic to come from tailscale0. There
|
||||||
// is an exception carved out for ranges used by ChromeOS, for
|
// is an exception carved out for ranges used by ChromeOS, for
|
||||||
// which we fall out of the Tailscale chain.
|
// which we fall out of the Tailscale chain.
|
||||||
@ -580,11 +605,11 @@ func (r *linuxRouter) addNetfilterBase() error {
|
|||||||
// CGNAT range for other purposes :(.
|
// CGNAT range for other purposes :(.
|
||||||
args := []string{"!", "-i", r.tunname, "-s", tsaddr.ChromeOSVMRange().String(), "-j", "RETURN"}
|
args := []string{"!", "-i", r.tunname, "-s", tsaddr.ChromeOSVMRange().String(), "-j", "RETURN"}
|
||||||
if err := r.ipt4.Append("filter", "ts-input", args...); err != nil {
|
if err := r.ipt4.Append("filter", "ts-input", args...); err != nil {
|
||||||
return fmt.Errorf("adding %v in filter/ts-input: %w", args, err)
|
return fmt.Errorf("adding %v in v4/filter/ts-input: %w", args, err)
|
||||||
}
|
}
|
||||||
args = []string{"!", "-i", r.tunname, "-s", tsaddr.CGNATRange().String(), "-j", "DROP"}
|
args = []string{"!", "-i", r.tunname, "-s", tsaddr.CGNATRange().String(), "-j", "DROP"}
|
||||||
if err := r.ipt4.Append("filter", "ts-input", args...); err != nil {
|
if err := r.ipt4.Append("filter", "ts-input", args...); err != nil {
|
||||||
return fmt.Errorf("adding %v in filter/ts-input: %w", args, err)
|
return fmt.Errorf("adding %v in v4/filter/ts-input: %w", args, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward all traffic from the Tailscale interface, and drop
|
// Forward all traffic from the Tailscale interface, and drop
|
||||||
@ -600,19 +625,43 @@ func (r *linuxRouter) addNetfilterBase() error {
|
|||||||
// use to effectively run that same test again.
|
// use to effectively run that same test again.
|
||||||
args = []string{"-i", r.tunname, "-j", "MARK", "--set-mark", tailscaleSubnetRouteMark}
|
args = []string{"-i", r.tunname, "-j", "MARK", "--set-mark", tailscaleSubnetRouteMark}
|
||||||
if err := r.ipt4.Append("filter", "ts-forward", args...); err != nil {
|
if err := r.ipt4.Append("filter", "ts-forward", args...); err != nil {
|
||||||
return fmt.Errorf("adding %v in filter/ts-forward: %w", args, err)
|
return fmt.Errorf("adding %v in v4/filter/ts-forward: %w", args, err)
|
||||||
}
|
}
|
||||||
args = []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "ACCEPT"}
|
args = []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "ACCEPT"}
|
||||||
if err := r.ipt4.Append("filter", "ts-forward", args...); err != nil {
|
if err := r.ipt4.Append("filter", "ts-forward", args...); err != nil {
|
||||||
return fmt.Errorf("adding %v in filter/ts-forward: %w", args, err)
|
return fmt.Errorf("adding %v in v4/filter/ts-forward: %w", args, err)
|
||||||
}
|
}
|
||||||
args = []string{"-o", r.tunname, "-s", tsaddr.CGNATRange().String(), "-j", "DROP"}
|
args = []string{"-o", r.tunname, "-s", tsaddr.CGNATRange().String(), "-j", "DROP"}
|
||||||
if err := r.ipt4.Append("filter", "ts-forward", args...); err != nil {
|
if err := r.ipt4.Append("filter", "ts-forward", args...); err != nil {
|
||||||
return fmt.Errorf("adding %v in filter/ts-forward: %w", args, err)
|
return fmt.Errorf("adding %v in v4/filter/ts-forward: %w", args, err)
|
||||||
}
|
}
|
||||||
args = []string{"-o", r.tunname, "-j", "ACCEPT"}
|
args = []string{"-o", r.tunname, "-j", "ACCEPT"}
|
||||||
if err := r.ipt4.Append("filter", "ts-forward", args...); err != nil {
|
if err := r.ipt4.Append("filter", "ts-forward", args...); err != nil {
|
||||||
return fmt.Errorf("adding %v in filter/ts-forward: %w", args, err)
|
return fmt.Errorf("adding %v in v4/filter/ts-forward: %w", args, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addNetfilterBase4 adds some basic IPv6 processing rules to be
|
||||||
|
// supplemented by later calls to other helpers.
|
||||||
|
func (r *linuxRouter) addNetfilterBase6() error {
|
||||||
|
// TODO: only allow traffic from Tailscale's ULA range to come
|
||||||
|
// from tailscale0.
|
||||||
|
|
||||||
|
args := []string{"-i", r.tunname, "-j", "MARK", "--set-mark", tailscaleSubnetRouteMark}
|
||||||
|
if err := r.ipt6.Append("filter", "ts-forward", args...); err != nil {
|
||||||
|
return fmt.Errorf("adding %v in v6/filter/ts-forward: %w", args, err)
|
||||||
|
}
|
||||||
|
args = []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "ACCEPT"}
|
||||||
|
if err := r.ipt6.Append("filter", "ts-forward", args...); err != nil {
|
||||||
|
return fmt.Errorf("adding %v in v6/filter/ts-forward: %w", args, err)
|
||||||
|
}
|
||||||
|
// TODO: drop forwarded traffic to tailscale0 from tailscale's ULA
|
||||||
|
// (see corresponding IPv4 CGNAT rule).
|
||||||
|
args = []string{"-o", r.tunname, "-j", "ACCEPT"}
|
||||||
|
if err := r.ipt6.Append("filter", "ts-forward", args...); err != nil {
|
||||||
|
return fmt.Errorf("adding %v in v6/filter/ts-forward: %w", args, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -620,8 +669,8 @@ func (r *linuxRouter) addNetfilterBase() error {
|
|||||||
|
|
||||||
// delNetfilterChains removes the custom Tailscale chains from netfilter.
|
// delNetfilterChains removes the custom Tailscale chains from netfilter.
|
||||||
func (r *linuxRouter) delNetfilterChains() error {
|
func (r *linuxRouter) delNetfilterChains() error {
|
||||||
del := func(table, chain string) error {
|
del := func(ipt netfilterRunner, table, chain string) error {
|
||||||
if err := r.ipt4.ClearChain(table, chain); err != nil {
|
if err := ipt.ClearChain(table, chain); err != nil {
|
||||||
if errCode(err) == 1 {
|
if errCode(err) == 1 {
|
||||||
// nonexistent chain. That's fine, since it's
|
// nonexistent chain. That's fine, since it's
|
||||||
// the desired state anyway.
|
// the desired state anyway.
|
||||||
@ -629,7 +678,7 @@ func (r *linuxRouter) delNetfilterChains() error {
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("flushing %s/%s: %w", table, chain, err)
|
return fmt.Errorf("flushing %s/%s: %w", table, chain, err)
|
||||||
}
|
}
|
||||||
if err := r.ipt4.DeleteChain(table, chain); err != nil {
|
if err := ipt.DeleteChain(table, chain); err != nil {
|
||||||
// this shouldn't fail, because if the chain didn't
|
// this shouldn't fail, because if the chain didn't
|
||||||
// exist, we would have returned after ClearChain.
|
// exist, we would have returned after ClearChain.
|
||||||
return fmt.Errorf("deleting %s/%s: %v", table, chain, err)
|
return fmt.Errorf("deleting %s/%s: %v", table, chain, err)
|
||||||
@ -637,15 +686,17 @@ func (r *linuxRouter) delNetfilterChains() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := del("filter", "ts-input"); err != nil {
|
for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} {
|
||||||
|
if err := del(ipt, "filter", "ts-input"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := del("filter", "ts-forward"); err != nil {
|
if err := del(ipt, "filter", "ts-forward"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := del("nat", "ts-postrouting"); err != nil {
|
if err := del(ipt, "nat", "ts-postrouting"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -653,8 +704,8 @@ func (r *linuxRouter) delNetfilterChains() error {
|
|||||||
// delNetfilterBase empties but does not remove custom Tailscale chains from
|
// delNetfilterBase empties but does not remove custom Tailscale chains from
|
||||||
// netfilter.
|
// netfilter.
|
||||||
func (r *linuxRouter) delNetfilterBase() error {
|
func (r *linuxRouter) delNetfilterBase() error {
|
||||||
del := func(table, chain string) error {
|
del := func(ipt netfilterRunner, table, chain string) error {
|
||||||
if err := r.ipt4.ClearChain(table, chain); err != nil {
|
if err := ipt.ClearChain(table, chain); err != nil {
|
||||||
if errCode(err) == 1 {
|
if errCode(err) == 1 {
|
||||||
// nonexistent chain. That's fine, since it's
|
// nonexistent chain. That's fine, since it's
|
||||||
// the desired state anyway.
|
// the desired state anyway.
|
||||||
@ -665,15 +716,17 @@ func (r *linuxRouter) delNetfilterBase() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := del("filter", "ts-input"); err != nil {
|
for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} {
|
||||||
|
if err := del(ipt, "filter", "ts-input"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := del("filter", "ts-forward"); err != nil {
|
if err := del(ipt, "filter", "ts-forward"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := del("nat", "ts-postrouting"); err != nil {
|
if err := del(ipt, "nat", "ts-postrouting"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -682,42 +735,44 @@ func (r *linuxRouter) delNetfilterBase() error {
|
|||||||
// the relevant main netfilter chains. The tailscale chains must
|
// the relevant main netfilter chains. The tailscale chains must
|
||||||
// already exist.
|
// already exist.
|
||||||
func (r *linuxRouter) addNetfilterHooks() error {
|
func (r *linuxRouter) addNetfilterHooks() error {
|
||||||
divert := func(table, chain string) error {
|
divert := func(ipt netfilterRunner, table, chain string) error {
|
||||||
tsChain := tsChain(chain)
|
tsChain := tsChain(chain)
|
||||||
|
|
||||||
args := []string{"-j", tsChain}
|
args := []string{"-j", tsChain}
|
||||||
exists, err := r.ipt4.Exists(table, chain, args...)
|
exists, err := ipt.Exists(table, chain, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("checking for %v in %s/%s: %w", args, table, chain, err)
|
return fmt.Errorf("checking for %v in %s/%s: %w", args, table, chain, err)
|
||||||
}
|
}
|
||||||
if exists {
|
if exists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := r.ipt4.Insert(table, chain, 1, args...); err != nil {
|
if err := ipt.Insert(table, chain, 1, args...); err != nil {
|
||||||
return fmt.Errorf("adding %v in %s/%s: %w", args, table, chain, err)
|
return fmt.Errorf("adding %v in %s/%s: %w", args, table, chain, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := divert("filter", "INPUT"); err != nil {
|
for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} {
|
||||||
|
if err := divert(ipt, "filter", "INPUT"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := divert("filter", "FORWARD"); err != nil {
|
if err := divert(ipt, "filter", "FORWARD"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := divert("nat", "POSTROUTING"); err != nil {
|
if err := divert(ipt, "nat", "POSTROUTING"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// delNetfilterHooks deletes the calls to tailscale's netfilter chains
|
// delNetfilterHooks deletes the calls to tailscale's netfilter chains
|
||||||
// in the relevant main netfilter chains.
|
// in the relevant main netfilter chains.
|
||||||
func (r *linuxRouter) delNetfilterHooks() error {
|
func (r *linuxRouter) delNetfilterHooks() error {
|
||||||
del := func(table, chain string) error {
|
del := func(ipt netfilterRunner, table, chain string) error {
|
||||||
tsChain := tsChain(chain)
|
tsChain := tsChain(chain)
|
||||||
args := []string{"-j", tsChain}
|
args := []string{"-j", tsChain}
|
||||||
if err := r.ipt4.Delete(table, chain, args...); err != nil {
|
if err := ipt.Delete(table, chain, args...); err != nil {
|
||||||
// TODO(apenwarr): check for errCode(1) here.
|
// TODO(apenwarr): check for errCode(1) here.
|
||||||
// Unfortunately the error code from the iptables
|
// Unfortunately the error code from the iptables
|
||||||
// module resists unwrapping, unlike with other
|
// module resists unwrapping, unlike with other
|
||||||
@ -729,15 +784,17 @@ func (r *linuxRouter) delNetfilterHooks() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := del("filter", "INPUT"); err != nil {
|
for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} {
|
||||||
|
if err := del(ipt, "filter", "INPUT"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := del("filter", "FORWARD"); err != nil {
|
if err := del(ipt, "filter", "FORWARD"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := del("nat", "POSTROUTING"); err != nil {
|
if err := del(ipt, "nat", "POSTROUTING"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -750,7 +807,10 @@ func (r *linuxRouter) addSNATRule() error {
|
|||||||
|
|
||||||
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
||||||
if err := r.ipt4.Append("nat", "ts-postrouting", args...); err != nil {
|
if err := r.ipt4.Append("nat", "ts-postrouting", args...); err != nil {
|
||||||
return fmt.Errorf("adding %v in nat/ts-postrouting: %w", args, err)
|
return fmt.Errorf("adding %v in v4/nat/ts-postrouting: %w", args, err)
|
||||||
|
}
|
||||||
|
if err := r.ipt6.Append("nat", "ts-postrouting", args...); err != nil {
|
||||||
|
return fmt.Errorf("adding %v in v6/nat/ts-postrouting: %w", args, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -764,7 +824,10 @@ func (r *linuxRouter) delSNATRule() error {
|
|||||||
|
|
||||||
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark, "-j", "MASQUERADE"}
|
||||||
if err := r.ipt4.Delete("nat", "ts-postrouting", args...); err != nil {
|
if err := r.ipt4.Delete("nat", "ts-postrouting", args...); err != nil {
|
||||||
return fmt.Errorf("deleting %v in nat/ts-postrouting: %w", args, err)
|
return fmt.Errorf("deleting %v in v4/nat/ts-postrouting: %w", args, err)
|
||||||
|
}
|
||||||
|
if err := r.ipt6.Delete("nat", "ts-postrouting", args...); err != nil {
|
||||||
|
return fmt.Errorf("deleting %v in v6/nat/ts-postrouting: %w", args, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -34,10 +34,14 @@ func mustCIDRs(ss ...string) []netaddr.IPPrefix {
|
|||||||
|
|
||||||
func TestRouterStates(t *testing.T) {
|
func TestRouterStates(t *testing.T) {
|
||||||
basic := `
|
basic := `
|
||||||
ip rule add pref 5210 fwmark 0x80000 table main
|
ip rule add -4 pref 5210 fwmark 0x80000 table main
|
||||||
ip rule add pref 5230 fwmark 0x80000 table default
|
ip rule add -4 pref 5230 fwmark 0x80000 table default
|
||||||
ip rule add pref 5250 fwmark 0x80000 type unreachable
|
ip rule add -4 pref 5250 fwmark 0x80000 type unreachable
|
||||||
ip rule add pref 5270 table 52
|
ip rule add -4 pref 5270 table 52
|
||||||
|
ip rule add -6 pref 5210 fwmark 0x80000 table main
|
||||||
|
ip rule add -6 pref 5230 fwmark 0x80000 table default
|
||||||
|
ip rule add -6 pref 5250 fwmark 0x80000 type unreachable
|
||||||
|
ip rule add -6 pref 5270 table 52
|
||||||
`
|
`
|
||||||
states := []struct {
|
states := []struct {
|
||||||
name string
|
name string
|
||||||
@ -104,17 +108,24 @@ up
|
|||||||
ip addr add 100.101.102.104/10 dev tailscale0
|
ip addr add 100.101.102.104/10 dev tailscale0
|
||||||
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
||||||
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
||||||
`filter/FORWARD -j ts-forward
|
`v4/filter/FORWARD -j ts-forward
|
||||||
filter/INPUT -j ts-input
|
v4/filter/INPUT -j ts-input
|
||||||
filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
v4/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
filter/ts-forward -o tailscale0 -j ACCEPT
|
v4/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
||||||
filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
||||||
filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
nat/POSTROUTING -j ts-postrouting
|
v4/nat/POSTROUTING -j ts-postrouting
|
||||||
nat/ts-postrouting -m mark --mark 0x40000 -j MASQUERADE
|
v4/nat/ts-postrouting -m mark --mark 0x40000 -j MASQUERADE
|
||||||
|
v6/filter/FORWARD -j ts-forward
|
||||||
|
v6/filter/INPUT -j ts-input
|
||||||
|
v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
|
v6/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
|
v6/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
|
v6/nat/POSTROUTING -j ts-postrouting
|
||||||
|
v6/nat/ts-postrouting -m mark --mark 0x40000 -j MASQUERADE
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -129,16 +140,22 @@ up
|
|||||||
ip addr add 100.101.102.104/10 dev tailscale0
|
ip addr add 100.101.102.104/10 dev tailscale0
|
||||||
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
||||||
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
||||||
`filter/FORWARD -j ts-forward
|
`v4/filter/FORWARD -j ts-forward
|
||||||
filter/INPUT -j ts-input
|
v4/filter/INPUT -j ts-input
|
||||||
filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
v4/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
filter/ts-forward -o tailscale0 -j ACCEPT
|
v4/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
||||||
filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
||||||
filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
nat/POSTROUTING -j ts-postrouting
|
v4/nat/POSTROUTING -j ts-postrouting
|
||||||
|
v6/filter/FORWARD -j ts-forward
|
||||||
|
v6/filter/INPUT -j ts-input
|
||||||
|
v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
|
v6/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
|
v6/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
|
v6/nat/POSTROUTING -j ts-postrouting
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -156,16 +173,22 @@ up
|
|||||||
ip addr add 100.101.102.104/10 dev tailscale0
|
ip addr add 100.101.102.104/10 dev tailscale0
|
||||||
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
||||||
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
||||||
`filter/FORWARD -j ts-forward
|
`v4/filter/FORWARD -j ts-forward
|
||||||
filter/INPUT -j ts-input
|
v4/filter/INPUT -j ts-input
|
||||||
filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
v4/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
filter/ts-forward -o tailscale0 -j ACCEPT
|
v4/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
||||||
filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
||||||
filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
nat/POSTROUTING -j ts-postrouting
|
v4/nat/POSTROUTING -j ts-postrouting
|
||||||
|
v6/filter/FORWARD -j ts-forward
|
||||||
|
v6/filter/INPUT -j ts-input
|
||||||
|
v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
|
v6/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
|
v6/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
|
v6/nat/POSTROUTING -j ts-postrouting
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -180,16 +203,22 @@ up
|
|||||||
ip addr add 100.101.102.104/10 dev tailscale0
|
ip addr add 100.101.102.104/10 dev tailscale0
|
||||||
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
||||||
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
||||||
`filter/FORWARD -j ts-forward
|
`v4/filter/FORWARD -j ts-forward
|
||||||
filter/INPUT -j ts-input
|
v4/filter/INPUT -j ts-input
|
||||||
filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
v4/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
filter/ts-forward -o tailscale0 -j ACCEPT
|
v4/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
||||||
filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
||||||
filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
nat/POSTROUTING -j ts-postrouting
|
v4/nat/POSTROUTING -j ts-postrouting
|
||||||
|
v6/filter/FORWARD -j ts-forward
|
||||||
|
v6/filter/INPUT -j ts-input
|
||||||
|
v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
|
v6/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
|
v6/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
|
v6/nat/POSTROUTING -j ts-postrouting
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -205,13 +234,16 @@ up
|
|||||||
ip addr add 100.101.102.104/10 dev tailscale0
|
ip addr add 100.101.102.104/10 dev tailscale0
|
||||||
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
||||||
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
||||||
`filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
`v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
v4/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
filter/ts-forward -o tailscale0 -j ACCEPT
|
v4/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
||||||
filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
||||||
filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
|
v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
|
v6/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
|
v6/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -226,22 +258,28 @@ up
|
|||||||
ip addr add 100.101.102.104/10 dev tailscale0
|
ip addr add 100.101.102.104/10 dev tailscale0
|
||||||
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
ip route add 10.0.0.0/8 dev tailscale0 table 52
|
||||||
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
ip route add 100.100.100.100/32 dev tailscale0 table 52` + basic +
|
||||||
`filter/FORWARD -j ts-forward
|
`v4/filter/FORWARD -j ts-forward
|
||||||
filter/INPUT -j ts-input
|
v4/filter/INPUT -j ts-input
|
||||||
filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
v4/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
filter/ts-forward -o tailscale0 -j ACCEPT
|
v4/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
|
||||||
filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
|
||||||
filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
|
||||||
nat/POSTROUTING -j ts-postrouting
|
v4/nat/POSTROUTING -j ts-postrouting
|
||||||
|
v6/filter/FORWARD -j ts-forward
|
||||||
|
v6/filter/INPUT -j ts-input
|
||||||
|
v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
|
||||||
|
v6/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
|
||||||
|
v6/filter/ts-forward -o tailscale0 -j ACCEPT
|
||||||
|
v6/nat/POSTROUTING -j ts-postrouting
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fake := NewFakeOS(t)
|
fake := NewFakeOS(t)
|
||||||
router, err := newUserspaceRouterAdvanced(t.Logf, "tailscale0", fake, fake)
|
router, err := newUserspaceRouterAdvanced(t.Logf, "tailscale0", fake.netfilter4, fake.netfilter6, fake)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create router: %v", err)
|
t.Fatalf("failed to create router: %v", err)
|
||||||
}
|
}
|
||||||
@ -275,21 +313,15 @@ nat/POSTROUTING -j ts-postrouting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fakeOS implements netfilterRunner and commandRunner, but captures
|
type fakeNetfilter struct {
|
||||||
// changes without touching the OS.
|
|
||||||
type fakeOS struct {
|
|
||||||
t *testing.T
|
t *testing.T
|
||||||
up bool
|
n map[string][]string
|
||||||
ips []string
|
|
||||||
routes []string
|
|
||||||
rules []string
|
|
||||||
netfilter map[string][]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFakeOS(t *testing.T) *fakeOS {
|
func newNetfilter(t *testing.T) *fakeNetfilter {
|
||||||
return &fakeOS{
|
return &fakeNetfilter{
|
||||||
t: t,
|
t: t,
|
||||||
netfilter: map[string][]string{
|
n: map[string][]string{
|
||||||
"filter/INPUT": nil,
|
"filter/INPUT": nil,
|
||||||
"filter/OUTPUT": nil,
|
"filter/OUTPUT": nil,
|
||||||
"filter/FORWARD": nil,
|
"filter/FORWARD": nil,
|
||||||
@ -300,6 +332,118 @@ func NewFakeOS(t *testing.T) *fakeOS {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *fakeNetfilter) Insert(table, chain string, pos int, args ...string) error {
|
||||||
|
k := table + "/" + chain
|
||||||
|
if rules, ok := n.n[k]; ok {
|
||||||
|
if pos > len(rules)+1 {
|
||||||
|
n.t.Errorf("bad position %d in %s", pos, k)
|
||||||
|
return errExec
|
||||||
|
}
|
||||||
|
rules = append(rules, "")
|
||||||
|
copy(rules[pos:], rules[pos-1:])
|
||||||
|
rules[pos-1] = strings.Join(args, " ")
|
||||||
|
n.n[k] = rules
|
||||||
|
} else {
|
||||||
|
n.t.Errorf("unknown table/chain %s", k)
|
||||||
|
return errExec
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *fakeNetfilter) Append(table, chain string, args ...string) error {
|
||||||
|
k := table + "/" + chain
|
||||||
|
return n.Insert(table, chain, len(n.n[k])+1, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *fakeNetfilter) Exists(table, chain string, args ...string) (bool, error) {
|
||||||
|
k := table + "/" + chain
|
||||||
|
if rules, ok := n.n[k]; ok {
|
||||||
|
for _, rule := range rules {
|
||||||
|
if rule == strings.Join(args, " ") {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
} else {
|
||||||
|
n.t.Errorf("unknown table/chain %s", k)
|
||||||
|
return false, errExec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *fakeNetfilter) Delete(table, chain string, args ...string) error {
|
||||||
|
k := table + "/" + chain
|
||||||
|
if rules, ok := n.n[k]; ok {
|
||||||
|
for i, rule := range rules {
|
||||||
|
if rule == strings.Join(args, " ") {
|
||||||
|
rules = append(rules[:i], rules[i+1:]...)
|
||||||
|
n.n[k] = rules
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.t.Errorf("delete of unknown rule %q from %s", strings.Join(args, " "), k)
|
||||||
|
return errExec
|
||||||
|
} else {
|
||||||
|
n.t.Errorf("unknown table/chain %s", k)
|
||||||
|
return errExec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *fakeNetfilter) ClearChain(table, chain string) error {
|
||||||
|
k := table + "/" + chain
|
||||||
|
if _, ok := n.n[k]; ok {
|
||||||
|
n.n[k] = nil
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
n.t.Logf("note: ClearChain: unknown table/chain %s", k)
|
||||||
|
return errors.New("exitcode:1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *fakeNetfilter) NewChain(table, chain string) error {
|
||||||
|
k := table + "/" + chain
|
||||||
|
if _, ok := n.n[k]; ok {
|
||||||
|
n.t.Errorf("table/chain %s already exists", k)
|
||||||
|
return errExec
|
||||||
|
}
|
||||||
|
n.n[k] = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *fakeNetfilter) DeleteChain(table, chain string) error {
|
||||||
|
k := table + "/" + chain
|
||||||
|
if rules, ok := n.n[k]; ok {
|
||||||
|
if len(rules) != 0 {
|
||||||
|
n.t.Errorf("%s is not empty", k)
|
||||||
|
return errExec
|
||||||
|
}
|
||||||
|
delete(n.n, k)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
n.t.Errorf("%s does not exist", k)
|
||||||
|
return errExec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fakeOS implements commandRunner and provides v4 and v6
|
||||||
|
// netfilterRunners, but captures changes without touching the OS.
|
||||||
|
type fakeOS struct {
|
||||||
|
t *testing.T
|
||||||
|
up bool
|
||||||
|
ips []string
|
||||||
|
routes []string
|
||||||
|
rules []string
|
||||||
|
netfilter4 *fakeNetfilter
|
||||||
|
netfilter6 *fakeNetfilter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeOS(t *testing.T) *fakeOS {
|
||||||
|
return &fakeOS{
|
||||||
|
t: t,
|
||||||
|
netfilter4: newNetfilter(t),
|
||||||
|
netfilter6: newNetfilter(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var errExec = errors.New("execution failed")
|
var errExec = errors.New("execution failed")
|
||||||
|
|
||||||
func (o *fakeOS) String() string {
|
func (o *fakeOS) String() string {
|
||||||
@ -323,120 +467,30 @@ func (o *fakeOS) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var chains []string
|
var chains []string
|
||||||
for chain := range o.netfilter {
|
for chain := range o.netfilter4.n {
|
||||||
chains = append(chains, chain)
|
chains = append(chains, chain)
|
||||||
}
|
}
|
||||||
sort.Strings(chains)
|
sort.Strings(chains)
|
||||||
for _, chain := range chains {
|
for _, chain := range chains {
|
||||||
for _, rule := range o.netfilter[chain] {
|
for _, rule := range o.netfilter4.n[chain] {
|
||||||
fmt.Fprintf(&b, "%s %s\n", chain, rule)
|
fmt.Fprintf(&b, "v4/%s %s\n", chain, rule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chains = nil
|
||||||
|
for chain := range o.netfilter6.n {
|
||||||
|
chains = append(chains, chain)
|
||||||
|
}
|
||||||
|
sort.Strings(chains)
|
||||||
|
for _, chain := range chains {
|
||||||
|
for _, rule := range o.netfilter6.n[chain] {
|
||||||
|
fmt.Fprintf(&b, "v6/%s %s\n", chain, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return b.String()[:len(b.String())-1]
|
return b.String()[:len(b.String())-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *fakeOS) Insert(table, chain string, pos int, args ...string) error {
|
|
||||||
k := table + "/" + chain
|
|
||||||
if rules, ok := o.netfilter[k]; ok {
|
|
||||||
if pos > len(rules)+1 {
|
|
||||||
o.t.Errorf("bad position %d in %s", pos, k)
|
|
||||||
return errExec
|
|
||||||
}
|
|
||||||
rules = append(rules, "")
|
|
||||||
copy(rules[pos:], rules[pos-1:])
|
|
||||||
rules[pos-1] = strings.Join(args, " ")
|
|
||||||
o.netfilter[k] = rules
|
|
||||||
} else {
|
|
||||||
o.t.Errorf("unknown table/chain %s", k)
|
|
||||||
return errExec
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *fakeOS) Append(table, chain string, args ...string) error {
|
|
||||||
k := table + "/" + chain
|
|
||||||
return o.Insert(table, chain, len(o.netfilter[k])+1, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *fakeOS) Exists(table, chain string, args ...string) (bool, error) {
|
|
||||||
k := table + "/" + chain
|
|
||||||
if rules, ok := o.netfilter[k]; ok {
|
|
||||||
for _, rule := range rules {
|
|
||||||
if rule == strings.Join(args, " ") {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
} else {
|
|
||||||
o.t.Errorf("unknown table/chain %s", k)
|
|
||||||
return false, errExec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *fakeOS) Delete(table, chain string, args ...string) error {
|
|
||||||
k := table + "/" + chain
|
|
||||||
if rules, ok := o.netfilter[k]; ok {
|
|
||||||
for i, rule := range rules {
|
|
||||||
if rule == strings.Join(args, " ") {
|
|
||||||
rules = append(rules[:i], rules[i+1:]...)
|
|
||||||
o.netfilter[k] = rules
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o.t.Errorf("delete of unknown rule %q from %s", strings.Join(args, " "), k)
|
|
||||||
return errExec
|
|
||||||
} else {
|
|
||||||
o.t.Errorf("unknown table/chain %s", k)
|
|
||||||
return errExec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *fakeOS) ListChains(table string) (ret []string, err error) {
|
|
||||||
for chain := range o.netfilter {
|
|
||||||
pfx := table + "/"
|
|
||||||
if strings.HasPrefix(chain, pfx) {
|
|
||||||
ret = append(ret, chain[len(pfx):])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *fakeOS) ClearChain(table, chain string) error {
|
|
||||||
k := table + "/" + chain
|
|
||||||
if _, ok := o.netfilter[k]; ok {
|
|
||||||
o.netfilter[k] = nil
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
o.t.Logf("note: ClearChain: unknown table/chain %s", k)
|
|
||||||
return errors.New("exitcode:1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *fakeOS) NewChain(table, chain string) error {
|
|
||||||
k := table + "/" + chain
|
|
||||||
if _, ok := o.netfilter[k]; ok {
|
|
||||||
o.t.Errorf("table/chain %s already exists", k)
|
|
||||||
return errExec
|
|
||||||
}
|
|
||||||
o.netfilter[k] = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *fakeOS) DeleteChain(table, chain string) error {
|
|
||||||
k := table + "/" + chain
|
|
||||||
if rules, ok := o.netfilter[k]; ok {
|
|
||||||
if len(rules) != 0 {
|
|
||||||
o.t.Errorf("%s is not empty", k)
|
|
||||||
return errExec
|
|
||||||
}
|
|
||||||
delete(o.netfilter, k)
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
o.t.Errorf("%s does not exist", k)
|
|
||||||
return errExec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *fakeOS) run(args ...string) error {
|
func (o *fakeOS) run(args ...string) error {
|
||||||
unexpected := func() error {
|
unexpected := func() error {
|
||||||
o.t.Errorf("unexpected invocation %q", strings.Join(args, " "))
|
o.t.Errorf("unexpected invocation %q", strings.Join(args, " "))
|
||||||
@ -446,7 +500,20 @@ func (o *fakeOS) run(args ...string) error {
|
|||||||
return unexpected()
|
return unexpected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(args) == 2 && args[1] == "rule" {
|
||||||
|
// naked invocation of `ip rule` is a feature test. Return
|
||||||
|
// successfully.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
family := ""
|
||||||
rest := strings.Join(args[3:], " ")
|
rest := strings.Join(args[3:], " ")
|
||||||
|
if args[1] == "-4" || args[1] == "-6" {
|
||||||
|
family = args[1]
|
||||||
|
copy(args[1:], args[2:])
|
||||||
|
args = args[:len(args)-1]
|
||||||
|
rest = family + " " + strings.Join(args[3:], " ")
|
||||||
|
}
|
||||||
|
|
||||||
var l *[]string
|
var l *[]string
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
|
@ -24,6 +24,8 @@ type commandRunner interface {
|
|||||||
|
|
||||||
type osCommandRunner struct{}
|
type osCommandRunner struct{}
|
||||||
|
|
||||||
|
// errCode extracts and returns the process exit code from err, or
|
||||||
|
// zero if err is nil.
|
||||||
func errCode(err error) int {
|
func errCode(err error) int {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 0
|
return 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user