From fddbcb0c7b12688ecf51a08e196ea7371a8b9b87 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 22 Sep 2020 21:55:28 +0000 Subject: [PATCH] wgengine/router: support various degrees of broken IPv6. Gracefully skips touching the v6 NAT table on systems that don't have it, and doesn't configure IPv6 at all if IPv6 is globally disabled. Signed-off-by: David Anderson --- wgengine/router/router_linux.go | 167 ++++++++++++++++++++++++++------ 1 file changed, 138 insertions(+), 29 deletions(-) diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index da371f19a..e19572187 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -5,7 +5,12 @@ package router import ( + "bytes" "fmt" + "io/ioutil" + "os" + "os/exec" + "strconv" "strings" "github.com/coreos/go-iptables/iptables" @@ -79,13 +84,17 @@ type netfilterRunner interface { type linuxRouter struct { logf func(fmt string, args ...interface{}) - ipRuleAvailable bool tunname string addrs map[netaddr.IPPrefix]bool routes map[netaddr.IPPrefix]bool snatSubnetRoutes bool netfilterMode NetfilterMode + // Various feature checks for the network stack. + ipRuleAvailable bool + v6Available bool + v6NATAvailable bool + dns *dns.Manager ipt4 netfilterRunner @@ -104,9 +113,14 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) ( return nil, err } - ipt6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6) - if err != nil { - return nil, err + var ipt6 netfilterRunner + if supportsV6() { + // The iptables package probes for `ip6tables` and errors out + // if unavailable. We want that to be a non-fatal error. + ipt6, err = iptables.NewWithProtocol(iptables.ProtocolIPv6) + if err != nil { + return nil, err + } } return newUserspaceRouterAdvanced(logf, tunname, ipt4, ipt6, osCommandRunner{}) @@ -120,15 +134,21 @@ func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netfilter4, ne InterfaceName: tunname, } + supportsV6 := supportsV6() + return &linuxRouter{ - logf: logf, + logf: logf, + tunname: tunname, + netfilterMode: NetfilterOff, + ipRuleAvailable: ipRuleAvailable, - tunname: tunname, - netfilterMode: NetfilterOff, - ipt4: netfilter4, - ipt6: netfilter6, - cmd: cmd, - dns: dns.NewManager(mconfig), + v6Available: supportsV6, + v6NATAvailable: supportsV6 && supportsV6NAT(), + + ipt4: netfilter4, + ipt6: netfilter6, + cmd: cmd, + dns: dns.NewManager(mconfig), }, nil } @@ -423,6 +443,13 @@ func (r *linuxRouter) downInterface() error { return r.cmd.run("ip", "link", "set", "dev", r.tunname, "down") } +func (r *linuxRouter) iprouteFamilies() []string { + if r.v6Available { + return []string{"-4", "-6"} + } + return []string{"-4"} +} + // addIPRules adds the policy routing rule that avoids tailscaled // routing loops. If the rule exists and appears to be a // tailscale-managed rule, it is gracefully replaced. @@ -439,7 +466,7 @@ func (r *linuxRouter) addIPRules() error { rg := newRunGroup(nil, r.cmd) - for _, family := range []string{"-4", "-6"} { + for _, family := range r.iprouteFamilies() { // NOTE(apenwarr): We leave spaces between each pref number. // This is so the sysadmin can override by inserting rules in // between if they want. @@ -512,7 +539,7 @@ func (r *linuxRouter) delIPRules() error { // unknown rules during deletion. rg := newRunGroup([]int{2, 254}, r.cmd) - for _, family := range []string{"-4", "-6"} { + for _, family := range r.iprouteFamilies() { // When deleting rules, we want to be a bit specific (mention which // table we were routing to) but not *too* specific (fwmarks, etc). // That leaves us some flexibility to change these values in later @@ -554,6 +581,13 @@ func (r *linuxRouter) delIPRules() error { return rg.ErrAcc } +func (r *linuxRouter) netfilterFamilies() []netfilterRunner { + if r.v6Available { + return []netfilterRunner{r.ipt4, r.ipt6} + } + return []netfilterRunner{r.ipt4} +} + // addNetfilterChains creates custom Tailscale chains in netfilter. func (r *linuxRouter) addNetfilterChains() error { create := func(ipt netfilterRunner, table, chain string) error { @@ -568,14 +602,19 @@ func (r *linuxRouter) addNetfilterChains() error { return nil } - for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} { + for _, ipt := range r.netfilterFamilies() { if err := create(ipt, "filter", "ts-input"); err != nil { return err } if err := create(ipt, "filter", "ts-forward"); err != nil { return err } - if err := create(ipt, "nat", "ts-postrouting"); err != nil { + } + if err := create(r.ipt4, "nat", "ts-postrouting"); err != nil { + return err + } + if r.v6NATAvailable { + if err := create(r.ipt6, "nat", "ts-postrouting"); err != nil { return err } } @@ -588,8 +627,10 @@ func (r *linuxRouter) addNetfilterBase() error { if err := r.addNetfilterBase4(); err != nil { return err } - if err := r.addNetfilterBase6(); err != nil { - return err + if r.v6Available { + if err := r.addNetfilterBase6(); err != nil { + return err + } } return nil } @@ -686,14 +727,19 @@ func (r *linuxRouter) delNetfilterChains() error { return nil } - for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} { + for _, ipt := range r.netfilterFamilies() { if err := del(ipt, "filter", "ts-input"); err != nil { return err } if err := del(ipt, "filter", "ts-forward"); err != nil { return err } - if err := del(ipt, "nat", "ts-postrouting"); err != nil { + } + if err := del(r.ipt4, "nat", "ts-postrouting"); err != nil { + return err + } + if r.v6NATAvailable { + if err := del(r.ipt6, "nat", "ts-postrouting"); err != nil { return err } } @@ -716,14 +762,19 @@ func (r *linuxRouter) delNetfilterBase() error { return nil } - for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} { + for _, ipt := range r.netfilterFamilies() { if err := del(ipt, "filter", "ts-input"); err != nil { return err } if err := del(ipt, "filter", "ts-forward"); err != nil { return err } - if err := del(ipt, "nat", "ts-postrouting"); err != nil { + } + if err := del(r.ipt4, "nat", "ts-postrouting"); err != nil { + return err + } + if r.v6NATAvailable { + if err := del(r.ipt6, "nat", "ts-postrouting"); err != nil { return err } } @@ -752,14 +803,19 @@ func (r *linuxRouter) addNetfilterHooks() error { return nil } - for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} { + for _, ipt := range r.netfilterFamilies() { if err := divert(ipt, "filter", "INPUT"); err != nil { return err } if err := divert(ipt, "filter", "FORWARD"); err != nil { return err } - if err := divert(ipt, "nat", "POSTROUTING"); err != nil { + } + if err := divert(r.ipt4, "nat", "POSTROUTING"); err != nil { + return err + } + if r.v6NATAvailable { + if err := divert(r.ipt6, "nat", "POSTROUTING"); err != nil { return err } } @@ -784,14 +840,19 @@ func (r *linuxRouter) delNetfilterHooks() error { return nil } - for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} { + for _, ipt := range r.netfilterFamilies() { if err := del(ipt, "filter", "INPUT"); err != nil { return err } if err := del(ipt, "filter", "FORWARD"); err != nil { return err } - if err := del(ipt, "nat", "POSTROUTING"); err != nil { + } + if err := del(r.ipt4, "nat", "POSTROUTING"); err != nil { + return err + } + if r.v6NATAvailable { + if err := del(r.ipt6, "nat", "POSTROUTING"); err != nil { return err } } @@ -809,8 +870,10 @@ func (r *linuxRouter) addSNATRule() error { if err := r.ipt4.Append("nat", "ts-postrouting", args...); err != nil { 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) + if r.v6NATAvailable { + 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 } @@ -826,8 +889,10 @@ func (r *linuxRouter) delSNATRule() error { if err := r.ipt4.Delete("nat", "ts-postrouting", args...); err != nil { 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) + if r.v6NATAvailable { + 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 } @@ -915,3 +980,47 @@ func normalizeCIDR(cidr netaddr.IPPrefix) string { func cleanup(logf logger.Logf, interfaceName string) { // TODO(dmytro): clean up iptables. } + +// supportsV6 returns whether the system appears to have a working +// IPv6 network stack. +func supportsV6() bool { + _, err := os.Stat("/proc/sys/net/ipv6") + if os.IsNotExist(err) { + return false + } + bs, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6") + if err != nil { + // Be conservative if we can't find the ipv6 configuration knob. + return false + } + disabled, err := strconv.ParseBool(strings.TrimSpace(string(bs))) + if err != nil { + return false + } + if disabled { + return false + } + + // Some distros ship ip6tables separately from iptables. + if _, err := exec.LookPath("ip6tables"); err != nil { + return false + } + + return true +} + +// supportsV6NAT returns whether the system has a "nat" table in the +// IPv6 netfilter stack. +// +// The nat table was added after the initial release of ipv6 +// netfilter, so some older distros ship a kernel that can't NAT IPv6 +// traffic. +func supportsV6NAT() bool { + bs, err := ioutil.ReadFile("/proc/net/ip6_tables_names") + if err != nil { + // Can't read the file. Assume SNAT works. + return true + } + + return bytes.Contains(bs, []byte("nat\n")) +}