From 267531e4f8649c3c14915299eecf1f29b94a8b9e Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 1 Feb 2021 14:32:50 -0800 Subject: [PATCH] wgengine/router: probe better for v6 policy routing support. Previously we disabled v6 support if the disable_policy knob was missing in /proc, but some kernels support policy routing without exposing the toggle. So instead, treat disable_policy absence as a "maybe", and make the direct `ip -6 rule` probing a bit more elaborate to compensate. Fixes #1241. Signed-off-by: David Anderson --- wgengine/router/router_linux.go | 49 +++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index 2f92d5d69..50898e071 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -1045,18 +1045,22 @@ func checkIPv6() error { return errors.New("disable_ipv6 is set") } - // Older kernels don't support IPv6 policy routing. + // Older kernels don't support IPv6 policy routing. Some kernels + // support policy routing but don't have this knob, so absence of + // the knob is not fatal. bs, err = ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy") - if err != nil { - // Absent knob means policy routing is unsupported. - return err - } - disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs))) - if err != nil { - return errors.New("disable_policy has invalid bool") + if err == nil { + disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs))) + if err != nil { + return errors.New("disable_policy has invalid bool") + } + if disabled { + return errors.New("disable_policy is set") + } } - if disabled { - return errors.New("disable_policy is set") + + if err := checkIPRuleSupportsV6(); err != nil { + return fmt.Errorf("kernel doesn't support IPv6 policy routing: %w", err) } // Some distros ship ip6tables separately from iptables. @@ -1064,10 +1068,6 @@ func checkIPv6() error { return err } - if err := checkIPRuleSupportsV6(); err != nil { - return err - } - return nil } @@ -1088,13 +1088,17 @@ func supportsV6NAT() bool { } 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() + add := []string{"-6", "rule", "add", "pref", "1234", "fwmark", tailscaleBypassMark, "table", tailscaleRouteTable} + del := []string{"-6", "rule", "del", "pref", "1234", "fwmark", tailscaleBypassMark, "table", tailscaleRouteTable} + + // First delete the rule unconditionally, and don't check for + // errors. This is just cleaning up anything that might be already + // there. + exec.Command("ip", del...).Run() + + // Try adding the rule. This will fail on systems that support + // IPv6, but not IPv6 policy routing. + out, err := exec.Command("ip", add...).CombinedOutput() if err != nil { out = bytes.TrimSpace(out) var detail interface{} = out @@ -1103,5 +1107,8 @@ func checkIPRuleSupportsV6() error { } return fmt.Errorf("ip -6 rule failed: %s", detail) } + + // Delete again. + exec.Command("ip", del...).Run() return nil }