From 3117d002103a75ea16b710c04f1a24ca96fd23fe Mon Sep 17 00:00:00 2001 From: Donavan Fritz Date: Sat, 25 Apr 2026 08:16:45 -0500 Subject: [PATCH] =?UTF-8?q?bird:=20declare=20anycast=20as=20protocol=20sta?= =?UTF-8?q?tic;=20filter=20static=E2=86=92kernel=20export?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two coupled changes that fix the anycast advertisement path: 1. Add anycast /128 + /32 prefixes as `route … blackhole` lines in the protocol static stanzas. BIRD's master tables pick them up at preference 200 — higher than kernel-learned routes — so they're the ones the BGP export filter sees. 2. The kernel protocol's export filter now rejects RTS_STATIC. Without this, BIRD would push its blackhole back into the kernel, clobbering the agent-installed ` via dev flock<8hex>` route that's actually responsible for forwarding to the pod. Result: BIRD has the route to advertise via BGP; the kernel has the right route to forward; nothing fights over the kernel table. Replaces the abandoned `gateway recursive` attempt — that's a BIRD 1.x keyword, not BIRD 2.15. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- pkg/routing/bird/config.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pkg/routing/bird/config.go b/pkg/routing/bird/config.go index c23d144..2eb9a4d 100644 --- a/pkg/routing/bird/config.go +++ b/pkg/routing/bird/config.go @@ -53,30 +53,36 @@ protocol kernel kernel6 { learn; ipv6 { import all; - export all; + # Do NOT push BIRD static routes back to the kernel; the agent owns + # the kernel host routes for anycast. BIRD static is for advertise only. + export filter { + if source = RTS_STATIC then reject; + accept; + }; }; } protocol kernel kernel4 { learn; ipv4 { import all; - export all; + export filter { + if source = RTS_STATIC then reject; + accept; + }; }; } -# gateway recursive is set per BGP protocol below — it controls how -# BIRD resolves a route's next-hop when the gateway isn't on a directly -# connected interface (our case: anycast routes use the pod's /128 eth0 -# IP as via, which is itself a host route, not a network). protocol static static6 { ipv6; {{range $cidr := .CIDR6}}route {{$cidr}} blackhole; + {{end}}{{range $a := .Anycast6}}route {{$a}}/128 blackhole; {{end}} } protocol static static4 { ipv4; {{range $cidr := .CIDR4}}route {{$cidr}} blackhole; + {{end}}{{range $a := .Anycast4}}route {{$a}}/32 blackhole; {{end}} } {{range $i, $p := .Peers}}{{if eq $p.Family "v6"}} @@ -84,7 +90,6 @@ protocol bgp upstream6_{{$i}} { local{{if $.LocalV6}} {{$.LocalV6}}{{end}} as {{$.LocalASN}}; neighbor {{$p.Address}} as {{$p.ASN}}; graceful restart; - gateway recursive; ipv6 { import all; next hop self; @@ -100,7 +105,6 @@ protocol bgp upstream4_{{$i}} { local{{if $.LocalV4}} {{$.LocalV4}}{{end}} as {{$.LocalASN}}; neighbor {{$p.Address}} as {{$p.ASN}}; graceful restart; - gateway recursive; ipv4 { import all; next hop self;