diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index 279d8c93b..c43433f7b 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -533,7 +533,21 @@ func (r *linuxRouter) addRouteDef(routeDef []string, cidr netaddr.IPPrefix) erro if r.ipRuleAvailable { args = append(args, "table", tailscaleRouteTable) } - return r.cmd.run(args...) + err := r.cmd.run(args...) + if err == nil { + return nil + } + + // TODO(bradfitz): remove this ugly hack to detect failure to + // add a route that already exists (as happens in when we're + // racing to add kernel-maintained routes when enabling exit + // nodes w/o Local LAN access, Issue 3060) and use netlink + // directly instead (Issue 391). + if errCode(err) == 2 && strings.Contains(err.Error(), "RTNETLINK answers: File exists") { + r.logf("ignoring route add of %v; already exists", cidr) + return nil + } + return err } // delRoute removes the route for cidr pointing to the tunnel diff --git a/wgengine/router/runner.go b/wgengine/router/runner.go index 1d1f44af3..736b54b6c 100644 --- a/wgengine/router/runner.go +++ b/wgengine/router/runner.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" + "os" "os/exec" "strconv" "strings" @@ -68,6 +69,7 @@ func (o osCommandRunner) output(args ...string) ([]byte, error) { } cmd := exec.Command(args[0], args[1:]...) + cmd.Env = append(os.Environ(), "LC_ALL=C") if o.ambientCapNetAdmin { cmd.SysProcAttr = &syscall.SysProcAttr{ AmbientCaps: []uintptr{unix.CAP_NET_ADMIN},