diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index 7fb42c9cf..2eeb08a55 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -147,17 +147,14 @@ func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, linkMon *monitor.Mo } func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, linkMon *monitor.Mon, netfilter4, netfilter6 netfilterRunner, cmd commandRunner, supportsV6, supportsV6NAT bool) (Router, error) { - ipRuleAvailable := (cmd.run("ip", "rule") == nil) - r := &linuxRouter{ logf: logf, tunname: tunname, netfilterMode: netfilterOff, linkMon: linkMon, - ipRuleAvailable: ipRuleAvailable, - v6Available: supportsV6, - v6NATAvailable: supportsV6NAT, + v6Available: supportsV6, + v6NATAvailable: supportsV6NAT, ipt4: netfilter4, ipt6: netfilter6, @@ -165,6 +162,12 @@ func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, linkMon *monit ipRuleFixLimiter: rate.NewLimiter(rate.Every(5*time.Second), 10), } + if r.useIPCommand() { + r.ipRuleAvailable = (cmd.run("ip", "rule") == nil) + } else { + // Pretend it is. + r.ipRuleAvailable = true + } return r, nil } @@ -183,6 +186,9 @@ func useAmbientCaps() bool { // useIPCommand reports whether r should use the "ip" command (or its // fake commandRunner for tests) instead of netlink. func (r *linuxRouter) useIPCommand() bool { + if r.cmd == nil { + panic("invalid init") + } // In the future we might need to fall back to using the "ip" // command if, say, netlink is blocked somewhere but the ip // command is allowed to use netlink. For now we only use the ip @@ -1537,29 +1543,17 @@ func supportsV6NAT() bool { } func checkIPRuleSupportsV6() error { - add := []string{"-6", "rule", "add", "pref", "1234", "fwmark", tailscaleBypassMark, "table", tailscaleRouteTable.ipCmdArg()} - del := []string{"-6", "rule", "del", "pref", "1234", "fwmark", tailscaleBypassMark, "table", tailscaleRouteTable.ipCmdArg()} - + rule := netlink.NewRule() + rule.Priority = 1234 + rule.Mark = tailscaleBypassMarkNum + rule.Table = tailscaleRouteTable.num // 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 - if len(out) == 0 { - detail = err.Error() - } - return fmt.Errorf("ip -6 rule failed: %s", detail) - } - - // Delete again. - exec.Command("ip", del...).Run() - return nil + netlink.RuleDel(rule) + // And clean up on exit. + defer netlink.RuleDel(rule) + return netlink.RuleAdd(rule) } func nlAddrOfPrefix(p netaddr.IPPrefix) *netlink.Addr { diff --git a/wgengine/router/router_linux_test.go b/wgengine/router/router_linux_test.go index 7d59051a9..186be209f 100644 --- a/wgengine/router/router_linux_test.go +++ b/wgengine/router/router_linux_test.go @@ -801,3 +801,13 @@ func TestDebugListRules(t *testing.T) { }) } } + +func TestCheckIPRuleSupportsV6(t *testing.T) { + err := checkIPRuleSupportsV6() + if err != nil && os.Getuid() != 0 { + t.Skipf("skipping, error when not root: %v", err) + } + // Just log it. For interactive testing only. + // Some machines running our tests might not have IPv6. + t.Logf("Got: %v", err) +}