wgengine/userspace: add support to automatically enable/disable the tailscale
protocol in BIRD, when the node is a primary subnet router as determined by control. Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
		
							
								
								
									
										83
									
								
								chirp/chirp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								chirp/chirp.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
			
		||||
// Copyright (c) 2021 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.
 | 
			
		||||
 | 
			
		||||
// Package chirp implements a client to communicate with the BIRD Internet
 | 
			
		||||
// Routing Daemon.
 | 
			
		||||
package chirp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// New creates a BIRDClient.
 | 
			
		||||
func New(socket string) (*BIRDClient, error) {
 | 
			
		||||
	conn, err := net.Dial("unix", socket)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to connect to BIRD: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	b := &BIRDClient{socket: socket, conn: conn, bs: bufio.NewScanner(conn)}
 | 
			
		||||
	// Read and discard the first line as that is the welcome message.
 | 
			
		||||
	if _, err := b.readLine(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return b, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BIRDClient handles communication with the BIRD Internet Routing Daemon.
 | 
			
		||||
type BIRDClient struct {
 | 
			
		||||
	socket string
 | 
			
		||||
	conn   net.Conn
 | 
			
		||||
	bs     *bufio.Scanner
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes the underlying connection to BIRD.
 | 
			
		||||
func (b *BIRDClient) Close() error { return b.conn.Close() }
 | 
			
		||||
 | 
			
		||||
// DisableProtocol disables the provided protocol.
 | 
			
		||||
func (b *BIRDClient) DisableProtocol(protocol string) error {
 | 
			
		||||
	out, err := b.exec("disable %s\n", protocol)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Contains(out, fmt.Sprintf("%s: already disabled", protocol)) {
 | 
			
		||||
		return nil
 | 
			
		||||
	} else if strings.Contains(out, fmt.Sprintf("%s: disabled", protocol)) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf("failed to disable %s: %v", protocol, out)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnableProtocol enables the provided protocol.
 | 
			
		||||
func (b *BIRDClient) EnableProtocol(protocol string) error {
 | 
			
		||||
	out, err := b.exec("enable %s\n", protocol)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Contains(out, fmt.Sprintf("%s: already enabled", protocol)) {
 | 
			
		||||
		return nil
 | 
			
		||||
	} else if strings.Contains(out, fmt.Sprintf("%s: enabled", protocol)) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf("failed to enable %s: %v", protocol, out)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *BIRDClient) exec(cmd string, args ...interface{}) (string, error) {
 | 
			
		||||
	if _, err := fmt.Fprintf(b.conn, cmd, args...); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return b.readLine()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *BIRDClient) readLine() (string, error) {
 | 
			
		||||
	if !b.bs.Scan() {
 | 
			
		||||
		return "", fmt.Errorf("reading response from bird failed")
 | 
			
		||||
	}
 | 
			
		||||
	if err := b.bs.Err(); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return b.bs.Text(), nil
 | 
			
		||||
}
 | 
			
		||||
@ -88,6 +88,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
 | 
			
		||||
        inet.af/peercred                                             from tailscale.com/ipn/ipnserver
 | 
			
		||||
   W 💣 inet.af/wf                                                   from tailscale.com/wf
 | 
			
		||||
        tailscale.com/atomicfile                                     from tailscale.com/ipn+
 | 
			
		||||
  LD    tailscale.com/chirp                                          from tailscale.com/cmd/tailscaled
 | 
			
		||||
        tailscale.com/client/tailscale                               from tailscale.com/derp
 | 
			
		||||
        tailscale.com/client/tailscale/apitype                       from tailscale.com/ipn/ipnlocal+
 | 
			
		||||
        tailscale.com/control/controlclient                          from tailscale.com/ipn/ipnlocal+
 | 
			
		||||
 | 
			
		||||
@ -73,18 +73,20 @@ var args struct {
 | 
			
		||||
	// or comma-separated list thereof.
 | 
			
		||||
	tunname string
 | 
			
		||||
 | 
			
		||||
	cleanup    bool
 | 
			
		||||
	debug      string
 | 
			
		||||
	port       uint16
 | 
			
		||||
	statepath  string
 | 
			
		||||
	socketpath string
 | 
			
		||||
	verbose    int
 | 
			
		||||
	socksAddr  string // listen address for SOCKS5 server
 | 
			
		||||
	cleanup        bool
 | 
			
		||||
	debug          string
 | 
			
		||||
	port           uint16
 | 
			
		||||
	statepath      string
 | 
			
		||||
	socketpath     string
 | 
			
		||||
	birdSocketPath string
 | 
			
		||||
	verbose        int
 | 
			
		||||
	socksAddr      string // listen address for SOCKS5 server
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	installSystemDaemon   func([]string) error // non-nil on some platforms
 | 
			
		||||
	uninstallSystemDaemon func([]string) error // non-nil on some platforms
 | 
			
		||||
	installSystemDaemon   func([]string) error                      // non-nil on some platforms
 | 
			
		||||
	uninstallSystemDaemon func([]string) error                      // non-nil on some platforms
 | 
			
		||||
	createBIRDClient      func(string) (wgengine.BIRDClient, error) // non-nil on some platforms
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var subCommands = map[string]*func([]string) error{
 | 
			
		||||
@ -111,6 +113,7 @@ func main() {
 | 
			
		||||
	flag.Var(flagtype.PortValue(&args.port, 0), "port", "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
 | 
			
		||||
	flag.StringVar(&args.statepath, "state", paths.DefaultTailscaledStateFile(), "path of state file")
 | 
			
		||||
	flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket")
 | 
			
		||||
	flag.StringVar(&args.birdSocketPath, "bird-socket", "", "path of the bird unix socket")
 | 
			
		||||
	flag.BoolVar(&printVersion, "version", false, "print version information and exit")
 | 
			
		||||
 | 
			
		||||
	if len(os.Args) > 1 {
 | 
			
		||||
@ -152,6 +155,11 @@ func main() {
 | 
			
		||||
		log.Fatalf("--socket is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if args.birdSocketPath != "" && createBIRDClient == nil {
 | 
			
		||||
		log.SetFlags(0)
 | 
			
		||||
		log.Fatalf("--bird-socket is not supported on %s", runtime.GOOS)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := run()
 | 
			
		||||
 | 
			
		||||
	// Remove file sharing from Windows shell (noop in non-windows)
 | 
			
		||||
@ -379,6 +387,13 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.
 | 
			
		||||
		ListenPort:  args.port,
 | 
			
		||||
		LinkMonitor: linkMon,
 | 
			
		||||
	}
 | 
			
		||||
	if args.birdSocketPath != "" && createBIRDClient != nil {
 | 
			
		||||
		log.Printf("Connecting to BIRD at %s ...", args.birdSocketPath)
 | 
			
		||||
		conf.BIRDClient, err = createBIRDClient(args.birdSocketPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, false, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	useNetstack = name == "userspace-networking"
 | 
			
		||||
	if !useNetstack {
 | 
			
		||||
		dev, devName, err := tstun.New(logf, name)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								cmd/tailscaled/tailscaled_bird.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								cmd/tailscaled/tailscaled_bird.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
// Copyright (c) 2021 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 linux || darwin || freebsd || openbsd
 | 
			
		||||
// +build linux darwin freebsd openbsd
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"tailscale.com/chirp"
 | 
			
		||||
	"tailscale.com/wgengine"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	createBIRDClient = func(ctlSocket string) (wgengine.BIRDClient, error) {
 | 
			
		||||
		return chirp.New(ctlSocket)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								docs/bird/sample_bird.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								docs/bird/sample_bird.conf
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
log syslog all;
 | 
			
		||||
 | 
			
		||||
protocol device {
 | 
			
		||||
  scan time 10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
protocol bgp {
 | 
			
		||||
  local as 64001;
 | 
			
		||||
  neighbor 10.40.2.101 as 64002;
 | 
			
		||||
  ipv4 {
 | 
			
		||||
    import none;
 | 
			
		||||
    export all;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
include "tailscale_bird.conf";
 | 
			
		||||
							
								
								
									
										4
									
								
								docs/bird/tailscale_bird.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								docs/bird/tailscale_bird.conf
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
protocol static tailscale {
 | 
			
		||||
  ipv4;
 | 
			
		||||
  route 100.64.0.0/10 via "tailscale0";
 | 
			
		||||
}
 | 
			
		||||
@ -36,6 +36,7 @@ import (
 | 
			
		||||
	_ "strconv"
 | 
			
		||||
	_ "strings"
 | 
			
		||||
	_ "syscall"
 | 
			
		||||
	_ "tailscale.com/chirp"
 | 
			
		||||
	_ "tailscale.com/derp/derphttp"
 | 
			
		||||
	_ "tailscale.com/ipn"
 | 
			
		||||
	_ "tailscale.com/ipn/ipnserver"
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,7 @@ import (
 | 
			
		||||
	_ "strconv"
 | 
			
		||||
	_ "strings"
 | 
			
		||||
	_ "syscall"
 | 
			
		||||
	_ "tailscale.com/chirp"
 | 
			
		||||
	_ "tailscale.com/derp/derphttp"
 | 
			
		||||
	_ "tailscale.com/ipn"
 | 
			
		||||
	_ "tailscale.com/ipn/ipnserver"
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,7 @@ import (
 | 
			
		||||
	_ "strconv"
 | 
			
		||||
	_ "strings"
 | 
			
		||||
	_ "syscall"
 | 
			
		||||
	_ "tailscale.com/chirp"
 | 
			
		||||
	_ "tailscale.com/derp/derphttp"
 | 
			
		||||
	_ "tailscale.com/ipn"
 | 
			
		||||
	_ "tailscale.com/ipn/ipnserver"
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,7 @@ import (
 | 
			
		||||
	_ "strconv"
 | 
			
		||||
	_ "strings"
 | 
			
		||||
	_ "syscall"
 | 
			
		||||
	_ "tailscale.com/chirp"
 | 
			
		||||
	_ "tailscale.com/derp/derphttp"
 | 
			
		||||
	_ "tailscale.com/ipn"
 | 
			
		||||
	_ "tailscale.com/ipn/ipnserver"
 | 
			
		||||
 | 
			
		||||
@ -93,8 +93,9 @@ type userspaceEngine struct {
 | 
			
		||||
	dns               *dns.Manager
 | 
			
		||||
	magicConn         *magicsock.Conn
 | 
			
		||||
	linkMon           *monitor.Mon
 | 
			
		||||
	linkMonOwned      bool   // whether we created linkMon (and thus need to close it)
 | 
			
		||||
	linkMonUnregister func() // unsubscribes from changes; used regardless of linkMonOwned
 | 
			
		||||
	linkMonOwned      bool       // whether we created linkMon (and thus need to close it)
 | 
			
		||||
	linkMonUnregister func()     // unsubscribes from changes; used regardless of linkMonOwned
 | 
			
		||||
	birdClient        BIRDClient // or nil
 | 
			
		||||
 | 
			
		||||
	testMaybeReconfigHook func() // for tests; if non-nil, fires if maybeReconfigWireguardLocked called
 | 
			
		||||
 | 
			
		||||
@ -121,6 +122,8 @@ type userspaceEngine struct {
 | 
			
		||||
	statusBufioReader   *bufio.Reader // reusable for UAPI
 | 
			
		||||
	lastStatusPollTime  mono.Time     // last time we polled the engine status
 | 
			
		||||
 | 
			
		||||
	lastIsSubnetRouter bool // was the node a primary subnet router in the last run.
 | 
			
		||||
 | 
			
		||||
	mu                  sync.Mutex         // guards following; see lock order comment below
 | 
			
		||||
	netMap              *netmap.NetworkMap // or nil
 | 
			
		||||
	closing             bool               // Close was called (even if we're still closing)
 | 
			
		||||
@ -144,6 +147,13 @@ func (e *userspaceEngine) GetInternals() (_ *tstun.Wrapper, _ *magicsock.Conn, o
 | 
			
		||||
	return e.tundev, e.magicConn, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BIRDClient handles communication with the BIRD Internet Routing Daemon.
 | 
			
		||||
type BIRDClient interface {
 | 
			
		||||
	EnableProtocol(proto string) error
 | 
			
		||||
	DisableProtocol(proto string) error
 | 
			
		||||
	Close() error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Config is the engine configuration.
 | 
			
		||||
type Config struct {
 | 
			
		||||
	// Tun is the device used by the Engine to exchange packets with
 | 
			
		||||
@ -175,6 +185,10 @@ type Config struct {
 | 
			
		||||
	// reply to ICMP pings, without involving the OS.
 | 
			
		||||
	// Used in "fake" mode for development.
 | 
			
		||||
	RespondToPing bool
 | 
			
		||||
 | 
			
		||||
	// BIRDClient, if non-nil, will be used to configure BIRD whenever
 | 
			
		||||
	// this node is a primary subnet router.
 | 
			
		||||
	BIRDClient BIRDClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) {
 | 
			
		||||
@ -258,6 +272,14 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
 | 
			
		||||
		router:           conf.Router,
 | 
			
		||||
		confListenPort:   conf.ListenPort,
 | 
			
		||||
		magicConnStarted: make(chan struct{}),
 | 
			
		||||
		birdClient:       conf.BIRDClient,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if e.birdClient != nil {
 | 
			
		||||
		// Disable the protocol at start time.
 | 
			
		||||
		if err := e.birdClient.DisableProtocol("tailscale"); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	e.isLocalAddr.Store(tsaddr.NewContainsIPFunc(nil))
 | 
			
		||||
	e.isDNSIPOverTailscale.Store(tsaddr.NewContainsIPFunc(nil))
 | 
			
		||||
@ -759,6 +781,19 @@ func (e *userspaceEngine) updateActivityMapsLocked(trackDisco []tailcfg.DiscoKey
 | 
			
		||||
	e.tundev.SetDestIPActivityFuncs(e.destIPActivityFuncs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// hasOverlap checks if there is a IPPrefix which is common amongst the two
 | 
			
		||||
// provided slices.
 | 
			
		||||
func hasOverlap(aips, rips []netaddr.IPPrefix) bool {
 | 
			
		||||
	for _, aip := range aips {
 | 
			
		||||
		for _, rip := range rips {
 | 
			
		||||
			if aip == rip {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *dns.Config, debug *tailcfg.Debug) error {
 | 
			
		||||
	if routerCfg == nil {
 | 
			
		||||
		panic("routerCfg must not be nil")
 | 
			
		||||
@ -787,9 +822,15 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
 | 
			
		||||
		listenPort = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isSubnetRouter := false
 | 
			
		||||
	if e.birdClient != nil {
 | 
			
		||||
		isSubnetRouter = hasOverlap(e.netMap.SelfNode.PrimaryRoutes, e.netMap.Hostinfo.RoutableIPs)
 | 
			
		||||
	}
 | 
			
		||||
	isSubnetRouterChanged := isSubnetRouter != e.lastIsSubnetRouter
 | 
			
		||||
 | 
			
		||||
	engineChanged := deephash.Update(&e.lastEngineSigFull, cfg)
 | 
			
		||||
	routerChanged := deephash.Update(&e.lastRouterSig, routerCfg, dnsCfg)
 | 
			
		||||
	if !engineChanged && !routerChanged && listenPort == e.magicConn.LocalPort() {
 | 
			
		||||
	if !engineChanged && !routerChanged && listenPort == e.magicConn.LocalPort() && !isSubnetRouterChanged {
 | 
			
		||||
		return ErrNoChanges
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -859,6 +900,22 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isSubnetRouterChanged && e.birdClient != nil {
 | 
			
		||||
		e.logf("wgengine: Reconfig: configuring BIRD")
 | 
			
		||||
		var err error
 | 
			
		||||
		if isSubnetRouter {
 | 
			
		||||
			err = e.birdClient.EnableProtocol("tailscale")
 | 
			
		||||
		} else {
 | 
			
		||||
			err = e.birdClient.DisableProtocol("tailscale")
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// Log but don't fail here.
 | 
			
		||||
			e.logf("wgengine: error configuring BIRD: %v", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			e.lastIsSubnetRouter = isSubnetRouter
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	e.logf("[v1] wgengine: Reconfig done")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -1077,6 +1134,10 @@ func (e *userspaceEngine) Close() {
 | 
			
		||||
	e.router.Close()
 | 
			
		||||
	e.wgdev.Close()
 | 
			
		||||
	e.tundev.Close()
 | 
			
		||||
	if e.birdClient != nil {
 | 
			
		||||
		e.birdClient.DisableProtocol("tailscale")
 | 
			
		||||
		e.birdClient.Close()
 | 
			
		||||
	}
 | 
			
		||||
	close(e.waitCh)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user