bird: add source address + next hop self (v6 anycast fix)
Build flock Image / build (push) Has been cancelled

Cisco IOS rejects IPv6 BGP advertisements whose next-hop is link-local-
only. BIRD2 was synthesising a link-local next-hop for kernel-learned
routes whose dev had no via gateway (our anycast /128s). Symptom: v4
anycast worked (Cisco doesn't have the same constraint for /32s), v6
anycast didn't make it past crt001.

- pkg/routing/bird/config.go: NodeBGP.LocalV6/LocalV4. Template now
  emits `local <addr> as <asn>` and `next hop self;` in the BGP
  channel for both families, mirroring Calico's `source address` +
  `next hop self` pattern.
- pkg/agent/bird.go: localAddrSameSubnet picks an interface address
  on the peer's /64 or /24 to use as source.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Donavan Fritz
2026-04-25 07:45:51 -05:00
parent 89a3502446
commit 3f6dfd3e88
2 changed files with 66 additions and 2 deletions
+11 -2
View File
@@ -18,6 +18,13 @@ type NodeBGP struct {
RouterID string // IPv4 (any usable v4 on the node, typically the host's)
LocalASN uint32
Peers []Peer
// LocalV6 / LocalV4 are this node's local source addresses on the
// same subnet as the v6 / v4 BGP peers. Used as `source address` in
// each BGP protocol stanza (Cisco rejects v6 advertisements whose
// next-hop is link-local-only — explicit source forces a global next-
// hop self that crt001 accepts).
LocalV6 string
LocalV4 string
// CIDR6 / CIDR4 are the per-node summary aggregates the agent wants
// advertised. The agent installs blackhole kernel routes for each so
// BIRD's protocol kernel imports them.
@@ -69,11 +76,12 @@ protocol static static4 {
}
{{range $i, $p := .Peers}}{{if eq $p.Family "v6"}}
protocol bgp upstream6_{{$i}} {
local as {{$.LocalASN}};
local{{if $.LocalV6}} {{$.LocalV6}}{{end}} as {{$.LocalASN}};
neighbor {{$p.Address}} as {{$p.ASN}};
graceful restart;
ipv6 {
import all;
next hop self;
export filter {
{{range $cidr := $.CIDR6}}if net = {{$cidr}} then accept;
{{end}}{{range $a := $.Anycast6}}if net = {{$a}}/128 then accept;
@@ -83,11 +91,12 @@ protocol bgp upstream6_{{$i}} {
}
{{else if eq $p.Family "v4"}}
protocol bgp upstream4_{{$i}} {
local as {{$.LocalASN}};
local{{if $.LocalV4}} {{$.LocalV4}}{{end}} as {{$.LocalASN}};
neighbor {{$p.Address}} as {{$p.ASN}};
graceful restart;
ipv4 {
import all;
next hop self;
export filter {
{{range $cidr := $.CIDR4}}if net = {{$cidr}} then accept;
{{end}}{{range $a := $.Anycast4}}if net = {{$a}}/32 then accept;