bird: per-peer import filter rejects connected subnet
Build flock Image / build (push) Successful in 2m17s
Build flock Image / build (push) Successful in 2m17s
Without a filter, crt001's `network 2602:817:3000:A25::/64` gets
re-advertised to every peer on that subnet. bird installs the BGP /64
with metric 32, beating the kernel-connected route at 256, and all
inter-host VLAN-25 traffic hairpins through the gateway — losing PMTU
9000 and ~30x throughput. Broke Plex 2026-05-04: NFS to nas002 capped
at 7 MB/s, jumbo blackholed.
Add LocalSubnetV6/V4 (CIDR) to NodeBGP. Agent populates by masking the
peer's address to /64 (v6) or /24 (v4) — same fritzlab convention
already in localAddrSameSubnet. Render emits `import where net !=
<subnet>;` per BGP channel when set, falls back to `import all;`
otherwise so existing tests stay green.
Defence in depth: with the matching outbound route-map on crt001
(ROUTE_MAP_CLUSTER_OUT_V{4,6}) the agent now refuses the leak on its
own if the router filter ever drifts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,12 @@ func (b *BirdManager) Render(nc *flockv1alpha1.NodeConfig, anycast6, anycast4 []
|
||||
// the BGP peer. crt001 rejects IPv6 advertisements whose next-hop is
|
||||
// link-local-only; an explicit `source address` makes BIRD use a
|
||||
// global next-hop self, which Cisco accepts.
|
||||
//
|
||||
// Also derive the connected subnet (peer IP masked to /64 v6 / /24 v4)
|
||||
// per family. Render uses it to install `import where net != <subnet>`
|
||||
// on the BGP channel so the gateway can't readvertise our own connected
|
||||
// /64 back to us — accepting it would override the kernel route and
|
||||
// hairpin all inter-host traffic via the gateway.
|
||||
for _, p := range nc.Spec.BGP.Peers {
|
||||
fam := bird.FamilyOf(p.Address)
|
||||
if fam == "" {
|
||||
@@ -69,6 +75,14 @@ func (b *BirdManager) Render(nc *flockv1alpha1.NodeConfig, anycast6, anycast4 []
|
||||
in.LocalV4 = local
|
||||
}
|
||||
}
|
||||
if subnet := peerSubnet(p.Address); subnet != "" {
|
||||
if fam == "v6" && in.LocalSubnetV6 == "" {
|
||||
in.LocalSubnetV6 = subnet
|
||||
}
|
||||
if fam == "v4" && in.LocalSubnetV4 == "" {
|
||||
in.LocalSubnetV4 = subnet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := bird.Render(in)
|
||||
@@ -165,6 +179,25 @@ func (b *BirdManager) SummaryRoutes(nc *flockv1alpha1.NodeConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// peerSubnet returns the canonical CIDR of the assumed connected subnet
|
||||
// containing `peer` — /64 for IPv6, /24 for IPv4. Returns "" if peer
|
||||
// doesn't parse. Matches the assumption already baked into
|
||||
// localAddrSameSubnet: fritzlab convention is /64 v6 and /24 v4.
|
||||
func peerSubnet(peer string) string {
|
||||
pip := net.ParseIP(peer)
|
||||
if pip == nil {
|
||||
return ""
|
||||
}
|
||||
var mask net.IPMask
|
||||
if pip.To4() != nil {
|
||||
mask = net.CIDRMask(24, 32)
|
||||
} else {
|
||||
mask = net.CIDRMask(64, 128)
|
||||
}
|
||||
n := &net.IPNet{IP: pip.Mask(mask), Mask: mask}
|
||||
return n.String()
|
||||
}
|
||||
|
||||
// localAddrSameSubnet finds an IP on a local interface that's in the same
|
||||
// /64 (v6) or /24 (v4) as `peer`. Returns "" if none. Used to derive the
|
||||
// `source address` for a BGP session.
|
||||
|
||||
Reference in New Issue
Block a user