mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01:00 
			
		
		
		
	Also pull out interface method only needed in Linux. Instead of having userspace do the call into the router, just let the router pick up the change itself. Updates #15160 Signed-off-by: Claus Lensbøl <claus@tailscale.com>
		
			
				
	
	
		
			212 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			212 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| //go:build darwin || freebsd
 | |
| 
 | |
| package osrouter
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"net/netip"
 | |
| 	"os/exec"
 | |
| 	"runtime"
 | |
| 
 | |
| 	"github.com/tailscale/wireguard-go/tun"
 | |
| 	"go4.org/netipx"
 | |
| 	"tailscale.com/health"
 | |
| 	"tailscale.com/net/netmon"
 | |
| 	"tailscale.com/net/tsaddr"
 | |
| 	"tailscale.com/types/logger"
 | |
| 	"tailscale.com/version"
 | |
| 	"tailscale.com/wgengine/router"
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	router.HookNewUserspaceRouter.Set(func(opts router.NewOpts) (router.Router, error) {
 | |
| 		return newUserspaceBSDRouter(opts.Logf, opts.Tun, opts.NetMon, opts.Health)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| type userspaceBSDRouter struct {
 | |
| 	logf    logger.Logf
 | |
| 	netMon  *netmon.Monitor
 | |
| 	health  *health.Tracker
 | |
| 	tunname string
 | |
| 	local   []netip.Prefix
 | |
| 	routes  map[netip.Prefix]bool
 | |
| }
 | |
| 
 | |
| func newUserspaceBSDRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (router.Router, error) {
 | |
| 	tunname, err := tundev.Name()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &userspaceBSDRouter{
 | |
| 		logf:    logf,
 | |
| 		netMon:  netMon,
 | |
| 		health:  health,
 | |
| 		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 *router.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
 | |
| }
 |