wgengine,cmd/tailscaled,feature/bird: move BIRD integration to ./feature

The BIRD (BGP) integration previously lived half in cmd/tailscaled
(which created a chirp client via a build-tag-gated file on some
platforms) and half in wgengine (which carried the client in its
Config and toggled the "tailscale" protocol as the node gained or
lost primary subnet router duty).

Move it all to a new feature/bird package, installed on the engine
via the new wgengine.HookNewBird hook, like other feature/* packages.
wgengine.Config.BIRDClient (and the wgengine.BIRDClient interface)
are replaced by a BIRDSocket path from which the engine constructs
the feature's Bird handle at startup. The subnet router overlap
detection and protocol toggling move into feature/bird, preserving
the previous ordering: state is recomputed before Reconfig's
ErrNoChanges early return and applied after the router is configured.

tailscaled keeps BIRD support by default on the platforms that
previously had it (linux, darwin, freebsd, openbsd) via
feature/condregister.

Also, add an integration test, as this feature lacked much test
coverage previously.

Updates #12614

Change-Id: I7866a50779e454c87933b358735f7dcd9e2b126f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-07-14 10:48:09 -07:00
committed by Brad Fitzpatrick
parent 9bd62683dd
commit 3d07b5d6e1
14 changed files with 419 additions and 88 deletions
+2 -1
View File
@@ -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
+14
View File
@@ -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",
+5 -10
View File
@@ -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 {
-17
View File
@@ -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)
}
}