mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-20 21:23:07 +08:00
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 <[email protected]>
108 lines
3.1 KiB
Go
108 lines
3.1 KiB
Go
// 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
|
|
}
|