mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 21:15:39 +00:00
d585cbf02a
Noticed when testing FUS on tailscale-on-macOS, that routing would break completely when switching between profiles. However, it would start working again when going back to the original profile tailscaled started with. Turns out that if we change the addrs on the interface we need to remove and readd all the routes. Updates #713 Signed-off-by: Maisem Ali <maisem@tailscale.com>
203 lines
5.0 KiB
Go
203 lines
5.0 KiB
Go
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
//go:build darwin || freebsd
|
|
|
|
package router
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/netip"
|
|
"os/exec"
|
|
"runtime"
|
|
|
|
"go4.org/netipx"
|
|
"golang.zx2c4.com/wireguard/tun"
|
|
"tailscale.com/net/tsaddr"
|
|
"tailscale.com/types/logger"
|
|
"tailscale.com/version"
|
|
"tailscale.com/wgengine/monitor"
|
|
)
|
|
|
|
type userspaceBSDRouter struct {
|
|
logf logger.Logf
|
|
linkMon *monitor.Mon
|
|
tunname string
|
|
local []netip.Prefix
|
|
routes map[netip.Prefix]bool
|
|
}
|
|
|
|
func newUserspaceBSDRouter(logf logger.Logf, tundev tun.Device, linkMon *monitor.Mon) (Router, error) {
|
|
tunname, err := tundev.Name()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &userspaceBSDRouter{
|
|
logf: logf,
|
|
linkMon: linkMon,
|
|
tunname: tunname,
|
|
}, nil
|
|
}
|
|
|
|
func (r *userspaceBSDRouter) addrsToRemove(newLocalAddrs []netip.Prefix) (remove []netip.Prefix) {
|
|
for _, cur := range r.local {
|
|
found := false
|
|
for _, v := range newLocalAddrs {
|
|
found = (v == cur)
|
|
if found {
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
remove = append(remove, cur)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *userspaceBSDRouter) addrsToAdd(newLocalAddrs []netip.Prefix) (add []netip.Prefix) {
|
|
for _, cur := range newLocalAddrs {
|
|
found := false
|
|
for _, v := range r.local {
|
|
found = (v == cur)
|
|
if found {
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
add = append(add, cur)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func cmd(args ...string) *exec.Cmd {
|
|
if len(args) == 0 {
|
|
log.Fatalf("exec.Cmd(%#v) invalid; need argv[0]", args)
|
|
}
|
|
return exec.Command(args[0], args[1:]...)
|
|
}
|
|
|
|
func (r *userspaceBSDRouter) Up() error {
|
|
ifup := []string{"ifconfig", r.tunname, "up"}
|
|
if out, err := cmd(ifup...).CombinedOutput(); err != nil {
|
|
r.logf("running ifconfig failed: %v\n%s", err, out)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func inet(p netip.Prefix) string {
|
|
if p.Addr().Is6() {
|
|
return "inet6"
|
|
}
|
|
return "inet"
|
|
}
|
|
|
|
func (r *userspaceBSDRouter) Set(cfg *Config) (reterr error) {
|
|
if cfg == nil {
|
|
cfg = &shutdownConfig
|
|
}
|
|
|
|
setErr := func(err error) {
|
|
if reterr == nil {
|
|
reterr = err
|
|
}
|
|
}
|
|
addrsToRemove := r.addrsToRemove(cfg.LocalAddrs)
|
|
|
|
// If we're removing all addresses, we need to remove and re-add all
|
|
// routes.
|
|
resetRoutes := len(r.local) > 0 && len(addrsToRemove) == len(r.local)
|
|
|
|
// Update the addresses.
|
|
for _, addr := range addrsToRemove {
|
|
arg := []string{"ifconfig", r.tunname, inet(addr), addr.String(), "-alias"}
|
|
out, err := cmd(arg...).CombinedOutput()
|
|
if err != nil {
|
|
r.logf("addr del failed: %v => %v\n%s", arg, err, out)
|
|
setErr(err)
|
|
}
|
|
}
|
|
for _, addr := range r.addrsToAdd(cfg.LocalAddrs) {
|
|
var arg []string
|
|
if runtime.GOOS == "freebsd" && addr.Addr().Is6() && addr.Bits() == 128 {
|
|
// FreeBSD rejects tun addresses of the form fc00::1/128 -> fc00::1,
|
|
// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=218508
|
|
// Instead add our whole /48, which works because we use a /48 route.
|
|
// Full history: https://github.com/tailscale/tailscale/issues/1307
|
|
tmp := netip.PrefixFrom(addr.Addr(), 48)
|
|
arg = []string{"ifconfig", r.tunname, inet(tmp), tmp.String()}
|
|
} else {
|
|
arg = []string{"ifconfig", r.tunname, inet(addr), addr.String(), addr.Addr().String()}
|
|
}
|
|
out, err := cmd(arg...).CombinedOutput()
|
|
if err != nil {
|
|
r.logf("addr add failed: %v => %v\n%s", arg, err, out)
|
|
setErr(err)
|
|
}
|
|
}
|
|
|
|
newRoutes := make(map[netip.Prefix]bool)
|
|
for _, route := range cfg.Routes {
|
|
if runtime.GOOS != "darwin" && route == tsaddr.TailscaleULARange() {
|
|
// Because we added the interface address as a /48 above,
|
|
// the kernel already created the Tailscale ULA route
|
|
// implicitly. We mustn't try to add/delete it ourselves.
|
|
continue
|
|
}
|
|
newRoutes[route] = true
|
|
}
|
|
// Delete any preexisting routes.
|
|
for route := range r.routes {
|
|
if resetRoutes || !newRoutes[route] {
|
|
net := netipx.PrefixIPNet(route)
|
|
nip := net.IP.Mask(net.Mask)
|
|
nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
|
|
del := "del"
|
|
if version.OS() == "macOS" {
|
|
del = "delete"
|
|
}
|
|
routedel := []string{"route", "-q", "-n",
|
|
del, "-" + inet(route), nstr,
|
|
"-iface", r.tunname}
|
|
out, err := cmd(routedel...).CombinedOutput()
|
|
if err != nil {
|
|
r.logf("route del failed: %v: %v\n%s", routedel, err, out)
|
|
setErr(err)
|
|
}
|
|
}
|
|
}
|
|
// Add the routes.
|
|
for route := range newRoutes {
|
|
if resetRoutes || !r.routes[route] {
|
|
net := netipx.PrefixIPNet(route)
|
|
nip := net.IP.Mask(net.Mask)
|
|
nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
|
|
routeadd := []string{"route", "-q", "-n",
|
|
"add", "-" + inet(route), nstr,
|
|
"-iface", r.tunname}
|
|
out, err := cmd(routeadd...).CombinedOutput()
|
|
if err != nil {
|
|
r.logf("addr add failed: %v: %v\n%s", routeadd, err, out)
|
|
setErr(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store the interface and routes so we know what to change on an update.
|
|
if reterr == nil {
|
|
r.local = append([]netip.Prefix{}, cfg.LocalAddrs...)
|
|
}
|
|
r.routes = newRoutes
|
|
|
|
return reterr
|
|
}
|
|
|
|
func (r *userspaceBSDRouter) Close() error {
|
|
return nil
|
|
}
|