bird: add source address + next hop self (v6 anycast fix)
Build flock Image / build (push) Has been cancelled
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:
@@ -44,12 +44,24 @@ func (b *BirdManager) Render(nc *flockv1alpha1.NodeConfig, anycast6, anycast4 []
|
|||||||
Anycast6: anycast6,
|
Anycast6: anycast6,
|
||||||
Anycast4: anycast4,
|
Anycast4: anycast4,
|
||||||
}
|
}
|
||||||
|
// Pick a local source address per family that's on the same subnet as
|
||||||
|
// 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.
|
||||||
for _, p := range nc.Spec.BGP.Peers {
|
for _, p := range nc.Spec.BGP.Peers {
|
||||||
fam := bird.FamilyOf(p.Address)
|
fam := bird.FamilyOf(p.Address)
|
||||||
if fam == "" {
|
if fam == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
in.Peers = append(in.Peers, bird.Peer{Family: fam, Address: p.Address, ASN: p.ASN})
|
in.Peers = append(in.Peers, bird.Peer{Family: fam, Address: p.Address, ASN: p.ASN})
|
||||||
|
if local := localAddrSameSubnet(p.Address); local != "" {
|
||||||
|
if fam == "v6" && in.LocalV6 == "" {
|
||||||
|
in.LocalV6 = local
|
||||||
|
}
|
||||||
|
if fam == "v4" && in.LocalV4 == "" {
|
||||||
|
in.LocalV4 = local
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := bird.Render(in)
|
cfg, err := bird.Render(in)
|
||||||
@@ -126,6 +138,49 @@ func (b *BirdManager) SummaryRoutes(nc *flockv1alpha1.NodeConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func localAddrSameSubnet(peer string) string {
|
||||||
|
pip := net.ParseIP(peer)
|
||||||
|
if pip == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
addrs, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
v4 := pip.To4() != nil
|
||||||
|
for _, a := range addrs {
|
||||||
|
ipn, ok := a.(*net.IPNet)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip := ipn.IP
|
||||||
|
if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (ip.To4() != nil) != v4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Use the peer's mask (assume same subnet) for membership test.
|
||||||
|
var mask net.IPMask
|
||||||
|
if v4 {
|
||||||
|
mask = net.CIDRMask(24, 32)
|
||||||
|
} else {
|
||||||
|
mask = net.CIDRMask(64, 128)
|
||||||
|
}
|
||||||
|
peerSubnet := &net.IPNet{IP: pip, Mask: mask}
|
||||||
|
if peerSubnet.Contains(ip) {
|
||||||
|
if v4 {
|
||||||
|
return ip.To4().String()
|
||||||
|
}
|
||||||
|
return ip.To16().String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func installBlackhole(cidr string) error {
|
func installBlackhole(cidr string) error {
|
||||||
// Use `ip` rather than netlink so this file stays portable for non-Linux
|
// Use `ip` rather than netlink so this file stays portable for non-Linux
|
||||||
// builds (the agent on macOS just no-ops). The agent only runs in
|
// builds (the agent on macOS just no-ops). The agent only runs in
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ type NodeBGP struct {
|
|||||||
RouterID string // IPv4 (any usable v4 on the node, typically the host's)
|
RouterID string // IPv4 (any usable v4 on the node, typically the host's)
|
||||||
LocalASN uint32
|
LocalASN uint32
|
||||||
Peers []Peer
|
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
|
// CIDR6 / CIDR4 are the per-node summary aggregates the agent wants
|
||||||
// advertised. The agent installs blackhole kernel routes for each so
|
// advertised. The agent installs blackhole kernel routes for each so
|
||||||
// BIRD's protocol kernel imports them.
|
// BIRD's protocol kernel imports them.
|
||||||
@@ -69,11 +76,12 @@ protocol static static4 {
|
|||||||
}
|
}
|
||||||
{{range $i, $p := .Peers}}{{if eq $p.Family "v6"}}
|
{{range $i, $p := .Peers}}{{if eq $p.Family "v6"}}
|
||||||
protocol bgp upstream6_{{$i}} {
|
protocol bgp upstream6_{{$i}} {
|
||||||
local as {{$.LocalASN}};
|
local{{if $.LocalV6}} {{$.LocalV6}}{{end}} as {{$.LocalASN}};
|
||||||
neighbor {{$p.Address}} as {{$p.ASN}};
|
neighbor {{$p.Address}} as {{$p.ASN}};
|
||||||
graceful restart;
|
graceful restart;
|
||||||
ipv6 {
|
ipv6 {
|
||||||
import all;
|
import all;
|
||||||
|
next hop self;
|
||||||
export filter {
|
export filter {
|
||||||
{{range $cidr := $.CIDR6}}if net = {{$cidr}} then accept;
|
{{range $cidr := $.CIDR6}}if net = {{$cidr}} then accept;
|
||||||
{{end}}{{range $a := $.Anycast6}}if net = {{$a}}/128 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"}}
|
{{else if eq $p.Family "v4"}}
|
||||||
protocol bgp upstream4_{{$i}} {
|
protocol bgp upstream4_{{$i}} {
|
||||||
local as {{$.LocalASN}};
|
local{{if $.LocalV4}} {{$.LocalV4}}{{end}} as {{$.LocalASN}};
|
||||||
neighbor {{$p.Address}} as {{$p.ASN}};
|
neighbor {{$p.Address}} as {{$p.ASN}};
|
||||||
graceful restart;
|
graceful restart;
|
||||||
ipv4 {
|
ipv4 {
|
||||||
import all;
|
import all;
|
||||||
|
next hop self;
|
||||||
export filter {
|
export filter {
|
||||||
{{range $cidr := $.CIDR4}}if net = {{$cidr}} then accept;
|
{{range $cidr := $.CIDR4}}if net = {{$cidr}} then accept;
|
||||||
{{end}}{{range $a := $.Anycast4}}if net = {{$a}}/32 then accept;
|
{{end}}{{range $a := $.Anycast4}}if net = {{$a}}/32 then accept;
|
||||||
|
|||||||
Reference in New Issue
Block a user