wgengine/router: disable IPv6 on Linux if ip rule -6 fails (#1074)

Updates #562
Fixes #973

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2020-12-29 08:26:17 -08:00 committed by GitHub
parent 1e88050403
commit 0d94fe5f69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -6,6 +6,7 @@
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -113,8 +114,15 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (
return nil, err return nil, err
} }
supportsV6 := supportsV6() v6err := checkIPv6()
if v6err != nil {
logf("disabling IPv6 due to system IPv6 config: %v", v6err)
}
supportsV6 := v6err == nil
supportsV6NAT := supportsV6 && supportsV6NAT() supportsV6NAT := supportsV6 && supportsV6NAT()
if supportsV6 {
logf("v6nat = %v", supportsV6NAT)
}
var ipt6 netfilterRunner var ipt6 netfilterRunner
if supportsV6 { if supportsV6 {
@ -1003,46 +1011,53 @@ func cleanup(logf logger.Logf, interfaceName string) {
// TODO(dmytro): clean up iptables. // TODO(dmytro): clean up iptables.
} }
// supportsV6 returns whether the system appears to have a working // checkIPv6 checks whether the system appears to have a working IPv6
// IPv6 network stack. // network stack. It returns an error explaining what looks wrong or
func supportsV6() bool { // missing. It does not check that IPv6 is currently functional or
// that there's a global address, just that the system would support
// IPv6 if it were on an IPv6 network.
func checkIPv6() error {
_, err := os.Stat("/proc/sys/net/ipv6") _, err := os.Stat("/proc/sys/net/ipv6")
if os.IsNotExist(err) { if os.IsNotExist(err) {
return false return err
} }
bs, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6") bs, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6")
if err != nil { if err != nil {
// Be conservative if we can't find the ipv6 configuration knob. // Be conservative if we can't find the ipv6 configuration knob.
return false return err
} }
disabled, err := strconv.ParseBool(strings.TrimSpace(string(bs))) disabled, err := strconv.ParseBool(strings.TrimSpace(string(bs)))
if err != nil { if err != nil {
return false return errors.New("disable_ipv6 has invalid bool")
} }
if disabled { if disabled {
return false return errors.New("disable_ipv6 is set")
} }
// Older kernels don't support IPv6 policy routing. // Older kernels don't support IPv6 policy routing.
bs, err = ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy") bs, err = ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
if err != nil { if err != nil {
// Absent knob means policy routing is unsupported. // Absent knob means policy routing is unsupported.
return false return err
} }
disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs))) disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs)))
if err != nil { if err != nil {
return false return errors.New("disable_policy has invalid bool")
} }
if disabled { if disabled {
return false return errors.New("disable_policy is set")
} }
// Some distros ship ip6tables separately from iptables. // Some distros ship ip6tables separately from iptables.
if _, err := exec.LookPath("ip6tables"); err != nil { if _, err := exec.LookPath("ip6tables"); err != nil {
return false return err
} }
return true if err := checkIPRuleSupportsV6(); err != nil {
return err
}
return nil
} }
// supportsV6NAT returns whether the system has a "nat" table in the // supportsV6NAT returns whether the system has a "nat" table in the
@ -1060,3 +1075,22 @@ func supportsV6NAT() bool {
return bytes.Contains(bs, []byte("nat\n")) return bytes.Contains(bs, []byte("nat\n"))
} }
func checkIPRuleSupportsV6() error {
// First add a rule for "ip rule del" to delete.
// We ignore the "add" operation's error because it can also
// fail if the rule already exists.
exec.Command("ip", "-6", "rule", "add",
"pref", "123", "fwmark", tailscaleBypassMark, "table", fmt.Sprint(tailscaleRouteTable)).Run()
out, err := exec.Command("ip", "-6", "rule", "del",
"pref", "123", "fwmark", tailscaleBypassMark, "table", fmt.Sprint(tailscaleRouteTable)).CombinedOutput()
if err != nil {
out = bytes.TrimSpace(out)
var detail interface{} = out
if len(out) == 0 {
detail = err.Error()
}
return fmt.Errorf("ip -6 rule failed: %s", detail)
}
return nil
}