WIP: rebase fork onto upstream/main (v1.103.0) #15
@@ -258,7 +258,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com from tailscale.com/version
|
||||
tailscale.com/appc from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/atomicfile from tailscale.com/ipn+
|
||||
LD tailscale.com/chirp from tailscale.com/cmd/tailscaled
|
||||
LD tailscale.com/chirp from tailscale.com/feature/bird
|
||||
tailscale.com/client/local from tailscale.com/client/web+
|
||||
tailscale.com/client/tailscale/apitype from tailscale.com/client/local+
|
||||
tailscale.com/client/web from tailscale.com/ipn/ipnlocal
|
||||
@@ -292,6 +292,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/feature/ace from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/acme from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/appconnectors from tailscale.com/feature/condregister
|
||||
LD tailscale.com/feature/bird from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/buildfeatures from tailscale.com/wgengine/magicsock+
|
||||
tailscale.com/feature/c2n from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/capture from tailscale.com/feature/condregister
|
||||
|
||||
@@ -137,6 +137,20 @@ func TestOmitCaptivePortal(t *testing.T) {
|
||||
}.Check(t)
|
||||
}
|
||||
|
||||
func TestOmitBird(t *testing.T) {
|
||||
deptest.DepChecker{
|
||||
GOOS: "linux",
|
||||
GOARCH: "amd64",
|
||||
Tags: "ts_omit_bird,ts_include_cli",
|
||||
OnDep: func(dep string) {
|
||||
switch dep {
|
||||
case "tailscale.com/chirp", "tailscale.com/feature/bird":
|
||||
t.Errorf("unexpected dep with ts_omit_bird: %q", dep)
|
||||
}
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
||||
func TestOmitAuth(t *testing.T) {
|
||||
deptest.DepChecker{
|
||||
GOOS: "linux",
|
||||
|
||||
@@ -142,9 +142,8 @@ var args struct {
|
||||
}
|
||||
|
||||
var (
|
||||
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
|
||||
installSystemDaemon func([]string) error // non-nil on some platforms
|
||||
uninstallSystemDaemon func([]string) error // non-nil on some platforms
|
||||
)
|
||||
|
||||
// Note - we use function pointers for subcommands so that subcommands like
|
||||
@@ -281,7 +280,7 @@ store state on filesystem.`)
|
||||
log.Fatalf("--socket is required")
|
||||
}
|
||||
|
||||
if buildfeatures.HasBird && args.birdSocketPath != "" && createBIRDClient == nil {
|
||||
if buildfeatures.HasBird && args.birdSocketPath != "" && !wgengine.HookNewBird.IsSet() {
|
||||
log.SetFlags(0)
|
||||
log.Fatalf("--bird-socket is not supported on %s", runtime.GOOS)
|
||||
}
|
||||
@@ -804,12 +803,8 @@ func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack boo
|
||||
netstackSubnetRouter := onlyNetstack // but mutated later on some platforms
|
||||
netns.SetEnabled(!onlyNetstack)
|
||||
|
||||
if args.birdSocketPath != "" && createBIRDClient != nil {
|
||||
log.Printf("Connecting to BIRD at %s ...", args.birdSocketPath)
|
||||
conf.BIRDClient, err = createBIRDClient(args.birdSocketPath)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("createBIRDClient: %w", err)
|
||||
}
|
||||
if buildfeatures.HasBird && args.birdSocketPath != "" {
|
||||
conf.BIRDSocket = args.birdSocketPath
|
||||
}
|
||||
if onlyNetstack {
|
||||
if runtime.GOOS == "linux" && distro.Get() == distro.Synology {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build go1.19 && (linux || darwin || freebsd || openbsd) && !ts_omit_bird
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"tailscale.com/chirp"
|
||||
"tailscale.com/wgengine"
|
||||
)
|
||||
|
||||
func init() {
|
||||
createBIRDClient = func(ctlSocket string) (wgengine.BIRDClient, error) {
|
||||
return chirp.New(ctlSocket)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package bird integrates Tailscale with the BIRD Internet Routing
|
||||
// Daemon: it enables the "tailscale" protocol in BIRD while this node
|
||||
// is a primary subnet router and disables it otherwise. The BIRD
|
||||
// client implementation lives in tailscale.com/chirp.
|
||||
package bird
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"tailscale.com/chirp"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/wgengine"
|
||||
)
|
||||
|
||||
func init() {
|
||||
feature.Register("bird")
|
||||
wgengine.HookNewBird.Set(newBird)
|
||||
}
|
||||
|
||||
// protocolName is the name of the BIRD protocol that Tailscale enables
|
||||
// while this node is a primary subnet router.
|
||||
const protocolName = "tailscale"
|
||||
|
||||
// bird implements [wgengine.Bird] on top of [chirp.BIRDClient],
|
||||
// tracking the primary subnet router state across engine
|
||||
// reconfigurations. One bird exists per engine.
|
||||
type bird struct {
|
||||
logf logger.Logf
|
||||
client *chirp.BIRDClient
|
||||
|
||||
// The fields below are only accessed from Reconfig and
|
||||
// ReconfigDone, which the engine serializes under its internal
|
||||
// lock.
|
||||
|
||||
// isSubnetRouter is whether the last Reconfig found this node to
|
||||
// be a primary subnet router.
|
||||
isSubnetRouter bool
|
||||
// lastIsSubnetRouter is the primary subnet router state last
|
||||
// successfully applied to BIRD. ReconfigDone compares it against
|
||||
// isSubnetRouter to decide whether to toggle the protocol.
|
||||
lastIsSubnetRouter bool
|
||||
}
|
||||
|
||||
func newBird(logf logger.Logf, socketPath string) (wgengine.Bird, error) {
|
||||
logf("wgengine: connecting to BIRD at %s ...", socketPath)
|
||||
client, err := chirp.New(socketPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Disable the protocol at start time; ReconfigDone enables it only
|
||||
// once this node becomes a primary subnet router.
|
||||
if err := client.DisableProtocol(protocolName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &bird{logf: logf, client: client}, nil
|
||||
}
|
||||
|
||||
func (b *bird) Reconfig(self tailcfg.NodeView) (changed bool) {
|
||||
b.isSubnetRouter = false
|
||||
if self.Valid() {
|
||||
b.isSubnetRouter = hasOverlap(self.PrimaryRoutes(), self.Hostinfo().RoutableIPs())
|
||||
b.logf("[v1] Reconfig: hasOverlap(%v, %v) = %v; isSubnetRouter=%v lastIsSubnetRouter=%v",
|
||||
self.PrimaryRoutes(), self.Hostinfo().RoutableIPs(),
|
||||
b.isSubnetRouter, b.isSubnetRouter, b.lastIsSubnetRouter)
|
||||
}
|
||||
return b.isSubnetRouter != b.lastIsSubnetRouter
|
||||
}
|
||||
|
||||
func (b *bird) ReconfigDone() {
|
||||
if b.isSubnetRouter == b.lastIsSubnetRouter {
|
||||
return
|
||||
}
|
||||
b.logf("wgengine: Reconfig: configuring BIRD")
|
||||
var err error
|
||||
if b.isSubnetRouter {
|
||||
err = b.client.EnableProtocol(protocolName)
|
||||
} else {
|
||||
err = b.client.DisableProtocol(protocolName)
|
||||
}
|
||||
if err != nil {
|
||||
// Log but don't fail here; a later Reconfig will retry.
|
||||
b.logf("wgengine: error configuring BIRD: %v", err)
|
||||
return
|
||||
}
|
||||
b.lastIsSubnetRouter = b.isSubnetRouter
|
||||
}
|
||||
|
||||
func (b *bird) Close() {
|
||||
b.client.DisableProtocol(protocolName)
|
||||
b.client.Close()
|
||||
}
|
||||
|
||||
// hasOverlap reports whether any prefix in rips is also present in aips.
|
||||
func hasOverlap(aips, rips views.Slice[netip.Prefix]) bool {
|
||||
for _, aip := range aips.All() {
|
||||
if views.SliceContains(rips, aip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package bird
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/wgengine"
|
||||
)
|
||||
|
||||
// fakeBIRD is a fake BIRD server listening on a unix socket. It speaks
|
||||
// enough of the BIRD CLI wire protocol to satisfy chirp and records the
|
||||
// commands it receives so tests can assert on them.
|
||||
type fakeBIRD struct {
|
||||
ln net.Listener
|
||||
sock string
|
||||
|
||||
mu sync.Mutex
|
||||
calls []string // commands received, e.g. "enable tailscale"
|
||||
enabled bool // whether the "tailscale" protocol is enabled
|
||||
failNext bool // whether to reply to the next command with a runtime error
|
||||
}
|
||||
|
||||
func newFakeBIRD(t *testing.T) *fakeBIRD {
|
||||
t.Helper()
|
||||
sock := filepath.Join(t.TempDir(), "bird.sock")
|
||||
ln, err := net.Listen("unix", sock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fb := &fakeBIRD{ln: ln, sock: sock}
|
||||
t.Cleanup(func() { ln.Close() })
|
||||
go fb.listen()
|
||||
return fb
|
||||
}
|
||||
|
||||
func (fb *fakeBIRD) listen() {
|
||||
for {
|
||||
c, err := fb.ln.Accept()
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
go fb.handle(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *fakeBIRD) handle(c net.Conn) {
|
||||
fmt.Fprintln(c, "0001 BIRD 2.0.8 ready.")
|
||||
sc := bufio.NewScanner(c)
|
||||
for sc.Scan() {
|
||||
cmd := sc.Text()
|
||||
var proto string
|
||||
var wantEnabled bool
|
||||
switch {
|
||||
case strings.HasPrefix(cmd, "enable "):
|
||||
proto, wantEnabled = strings.TrimPrefix(cmd, "enable "), true
|
||||
case strings.HasPrefix(cmd, "disable "):
|
||||
proto, wantEnabled = strings.TrimPrefix(cmd, "disable "), false
|
||||
}
|
||||
|
||||
fb.mu.Lock()
|
||||
fb.calls = append(fb.calls, cmd)
|
||||
fail := fb.failNext
|
||||
fb.failNext = false
|
||||
switch {
|
||||
case proto != protocolName:
|
||||
fmt.Fprintln(c, "9001 syntax error, unexpected CF_SYM_UNDEFINED, expecting CF_SYM_KNOWN or TEXT or ALL")
|
||||
case fail:
|
||||
fmt.Fprintln(c, "8001 fake runtime error")
|
||||
case wantEnabled == fb.enabled:
|
||||
fmt.Fprintf(c, "0010-%s: already %s\n0000 \n", proto, verb(wantEnabled))
|
||||
default:
|
||||
fb.enabled = wantEnabled
|
||||
fmt.Fprintf(c, "0011-%s: %s\n0000 \n", proto, verb(wantEnabled))
|
||||
}
|
||||
fb.mu.Unlock()
|
||||
}
|
||||
if err := sc.Err(); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func verb(enabled bool) string {
|
||||
if enabled {
|
||||
return "enabled"
|
||||
}
|
||||
return "disabled"
|
||||
}
|
||||
|
||||
// takeCalls returns the commands received since the last call and
|
||||
// resets the record. It is safe to call once the chirp request that
|
||||
// caused them has returned, as chirp waits for each response.
|
||||
func (fb *fakeBIRD) takeCalls() []string {
|
||||
fb.mu.Lock()
|
||||
defer fb.mu.Unlock()
|
||||
calls := fb.calls
|
||||
fb.calls = nil
|
||||
return calls
|
||||
}
|
||||
|
||||
func (fb *fakeBIRD) checkCalls(t *testing.T, want ...string) {
|
||||
t.Helper()
|
||||
if diff := cmp.Diff(fb.takeCalls(), want, cmpopts.EquateEmpty()); diff != "" {
|
||||
t.Errorf("BIRD calls mismatch (-got +want):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func node(primaryRoutes, routableIPs []netip.Prefix) tailcfg.NodeView {
|
||||
return (&tailcfg.Node{
|
||||
PrimaryRoutes: primaryRoutes,
|
||||
Hostinfo: (&tailcfg.Hostinfo{RoutableIPs: routableIPs}).View(),
|
||||
}).View()
|
||||
}
|
||||
|
||||
func TestBird(t *testing.T) {
|
||||
if !feature.IsRegistered("bird") {
|
||||
t.Fatal("bird feature not registered")
|
||||
}
|
||||
|
||||
fb := newFakeBIRD(t)
|
||||
newBird, ok := wgengine.HookNewBird.GetOk()
|
||||
if !ok {
|
||||
t.Fatal("HookNewBird not set")
|
||||
}
|
||||
b, err := newBird(t.Logf, fb.sock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Construction disables the protocol.
|
||||
fb.checkCalls(t, "disable tailscale")
|
||||
|
||||
pfx := netip.MustParsePrefix
|
||||
subnetRouter := node(
|
||||
[]netip.Prefix{pfx("192.168.1.0/24")},
|
||||
[]netip.Prefix{pfx("192.168.1.0/24"), pfx("10.0.0.0/8")},
|
||||
)
|
||||
notSubnetRouter := node(
|
||||
[]netip.Prefix{pfx("192.168.2.0/24")},
|
||||
[]netip.Prefix{pfx("192.168.1.0/24")},
|
||||
)
|
||||
|
||||
// An invalid self node is not a subnet router; no state change, no calls.
|
||||
if changed := b.Reconfig(tailcfg.NodeView{}); changed {
|
||||
t.Error("Reconfig(invalid) reported change")
|
||||
}
|
||||
b.ReconfigDone()
|
||||
fb.checkCalls(t)
|
||||
|
||||
// Becoming a primary subnet router enables the protocol.
|
||||
if changed := b.Reconfig(subnetRouter); !changed {
|
||||
t.Error("Reconfig(subnetRouter) reported no change")
|
||||
}
|
||||
b.ReconfigDone()
|
||||
fb.checkCalls(t, "enable tailscale")
|
||||
|
||||
// Reconfig with the same state is a no-op.
|
||||
if changed := b.Reconfig(subnetRouter); changed {
|
||||
t.Error("Reconfig(subnetRouter) again reported change")
|
||||
}
|
||||
b.ReconfigDone()
|
||||
fb.checkCalls(t)
|
||||
|
||||
// A BIRD failure leaves the state unapplied, so the next Reconfig
|
||||
// still reports a change and retries.
|
||||
fb.mu.Lock()
|
||||
fb.failNext = true
|
||||
fb.mu.Unlock()
|
||||
if changed := b.Reconfig(notSubnetRouter); !changed {
|
||||
t.Error("Reconfig(notSubnetRouter) reported no change")
|
||||
}
|
||||
b.ReconfigDone()
|
||||
fb.checkCalls(t, "disable tailscale")
|
||||
if changed := b.Reconfig(notSubnetRouter); !changed {
|
||||
t.Error("Reconfig(notSubnetRouter) retry reported no change")
|
||||
}
|
||||
b.ReconfigDone()
|
||||
fb.checkCalls(t, "disable tailscale")
|
||||
|
||||
// And becoming a subnet router again enables it again.
|
||||
if changed := b.Reconfig(subnetRouter); !changed {
|
||||
t.Error("Reconfig(subnetRouter) reported no change")
|
||||
}
|
||||
b.ReconfigDone()
|
||||
fb.checkCalls(t, "enable tailscale")
|
||||
|
||||
// Close disables the protocol on the way out.
|
||||
b.Close()
|
||||
fb.checkCalls(t, "disable tailscale")
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_bird && (linux || darwin || freebsd || openbsd)
|
||||
|
||||
package condregister
|
||||
|
||||
import _ "tailscale.com/feature/bird"
|
||||
@@ -3409,6 +3409,8 @@ func TestDeps(t *testing.T) {
|
||||
BadDeps: map[string]string{
|
||||
"golang.org/x/crypto/ssh": "tsnet should not depend on SSH",
|
||||
"golang.org/x/crypto/ssh/internal/bcrypt_pbkdf": "tsnet should not depend on SSH",
|
||||
"tailscale.com/chirp": "tsnet should not depend on BIRD integration",
|
||||
"tailscale.com/feature/bird": "tsnet should not depend on BIRD integration",
|
||||
"tailscale.com/feature/clientupdate": "tsnet should not depend on feature/clientupdate",
|
||||
"tailscale.com/feature/remoteconfig": "tsnet should not depend on feature/remoteconfig",
|
||||
"tailscale.com/feature/syspolicy": "tsnet should not depend on syspolicy",
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
// Otherwise cmd/go never sees that we depend on these packages'
|
||||
// transitive deps when we run "go install tailscaled" in a child
|
||||
// process and can cache a prior success when a dependency changes.
|
||||
_ "tailscale.com/chirp"
|
||||
_ "tailscale.com/client/local"
|
||||
_ "tailscale.com/cmd/tailscaled/childproc"
|
||||
_ "tailscale.com/control/controlclient"
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
// Otherwise cmd/go never sees that we depend on these packages'
|
||||
// transitive deps when we run "go install tailscaled" in a child
|
||||
// process and can cache a prior success when a dependency changes.
|
||||
_ "tailscale.com/chirp"
|
||||
_ "tailscale.com/client/local"
|
||||
_ "tailscale.com/cmd/tailscaled/childproc"
|
||||
_ "tailscale.com/control/controlclient"
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
// Otherwise cmd/go never sees that we depend on these packages'
|
||||
// transitive deps when we run "go install tailscaled" in a child
|
||||
// process and can cache a prior success when a dependency changes.
|
||||
_ "tailscale.com/chirp"
|
||||
_ "tailscale.com/client/local"
|
||||
_ "tailscale.com/cmd/tailscaled/childproc"
|
||||
_ "tailscale.com/control/controlclient"
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
// Otherwise cmd/go never sees that we depend on these packages'
|
||||
// transitive deps when we run "go install tailscaled" in a child
|
||||
// process and can cache a prior success when a dependency changes.
|
||||
_ "tailscale.com/chirp"
|
||||
_ "tailscale.com/client/local"
|
||||
_ "tailscale.com/cmd/tailscaled/childproc"
|
||||
_ "tailscale.com/control/controlclient"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package wgengine
|
||||
|
||||
import (
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// Bird is the engine's handle on the BIRD Internet Routing Daemon
|
||||
// integration, implemented by the feature/bird package. It enables the
|
||||
// "tailscale" protocol in BIRD while this node is a primary subnet
|
||||
// router and disables it otherwise.
|
||||
//
|
||||
// Reconfig and ReconfigDone are only called from [Engine.Reconfig],
|
||||
// serialized by the engine's internal lock. Close is called from
|
||||
// [Engine.Close].
|
||||
type Bird interface {
|
||||
// Reconfig recomputes from the given self node whether this node is
|
||||
// a primary subnet router. It is called near the top of every
|
||||
// [Engine.Reconfig], before its ErrNoChanges early return. It
|
||||
// reports whether the primary subnet router state changed, which
|
||||
// suppresses the engine's ErrNoChanges early return.
|
||||
Reconfig(self tailcfg.NodeView) (changed bool)
|
||||
|
||||
// ReconfigDone is called at the end of [Engine.Reconfig], after the
|
||||
// router is configured, and applies any protocol state change
|
||||
// computed by the preceding Reconfig call.
|
||||
ReconfigDone()
|
||||
|
||||
// Close disables the protocol and closes the connection to BIRD.
|
||||
Close()
|
||||
}
|
||||
|
||||
// HookNewBird is set by the feature/bird package to construct each
|
||||
// engine's [Bird], connecting to the BIRD unix socket at socketPath.
|
||||
// If unset, BIRD integration is not linked into the binary and
|
||||
// [Config.BIRDSocket] must not be set.
|
||||
var HookNewBird feature.Hook[func(logf logger.Logf, socketPath string) (Bird, error)]
|
||||
+35
-56
@@ -85,9 +85,13 @@ type userspaceEngine struct {
|
||||
netMon *netmon.Monitor
|
||||
health *health.Tracker
|
||||
netMonOwned bool // whether we created netMon (and thus need to close it)
|
||||
birdClient BIRDClient // or nil
|
||||
controlKnobs *controlknobs.Knobs // or nil
|
||||
|
||||
// bird is the BIRD integration handle constructed via
|
||||
// [HookNewBird], or nil if [Config.BIRDSocket] was empty or the
|
||||
// feature/bird package is not linked into the binary.
|
||||
bird Bird
|
||||
|
||||
testMaybeReconfigHook func() // for tests; if non-nil, fires if maybeReconfigWireguardLocked called
|
||||
|
||||
// isLocalAddr reports the whether an IP is assigned to the local
|
||||
@@ -112,11 +116,10 @@ type userspaceEngine struct {
|
||||
// no longer install per-config lookup closures.
|
||||
peerConfigFn atomic.Pointer[func(key.NodePublic) (allowedIPs []netip.Prefix, ok bool)]
|
||||
|
||||
lastCfgFull wgcfg.Config
|
||||
lastRouter *router.Config
|
||||
lastDNSConfig dns.ConfigView // or invalid if none
|
||||
lastIsSubnetRouter bool // was the node a primary subnet router in the last run.
|
||||
reconfigureVPN func() error // or nil
|
||||
lastCfgFull wgcfg.Config
|
||||
lastRouter *router.Config
|
||||
lastDNSConfig dns.ConfigView // or invalid if none
|
||||
reconfigureVPN func() error // or nil
|
||||
|
||||
// lastAppliedDisableTUNUDPGRO and lastAppliedDisableTUNTCPGRO cache the
|
||||
// controlknobs values that were last applied to the TUN device. They are
|
||||
@@ -162,13 +165,6 @@ type userspaceEngine struct {
|
||||
// Lock ordering: magicsock.Conn.mu, wgLock, then mu.
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -228,9 +224,10 @@ type Config struct {
|
||||
// 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
|
||||
// BIRDSocket, if non-empty, is the path of the BIRD unix socket to
|
||||
// configure whenever this node is a primary subnet router. It
|
||||
// requires the feature/bird package to be linked in.
|
||||
BIRDSocket string
|
||||
|
||||
// SetSubsystem, if non-nil, is called for each new subsystem created, just before a successful return.
|
||||
SetSubsystem func(any)
|
||||
@@ -369,17 +366,21 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
router: rtr,
|
||||
dialer: conf.Dialer,
|
||||
confListenPort: conf.ListenPort,
|
||||
birdClient: conf.BIRDClient,
|
||||
controlKnobs: conf.ControlKnobs,
|
||||
reconfigureVPN: conf.ReconfigureVPN,
|
||||
health: conf.HealthTracker,
|
||||
}
|
||||
|
||||
if e.birdClient != nil {
|
||||
// Disable the protocol at start time.
|
||||
if err := e.birdClient.DisableProtocol("tailscale"); err != nil {
|
||||
if buildfeatures.HasBird && conf.BIRDSocket != "" {
|
||||
newBird, ok := HookNewBird.GetOk()
|
||||
if !ok {
|
||||
return nil, errors.New("wgengine: Config.BIRDSocket set but the feature/bird package is not linked in")
|
||||
}
|
||||
bird, err := newBird(logf, conf.BIRDSocket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.bird = bird
|
||||
}
|
||||
e.isLocalAddr.Store(ipset.FalseContainsIPFunc())
|
||||
e.isDNSIPOverTailscale.Store(ipset.FalseContainsIPFunc())
|
||||
@@ -827,17 +828,6 @@ func peerWireGuardStateFromDevice(state device.PeerSessionState) PeerWireGuardSt
|
||||
}
|
||||
}
|
||||
|
||||
// hasOverlap checks if there is a IPPrefix which is common amongst the two
|
||||
// provided slices.
|
||||
func hasOverlap(aips, rips views.Slice[netip.Prefix]) bool {
|
||||
for _, aip := range aips.All() {
|
||||
if views.SliceContains(rips, aip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ResetAndStop resets the engine to a clean state (like calling Reconfig
|
||||
// with all pointers to zero values) and returns the resulting status.
|
||||
//
|
||||
@@ -877,14 +867,14 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
|
||||
|
||||
peerMTUEnable := e.magicConn.ShouldPMTUD()
|
||||
|
||||
isSubnetRouter := false
|
||||
if buildfeatures.HasBird && e.birdClient != nil && self.Valid() {
|
||||
isSubnetRouter = hasOverlap(self.PrimaryRoutes(), self.Hostinfo().RoutableIPs())
|
||||
e.logf("[v1] Reconfig: hasOverlap(%v, %v) = %v; isSubnetRouter=%v lastIsSubnetRouter=%v",
|
||||
self.PrimaryRoutes(), self.Hostinfo().RoutableIPs(),
|
||||
isSubnetRouter, isSubnetRouter, e.lastIsSubnetRouter)
|
||||
// Let the BIRD integration recompute whether this node is a
|
||||
// primary subnet router, before the early return below so that a
|
||||
// change in that state alone still reaches the protocol toggle in
|
||||
// ReconfigDone at the end.
|
||||
birdChanged := false
|
||||
if e.bird != nil {
|
||||
birdChanged = e.bird.Reconfig(self)
|
||||
}
|
||||
isSubnetRouterChanged := buildfeatures.HasAdvertiseRoutes && isSubnetRouter != e.lastIsSubnetRouter
|
||||
|
||||
engineChanged := !e.lastCfgFull.Equal(cfg)
|
||||
routerChanged := checkchange.Update(&e.lastRouter, routerCfg)
|
||||
@@ -905,7 +895,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
|
||||
netlogChanged = e.netlogger.Reconfig(routerCfg, routerChanged)
|
||||
}
|
||||
|
||||
if !engineChanged && !routerChanged && !dnsChanged && !listenPortChanged && !isSubnetRouterChanged && !peerMTUChanged && !netlogChanged {
|
||||
if !engineChanged && !routerChanged && !dnsChanged && !listenPortChanged && !birdChanged && !peerMTUChanged && !netlogChanged {
|
||||
return ErrNoChanges
|
||||
}
|
||||
|
||||
@@ -990,20 +980,10 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
|
||||
e.netlogger.ReconfigDone()
|
||||
}
|
||||
|
||||
if buildfeatures.HasBird && 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
|
||||
}
|
||||
// Let the BIRD integration apply any protocol state change now,
|
||||
// after the router is configured.
|
||||
if e.bird != nil {
|
||||
e.bird.ReconfigDone()
|
||||
}
|
||||
|
||||
e.logf("[v1] wgengine: Reconfig done")
|
||||
@@ -1174,9 +1154,8 @@ func (e *userspaceEngine) Close() {
|
||||
e.router.Close()
|
||||
e.wgdev.Close()
|
||||
e.tundev.Close()
|
||||
if e.birdClient != nil {
|
||||
e.birdClient.DisableProtocol("tailscale")
|
||||
e.birdClient.Close()
|
||||
if e.bird != nil {
|
||||
e.bird.Close()
|
||||
}
|
||||
close(e.waitCh)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user