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
1 changed files with 47 additions and 13 deletions

View File

@ -6,6 +6,7 @@ package router
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
@ -113,8 +114,15 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (
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()
if supportsV6 {
logf("v6nat = %v", supportsV6NAT)
}
var ipt6 netfilterRunner
if supportsV6 {
@ -1003,46 +1011,53 @@ 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 {
// checkIPv6 checks whether the system appears to have a working IPv6
// network stack. It returns an error explaining what looks wrong or
// 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")
if os.IsNotExist(err) {
return false
return err
}
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
return err
}
disabled, err := strconv.ParseBool(strings.TrimSpace(string(bs)))
if err != nil {
return false
return errors.New("disable_ipv6 has invalid bool")
}
if disabled {
return false
return errors.New("disable_ipv6 is set")
}
// Older kernels don't support IPv6 policy routing.
bs, err = ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
if err != nil {
// Absent knob means policy routing is unsupported.
return false
return err
}
disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs)))
if err != nil {
return false
return errors.New("disable_policy has invalid bool")
}
if disabled {
return false
return errors.New("disable_policy is set")
}
// Some distros ship ip6tables separately from iptables.
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
@ -1060,3 +1075,22 @@ func supportsV6NAT() bool {
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
}