[PATCH 0/4] BGP unnumbered automatic peering based on IPv6 ND
Dear BIRD dev-team and community, This is a patch proposal spawned from the discussion in a previous mailing list thread [0], regarding how to achieve automatic BGP peering establishment using the IPv6 Neighbor Discovery mechanism. Over the last few weeks, I worked on implementing the feature with the outline Maria Matejka kindly provided. As expected, it proved to be quite challenging, especially when it came to integrate the peer discovery approach with the current dynamic BGP implementation. To briefly summarize the implemented automatic peering mechanism and added capabilites: - The ability for the RAdv protocol to receive and read incoming ICMPv6 RAs was added. - Route-like objects, containing RAs data are created, based on advertisments detections. These are stored in a new kind of routing table. - Lifetime of the objects in the table is determined by the ICMPv6 RA Router Lifetime field. RA expiration will remove the object from the table. - The discovery process is enabled only if a new neighbor discovery option is enabled in the RAdv protocol configuration. - A new channel was added to the BGP protocol called peers. - When present in the BGP configuration, an export to this BGP channel (allowed only in dynamic BGP) will trigger the spawn of a new session. - Withdrawal the object from the peers routing table will trigger the corresponding BGP session shutdown. - An automatic BGP session can be made permanent via configuration with a persist option in the peers channel. To test the main functionality of this feature it I used multiple VMs linked by a bridge with BIRD setup on all of them. BGP session establishment is successful between all routers even with a very minimal configuration. I will provide an example, so you can also see the new configuration parameters in action: protocol device { } peer table nd_peers; protocol radv radv1 { peer { table nd_peers; }; interface "enp5s0" { max ra interval 60; min ra interval 20; # Enable neighbor discovery on this interface # When enabled, BIRD will parse incoming RAs and store router info neighbor discovery yes; }; } protocol bgp bgp1 { dynamic name "bgp_peer"; dynamic name digits 3; local as 65000; neighbor range fe80::/12 external; # Link-local range for BGP unnumbered # Peers channel for importing peer discoveries and spawning sessions peers { # Interface for BGP session establishment from "enp5s0"; # Persist sessions on route withdrawal persist yes; table nd_peers; import all; export all; }; ipv4 { import all; export all; }; ipv6 { import all; export all; }; } Of course, I have some concerns about some design choices and code optimization, I tried to find the best solution that did not involve major refactorings or moving code around and some comments on how to improve rough edges from people more experienced on the current codebase would be really appreciated. Mostly, the two main points I am not entirely happy about are: - How I handled the RAs staleness mechanism based on router lifetime. I looked for it but there does not seem to be a per-route staleness mechanism already in place, so I resorted to adding a check for expired entries when the RAdv protocol timer is triggerd, which is not ideal. - The mechanism to avoid spawning multiple sessions when both dynamic BGP and this new feature are both configured. For now, I try looking for BGP session with the same remote peer address and give precedence to the ones spawned by the dynamic BGP mechanism, but I am quite sure it is possible to find a better solution for this. I tried to make the commit messages both informative and concise, but please ask if something is unclear or missing. I am still working on adding documentation and automated testing, but since I think the discussion about the implemetation is more useful with actual code in hand, I decided to send the main bulk of the patch right now and amend this later along with changes addressing incoming reviews. I hope you will appreciate the work done. Best Regards, Matteo [0] https://bird.network.cz/pipermail/bird-users/2025-November/018482.html Matteo Perin (4): Nest: Add net_peer route type to track discovered peers RAdv: Add neighbor discovery based on incoming RAs to RAdv proto BGP: Move postponed socket reloop logic from start to start_locked to achieve locking requirements BGP: Add peers channel and sessions spawn on export to achieve automatic peering lib/net.c | 16 ++++ lib/net.h | 31 ++++++- nest/config.Y | 3 +- nest/proto.c | 2 +- proto/bgp/attrs.c | 57 +++++++++++++ proto/bgp/bgp.c | 187 +++++++++++++++++++++++++++++++++++++++---- proto/bgp/bgp.h | 28 ++++++- proto/bgp/config.Y | 15 +++- proto/bgp/packets.c | 11 +++ proto/radv/config.Y | 3 +- proto/radv/packets.c | 41 +++++++++- proto/radv/radv.c | 163 ++++++++++++++++++++++++++++++++++++- proto/radv/radv.h | 4 + 13 files changed, 533 insertions(+), 28 deletions(-) -- 2.43.0
The definition and helper functions for a new route-like object to track peers discovery data has been added. It only contains the (v4 or v6) remote peer address for now. The main intent of this is, currently, to enable BGP unnumbered auto peer discovery via RAdv incoming advertisments, but in the future the same data structure could be used to allow discovery coming from different protocols. Signed-off-by: Matteo Perin <matteo.perin@canonical.com> --- lib/net.c | 16 ++++++++++++++++ lib/net.h | 31 ++++++++++++++++++++++++++++++- nest/config.Y | 3 ++- nest/proto.c | 2 +- 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/lib/net.c b/lib/net.c index 64cf9e04..3eae6605 100644 --- a/lib/net.c +++ b/lib/net.c @@ -17,6 +17,7 @@ const char * const net_label[] = { [NET_IP6_SADR]= "ipv6-sadr", [NET_MPLS] = "mpls", [NET_ASPA] = "aspa", + [NET_PEER] = "peer", }; const u16 net_addr_length[] = { @@ -31,6 +32,7 @@ const u16 net_addr_length[] = { [NET_IP6_SADR]= sizeof(net_addr_ip6_sadr), [NET_MPLS] = sizeof(net_addr_mpls), [NET_ASPA] = sizeof(net_addr_aspa), + [NET_PEER] = sizeof(net_addr_peer), }; const u8 net_max_prefix_length[] = { @@ -45,6 +47,7 @@ const u8 net_max_prefix_length[] = { [NET_IP6_SADR]= IP6_MAX_PREFIX_LENGTH, [NET_MPLS] = 0, [NET_ASPA] = 0, + [NET_PEER] = IP6_MAX_PREFIX_LENGTH, }; const u16 net_max_text_length[] = { @@ -59,6 +62,7 @@ const u16 net_max_text_length[] = { [NET_IP6_SADR]= 92, /* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128 from ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" */ [NET_MPLS] = 7, /* "1048575" */ [NET_ASPA] = 10, /* "4294967295" */ + [NET_PEER] = 43, /* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" */ }; /* There should be no implicit padding in net_addr structures */ @@ -74,6 +78,7 @@ STATIC_ASSERT(sizeof(net_addr_flow6) == 20); STATIC_ASSERT(sizeof(net_addr_ip6_sadr) == 40); STATIC_ASSERT(sizeof(net_addr_mpls) == 8); STATIC_ASSERT(sizeof(net_addr_aspa) == 8); +STATIC_ASSERT(sizeof(net_addr_peer) == 20); /* Ensure that all net_addr structures have the same alignment */ STATIC_ASSERT(alignof(net_addr_ip4) == alignof(net_addr)); @@ -89,6 +94,7 @@ STATIC_ASSERT(alignof(net_addr_flow6) == alignof(net_addr)); STATIC_ASSERT(alignof(net_addr_ip6_sadr) == alignof(net_addr)); STATIC_ASSERT(alignof(net_addr_mpls) == alignof(net_addr)); STATIC_ASSERT(alignof(net_addr_aspa) == alignof(net_addr)); +STATIC_ASSERT(alignof(net_addr_peer) == alignof(net_addr)); int @@ -147,6 +153,8 @@ net_format(const net_addr *N, char *buf, int buflen) return bsnprintf(buf, buflen, "%u", n->mpls.label); case NET_ASPA: return bsnprintf(buf, buflen, "%u", n->aspa.asn); + case NET_PEER: + return bsnprintf(buf, buflen, "%I", n->peer.addr); } bug("unknown network type"); @@ -172,6 +180,7 @@ net_pxmask(const net_addr *a) case NET_MPLS: case NET_ASPA: + case NET_PEER: default: return IPA_NONE; } @@ -207,6 +216,8 @@ net_compare(const net_addr *a, const net_addr *b) return net_compare_mpls((const net_addr_mpls *) a, (const net_addr_mpls *) b); case NET_ASPA: return net_compare_aspa((const net_addr_aspa *) a, (const net_addr_aspa *) b); + case NET_PEER: + return net_compare_peer((const net_addr_peer *) a, (const net_addr_peer *) b); } return 0; } @@ -229,6 +240,7 @@ net_hash(const net_addr *n) case NET_IP6_SADR: return NET_HASH(n, ip6_sadr); case NET_MPLS: return NET_HASH(n, mpls); case NET_ASPA: return NET_HASH(n, aspa); + case NET_PEER: return NET_HASH(n, peer); default: bug("invalid type"); } } @@ -252,6 +264,7 @@ net_validate(const net_addr *n) case NET_IP6_SADR: return NET_VALIDATE(n, ip6_sadr); case NET_MPLS: return NET_VALIDATE(n, mpls); case NET_ASPA: return NET_VALIDATE(n, aspa); + case NET_PEER: return NET_VALIDATE(n, peer); default: return 0; } } @@ -280,6 +293,7 @@ net_normalize(net_addr *N) case NET_MPLS: case NET_ASPA: + case NET_PEER: return; } } @@ -308,6 +322,7 @@ net_classify(const net_addr *N) case NET_MPLS: case NET_ASPA: + case NET_PEER: return IADDR_HOST | SCOPE_UNIVERSE; } @@ -342,6 +357,7 @@ ipa_in_netX(const ip_addr a, const net_addr *n) case NET_MPLS: case NET_ASPA: + case NET_PEER: default: return 0; } diff --git a/lib/net.h b/lib/net.h index 24aae87d..c301c803 100644 --- a/lib/net.h +++ b/lib/net.h @@ -24,7 +24,8 @@ #define NET_IP6_SADR 9 #define NET_MPLS 10 #define NET_ASPA 11 -#define NET_MAX 12 +#define NET_PEER 12 +#define NET_MAX 13 #define NB_IP4 (1 << NET_IP4) #define NB_IP6 (1 << NET_IP6) @@ -37,6 +38,7 @@ #define NB_IP6_SADR (1 << NET_IP6_SADR) #define NB_MPLS (1 << NET_MPLS) #define NB_ASPA (1 << NET_ASPA) +#define NB_PEER (1 << NET_PEER) #define NB_IP (NB_IP4 | NB_IP6) #define NB_VPN (NB_VPN4 | NB_VPN6) @@ -133,6 +135,13 @@ typedef struct net_addr_aspa { u32 asn; } net_addr_aspa; +typedef struct net_addr_peer { + u8 type; + u8 pxlen; + u16 length; + ip_addr addr; /* Peer IP address (IPv6 or IPv4) */ +} net_addr_peer; + typedef struct net_addr_ip6_sadr { u8 type; u8 dst_pxlen; @@ -155,6 +164,7 @@ typedef union net_addr_union { net_addr_ip6_sadr ip6_sadr; net_addr_mpls mpls; net_addr_aspa aspa; + net_addr_peer peer; } net_addr_union; @@ -182,6 +192,7 @@ extern const u16 net_max_text_length[]; #define NET_PTR_FLOW6(_n) NET_PTR_GEN((_n), NET_FLOW6, flow6) #define NET_PTR_IP6_SADR(_n) NET_PTR_GEN((_n), NET_IP6_SADR, ip6_sadr) #define NET_PTR_MPLS(_n) NET_PTR_GEN((_n), NET_MPLS, mpls) +#define NET_PTR_PEER(_n) NET_PTR_GEN((_n), NET_PEER, peer) #define NET_ADDR_IP4(prefix,pxlen) \ @@ -217,6 +228,9 @@ extern const u16 net_max_text_length[]; #define NET_ADDR_MPLS(label) \ ((net_addr_mpls) { NET_MPLS, 20, sizeof(net_addr_mpls), label }) +#define NET_ADDR_PEER(addr) \ + ((net_addr_peer) { NET_PEER, ipa_is_ip4(addr) ? 32 : 128, sizeof(net_addr_peer), addr }) + static inline void net_fill_ip4(net_addr *a, ip4_addr prefix, uint pxlen) { *(net_addr_ip4 *)a = NET_ADDR_IP4(prefix, pxlen); } @@ -245,6 +259,9 @@ static inline void net_fill_mpls(net_addr *a, u32 label) static inline void net_fill_aspa(net_addr *a, u32 asn) { *(net_addr_aspa *)a = NET_ADDR_ASPA(asn); } +static inline void net_fill_peer(net_addr *a, ip_addr addr) +{ *(net_addr_peer *)a = NET_ADDR_PEER(addr); } + static inline void net_fill_ipa(net_addr *a, ip_addr prefix, uint pxlen) { if (ipa_is_ip4(prefix)) @@ -489,6 +506,9 @@ static inline int net_compare_mpls(const net_addr_mpls *a, const net_addr_mpls * static inline int net_compare_aspa(const net_addr_aspa *a, const net_addr_aspa *b) { return uint_cmp(a->asn, b->asn); } +static inline int net_compare_peer(const net_addr_peer *a, const net_addr_peer *b) +{ return ipa_compare(a->addr, b->addr); } + int net_compare(const net_addr *a, const net_addr *b); @@ -528,6 +548,9 @@ static inline void net_copy_mpls(net_addr_mpls *dst, const net_addr_mpls *src) static inline void net_copy_aspa(net_addr_aspa *dst, const net_addr_aspa *src) { memcpy(dst, src, sizeof(net_addr_aspa)); } +static inline void net_copy_peer(net_addr_peer *dst, const net_addr_peer *src) +{ memcpy(dst, src, sizeof(net_addr_peer)); } + static inline u32 px4_hash(ip4_addr prefix, u32 pxlen) { return ip4_hash(prefix) ^ (pxlen << 26); } @@ -574,6 +597,9 @@ static inline u32 net_hash_mpls(const net_addr_mpls *n) static inline u32 net_hash_aspa(const net_addr_aspa *n) { return u32_hash(n->asn); } +static inline u32 net_hash_peer(const net_addr_peer *n) +{ return ipa_hash(n->addr); } + u32 net_hash(const net_addr *a); @@ -626,6 +652,9 @@ static inline int net_validate_mpls(const net_addr_mpls *n) static inline int net_validate_aspa(const net_addr_aspa *n) { return n->asn > 0; } +static inline int net_validate_peer(const net_addr_peer *n) +{ return ipa_nonzero(n->addr); } + static inline int net_validate_ip6_sadr(const net_addr_ip6_sadr *n) { return net_validate_px6(n->dst_prefix, n->dst_pxlen) && net_validate_px6(n->src_prefix, n->src_pxlen); } diff --git a/nest/config.Y b/nest/config.Y index 681d0edd..f12103d9 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -153,7 +153,7 @@ CF_DECLS CF_KEYWORDS(ROUTER, ID, HOSTNAME, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT, PIPE) CF_KEYWORDS(INTERFACE, IMPORT, EXPORT, FILTER, NONE, VRF, DEFAULT, TABLE, TABLES, STATES, ROUTES, FILTERS) -CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS, ASPA) +CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS, ASPA, PEER) CF_KEYWORDS(RECEIVE, LIMIT, ACTION, WARN, BLOCK, RESTART, DISABLE, KEEP, FILTERED, RPKI) CF_KEYWORDS(PASSWORD, KEY, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, CHANNELS, INTERFACES) CF_KEYWORDS(ALGORITHM, KEYED, HMAC, MD5, SHA1, SHA256, SHA384, SHA512, BLAKE2S128, BLAKE2S256, BLAKE2B256, BLAKE2B512) @@ -231,6 +231,7 @@ net_type_base: | FLOW4{ $$ = NET_FLOW4; } | FLOW6{ $$ = NET_FLOW6; } | ASPA { $$ = NET_ASPA; } + | PEER { $$ = NET_PEER; } ; net_type: diff --git a/nest/proto.c b/nest/proto.c index d6620065..e8d0021f 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -1168,7 +1168,7 @@ channel_config_new(const struct channel_class *cc, const char *name, uint net_ty if (!net_val_match(net_type, proto->protocol->channel_mask)) cf_error("Unsupported channel type"); - if (proto->net_type && (net_type != proto->net_type) && (net_type != NET_MPLS)) + if (proto->net_type && (net_type != proto->net_type) && (net_type != NET_MPLS) && (net_type != NET_PEER)) cf_error("Different channel type"); tab = rt_get_default_table(new_config, net_type); -- 2.43.0
Implementation of peer discovery based on incoming Router Advertisments to the RAdv protocol. Up until this point no much use has been make of incoming RAs, this commit tries to amend that by importing peer-based routes to a new peers channel. This will allow to use the information discovered about remote routers on other protocols which can export peers on this same channel. RA staleness has also been taken into consideration and the routes are withdrawn whenever the advertised router lifetime expires. The feature is meant to be enabled via a new configuration option in the RAdv protocol called neighbor discovery [yes/no]. Signed-off-by: Matteo Perin <matteo.perin@canonical.com> --- proto/radv/config.Y | 3 +- proto/radv/packets.c | 41 ++++++++++- proto/radv/radv.c | 163 ++++++++++++++++++++++++++++++++++++++++++- proto/radv/radv.h | 4 ++ 4 files changed, 206 insertions(+), 5 deletions(-) diff --git a/proto/radv/config.Y b/proto/radv/config.Y index 372081d2..09d39863 100644 --- a/proto/radv/config.Y +++ b/proto/radv/config.Y @@ -42,7 +42,7 @@ CF_KEYWORDS(RADV, PREFIX, INTERFACE, MIN, MAX, RA, DELAY, INTERVAL, SOLICITED, RETRANS, TIMER, CURRENT, HOP, LIMIT, DEFAULT, VALID, PREFERRED, MULT, LIFETIME, SKIP, ONLINK, AUTONOMOUS, RDNSS, DNSSL, NS, DOMAIN, LOCAL, TRIGGER, SENSITIVE, PREFERENCE, LOW, MEDIUM, HIGH, PROPAGATE, ROUTE, - ROUTES, CUSTOM, OPTION, TYPE, VALUE, PD) + ROUTES, CUSTOM, OPTION, TYPE, VALUE, PD, NEIGHBOR, DISCOVERY) CF_ENUM(T_ENUM_RA_PREFERENCE, RA_PREF_, LOW, MEDIUM, HIGH) @@ -140,6 +140,7 @@ radv_iface_item: | RDNSS LOCAL bool { RADV_IFACE->rdnss_local = $3; } | DNSSL LOCAL bool { RADV_IFACE->dnssl_local = $3; } | CUSTOM OPTION LOCAL bool { RADV_IFACE->custom_local = $4; } + | NEIGHBOR DISCOVERY bool { RADV_IFACE->neighbor_discovery = $3; } ; radv_preference: diff --git a/proto/radv/packets.c b/proto/radv/packets.c index 40f3c41e..bd0e4fae 100644 --- a/proto/radv/packets.c +++ b/proto/radv/packets.c @@ -460,6 +460,44 @@ radv_receive_rs(struct radv_proto *p, struct radv_iface *ifa, ip_addr from) radv_iface_notify(ifa, RA_EV_RS); } +void +radv_process_ra(struct radv_iface *ifa, ip_addr from, struct radv_ra_packet *pkt, int length) +{ + struct radv_proto *p = ifa->ra; + + if (!ifa->cf->neighbor_discovery) + return; + + /* Basic validation */ + if ((uint)length < sizeof(struct radv_ra_packet)) + { + RADV_TRACE(D_PACKETS, "Malformed RA received from %I via %s", + from, ifa->iface->name); + return; + } + + u16 router_lifetime = ntohs(pkt->router_lifetime); + + /* Router lifetime of 0 means withdrawal */ + if (router_lifetime == 0) + { + RADV_TRACE(D_EVENTS, "Neighbor %I on %s withdrawing (lifetime=0)", + from, ifa->iface->name); + + /* Withdraw from routing table if peers channel is configured */ + radv_withdraw_peer(p, from); + + return; + } + + /* Announce/update the peer in routing table */ + RADV_TRACE(D_PACKETS, "Processed RA from %I: lifetime=%u, hop_limit=%u", + from, router_lifetime, pkt->current_hop_limit); + + /* Announce to routing table with router lifetime for expiration tracking */ + radv_announce_peer(p, from, router_lifetime); +} + static int radv_rx_hook(sock *sk, uint size) { @@ -493,7 +531,8 @@ radv_rx_hook(sock *sk, uint size) case ICMPV6_RA: RADV_TRACE(D_PACKETS, "Received RA from %I via %s", sk->faddr, ifa->iface->name); - /* FIXME - there should be some checking of received RAs, but we just ignore them */ + if (ifa->cf->neighbor_discovery) + radv_process_ra(ifa, sk->faddr, (struct radv_ra_packet *)buf, size); return 1; default: diff --git a/proto/radv/radv.c b/proto/radv/radv.c index 8ae411da..14b08212 100644 --- a/proto/radv/radv.c +++ b/proto/radv/radv.c @@ -43,10 +43,60 @@ * RFC 6106 - DNS extensions (RDDNS, DNSSL) */ -static struct ea_class ea_radv_preference, ea_radv_lifetime; +static struct ea_class ea_radv_preference, ea_radv_lifetime, ea_radv_expires_at; static void radv_prune_prefixes(struct radv_iface *ifa); static void radv_prune_routes(struct radv_proto *p); +static void radv_neighbor_prune(struct radv_iface *ifa); + +void +radv_announce_peer(struct radv_proto *p, ip_addr peer_ip, u16 router_lifetime) +{ + if (!p->peers_channel) + return; + + /* Check if channel is ready */ + if (p->peers_channel->channel_state != CS_UP) { + RADV_TRACE(D_EVENTS, "Peers channel not UP yet (state=%d), skipping peer announcement", + p->peers_channel->channel_state); + return; + } + + /* Compute expiration time */ + btime now = current_time(); + btime expires_at = now + (router_lifetime S); + + net_addr_peer n; + net_fill_peer((net_addr *) &n, peer_ip); + + ea_list *ea = NULL; + ea_set_attr_u32(&ea, &ea_gen_preference, 0, p->peers_channel->preference); + ea_set_attr_u32(&ea, &ea_gen_source, 0, RTS_DEVICE); + ea_set_attr_u32(&ea, &ea_radv_expires_at, 0, expires_at / 1000000); + + rte e0 = { + .attrs = ea, + .src = p->p.main_source, + }; + + RADV_TRACE(D_EVENTS, "Announcing peer to channel: peer_ip: %I, lifetime=%u, expires_at=%T", + peer_ip, router_lifetime, expires_at); + + rte_update(p->peers_channel, (net_addr *) &n, &e0, p->p.main_source); +} + +void +radv_withdraw_peer(struct radv_proto *p, ip_addr peer_ip) +{ + if (!p->peers_channel) + return; + + net_addr_peer n; + net_fill_peer((net_addr *) &n, peer_ip); + + /* Withdraw the route */ + rte_update(p->peers_channel, (net_addr *) &n, NULL, p->p.main_source); +} static void radv_timer(timer *tm) @@ -63,6 +113,9 @@ radv_timer(timer *tm) if (p->prune_time <= now) radv_prune_routes(p); + /* Prune stale discovered neighbor routers entries */ + radv_neighbor_prune(ifa); + radv_send_ra(ifa, IPA_NONE); /* Update timer */ @@ -212,6 +265,84 @@ radv_prune_prefixes(struct radv_iface *ifa) ifa->prune_time = next; } +void +radv_neighbor_prune(struct radv_iface *ifa) +{ + struct radv_proto *p = ifa->ra; + + /* Skip pruning check if neighbor discovery is not enabled */ + if (!ifa->cf->neighbor_discovery) + return; + + /* Skip if no peers channel configured */ + if (!p->peers_channel || p->peers_channel->channel_state != CS_UP) + return; + + /* Check for expired peers by walking the routing table */ + btime now = current_time(); + + /* Temporary list to store expired peers (can't withdraw during export walk) */ + struct expired_peer { + struct expired_peer *next; + ip_addr peer_ip; + }; + struct expired_peer *expired_list = NULL; + + /* Walk all routes in the peers channel to check for expired entries */ + RT_EXPORT_WALK(&p->peers_channel->out_req, u) + { + switch (u->kind) + { + case RT_EXPORT_FEED: + /* Check each route in the feed */ + for (uint i = 0; i < u->feed->count_routes; i++) + { + rte *e = &u->feed->block[i]; + if (e->flags & REF_OBSOLETE) + continue; + + /* Only process routes from our protocol */ + if (e->src != p->p.main_source) + continue; + + /* Get the expiration time EA */ + eattr *expires_ea = ea_find(e->attrs, &ea_radv_expires_at); + if (!expires_ea) + continue; + + btime expires_at = expires_ea->u.data * 1000000; + + if (expires_at <= now) + { + /* Peer has expired, add to withdrawal list */ + net_addr_peer *peer_addr = (net_addr_peer *) e->net; + + struct expired_peer *ep = mb_alloc(p->p.pool, sizeof(struct expired_peer)); + ep->peer_ip = peer_addr->addr; + ep->next = expired_list; + expired_list = ep; + + RADV_TRACE(D_EVENTS, "Peer %I expired (expires_at=%T, now=%T)", + peer_addr->addr, expires_at, now); + } + } + break; + default: + /* Nothing to do for RT_EXPORT_UPDATE or RT_EXPORT_STOP */ + break; + } + } + + /* Withdraw all expired peers */ + while (expired_list) + { + struct expired_peer *ep = expired_list; + radv_withdraw_peer(p, ep->peer_ip); + expired_list = ep->next; + mb_free(ep); + } +} + static char* ev_name[] = { NULL, "Init", "Change", "RS" }; void @@ -581,6 +712,12 @@ radv_init(struct proto_config *CF) P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF)); + /* Add all other configured channels (e.g., peer channel) */ + struct channel_config *cc; + WALK_LIST(cc, CF->channels) + if (cc != proto_cf_main_channel(CF)) + proto_add_channel(P, cc); + P->preexport = radv_preexport; P->rt_notify = radv_rt_notify; P->iface_sub.if_notify = radv_if_notify; @@ -619,6 +756,19 @@ radv_start(struct proto *P) radv_set_fib(p, cf->propagate_routes); p->prune_time = TIME_INFINITY; + /* Find the peers channel if configured */ + p->peers_channel = NULL; + struct channel *c; + WALK_LIST(c, P->channels) + { + if (c->net_type == NET_PEER) + { + p->peers_channel = c; + RADV_TRACE(D_EVENTS, "Found peers discovery channel: %s", c->name); + break; + } + } + return PS_UP; } @@ -765,10 +915,16 @@ static struct ea_class ea_radv_lifetime = { .type = T_INT, }; +static struct ea_class ea_radv_expires_at = { + .name = "radv_expires_at", + .legacy_name = "RAdv.expires_at", + .type = T_INT, +}; + struct protocol proto_radv = { .name = "RAdv", .template = "radv%d", - .channel_mask = NB_IP6, + .channel_mask = NB_IP6 | NB_PEER, .proto_size = sizeof(struct radv_proto), .config_size = sizeof(struct radv_config), .postconfig = radv_postconfig, @@ -787,6 +943,7 @@ radv_build(void) EA_REGISTER_ALL( &ea_radv_preference, - &ea_radv_lifetime + &ea_radv_lifetime, + &ea_radv_expires_at ); } diff --git a/proto/radv/radv.h b/proto/radv/radv.h index 1a4afc55..f046e101 100644 --- a/proto/radv/radv.h +++ b/proto/radv/radv.h @@ -91,6 +91,7 @@ struct radv_iface_config u8 route_lifetime_sensitive; /* Whether route_lifetime depends on trigger */ u8 default_preference; /* Default Router Preference (RFC 4191) */ u8 route_preference; /* Specific Route Preference (RFC 4191) */ + u8 neighbor_discovery; /* Enable neighbor router discovery */ }; struct radv_prefix_config @@ -161,6 +162,7 @@ struct radv_proto u8 fib_up; /* FIB table (routes) is initialized */ struct fib routes; /* FIB table of specific routes (struct radv_route) */ btime prune_time; /* Next time of route table pruning */ + struct channel *peers_channel; /* Channel for peer discovery (NET_PEER) */ }; struct radv_prefix /* One prefix we advertise */ @@ -221,6 +223,8 @@ static inline void radv_invalidate(struct radv_iface *ifa) /* radv.c */ void radv_iface_notify(struct radv_iface *ifa, int event); +void radv_announce_peer(struct radv_proto *p, ip_addr peer_ip, u16 router_lifetime); +void radv_withdraw_peer(struct radv_proto *p, ip_addr peer_ip); /* packets.c */ int radv_process_domain(struct radv_dnssl_config *cf); -- 2.43.0
The dynamic BGP postponed socket handling was moved from bgp_init() to bgp_start_locked() to ensure both domains are properly locked during the rmove() operation. In bgp_init(), the protocol birdloop does not exist yet, but in bgp_start_locked() it is possible to safely enter both the socket original birdloop and the protocol birdloop to satisfy dual-lock requirement and avoid DG_IS_LOCKED failed assertions. Signed-off-by: Matteo Perin <matteo.perin@canonical.com> --- proto/bgp/bgp.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index d3d61eaf..ad6dd334 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -2648,6 +2648,22 @@ bgp_start_locked(void *_p) DBG("BGP: Got lock\n"); + /* Move postponed socket to protocol's birdloop if present */ + if (p->postponed_sk) + { + sock *sk = p->postponed_sk; + struct birdloop *sk_loop = sk->loop; + + if (sk_loop && (sk_loop != p->p.loop)) + birdloop_enter(sk_loop); + + rmove(sk, p->p.pool); + sk_reloop(sk, p->p.loop); + + if (sk_loop && (sk_loop != p->p.loop)) + birdloop_leave(sk_loop); + } + if (cf->multihop || bgp_is_dynamic(p)) { /* Multi-hop sessions do not use neighbor entries */ @@ -2770,15 +2786,6 @@ bgp_start(struct proto *P) channel_graceful_restart_lock(&c->c); } - /* Now it's the last chance to move the postponed socket to this BGP, - * as bgp_start is the only hook running from main loop. */ - if (p->postponed_sk) - BGP_LISTEN_LOCKED(bl) - { - rmove(p->postponed_sk, p->p.pool); - sk_reloop(p->postponed_sk, p->p.loop); - } - /* * Before attempting to create the connection, we need to lock the port, * so that we are the only instance attempting to talk with that neighbor. -- 2.43.0
This change proposal is made in order to achieve BGP automatic peering based on discovered link-local addresses leveraging the IPv6 ND protocol. A big part of the necessary logic is already present in the dynamic BGP implementation. The problem is that dynamic BGP works in a passive/reactive model only, as it waits for peers within a specified range to connect. This integrates with this implementation by triggering the spawn of a BGP process sending OPEN requests in order to achieve active peer connection when a valid peer remote address has been discovered. The discovery is done by exporting routing information from a table containing peer data through a newly introduced peers channel. This means that the feature will only work if there is another protocol actively importing peer data (e.g. RAdv). The feature is also meant to work only in the presence of a valid dynamic BGP configuration. The BGP peers channel has two additional configuration parameters: - from [iface]: determines the interface used for automatic BGP session establishment - persist [yes/no]: controls the behavior on peer route withdrawal. If enabled the BGP connection spawned from peer discovery will be left running, if disabled the session will be torn down. Logic to avoid duplicate sessions from both the dynamic BGP and automatic peers discovery paths has also been introduced. Signed-off-by: Matteo Perin <matteo.perin@canonical.com> --- proto/bgp/attrs.c | 57 ++++++++++++++++ proto/bgp/bgp.c | 162 +++++++++++++++++++++++++++++++++++++++++--- proto/bgp/bgp.h | 28 +++++++- proto/bgp/config.Y | 15 +++- proto/bgp/packets.c | 11 +++ 5 files changed, 262 insertions(+), 11 deletions(-) diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c index f072e4c9..dcec1c45 100644 --- a/proto/bgp/attrs.c +++ b/proto/bgp/attrs.c @@ -2468,6 +2468,63 @@ bgp_rt_notify(struct proto *P, struct channel *C, const net_addr *n, rte *new, c if (SHUTTING_DOWN) return; + /* Handle peers channel - spawn BGP sessions for discovered peers */ + if (C->net_type == NET_PEER) + { + /* If this is a spawned child, ignore all notifications from the peers channel */ + if (p->cf->c.parent) + return; + + const net_addr_peer *peer = (const net_addr_peer *) n; + + /* Only handle peer management for dynamic BGP instances and link-local addresses */ + if (bgp_is_dynamic(p) && ipa_is_link_local(peer->addr)) + { + if (new && !old) + { + /* New peer discovered - send spawn event to main loop */ + struct bgp_peer_spawn *ev = mb_alloc(p->p.pool, sizeof(struct bgp_peer_spawn)); + *ev = (struct bgp_peer_spawn) { + .p = p, + .peer_addr = peer->addr, + .iface = bc->cf->peers_iface, + }; + + /* Initialize callback to run on main_birdloop */ + callback_init(&ev->cb, bgp_peer_spawn, &main_birdloop); + + /* Send the event */ + callback_activate(&ev->cb); + + log(L_INFO "%s: Peer %I discovered, queued for BGP session spawn", + p->p.name, peer->addr); + } + else if (!new && old) + { + /* If persist is not enabled, stop spawned sessions for this peer */ + if (!bc->cf->peers_persist) + { + /* Send removal event to main loop */ + struct bgp_peer_remove *ev = mb_alloc(p->p.pool, sizeof(struct bgp_peer_remove)); + *ev = (struct bgp_peer_remove) { + .p = p, + .peer_addr = peer->addr, + }; + + /* Initialize callback to run on main_birdloop */ + callback_init(&ev->cb, bgp_peer_remove, &main_birdloop); + + /* Send the event */ + callback_activate(&ev->cb); + + log(L_INFO "%s: Peer %I withdrawn from channel, queued for BGP session removal", + p->p.name, peer->addr); + } + } + } + return; + } + /* Ignore non-BGP channels */ if (C->class != &channel_bgp) return; diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index ad6dd334..e17459c6 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -173,13 +173,6 @@ static void bgp_listen_close(struct bgp_proto *, struct bgp_listen_request *); static void bgp_graceful_restart_feed(struct bgp_channel *c); static void bgp_restart_route_refresh(void *_bc); -/* Dynamic BGP detection */ -#define bgp_is_dynamic(x) (_Generic((x), \ - struct bgp_proto *: ipa_zero((x)->remote_ip), \ - struct bgp_config *: ipa_zero((x)->remote_ip), \ - struct bgp_listen_request *: ipa_zero((x)->remote_ip))) - - /* * BGP Instance Management */ @@ -1213,12 +1206,63 @@ bgp_decision(void *vp) bgp_down(p); } +/** + * bgp_find_existing_session - check if a dynamic BGP session already exists for a peer + * @peer_addr: Remote peer IP address to check + * + * Checks if there's already an existing dynamic BGP session for the given peer + * by walking through all BGP protocols. + * + * Returns: pointer to the existing BGP protocol, or NULL if none found + */ +static struct bgp_proto * +bgp_find_existing_session(ip_addr peer_addr) +{ + struct config *cfg = OBSREF_GET(config); + if (!cfg) + return NULL; + + struct proto_config *pc; + WALK_LIST(pc, cfg->protos) + { + if (pc->protocol != &proto_bgp) + continue; + + if (pc->proto) + { + struct bgp_proto *child_p = (struct bgp_proto *) pc->proto; + + if (ipa_equal(child_p->remote_ip, peer_addr)) + { + log(L_DEBUG "BGP: Found existing session %s for peer %I", + child_p->p.name, peer_addr); + return child_p; + } + } + } + return NULL; +} + static void bgp_spawn(struct bgp_proto *pp, struct birdsock *sk) { struct symbol *sym; char fmt[SYM_MAX_LEN]; + /* Check if there's an existing session for this peer and shut it down. + * The dynamic BGP session (with the incoming socket) takes precedence. */ + struct bgp_proto *existing = bgp_find_existing_session(sk->daddr); + if (existing) + { + log(L_DEBUG "BGP: Found existing session %s for %I, shutting it down to use incoming connection", + existing->p.name, sk->daddr); + + /* Mark for deletion and disable */ + existing->p.cf_new = NULL; + existing->p.reconfiguring = 1; + proto_disable(&existing->p); + } + bsprintf(fmt, "%s%%0%dd", pp->cf->dynamic_name, pp->cf->dynamic_name_digits); /* This is hack, we would like to share config, but we need to copy it now */ @@ -1247,6 +1291,92 @@ bgp_spawn(struct bgp_proto *pp, struct birdsock *sk) proto_enable(&p->p); } +void +bgp_peer_spawn(struct callback *cb) +{ + struct bgp_peer_spawn *ev = (void *) cb; + + /* Check if a session for this peer already exists */ + if (bgp_find_existing_session(ev->peer_addr)) + { + log(L_DEBUG "BGP: Peer %I already has existing session, not spawning duplicate", ev->peer_addr); + return; + } + + struct symbol *sym; + char fmt[SYM_MAX_LEN]; + + log(L_DEBUG "BGP: Spawning new peer session for %I", ev->peer_addr); + bsprintf(fmt, "%s%%0%dd", ev->p->cf->dynamic_name, ev->p->cf->dynamic_name_digits); + + /* Clone the configuration */ + new_config = OBSREF_GET(config); + cfg_mem = new_config->mem; + new_config->current_scope = new_config->root_scope; + sym = cf_default_name(new_config, fmt, &(ev->p->dynamic_name_counter)); + proto_clone_config(sym, ev->p->p.cf); + new_config = NULL; + cfg_mem = NULL; + + /* Configure for active connection to discovered peer */ + struct bgp_config *cf = SKIP_BACK(struct bgp_config, c, sym->proto); + cf->remote_ip = ev->peer_addr; + cf->local_ip = ev->p->cf->local_ip; + cf->iface = ev->iface; + cf->ipatt = NULL; + cf->passive = 0; + + /* Create and enable the protocol */ + SKIP_BACK_DECLARE(struct bgp_proto, p, p, proto_spawn(sym->proto, 1)); + + proto_enable(&p->p); +} + +void +bgp_peer_remove(struct callback *cb) +{ + struct bgp_peer_remove *ev = (void *) cb; + + /* Find ALL BGP sessions for this peer and remove them */ + struct config *cfg = OBSREF_GET(config); + if (!cfg) + return; + + struct proto_config *pc; + WALK_LIST(pc, cfg->protos) + { + if (pc->protocol != &proto_bgp) + continue; + + /* Check protocol instance */ + if (pc->proto) + { + struct bgp_proto *bgp_p = (struct bgp_proto *) pc->proto; + + /* Found a BGP session with matching peer address */ + if (ipa_equal(bgp_p->remote_ip, ev->peer_addr)) + { + /* Skip parent protocol */ + if (bgp_is_dynamic(bgp_p)) + continue; + + log(L_INFO "%s: Removing BGP session %s for withdrawn peer %I", + ev->p->p.name, pc->name, ev->peer_addr); + + /* Mark protocol for deletion and disable it */ + bgp_p->p.cf_new = NULL; + bgp_p->p.reconfiguring = 1; + proto_disable(&bgp_p->p); + } + else + { + log(L_DEBUG "BGP: Session %s with remote %I does not match withdrawn peer %I", + pc->name, bgp_p->remote_ip, ev->peer_addr); + } + } + } +} + void bgp_stop(struct bgp_proto *p, int subcode, byte *data, uint len) { @@ -2664,6 +2794,18 @@ bgp_start_locked(void *_p) birdloop_leave(sk_loop); } + if (bgp_is_dynamic(p)) { + /* Start peers channels immediately for dynamic BGP */ + struct bgp_channel *c; + BGP_WALK_CHANNELS(p, c) + { + if (c->c.net_type == NET_PEER && !c->c.disabled) + { + channel_set_state(&c->c, CS_UP); + } + } + } + if (cf->multihop || bgp_is_dynamic(p)) { /* Multi-hop sessions do not use neighbor entries */ @@ -2974,6 +3116,10 @@ bgp_channel_init(struct channel *C, struct channel_config *CF) c->afi = cf->afi; c->desc = cf->desc; + /* Set peers channels to get announcement of any route change */ + if (C->net_type == NET_PEER && C->ra_mode == RA_UNDEF) + C->ra_mode = RA_ANY; + if (cf->igp_table_ip4) c->igp_table_ip4 = cf->igp_table_ip4->table; @@ -4090,7 +4236,7 @@ struct protocol proto_bgp = { .name = "BGP", .template = "bgp%d", .preference = DEF_PREF_BGP, - .channel_mask = NB_IP | NB_VPN | NB_FLOW | NB_MPLS, + .channel_mask = NB_IP | NB_VPN | NB_FLOW | NB_MPLS | NB_PEER, .proto_size = sizeof(struct bgp_proto), .config_size = sizeof(struct bgp_config), .postconfig = bgp_postconfig, diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index 8f76e7a5..e393a34e 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -26,6 +26,7 @@ struct eattr; #define BGP_AFI_IPV4 1 #define BGP_AFI_IPV6 2 +#define BGP_AFI_PEER 3 #define BGP_SAFI_UNICAST 1 #define BGP_SAFI_MULTICAST 2 @@ -52,7 +53,7 @@ struct eattr; #define BGP_AF_VPN6_MC BGP_AF( BGP_AFI_IPV6, BGP_SAFI_VPN_MULTICAST ) #define BGP_AF_FLOW4 BGP_AF( BGP_AFI_IPV4, BGP_SAFI_FLOW ) #define BGP_AF_FLOW6 BGP_AF( BGP_AFI_IPV6, BGP_SAFI_FLOW ) - +#define BGP_AF_PEER BGP_AF( BGP_AFI_PEER, BGP_SAFI_UNICAST ) struct bgp_write_state; struct bgp_parse_state; @@ -191,6 +192,8 @@ struct bgp_channel_config { u32 cost; /* IGP cost for direct next hops */ u8 import_table; /* Use c.in_table as Adj-RIB-In */ u8 export_table; /* Keep Adj-RIB-Out and export it */ + u8 peers_persist; /* Keep spawned BGP sessions after peer discovery route withdrawal */ + struct iface *peers_iface; /* Interface for peer discovery (for link-local addresses) */ struct settle_config ptx_exporter_settle; /* Settle timer for export dumps */ @@ -401,6 +404,23 @@ struct bgp_incoming_socket { sock *sk; /* The actual socket */ }; +struct bgp_peer_spawn { + callback cb; + struct bgp_proto *p; /* Parent protocol */ + ip_addr peer_addr; /* Peer address to spawn session for */ + struct iface *iface; /* Interface for link-local peers */ +}; + +struct bgp_peer_remove { + callback cb; + struct bgp_proto *p; /* Parent protocol */ + ip_addr peer_addr; /* Peer address to remove session for */ +}; + +/* Callback hooks (implemented in bgp.c) */ +void bgp_peer_spawn(struct callback *cb); +void bgp_peer_remove(struct callback *cb); + struct bgp_listen_request { node pn; /* Node in bgp_proto listen list */ node sn; /* Node in bgp_socket requests list */ @@ -978,5 +998,11 @@ enum bgp_attr_id { #define ORIGIN_EGP 1 #define ORIGIN_INCOMPLETE 2 +/* Dynamic BGP detection */ + +#define bgp_is_dynamic(x) (_Generic((x), \ + struct bgp_proto *: ipa_zero((x)->remote_ip), \ + struct bgp_config *: ipa_zero((x)->remote_ip), \ + struct bgp_listen_request *: ipa_zero((x)->remote_ip))) #endif diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index c7316d86..11ee90f1 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -33,9 +33,9 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE, STRICT, BIND, CONFEDERATION, MEMBER, MULTICAST, FLOW4, FLOW6, LONG, LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS, DYNAMIC, RANGE, NAME, DIGITS, AIGP, ORIGINATE, COST, ENFORCE, - FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER, + FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PEERS, PROVIDER, CUSTOMER, RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND, RECV, MIN, MAX, - TX, SIZE, WARNING, + TX, SIZE, WARNING, PERSIST, AUTHENTICATION, NONE, MD5, AO, FORMAT, NATIVE, SINGLE, DOUBLE) CF_KEYWORDS(KEY, KEYS, SECRET, DEPRECATED, PREFERRED, ALGORITHM, CMAC, AES128) @@ -270,6 +270,7 @@ bgp_afi: | VPN6 MULTICAST { $$ = BGP_AF_VPN6_MC; } | FLOW4 { $$ = BGP_AF_FLOW4; } | FLOW6 { $$ = BGP_AF_FLOW6; } + | PEERS { $$ = BGP_AF_PEER; } ; tcp_ao_key_start: KEY { @@ -444,6 +445,16 @@ bgp_channel_item: if (BGP_SAFI(BGP_CC->afi) != BGP_SAFI_FLOW) cf_error("Validate option limited to flowspec channels"); } + | PERSIST bool { + BGP_CC->peers_persist = $2; + if (BGP_CC->c.net_type != NET_PEER) + cf_error("Persist option only available for peers channels"); + } + | FROM text { + BGP_CC->peers_iface = if_get_by_name($2); + if (BGP_CC->c.net_type != NET_PEER) + cf_error("From option only available for peers channels"); + } | GRACEFUL RESTART bool { BGP_CC->gr_able = $3; } | LONG LIVED GRACEFUL RESTART bool { BGP_CC->llgr_able = $5; } | LONG LIVED STALE TIME expr { BGP_CC->llgr_time = $5; if ($5 >= (1 << 24)) cf_error("Long-lived stale time must be less than 2^24"); } diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c index bdbeabd1..87a5b777 100644 --- a/proto/bgp/packets.c +++ b/proto/bgp/packets.c @@ -2342,6 +2342,17 @@ static const struct bgp_af_desc bgp_af_table[] = { .decode_next_hop = bgp_decode_next_hop_none, .update_next_hop = bgp_update_next_hop_none, }, + { + .afi = BGP_AF_PEER, + .net = NET_PEER, + .no_igp = 1, + .name = "peers", + .encode_nlri = NULL, + .decode_nlri = NULL, + .encode_next_hop = NULL, + .decode_next_hop = NULL, + .update_next_hop = NULL, + }, }; const struct bgp_af_desc * -- 2.43.0
On Fri, Dec 05, 2025 at 02:52:28PM +0100, Matteo Perin via Bird-users wrote:
Dear BIRD dev-team and community,
This is a patch proposal spawned from the discussion in a previous mailing list thread [0], regarding how to achieve automatic BGP peering establishment using the IPv6 Neighbor Discovery mechanism.
Over the last few weeks, I worked on implementing the feature with the outline Maria Matejka kindly provided. As expected, it proved to be quite challenging, especially when it came to integrate the peer discovery approach with the current dynamic BGP implementation.
Hi Thanks for the patches. From cursory reading these patches look good. I have several comments: - I think that peer net_addr schould contain interface (in the form of interface id). That would eliminate need for 'from' option in BGP and also simplify config in case of link-local neighbors on multiple interfaces. Alternatively, one could consider sending interface as a route attribute (like in next hop attribute for regular IP routes) but i think in this case having it as a part of net_addr would make more sense. - peer channel in BGP should be regular struct channel (like MPLS channel) and not struct bgp_channel. This is for channels that are also AFI/SAFI on the BGP session. 'from' option is not necessary as above, 'persist' option could be BGP option. - It seems to me that option 'neighbor discovery' should be called more like 'router discovery' to avoid confusion with detecting regular IP neighbors by listening to Neighbor Discovery messages. - I would consider to set pxlen for peer_net_addr as 0 in the secne that the field is unused. -- Elen sila lumenn' omentielvo Ondrej 'Santiago' Zajicek (email: santiago@crfreenet.org) "To err is human -- to blame it on a computer is even more so."
The v2 of the patch changes the following: - The peer route type now contains information on the interface on which the peer was discovered. - The change above allowed to remove the need to configure the "from" option on the BGP peers channel, so it was removed. - pxlen of net_add_peer has been set to 0, since the field is unused. - RAdv neighbor discovery option has been renamed to router discovery. - The peers channel in BGP has been made a regular channel (instead of a bgp_channel). - The new BGP channel persist option has been made a protocol-wide option and it now also dictates the behavior of spawed dynamic BGP sessions. - Documentation on changes on both RAdv and BGP has been added. - Added testcase for the bird-tools testing suite. Thank you Ondrej for the review, your insights (especially the advice on changing the channel type) allowed me to refactor how the implementation works a bit and make it tidier (I think). My response was delayed by a few day so I could also provide some added documentation work and finish implementing automated testing for the bird-tools project. Let me know if you have any other suggestion on changes needed. Best Regards, Matteo Matteo Perin (4): Nest: Add net_peer route type to track discovered peers RAdv: Add neighbor discovery based on incoming RAs to RAdv proto BGP: Move postponed socket reloop logic from start to start_locked to achieve locking requirements BGP: Add peers channel and sessions spawn on export to achieve automatic peering doc/bird.sgml | 65 +++++++++++++ lib/net.c | 16 ++++ lib/net.h | 32 ++++++- nest/config.Y | 3 +- nest/proto.c | 2 +- nest/protocol.h | 3 + proto/bgp/attrs.c | 51 ++++++++++ proto/bgp/bgp.c | 216 +++++++++++++++++++++++++++++++++++++++---- proto/bgp/bgp.h | 27 +++++- proto/bgp/config.Y | 12 ++- proto/bgp/packets.c | 11 +++ proto/radv/config.Y | 3 +- proto/radv/packets.c | 41 +++++++- proto/radv/radv.c | 165 ++++++++++++++++++++++++++++++++- proto/radv/radv.h | 4 + 15 files changed, 622 insertions(+), 29 deletions(-) -- 2.43.0
The definition and helper functions for a new route-like object to track peers discovery data has been added. It only contains the (v4 or v6) remote peer address and the ingress iface index, for now. The main intent of this is, currently, to enable BGP unnumbered auto peer discovery via RAdv incoming advertisments, but in the future the same data structure could be used to allow discovery coming from different protocols. Signed-off-by: Matteo Perin <matteo.perin@canonical.com> --- lib/net.c | 16 ++++++++++++++++ lib/net.h | 32 +++++++++++++++++++++++++++++++- nest/config.Y | 3 ++- nest/proto.c | 2 +- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/lib/net.c b/lib/net.c index 64cf9e04..b5dee235 100644 --- a/lib/net.c +++ b/lib/net.c @@ -17,6 +17,7 @@ const char * const net_label[] = { [NET_IP6_SADR]= "ipv6-sadr", [NET_MPLS] = "mpls", [NET_ASPA] = "aspa", + [NET_PEER] = "peer", }; const u16 net_addr_length[] = { @@ -31,6 +32,7 @@ const u16 net_addr_length[] = { [NET_IP6_SADR]= sizeof(net_addr_ip6_sadr), [NET_MPLS] = sizeof(net_addr_mpls), [NET_ASPA] = sizeof(net_addr_aspa), + [NET_PEER] = sizeof(net_addr_peer), }; const u8 net_max_prefix_length[] = { @@ -45,6 +47,7 @@ const u8 net_max_prefix_length[] = { [NET_IP6_SADR]= IP6_MAX_PREFIX_LENGTH, [NET_MPLS] = 0, [NET_ASPA] = 0, + [NET_PEER] = IP6_MAX_PREFIX_LENGTH, }; const u16 net_max_text_length[] = { @@ -59,6 +62,7 @@ const u16 net_max_text_length[] = { [NET_IP6_SADR]= 92, /* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128 from ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" */ [NET_MPLS] = 7, /* "1048575" */ [NET_ASPA] = 10, /* "4294967295" */ + [NET_PEER] = 43, /* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" */ }; /* There should be no implicit padding in net_addr structures */ @@ -74,6 +78,7 @@ STATIC_ASSERT(sizeof(net_addr_flow6) == 20); STATIC_ASSERT(sizeof(net_addr_ip6_sadr) == 40); STATIC_ASSERT(sizeof(net_addr_mpls) == 8); STATIC_ASSERT(sizeof(net_addr_aspa) == 8); +STATIC_ASSERT(sizeof(net_addr_peer) == 24); /* Ensure that all net_addr structures have the same alignment */ STATIC_ASSERT(alignof(net_addr_ip4) == alignof(net_addr)); @@ -89,6 +94,7 @@ STATIC_ASSERT(alignof(net_addr_flow6) == alignof(net_addr)); STATIC_ASSERT(alignof(net_addr_ip6_sadr) == alignof(net_addr)); STATIC_ASSERT(alignof(net_addr_mpls) == alignof(net_addr)); STATIC_ASSERT(alignof(net_addr_aspa) == alignof(net_addr)); +STATIC_ASSERT(alignof(net_addr_peer) == alignof(net_addr)); int @@ -147,6 +153,8 @@ net_format(const net_addr *N, char *buf, int buflen) return bsnprintf(buf, buflen, "%u", n->mpls.label); case NET_ASPA: return bsnprintf(buf, buflen, "%u", n->aspa.asn); + case NET_PEER: + return bsnprintf(buf, buflen, "%I from iface %u", n->peer.addr, n->peer.ifindex); } bug("unknown network type"); @@ -172,6 +180,7 @@ net_pxmask(const net_addr *a) case NET_MPLS: case NET_ASPA: + case NET_PEER: default: return IPA_NONE; } @@ -207,6 +216,8 @@ net_compare(const net_addr *a, const net_addr *b) return net_compare_mpls((const net_addr_mpls *) a, (const net_addr_mpls *) b); case NET_ASPA: return net_compare_aspa((const net_addr_aspa *) a, (const net_addr_aspa *) b); + case NET_PEER: + return net_compare_peer((const net_addr_peer *) a, (const net_addr_peer *) b); } return 0; } @@ -229,6 +240,7 @@ net_hash(const net_addr *n) case NET_IP6_SADR: return NET_HASH(n, ip6_sadr); case NET_MPLS: return NET_HASH(n, mpls); case NET_ASPA: return NET_HASH(n, aspa); + case NET_PEER: return NET_HASH(n, peer); default: bug("invalid type"); } } @@ -252,6 +264,7 @@ net_validate(const net_addr *n) case NET_IP6_SADR: return NET_VALIDATE(n, ip6_sadr); case NET_MPLS: return NET_VALIDATE(n, mpls); case NET_ASPA: return NET_VALIDATE(n, aspa); + case NET_PEER: return NET_VALIDATE(n, peer); default: return 0; } } @@ -280,6 +293,7 @@ net_normalize(net_addr *N) case NET_MPLS: case NET_ASPA: + case NET_PEER: return; } } @@ -308,6 +322,7 @@ net_classify(const net_addr *N) case NET_MPLS: case NET_ASPA: + case NET_PEER: return IADDR_HOST | SCOPE_UNIVERSE; } @@ -342,6 +357,7 @@ ipa_in_netX(const ip_addr a, const net_addr *n) case NET_MPLS: case NET_ASPA: + case NET_PEER: default: return 0; } diff --git a/lib/net.h b/lib/net.h index 24aae87d..ed08f6dc 100644 --- a/lib/net.h +++ b/lib/net.h @@ -24,7 +24,8 @@ #define NET_IP6_SADR 9 #define NET_MPLS 10 #define NET_ASPA 11 -#define NET_MAX 12 +#define NET_PEER 12 +#define NET_MAX 13 #define NB_IP4 (1 << NET_IP4) #define NB_IP6 (1 << NET_IP6) @@ -37,6 +38,7 @@ #define NB_IP6_SADR (1 << NET_IP6_SADR) #define NB_MPLS (1 << NET_MPLS) #define NB_ASPA (1 << NET_ASPA) +#define NB_PEER (1 << NET_PEER) #define NB_IP (NB_IP4 | NB_IP6) #define NB_VPN (NB_VPN4 | NB_VPN6) @@ -133,6 +135,14 @@ typedef struct net_addr_aspa { u32 asn; } net_addr_aspa; +typedef struct net_addr_peer { + u8 type; + u8 pxlen; + u16 length; + ip_addr addr; /* Peer IP address (IPv6 or IPv4) */ + u32 ifindex; /* Interface index */ +} net_addr_peer; + typedef struct net_addr_ip6_sadr { u8 type; u8 dst_pxlen; @@ -155,6 +165,7 @@ typedef union net_addr_union { net_addr_ip6_sadr ip6_sadr; net_addr_mpls mpls; net_addr_aspa aspa; + net_addr_peer peer; } net_addr_union; @@ -182,6 +193,7 @@ extern const u16 net_max_text_length[]; #define NET_PTR_FLOW6(_n) NET_PTR_GEN((_n), NET_FLOW6, flow6) #define NET_PTR_IP6_SADR(_n) NET_PTR_GEN((_n), NET_IP6_SADR, ip6_sadr) #define NET_PTR_MPLS(_n) NET_PTR_GEN((_n), NET_MPLS, mpls) +#define NET_PTR_PEER(_n) NET_PTR_GEN((_n), NET_PEER, peer) #define NET_ADDR_IP4(prefix,pxlen) \ @@ -217,6 +229,9 @@ extern const u16 net_max_text_length[]; #define NET_ADDR_MPLS(label) \ ((net_addr_mpls) { NET_MPLS, 20, sizeof(net_addr_mpls), label }) +#define NET_ADDR_PEER(addr, ifindex) \ + ((net_addr_peer) { NET_PEER, 0, sizeof(net_addr_peer), addr, ifindex }) + static inline void net_fill_ip4(net_addr *a, ip4_addr prefix, uint pxlen) { *(net_addr_ip4 *)a = NET_ADDR_IP4(prefix, pxlen); } @@ -245,6 +260,9 @@ static inline void net_fill_mpls(net_addr *a, u32 label) static inline void net_fill_aspa(net_addr *a, u32 asn) { *(net_addr_aspa *)a = NET_ADDR_ASPA(asn); } +static inline void net_fill_peer(net_addr *a, ip_addr addr, u32 ifindex) +{ *(net_addr_peer *)a = NET_ADDR_PEER(addr, ifindex); } + static inline void net_fill_ipa(net_addr *a, ip_addr prefix, uint pxlen) { if (ipa_is_ip4(prefix)) @@ -489,6 +507,9 @@ static inline int net_compare_mpls(const net_addr_mpls *a, const net_addr_mpls * static inline int net_compare_aspa(const net_addr_aspa *a, const net_addr_aspa *b) { return uint_cmp(a->asn, b->asn); } +static inline int net_compare_peer(const net_addr_peer *a, const net_addr_peer *b) +{ return ipa_compare(a->addr, b->addr) ?: uint_cmp(a->ifindex, b->ifindex); } + int net_compare(const net_addr *a, const net_addr *b); @@ -528,6 +549,9 @@ static inline void net_copy_mpls(net_addr_mpls *dst, const net_addr_mpls *src) static inline void net_copy_aspa(net_addr_aspa *dst, const net_addr_aspa *src) { memcpy(dst, src, sizeof(net_addr_aspa)); } +static inline void net_copy_peer(net_addr_peer *dst, const net_addr_peer *src) +{ memcpy(dst, src, sizeof(net_addr_peer)); } + static inline u32 px4_hash(ip4_addr prefix, u32 pxlen) { return ip4_hash(prefix) ^ (pxlen << 26); } @@ -574,6 +598,9 @@ static inline u32 net_hash_mpls(const net_addr_mpls *n) static inline u32 net_hash_aspa(const net_addr_aspa *n) { return u32_hash(n->asn); } +static inline u32 net_hash_peer(const net_addr_peer *n) +{ return ipa_hash(n->addr) ^ u32_hash(n->ifindex); } + u32 net_hash(const net_addr *a); @@ -626,6 +653,9 @@ static inline int net_validate_mpls(const net_addr_mpls *n) static inline int net_validate_aspa(const net_addr_aspa *n) { return n->asn > 0; } +static inline int net_validate_peer(const net_addr_peer *n) +{ return ipa_nonzero(n->addr); } + static inline int net_validate_ip6_sadr(const net_addr_ip6_sadr *n) { return net_validate_px6(n->dst_prefix, n->dst_pxlen) && net_validate_px6(n->src_prefix, n->src_pxlen); } diff --git a/nest/config.Y b/nest/config.Y index 681d0edd..f12103d9 100644 --- a/nest/config.Y +++ b/nest/config.Y @@ -153,7 +153,7 @@ CF_DECLS CF_KEYWORDS(ROUTER, ID, HOSTNAME, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT, PIPE) CF_KEYWORDS(INTERFACE, IMPORT, EXPORT, FILTER, NONE, VRF, DEFAULT, TABLE, TABLES, STATES, ROUTES, FILTERS) -CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS, ASPA) +CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS, ASPA, PEER) CF_KEYWORDS(RECEIVE, LIMIT, ACTION, WARN, BLOCK, RESTART, DISABLE, KEEP, FILTERED, RPKI) CF_KEYWORDS(PASSWORD, KEY, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, CHANNELS, INTERFACES) CF_KEYWORDS(ALGORITHM, KEYED, HMAC, MD5, SHA1, SHA256, SHA384, SHA512, BLAKE2S128, BLAKE2S256, BLAKE2B256, BLAKE2B512) @@ -231,6 +231,7 @@ net_type_base: | FLOW4{ $$ = NET_FLOW4; } | FLOW6{ $$ = NET_FLOW6; } | ASPA { $$ = NET_ASPA; } + | PEER { $$ = NET_PEER; } ; net_type: diff --git a/nest/proto.c b/nest/proto.c index d6620065..e8d0021f 100644 --- a/nest/proto.c +++ b/nest/proto.c @@ -1168,7 +1168,7 @@ channel_config_new(const struct channel_class *cc, const char *name, uint net_ty if (!net_val_match(net_type, proto->protocol->channel_mask)) cf_error("Unsupported channel type"); - if (proto->net_type && (net_type != proto->net_type) && (net_type != NET_MPLS)) + if (proto->net_type && (net_type != proto->net_type) && (net_type != NET_MPLS) && (net_type != NET_PEER)) cf_error("Different channel type"); tab = rt_get_default_table(new_config, net_type); -- 2.43.0
Implementation of peer discovery based on incoming Router Advertisments to the RAdv protocol. Up until this point no much use has been make of incoming RAs, this commit tries to amend that by importing peer-based routes to a new peers channel. This will allow to use the information discovered about remote routers on other protocols which can export peers on this same channel. RA staleness has also been taken into consideration and the routes are withdrawn whenever the advertised router lifetime expires. The feature is meant to be enabled via a new configuration option in the RAdv protocol called router discovery [yes/no]. Signed-off-by: Matteo Perin <matteo.perin@canonical.com> --- doc/bird.sgml | 11 +++ proto/radv/config.Y | 3 +- proto/radv/packets.c | 41 ++++++++++- proto/radv/radv.c | 165 ++++++++++++++++++++++++++++++++++++++++++- proto/radv/radv.h | 4 ++ 5 files changed, 219 insertions(+), 5 deletions(-) diff --git a/doc/bird.sgml b/doc/bird.sgml index b0ac094a..b0256b73 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -5841,6 +5841,17 @@ custom option type 38 value hex:0e:10:20:01:0d:b8:00:0a:00:0b:00:00:00:00; <tag><label id="radv-iface-custom-local">custom option local <m/switch/</tag> Use only local custom option definitions for this interface. See <cf/rdnss local/ option above. Default: no. + + <tag><label id="radv-router-discovery">router discovery <m/switch/</tag> + Enable reception and processing of incoming ICMPv6 Router Advertisement + messages. When enabled, the RAdv protocol listens for RA messages on + configured interfaces and creates route-like objects in a peers routing + table based on detected advertisements. These objects contain information + about discovered routers and their lifetimes are managed by the Router + Lifetime field in the RA messages. When an RA expires, the corresponding + object is automatically removed from the peers table. Currently, this feature + is primarily used in conjunction with BGP unnumbered automatic peering to + automatically discover BGP neighbors. Default: no. </descrip> <p>Prefix specific options diff --git a/proto/radv/config.Y b/proto/radv/config.Y index 372081d2..758370af 100644 --- a/proto/radv/config.Y +++ b/proto/radv/config.Y @@ -42,7 +42,7 @@ CF_KEYWORDS(RADV, PREFIX, INTERFACE, MIN, MAX, RA, DELAY, INTERVAL, SOLICITED, RETRANS, TIMER, CURRENT, HOP, LIMIT, DEFAULT, VALID, PREFERRED, MULT, LIFETIME, SKIP, ONLINK, AUTONOMOUS, RDNSS, DNSSL, NS, DOMAIN, LOCAL, TRIGGER, SENSITIVE, PREFERENCE, LOW, MEDIUM, HIGH, PROPAGATE, ROUTE, - ROUTES, CUSTOM, OPTION, TYPE, VALUE, PD) + ROUTES, CUSTOM, OPTION, TYPE, VALUE, PD, ROUTER, DISCOVERY) CF_ENUM(T_ENUM_RA_PREFERENCE, RA_PREF_, LOW, MEDIUM, HIGH) @@ -140,6 +140,7 @@ radv_iface_item: | RDNSS LOCAL bool { RADV_IFACE->rdnss_local = $3; } | DNSSL LOCAL bool { RADV_IFACE->dnssl_local = $3; } | CUSTOM OPTION LOCAL bool { RADV_IFACE->custom_local = $4; } + | ROUTER DISCOVERY bool { RADV_IFACE->router_discovery = $3; } ; radv_preference: diff --git a/proto/radv/packets.c b/proto/radv/packets.c index 40f3c41e..96da232b 100644 --- a/proto/radv/packets.c +++ b/proto/radv/packets.c @@ -460,6 +460,44 @@ radv_receive_rs(struct radv_proto *p, struct radv_iface *ifa, ip_addr from) radv_iface_notify(ifa, RA_EV_RS); } +void +radv_process_ra(struct radv_iface *ifa, ip_addr from, struct radv_ra_packet *pkt, int length) +{ + struct radv_proto *p = ifa->ra; + + if (!ifa->cf->router_discovery) + return; + + /* Basic validation */ + if ((uint)length < sizeof(struct radv_ra_packet)) + { + RADV_TRACE(D_PACKETS, "Malformed RA received from %I via %s", + from, ifa->iface->name); + return; + } + + u16 router_lifetime = ntohs(pkt->router_lifetime); + u32 ifindex = ifa->iface->index; + + /* Router lifetime of 0 means withdrawal */ + if (router_lifetime == 0) + { + RADV_TRACE(D_EVENTS, "Neighbor %I on %s (ifindex=%u) withdrawing (lifetime=0)", + from, ifa->iface->name, ifindex); + /* Withdraw from routing table if peers channel is configured */ + radv_withdraw_peer(p, from, ifindex); + + return; + } + + /* Announce/update the peer in routing table */ + RADV_TRACE(D_PACKETS, "Processed RA from %I on %s (ifindex=%u): lifetime=%u, hop_limit=%u", + from, ifa->iface->name, ifindex, router_lifetime, pkt->current_hop_limit); + + /* Announce to routing table with router lifetime for expiration tracking and interface index */ + radv_announce_peer(p, from, router_lifetime, ifindex); +} + static int radv_rx_hook(sock *sk, uint size) { @@ -493,7 +531,8 @@ radv_rx_hook(sock *sk, uint size) case ICMPV6_RA: RADV_TRACE(D_PACKETS, "Received RA from %I via %s", sk->faddr, ifa->iface->name); - /* FIXME - there should be some checking of received RAs, but we just ignore them */ + if (ifa->cf->router_discovery) + radv_process_ra(ifa, sk->faddr, (struct radv_ra_packet *)buf, size); return 1; default: diff --git a/proto/radv/radv.c b/proto/radv/radv.c index 8ae411da..58260c6d 100644 --- a/proto/radv/radv.c +++ b/proto/radv/radv.c @@ -43,10 +43,60 @@ * RFC 6106 - DNS extensions (RDDNS, DNSSL) */ -static struct ea_class ea_radv_preference, ea_radv_lifetime; +static struct ea_class ea_radv_preference, ea_radv_lifetime, ea_radv_expires_at; static void radv_prune_prefixes(struct radv_iface *ifa); static void radv_prune_routes(struct radv_proto *p); +static void radv_neighbor_prune(struct radv_iface *ifa); + +void +radv_announce_peer(struct radv_proto *p, ip_addr peer_ip, u16 router_lifetime, u32 ifindex) +{ + if (!p->peers_channel) + return; + + /* Check if channel is ready */ + if (p->peers_channel->channel_state != CS_UP) { + RADV_TRACE(D_EVENTS, "Peers channel not UP yet (state=%d), skipping peer announcement", + p->peers_channel->channel_state); + return; + } + + /* Compute expiration time */ + btime now = current_time(); + btime expires_at = now + (router_lifetime S); + + net_addr_peer n; + net_fill_peer((net_addr *) &n, peer_ip, ifindex); + + ea_list *ea = NULL; + ea_set_attr_u32(&ea, &ea_gen_preference, 0, p->peers_channel->preference); + ea_set_attr_u32(&ea, &ea_gen_source, 0, RTS_DEVICE); + ea_set_attr_u32(&ea, &ea_radv_expires_at, 0, expires_at / 1000000); + + rte e0 = { + .attrs = ea, + .src = p->p.main_source, + }; + + RADV_TRACE(D_EVENTS, "Announcing peer to channel: peer_ip: %I, ifindex=%u, lifetime=%u, expires_at=%T", + peer_ip, ifindex, router_lifetime, expires_at); + + rte_update(p->peers_channel, (net_addr *) &n, &e0, p->p.main_source); +} + +void +radv_withdraw_peer(struct radv_proto *p, ip_addr peer_ip, u32 ifindex) +{ + if (!p->peers_channel) + return; + + net_addr_peer n; + net_fill_peer((net_addr *) &n, peer_ip, ifindex); + + /* Withdraw the route */ + rte_update(p->peers_channel, (net_addr *) &n, NULL, p->p.main_source); +} static void radv_timer(timer *tm) @@ -63,6 +113,9 @@ radv_timer(timer *tm) if (p->prune_time <= now) radv_prune_routes(p); + /* Prune stale discovered neighbor routers entries */ + radv_neighbor_prune(ifa); + radv_send_ra(ifa, IPA_NONE); /* Update timer */ @@ -212,6 +265,86 @@ radv_prune_prefixes(struct radv_iface *ifa) ifa->prune_time = next; } +void +radv_neighbor_prune(struct radv_iface *ifa) +{ + struct radv_proto *p = ifa->ra; + + /* Skip pruning check if router discovery is not enabled */ + if (!ifa->cf->router_discovery) + return; + + /* Skip if no peers channel configured */ + if (!p->peers_channel || p->peers_channel->channel_state != CS_UP) + return; + + /* Check for expired peers by walking the routing table */ + btime now = current_time(); + + /* Temporary list to store expired peers (can't withdraw during export walk) */ + struct expired_peer { + struct expired_peer *next; + ip_addr peer_ip; + u32 ifindex; + }; + struct expired_peer *expired_list = NULL; + + /* Walk all routes in the peers channel to check for expired entries */ + RT_EXPORT_WALK(&p->peers_channel->out_req, u) + { + switch (u->kind) + { + case RT_EXPORT_FEED: + /* Check each route in the feed */ + for (uint i = 0; i < u->feed->count_routes; i++) + { + rte *e = &u->feed->block[i]; + if (e->flags & REF_OBSOLETE) + continue; + + /* Only process routes from our protocol */ + if (e->src != p->p.main_source) + continue; + + /* Get the expiration time EA */ + eattr *expires_ea = ea_find(e->attrs, &ea_radv_expires_at); + if (!expires_ea) + continue; + + btime expires_at = expires_ea->u.data * 1000000; + + if (expires_at <= now) + { + /* Peer has expired, add to withdrawal list */ + net_addr_peer *peer_addr = (net_addr_peer *) e->net; + + struct expired_peer *ep = mb_alloc(p->p.pool, sizeof(struct expired_peer)); + ep->peer_ip = peer_addr->addr; + ep->ifindex = peer_addr->ifindex; + ep->next = expired_list; + expired_list = ep; + + RADV_TRACE(D_EVENTS, "Peer %I (ifindex=%u) expired (expires_at=%T, now=%T)", + peer_addr->addr, peer_addr->ifindex, expires_at, now); + } + } + break; + default: + /* Nothing to do for RT_EXPORT_UPDATE or RT_EXPORT_STOP */ + break; + } + } + + /* Withdraw all expired peers */ + while (expired_list) + { + struct expired_peer *ep = expired_list; + radv_withdraw_peer(p, ep->peer_ip, ep->ifindex); + expired_list = ep->next; + mb_free(ep); + } +} + static char* ev_name[] = { NULL, "Init", "Change", "RS" }; void @@ -581,6 +714,12 @@ radv_init(struct proto_config *CF) P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF)); + /* Add all other configured channels (e.g., peer channel) */ + struct channel_config *cc; + WALK_LIST(cc, CF->channels) + if (cc != proto_cf_main_channel(CF)) + proto_add_channel(P, cc); + P->preexport = radv_preexport; P->rt_notify = radv_rt_notify; P->iface_sub.if_notify = radv_if_notify; @@ -619,6 +758,19 @@ radv_start(struct proto *P) radv_set_fib(p, cf->propagate_routes); p->prune_time = TIME_INFINITY; + /* Find the peers channel if configured */ + p->peers_channel = NULL; + struct channel *c; + WALK_LIST(c, P->channels) + { + if (c->net_type == NET_PEER) + { + p->peers_channel = c; + RADV_TRACE(D_EVENTS, "Found peers discovery channel: %s", c->name); + break; + } + } + return PS_UP; } @@ -765,10 +917,16 @@ static struct ea_class ea_radv_lifetime = { .type = T_INT, }; +static struct ea_class ea_radv_expires_at = { + .name = "radv_expires_at", + .legacy_name = "RAdv.expires_at", + .type = T_INT, +}; + struct protocol proto_radv = { .name = "RAdv", .template = "radv%d", - .channel_mask = NB_IP6, + .channel_mask = NB_IP6 | NB_PEER, .proto_size = sizeof(struct radv_proto), .config_size = sizeof(struct radv_config), .postconfig = radv_postconfig, @@ -787,6 +945,7 @@ radv_build(void) EA_REGISTER_ALL( &ea_radv_preference, - &ea_radv_lifetime + &ea_radv_lifetime, + &ea_radv_expires_at ); } diff --git a/proto/radv/radv.h b/proto/radv/radv.h index 1a4afc55..4757d164 100644 --- a/proto/radv/radv.h +++ b/proto/radv/radv.h @@ -91,6 +91,7 @@ struct radv_iface_config u8 route_lifetime_sensitive; /* Whether route_lifetime depends on trigger */ u8 default_preference; /* Default Router Preference (RFC 4191) */ u8 route_preference; /* Specific Route Preference (RFC 4191) */ + u8 router_discovery; /* Enable neighbor router discovery */ }; struct radv_prefix_config @@ -161,6 +162,7 @@ struct radv_proto u8 fib_up; /* FIB table (routes) is initialized */ struct fib routes; /* FIB table of specific routes (struct radv_route) */ btime prune_time; /* Next time of route table pruning */ + struct channel *peers_channel; /* Channel for peer discovery (NET_PEER) */ }; struct radv_prefix /* One prefix we advertise */ @@ -221,6 +223,8 @@ static inline void radv_invalidate(struct radv_iface *ifa) /* radv.c */ void radv_iface_notify(struct radv_iface *ifa, int event); +void radv_announce_peer(struct radv_proto *p, ip_addr peer_ip, u16 router_lifetime, u32 ifindex); +void radv_withdraw_peer(struct radv_proto *p, ip_addr peer_ip, u32 ifindex); /* packets.c */ int radv_process_domain(struct radv_dnssl_config *cf); -- 2.43.0
The dynamic BGP postponed socket handling was moved from bgp_init() to bgp_start_locked() to ensure both domains are properly locked during the rmove() operation. In bgp_init(), the protocol birdloop does not exist yet, but in bgp_start_locked() it is possible to safely enter both the socket original birdloop and the protocol birdloop to satisfy dual-lock requirement and avoid DG_IS_LOCKED failed assertions. Signed-off-by: Matteo Perin <matteo.perin@canonical.com> --- proto/bgp/bgp.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index d3d61eaf..ad6dd334 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -2648,6 +2648,22 @@ bgp_start_locked(void *_p) DBG("BGP: Got lock\n"); + /* Move postponed socket to protocol's birdloop if present */ + if (p->postponed_sk) + { + sock *sk = p->postponed_sk; + struct birdloop *sk_loop = sk->loop; + + if (sk_loop && (sk_loop != p->p.loop)) + birdloop_enter(sk_loop); + + rmove(sk, p->p.pool); + sk_reloop(sk, p->p.loop); + + if (sk_loop && (sk_loop != p->p.loop)) + birdloop_leave(sk_loop); + } + if (cf->multihop || bgp_is_dynamic(p)) { /* Multi-hop sessions do not use neighbor entries */ @@ -2770,15 +2786,6 @@ bgp_start(struct proto *P) channel_graceful_restart_lock(&c->c); } - /* Now it's the last chance to move the postponed socket to this BGP, - * as bgp_start is the only hook running from main loop. */ - if (p->postponed_sk) - BGP_LISTEN_LOCKED(bl) - { - rmove(p->postponed_sk, p->p.pool); - sk_reloop(p->postponed_sk, p->p.loop); - } - /* * Before attempting to create the connection, we need to lock the port, * so that we are the only instance attempting to talk with that neighbor. -- 2.43.0
This change proposal is made in order to achieve BGP automatic peering based on discovered link-local addresses leveraging the IPv6 ND protocol. A big part of the necessary logic is already present in the dynamic BGP implementation. The problem is that dynamic BGP works in a passive/reactive model only, as it waits for peers within a specified range to connect. This integrates with this implementation by triggering the spawn of a BGP process sending OPEN requests in order to achieve active peer connection when a valid peer remote address has been discovered. The discovery is done by exporting routing information from a table containing peer data through a newly introduced peers channel. This means that the feature will only work if there is another protocol actively importing peer data (e.g. RAdv). The feature is also meant to work only in the presence of a valid dynamic BGP configuration. A new configuration paramenter has been added to the BGP protocol, persist [yes/no] which controls the behavior of sessions shutdown on peer route withdrawal. If enabled, a BGP connection spawned from peer discovery or dyamic BGP will be left running. If disabled, the session will be torn down and removed. Logic to avoid duplicate sessions from both the dynamic BGP and automatic peers discovery paths has also been introduced. Signed-off-by: Matteo Perin <matteo.perin@canonical.com> --- doc/bird.sgml | 54 +++++++++++++ nest/protocol.h | 3 + proto/bgp/attrs.c | 51 ++++++++++++ proto/bgp/bgp.c | 191 ++++++++++++++++++++++++++++++++++++++++++-- proto/bgp/bgp.h | 27 ++++++- proto/bgp/config.Y | 12 ++- proto/bgp/packets.c | 11 +++ 7 files changed, 337 insertions(+), 12 deletions(-) diff --git a/doc/bird.sgml b/doc/bird.sgml index b0256b73..4e8ab1a6 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -3807,6 +3807,7 @@ together with their appropriate channels follows. @ <cf/vpn6 multicast/ | <cf/vpn6/ | <cf/ipv4/ and <cf/ipv6/ | 2 | 129 @ <cf/flow4/ | <cf/flow4/ | --- | 1 | 133 @ <cf/flow6/ | <cf/flow6/ | --- | 2 | 133 +@ <cf/peers/ | <cf/peer/ | --- | --- | --- </tabular> </table> @@ -3828,6 +3829,17 @@ policies of <cf/import all/ and <cf/export none/ are used in absence of explicit configuration). Note that blanket policies like <cf/all/ or <cf/none/ can still be used in explicit configuration. +<p>The <cf/peers/ channel is a special channel type used for BGP unnumbered +automatic peering. Unlike traditional AFI/SAFI channels that exchange routing +information, the peers channel is used to dynamically trigger the creation of +new BGP sessions. When present in a dynamic BGP configuration, exporting +peer discovery objects (typically from RAdv with router discovery enabled) to +this channel will automatically spawn new BGP sessions for each discovered peer. +The lifetime of these automatic sessions is tied to the presence of the peer +objects in the peers routing table, withdrawing an object will trigger the +corresponding BGP session shutdown. This channel is only allowed in dynamic BGP +instances (those configured with <cf/neighbor range/). + <p>BGP channels have additional config options (together with the common ones): <descrip> @@ -4072,6 +4084,12 @@ be used in explicit configuration. set <ref id="bgp-max-long-lived-stale-time" name="max long lived stale time"> per AFI/SAFI pair instead of per protocol. Default: set by protocol-wide option. + + <tag><label id="bgp-peers-persist">peers persist <m/switch/</tag> + This option controls whether automatically spawned BGP sessions should be permanent. + When enabled, a BGP session that was initially created automatically through the peers + discovery or the dynamic BGP mechanisms will continue to exist even after it has been + taken down. Default: on. </descrip> <sect1>Reconfiguration @@ -4338,6 +4356,42 @@ protocol bgp { } </code> +<p>Example configuration for BGP unnumbered automatic peering using router discovery: + +<p><code> +# Table for discovered peers +peers table bgp_peers; + +# RAdv protocol with router discovery enabled +protocol radv { + peers { table bgp_peers; }; # Export discovered peers to this table + + interface "eth*" { + # Normal RAdv configuration options... + router discovery yes; # Enable reception of ICMPv6 RAs + }; +} + +# Dynamic BGP with peers channel for automatic peering +protocol bgp { + local as 65000; + neighbor range fe80::/10 external; # Accept link-local peers + dynamic name "bgp_auto_"; # Name spawned sessions with this prefix + + peers persist yes; + + ipv6 { + export all; + import all; + }; + + peers { + table bgp_peers; # Import peer discovery objects from this table + export all; # Allow all discovered peers to trigger sessions + }; +} +</code> + <sect>BMP <label id="bmp"> diff --git a/nest/protocol.h b/nest/protocol.h index 036e91b4..7e439dd9 100644 --- a/nest/protocol.h +++ b/nest/protocol.h @@ -156,6 +156,7 @@ struct proto { TLIST_LIST(proto_neigh) neighbors; /* List of neighbor structures */ struct iface_subscription iface_sub; /* Interface notification subscription */ struct channel *mpls_channel; /* MPLS channel, when used */ + struct channel *peers_channel; /* Peers channel for dynamic BGP peer discovery */ const char *name; /* Name of this instance (== cf->name) */ u32 debug; /* Debugging flags */ @@ -754,6 +755,8 @@ static inline struct channel_config *proto_cf_main_channel(struct proto_config * { return proto_cf_find_channel(pc, pc->net_type); } static inline struct channel_config *proto_cf_mpls_channel(struct proto_config *pc) { return (pc->net_type != NET_MPLS) ? proto_cf_find_channel(pc, NET_MPLS) : NULL; } +static inline struct channel_config *proto_cf_peers_channel(struct proto_config *pc) +{ return proto_cf_find_channel(pc, NET_PEER); } struct channel *proto_find_channel_by_table(struct proto *p, rtable *t); struct channel *proto_find_channel_by_name(struct proto *p, const char *n); diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c index f072e4c9..5de04442 100644 --- a/proto/bgp/attrs.c +++ b/proto/bgp/attrs.c @@ -2468,6 +2468,57 @@ bgp_rt_notify(struct proto *P, struct channel *C, const net_addr *n, rte *new, c if (SHUTTING_DOWN) return; + /* Handle peers channel - spawn BGP sessions for discovered peers */ + if (C == P->peers_channel) + { + const net_addr_peer *peer = (const net_addr_peer *) n; + + /* Only handle peer management for dynamic BGP instances and link-local addresses */ + if (bgp_is_dynamic(p) && ipa_is_link_local(peer->addr)) + { + if (new && !old) + { + /* New peer discovered - send spawn event to main loop */ + struct iface *iface = if_find_by_index(peer->ifindex); + + struct bgp_peer_spawn *ev = mb_alloc(p->p.pool, sizeof(struct bgp_peer_spawn)); + *ev = (struct bgp_peer_spawn) { + .p = p, + .peer_addr = peer->addr, + .iface = iface, + }; + + /* Initialize callback to run on main_birdloop */ + callback_init(&ev->cb, bgp_peer_spawn, &main_birdloop); + + /* Send the event */ + callback_activate(&ev->cb); + + log(L_INFO "%s: Peer %I on interface %s discovered, queued for BGP session spawn", + p->p.name, peer->addr, iface->name); + } + else if (!new && old) + { + /* Send removal event to main loop */ + struct bgp_peer_remove *ev = mb_alloc(p->p.pool, sizeof(struct bgp_peer_remove)); + *ev = (struct bgp_peer_remove) { + .p = p, + .peer_addr = peer->addr, + }; + + /* Initialize callback to run on main_birdloop */ + callback_init(&ev->cb, bgp_peer_remove, &main_birdloop); + + /* Send the event */ + callback_activate(&ev->cb); + + log(L_INFO "%s: Peer %I withdrawn from channel, BGP session down", + p->p.name, peer->addr); + } + } + return; + } + /* Ignore non-BGP channels */ if (C->class != &channel_bgp) return; diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index ad6dd334..17fcd7ce 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -173,13 +173,6 @@ static void bgp_listen_close(struct bgp_proto *, struct bgp_listen_request *); static void bgp_graceful_restart_feed(struct bgp_channel *c); static void bgp_restart_route_refresh(void *_bc); -/* Dynamic BGP detection */ -#define bgp_is_dynamic(x) (_Generic((x), \ - struct bgp_proto *: ipa_zero((x)->remote_ip), \ - struct bgp_config *: ipa_zero((x)->remote_ip), \ - struct bgp_listen_request *: ipa_zero((x)->remote_ip))) - - /* * BGP Instance Management */ @@ -1191,6 +1184,18 @@ bgp_down(struct bgp_proto *p) p->neigh = NULL; } + /* Clean up spawned BGP sessions if peers_persist is not enabled */ + if (p->cf->c.parent) + { + struct bgp_config *parent_cf = (struct bgp_config *) p->cf->c.parent; + if (!parent_cf->peers_persist) + { + BGP_TRACE(D_EVENTS, "Spawned session going down, marking for removal (peers_persist disabled)"); + p->p.cf_new = NULL; + p->p.reconfiguring = 1; + } + } + BGP_TRACE(D_EVENTS, "Down"); proto_notify_state(&p->p, PS_FLUSH); } @@ -1213,12 +1218,63 @@ bgp_decision(void *vp) bgp_down(p); } +/** + * bgp_find_existing_session - check if a dynamic BGP session already exists for a peer + * @peer_addr: Remote peer IP address to check + * + * Checks if there's already an existing dynamic BGP session for the given peer + * by walking through all BGP protocols. + * + * Returns: pointer to the existing BGP protocol, or NULL if none found + */ +static struct bgp_proto * +bgp_find_existing_session(ip_addr peer_addr) +{ + struct config *cfg = OBSREF_GET(config); + if (!cfg) + return NULL; + + struct proto_config *pc; + WALK_LIST(pc, cfg->protos) + { + if (pc->protocol != &proto_bgp) + continue; + + if (pc->proto) + { + struct bgp_proto *child_p = (struct bgp_proto *) pc->proto; + + if (ipa_equal(child_p->remote_ip, peer_addr)) + { + log(L_DEBUG "BGP: Found existing session %s for peer %I", + child_p->p.name, peer_addr); + return child_p; + } + } + } + return NULL; +} + static void bgp_spawn(struct bgp_proto *pp, struct birdsock *sk) { struct symbol *sym; char fmt[SYM_MAX_LEN]; + /* Check if there's an existing session for this peer and shut it down. + * The dynamic BGP session (with the incoming socket) takes precedence. */ + struct bgp_proto *existing = bgp_find_existing_session(sk->daddr); + if (existing) + { + log(L_DEBUG "BGP: Found existing session %s for %I, shutting it down to use incoming connection", + existing->p.name, sk->daddr); + + /* Mark for deletion and disable */ + existing->p.cf_new = NULL; + existing->p.reconfiguring = 1; + proto_disable(&existing->p); + } + bsprintf(fmt, "%s%%0%dd", pp->cf->dynamic_name, pp->cf->dynamic_name_digits); /* This is hack, we would like to share config, but we need to copy it now */ @@ -1237,6 +1293,15 @@ bgp_spawn(struct bgp_proto *pp, struct birdsock *sk) cf->iface = sk->iface; cf->ipatt = NULL; + /* Remove peers channel from spawned session config */ + struct channel_config *cc, *cc_next; + WALK_LIST_DELSAFE(cc, cc_next, cf->c.channels) + if (cc->net_type == NET_PEER) + { + rem_node(&cc->n); + break; + } + /* Create the protocol disabled initially */ SKIP_BACK_DECLARE(struct bgp_proto, p, p, proto_spawn(sym->proto, 1)); @@ -1247,6 +1312,100 @@ bgp_spawn(struct bgp_proto *pp, struct birdsock *sk) proto_enable(&p->p); } +void +bgp_peer_spawn(struct callback *cb) +{ + struct bgp_peer_spawn *ev = (void *) cb; + + /* Check if a session for this peer already exists */ + struct bgp_proto *existing = bgp_find_existing_session(ev->peer_addr); + if (existing) + { + /* If the existing session is down, enable it to re-establish the connection */ + if (existing->p.proto_state == PS_DOWN_XX || existing->p.proto_state == PS_STOP) + { + log(L_DEBUG "BGP: Peer %I already has existing session in down state, enabling it", ev->peer_addr); + proto_enable(&existing->p); + } + else + log(L_DEBUG "BGP: Peer %I already has existing session, not spawning duplicate", ev->peer_addr); + return; + } + + struct symbol *sym; + char fmt[SYM_MAX_LEN]; + + log(L_DEBUG "BGP: Spawning new peer session for %I", ev->peer_addr); + bsprintf(fmt, "%s%%0%dd", ev->p->cf->dynamic_name, ev->p->cf->dynamic_name_digits); + + /* Clone the configuration */ + new_config = OBSREF_GET(config); + cfg_mem = new_config->mem; + new_config->current_scope = new_config->root_scope; + sym = cf_default_name(new_config, fmt, &(ev->p->dynamic_name_counter)); + proto_clone_config(sym, ev->p->p.cf); + new_config = NULL; + cfg_mem = NULL; + + /* Configure for active connection to discovered peer */ + struct bgp_config *cf = SKIP_BACK(struct bgp_config, c, sym->proto); + cf->remote_ip = ev->peer_addr; + cf->local_ip = ev->p->cf->local_ip; + cf->iface = ev->iface; + cf->ipatt = NULL; + cf->passive = 0; + + /* Remove peers channel from spawned session config */ + struct channel_config *cc, *cc_next; + WALK_LIST_DELSAFE(cc, cc_next, cf->c.channels) + if (cc->net_type == NET_PEER) + { + rem_node(&cc->n); + break; + } + + /* Create and enable the protocol */ + SKIP_BACK_DECLARE(struct bgp_proto, p, p, proto_spawn(sym->proto, 1)); + + proto_enable(&p->p); +} + +void +bgp_peer_remove(struct callback *cb) +{ + struct bgp_peer_remove *ev = (void *) cb; + + /* Find ALL BGP sessions for this peer and remove them */ + struct config *cfg = OBSREF_GET(config); + if (!cfg) + return; + + struct proto_config *pc; + WALK_LIST(pc, cfg->protos) + { + if (pc->protocol != &proto_bgp) + continue; + + /* Check protocol instance */ + if (pc->proto) + { + struct bgp_proto *bgp_p = (struct bgp_proto *) pc->proto; + + /* Found a BGP session with matching peer address */ + if (ipa_equal(bgp_p->remote_ip, ev->peer_addr)) + { + /* Skip parent protocol */ + if (bgp_is_dynamic(bgp_p)) + continue; + + log(L_INFO "%s: Stopping BGP session %s for withdrawn peer %I", + ev->p->p.name, pc->name, ev->peer_addr); + proto_disable(&bgp_p->p); + } + } + } +} + void bgp_stop(struct bgp_proto *p, int subcode, byte *data, uint len) { @@ -2664,6 +2823,14 @@ bgp_start_locked(void *_p) birdloop_leave(sk_loop); } + if (bgp_is_dynamic(p)) { + /* Start peers channel immediately */ + if (p->p.peers_channel && !p->p.peers_channel->disabled) + { + channel_set_state(p->p.peers_channel, CS_UP); + } + } + if (cf->multihop || bgp_is_dynamic(p)) { /* Multi-hop sessions do not use neighbor entries */ @@ -2951,6 +3118,14 @@ bgp_init(struct proto_config *CF) /* Add MPLS channel */ proto_configure_mpls_channel(P, CF, RTS_BGP); + /* Add Peers channel for dynamic BGP peer discovery (only for parent protocol) */ + if (!cf->c.parent) + { + struct channel_config *peers_cf = proto_cf_peers_channel(CF); + if (peers_cf) + proto_configure_channel(P, &P->peers_channel, peers_cf); + } + /* Export public info */ ea_list *pes = p->p.ea_state; ea_set_attr(&pes, EA_LITERAL_STORE_ADATA(&ea_bgp_rem_ip, 0, &cf->remote_ip, sizeof(ip_addr))); @@ -4090,7 +4265,7 @@ struct protocol proto_bgp = { .name = "BGP", .template = "bgp%d", .preference = DEF_PREF_BGP, - .channel_mask = NB_IP | NB_VPN | NB_FLOW | NB_MPLS, + .channel_mask = NB_IP | NB_VPN | NB_FLOW | NB_MPLS | NB_PEER, .proto_size = sizeof(struct bgp_proto), .config_size = sizeof(struct bgp_config), .postconfig = bgp_postconfig, diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h index 8f76e7a5..c106db71 100644 --- a/proto/bgp/bgp.h +++ b/proto/bgp/bgp.h @@ -26,6 +26,7 @@ struct eattr; #define BGP_AFI_IPV4 1 #define BGP_AFI_IPV6 2 +#define BGP_AFI_PEER 3 #define BGP_SAFI_UNICAST 1 #define BGP_SAFI_MULTICAST 2 @@ -52,7 +53,7 @@ struct eattr; #define BGP_AF_VPN6_MC BGP_AF( BGP_AFI_IPV6, BGP_SAFI_VPN_MULTICAST ) #define BGP_AF_FLOW4 BGP_AF( BGP_AFI_IPV4, BGP_SAFI_FLOW ) #define BGP_AF_FLOW6 BGP_AF( BGP_AFI_IPV6, BGP_SAFI_FLOW ) - +#define BGP_AF_PEER BGP_AF( BGP_AFI_PEER, BGP_SAFI_UNICAST ) struct bgp_write_state; struct bgp_parse_state; @@ -117,6 +118,7 @@ struct bgp_config { int enforce_first_as; /* Enable check for neighbor AS as first AS in AS_PATH */ int gr_mode; /* Graceful restart mode (BGP_GR_*) */ int llgr_mode; /* Long-lived graceful restart mode (BGP_LLGR_*) */ + int peers_persist; /* Keep spawned BGP sessions after peer/dynamic withdrawal */ int auth_type; /* Authentication type (BGP_AUTH_*) */ int setkey; /* Set MD5 password to system SA/SP database */ u8 local_role; /* Set peering role with neighbor [RFC 9234] */ @@ -401,6 +403,23 @@ struct bgp_incoming_socket { sock *sk; /* The actual socket */ }; +struct bgp_peer_spawn { + callback cb; + struct bgp_proto *p; /* Parent protocol */ + ip_addr peer_addr; /* Peer address to spawn session for */ + struct iface *iface; /* Interface for link-local peers */ +}; + +struct bgp_peer_remove { + callback cb; + struct bgp_proto *p; /* Parent protocol */ + ip_addr peer_addr; /* Peer address to remove session for */ +}; + +/* Callback hooks (implemented in bgp.c) */ +void bgp_peer_spawn(struct callback *cb); +void bgp_peer_remove(struct callback *cb); + struct bgp_listen_request { node pn; /* Node in bgp_proto listen list */ node sn; /* Node in bgp_socket requests list */ @@ -978,5 +997,11 @@ enum bgp_attr_id { #define ORIGIN_EGP 1 #define ORIGIN_INCOMPLETE 2 +/* Dynamic BGP detection */ + +#define bgp_is_dynamic(x) (_Generic((x), \ + struct bgp_proto *: ipa_zero((x)->remote_ip), \ + struct bgp_config *: ipa_zero((x)->remote_ip), \ + struct bgp_listen_request *: ipa_zero((x)->remote_ip))) #endif diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index c7316d86..9d1169f9 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -33,9 +33,9 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE, STRICT, BIND, CONFEDERATION, MEMBER, MULTICAST, FLOW4, FLOW6, LONG, LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL, SETS, DYNAMIC, RANGE, NAME, DIGITS, AIGP, ORIGINATE, COST, ENFORCE, - FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER, + FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PEERS, PROVIDER, CUSTOMER, RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND, RECV, MIN, MAX, - TX, SIZE, WARNING, + TX, SIZE, WARNING, PERSIST, AUTHENTICATION, NONE, MD5, AO, FORMAT, NATIVE, SINGLE, DOUBLE) CF_KEYWORDS(KEY, KEYS, SECRET, DEPRECATED, PREFERRED, ALGORITHM, CMAC, AES128) @@ -92,6 +92,7 @@ bgp_proto_start: proto_start BGP { BGP_CFG->check_link = -1; BGP_CFG->send_hold_time = -1; BGP_CFG->tx_size_warning = 0; + BGP_CFG->peers_persist = 1; } ; @@ -157,6 +158,7 @@ bgp_proto: BGP_CFG->local_ip = $3; if ($4) BGP_CFG->iface = $4; } + | bgp_proto PEERS PERSIST bool ';' { BGP_CFG->peers_persist = $4; } | bgp_proto NEIGHBOR bgp_nbr_opts ';' | bgp_proto NEIGHBOR ipa ipa_scope bgp_nbr_opts ';' { if (ipa_nonzero(BGP_CFG->remote_ip) || BGP_CFG->remote_range) @@ -270,6 +272,7 @@ bgp_afi: | VPN6 MULTICAST { $$ = BGP_AF_VPN6_MC; } | FLOW4 { $$ = BGP_AF_FLOW4; } | FLOW6 { $$ = BGP_AF_FLOW6; } + | PEERS { $$ = BGP_AF_PEER; } ; tcp_ao_key_start: KEY { @@ -398,7 +401,10 @@ bgp_channel_start: bgp_afi { BGP_CC->c.in_filter = FILTER_UNDEF; BGP_CC->c.out_filter = FILTER_UNDEF; - BGP_CC->c.ra_mode = RA_UNDEF; + if ($1 == BGP_AF_PEER) + BGP_CC->c.ra_mode = RA_ANY; + else + BGP_CC->c.ra_mode = RA_UNDEF; BGP_CC->afi = $1; BGP_CC->desc = desc; BGP_CC->next_hop_keep = 0xff; /* undefined */ diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c index bdbeabd1..87a5b777 100644 --- a/proto/bgp/packets.c +++ b/proto/bgp/packets.c @@ -2342,6 +2342,17 @@ static const struct bgp_af_desc bgp_af_table[] = { .decode_next_hop = bgp_decode_next_hop_none, .update_next_hop = bgp_update_next_hop_none, }, + { + .afi = BGP_AF_PEER, + .net = NET_PEER, + .no_igp = 1, + .name = "peers", + .encode_nlri = NULL, + .decode_nlri = NULL, + .encode_next_hop = NULL, + .decode_next_hop = NULL, + .update_next_hop = NULL, + }, }; const struct bgp_af_desc * -- 2.43.0
Add testcase for BGP unnumbered automatic peering and related data. Signed-off-by: Matteo Perin <matteo.perin@canonical.com> --- .../cf-bgp-unnumbered-autopeering/README.md | 132 ++++++++++++++++++ .../bird_m1.conf | 43 ++++++ .../bird_m2.conf | 43 ++++++ .../cf-bgp-unnumbered-autopeering | 1 + netlab/cf-bgp-unnumbered-autopeering/config | 10 ++ .../data-v2/nd_peers-m1 | 7 + .../data-v2/nd_peers-m1-down-m2 | 7 + .../data-v2/nd_peers-m1-up-m1 | 7 + .../data-v2/nd_peers-m1-up-m2 | 7 + .../data-v2/nd_peers-m2 | 7 + .../data-v2/nd_peers-m2-down-m1 | 7 + .../data-v2/nd_peers-m2-up-m1 | 7 + .../data-v2/nd_peers-m2-up-m2 | 7 + .../data-v2/proto-m1 | 121 ++++++++++++++++ .../data-v2/proto-m1-down-m2 | 121 ++++++++++++++++ .../data-v2/proto-m1-up-m1 | 121 ++++++++++++++++ .../data-v2/proto-m1-up-m2 | 121 ++++++++++++++++ .../data-v2/proto-m2 | 121 ++++++++++++++++ .../data-v2/proto-m2-down-m1 | 121 ++++++++++++++++ .../data-v2/proto-m2-up-m1 | 121 ++++++++++++++++ .../data-v2/proto-m2-up-m2 | 121 ++++++++++++++++ .../test-cf-bgp-unnumbered-autopeering.py | 101 ++++++++++++++ netlab/tests/get_stdout_bird | 1 + netlab/tests/get_stdout_protocols | 3 +- netlab/tests/strip_output | 10 ++ 25 files changed, 1367 insertions(+), 1 deletion(-) create mode 100644 netlab/cf-bgp-unnumbered-autopeering/README.md create mode 100644 netlab/cf-bgp-unnumbered-autopeering/bird_m1.conf create mode 100644 netlab/cf-bgp-unnumbered-autopeering/bird_m2.conf create mode 120000 netlab/cf-bgp-unnumbered-autopeering/cf-bgp-unnumbered-autopeering create mode 100644 netlab/cf-bgp-unnumbered-autopeering/config create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-down-m2 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-up-m1 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-up-m2 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-down-m1 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-up-m1 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-up-m2 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-down-m2 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-up-m1 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-up-m2 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-down-m1 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-up-m1 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-up-m2 create mode 100644 netlab/cf-bgp-unnumbered-autopeering/test-cf-bgp-unnumbered-autopeering.py diff --git a/netlab/cf-bgp-unnumbered-autopeering/README.md b/netlab/cf-bgp-unnumbered-autopeering/README.md new file mode 100644 index 00000000..c43c282e --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/README.md @@ -0,0 +1,132 @@ +<!--cf-bgp-unnumbered-autopeering--> +##### Introduction +This test case verifies the BGP unnumbered auto peering feature where BGP peer +processes are dynamically spawned based on router discovery via the RADV protocol. + +The test case uses a simple two-node topology where both nodes run: +- RADV protocol for neighbor discovery +- BGP protocol with dynamic peer instantiation + +When neighbors are discovered through router advertisement, BGP sessions should +be automatically established using link-local IPv6 addresses (fe80::/12). + +<br> + +##### Topology + +--- + +``` + AS1 AS2 + m1 -------------- m2 + (ve1) (ve2) +``` + +The nodes are connected via a virtual ethernet pair (`ve1` on m1, `ve2` on m2). + +<br> + +##### Configuration + +--- + +Both nodes have similar configurations with the following key features: + +1. **Device Protocol**: Tracks interface states +2. **Peer Table** (`nd_peers`): Stores discovered neighbors +3. **RADV Protocol** (`radv1`): + - Performs router discovery on the interconnecting interface + - Populates the peer table with discovered neighbors + - Configured with max RA interval of 20s +4. **BGP Protocol** (`bgp1`): + - Dynamically creates BGP peer instances with name pattern `bgp_peer_XXX` + - Accepts neighbors from the fe80::/12 range (link-local addresses) + - **m1**: `peers persist no` - Dynamic BGP protocols are removed when peers disappear + - **m2**: `peers persist yes` - Dynamic BGP protocols remain even when peers go down + - Imports peer information from the `nd_peers` table + - AS1 (m1): AS 65001 + - AS2 (m2): AS 65002 + +<br> + +##### Test suite + +--- + +The test suite verifies the following in multiple phases: + +**Phase 1: Initial Setup and Convergence** +1. **Peer table**: Checks that the `nd_peers` table contains discovered neighbors +2. **BGP protocols**: Verifies that dynamic BGP peer processes are spawned and established + +**Phase 2: m2 Down Testing (m1 has peers persist no)** +3. **Stop m2**: Node m2 is stopped to simulate a peer failure +4. **BGP protocols after m2 down**: Verifies that dynamic BGP protocols are **removed** from m1 + (due to `peers persist no` configuration) +5. **Peer table after m2 down**: Checks that the peer is removed from m1's `nd_peers` table + +**Phase 3: m2 Recovery Testing** +6. **Continue m2**: Node m2 is brought back up +7. **BGP protocols after m2 up**: Verifies that dynamic BGP protocols are re-spawned on both nodes +8. **Peer table after m2 up**: Checks that peers are rediscovered and added back to the `nd_peers` table + +**Phase 4: m1 Down Testing (m2 has peers persist yes)** +9. **Stop m1**: Node m1 is stopped to test `peers persist yes` on m2 +10. **BGP protocols after m1 down**: Verifies that dynamic BGP protocols **remain** on m2 + (albeit in a down state, due to `peers persist yes` configuration) +11. **Peer table after m1 down**: Checks the peer table state on m2 + +**Phase 5: m1 Recovery Testing** +12. **Continue m1**: Node m1 is brought back up +13. **BGP protocols after m1 up**: Verifies that BGP sessions re-establish on both nodes +14. **Peer table after m1 up**: Checks that peer table is updated after m1 recovery + +In save mode, the test suite captures: +- `nd_peers_m1`, `nd_peers_m2` - peer tables with discovered neighbors (initial) +- `proto_m1`, `proto_m2` - protocol states (initial) +- `proto-m2-down_m1` - protocol states on m1 after m2 is stopped +- `nd_peers-m2-down_m1` - peer table on m1 after m2 is stopped +- `proto-m2-up_m1`, `proto-m2-up_m2` - protocol states after m2 recovers +- `nd_peers-m2-up_m1`, `nd_peers-m2-up_m2` - peer tables after m2 recovers +- `proto-m1-down_m2` - protocol states on m2 after m1 is stopped (should show protocols in down state) +- `nd_peers-m1-down_m2` - peer table on m2 after m1 is stopped +- `proto-m1-up_m1`, `proto-m1-up_m2` - protocol states after m1 recovers +- `nd_peers-m1-up_m1`, `nd_peers-m1-up_m2` - peer tables after m1 recovers + +<br> + +##### Expected Behavior + +--- + +**Normal Operation:** +1. Both nodes start with RADV enabled on their respective interfaces +2. Router advertisements are exchanged between m1 and m2 +3. Neighbors are discovered and added to the `nd_peers` table +4. BGP dynamically spawns peer instances (e.g., `bgp_peer_001`) based on discovered peers +5. BGP sessions establish using link-local IPv6 addresses + +**m2 Down Scenario (m1 has peers persist no):** +1. When m2 is stopped, router advertisements stop +2. The peer entry for m2 is removed from m1's `nd_peers` table +3. Due to `peers persist no`, the dynamic BGP protocol on m1 is **removed** +4. No stale BGP sessions remain on m1 + +**m2 Recovery Scenario:** +1. When m2 is brought back up, router advertisements resume +2. Neighbors are rediscovered through RADV +3. Peers are added back to the `nd_peers` table +4. Dynamic BGP protocols are re-spawned automatically +5. BGP sessions re-establish + +**m1 Down Scenario (m2 has peers persist yes):** +1. When m1 is stopped, router advertisements stop +2. The peer entry may be removed from m2's `nd_peers` table +3. Due to `peers persist yes`, the dynamic BGP protocol on m2 **remains** (in down state) +4. The BGP session persists, ready to reconnect when m1 returns + +**m1 Recovery Scenario:** +1. When m1 is brought back up, router advertisements resume +2. Neighbors are rediscovered through RADV +3. The existing dynamic BGP protocol on m2 detects the peer is back +4. BGP sessions re-establish automatically without needing to spawn new protocols diff --git a/netlab/cf-bgp-unnumbered-autopeering/bird_m1.conf b/netlab/cf-bgp-unnumbered-autopeering/bird_m1.conf new file mode 100644 index 00000000..2443228b --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/bird_m1.conf @@ -0,0 +1,43 @@ +log syslog all; +log "bird.log" all; +debug protocols all; + +router id 1; + +protocol device { +} + +peer table nd_peers; + +protocol radv radv1 { + peer { + table nd_peers; + }; + + interface "ve1" { + max ra interval 20; + min ra interval 10; + + router discovery yes; + }; +} + +protocol bgp bgp1 { + dynamic name "bgp_peer"; + dynamic name digits 3; + local as 65001; + neighbor range fe80::/12 external; + + peers persist no; + + peers { + table nd_peers; + import all; + export all; + }; + + ipv6 { + import all; + export all; + }; +} diff --git a/netlab/cf-bgp-unnumbered-autopeering/bird_m2.conf b/netlab/cf-bgp-unnumbered-autopeering/bird_m2.conf new file mode 100644 index 00000000..25e4104c --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/bird_m2.conf @@ -0,0 +1,43 @@ +log syslog all; +log "bird.log" all; +debug protocols all; + +router id 2; + +protocol device { +} + +peer table nd_peers; + +protocol radv radv1 { + peer { + table nd_peers; + }; + + interface "ve2" { + max ra interval 20; + min ra interval 10; + + router discovery yes; + }; +} + +protocol bgp bgp1 { + dynamic name "bgp_peer"; + dynamic name digits 3; + local as 65002; + neighbor range fe80::/12 external; + + peers persist yes; + + peers { + table nd_peers; + import all; + export all; + }; + + ipv6 { + import all; + export all; + }; +} diff --git a/netlab/cf-bgp-unnumbered-autopeering/cf-bgp-unnumbered-autopeering b/netlab/cf-bgp-unnumbered-autopeering/cf-bgp-unnumbered-autopeering new file mode 120000 index 00000000..2233a804 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/cf-bgp-unnumbered-autopeering @@ -0,0 +1 @@ +cf-bgp-unnumbered-autopeering \ No newline at end of file diff --git a/netlab/cf-bgp-unnumbered-autopeering/config b/netlab/cf-bgp-unnumbered-autopeering/config new file mode 100644 index 00000000..d8d4dff0 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/config @@ -0,0 +1,10 @@ +NETLAB_NODES="m1 m2" + +netlab_init + +if_dummy m1 ve0 10.1.1 2001:db8:1:1 +if_dummy m2 ve0 10.2.1 2001:db8:2:1 + +if_veth m1 ve1 m2 ve2 10.1.10 2001:db8:1:10 + +netlab_start diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1 new file mode 100644 index 00000000..346a1f3b --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1 @@ -0,0 +1,7 @@ +BIRD <removed> ready. +Table nd_peers: +fe80::88c6:96ff:feb5:9492 from iface 3 [radv1 <removed>] * (0) + preference: 0 + source: device + radv_expires_at: <removed> + Internal route handling values: <removed> diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-down-m2 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-down-m2 new file mode 100644 index 00000000..ecba0af3 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-down-m2 @@ -0,0 +1,7 @@ +BIRD <removed> ready. +Table nd_peers: +fe80::c8f0:65ff:febc:62b4 from iface 3 [radv1 <removed>] * (0) + preference: 0 + source: device + radv_expires_at: <removed> + Internal route handling values: <removed> diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-up-m1 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-up-m1 new file mode 100644 index 00000000..346a1f3b --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-up-m1 @@ -0,0 +1,7 @@ +BIRD <removed> ready. +Table nd_peers: +fe80::88c6:96ff:feb5:9492 from iface 3 [radv1 <removed>] * (0) + preference: 0 + source: device + radv_expires_at: <removed> + Internal route handling values: <removed> diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-up-m2 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-up-m2 new file mode 100644 index 00000000..ecba0af3 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m1-up-m2 @@ -0,0 +1,7 @@ +BIRD <removed> ready. +Table nd_peers: +fe80::c8f0:65ff:febc:62b4 from iface 3 [radv1 <removed>] * (0) + preference: 0 + source: device + radv_expires_at: <removed> + Internal route handling values: <removed> diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2 new file mode 100644 index 00000000..ecba0af3 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2 @@ -0,0 +1,7 @@ +BIRD <removed> ready. +Table nd_peers: +fe80::c8f0:65ff:febc:62b4 from iface 3 [radv1 <removed>] * (0) + preference: 0 + source: device + radv_expires_at: <removed> + Internal route handling values: <removed> diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-down-m1 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-down-m1 new file mode 100644 index 00000000..346a1f3b --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-down-m1 @@ -0,0 +1,7 @@ +BIRD <removed> ready. +Table nd_peers: +fe80::88c6:96ff:feb5:9492 from iface 3 [radv1 <removed>] * (0) + preference: 0 + source: device + radv_expires_at: <removed> + Internal route handling values: <removed> diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-up-m1 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-up-m1 new file mode 100644 index 00000000..346a1f3b --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-up-m1 @@ -0,0 +1,7 @@ +BIRD <removed> ready. +Table nd_peers: +fe80::88c6:96ff:feb5:9492 from iface 3 [radv1 <removed>] * (0) + preference: 0 + source: device + radv_expires_at: <removed> + Internal route handling values: <removed> diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-up-m2 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-up-m2 new file mode 100644 index 00000000..ecba0af3 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/nd_peers-m2-up-m2 @@ -0,0 +1,7 @@ +BIRD <removed> ready. +Table nd_peers: +fe80::c8f0:65ff:febc:62b4 from iface 3 [radv1 <removed>] * (0) + preference: 0 + source: device + radv_expires_at: <removed> + Internal route handling values: <removed> diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1 new file mode 100644 index 00000000..d018ee0f --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1 @@ -0,0 +1,121 @@ +BIRD <removed> ready. +Name Proto Table State Since Info +device1 Device --- up <removed> + Created: <removed> + +radv1 RAdv master6 up <removed> + Created: <removed> + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + Channel peer + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 1 imported, 0 exported, 1 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + +bgp1 BGP --- start <removed> Passive + Created: <removed> + BGP state: Passive + Neighbor range: fe80::/12 + Neighbor AS: 0 + Local AS: 65001 + Channel peers + State: DOWN + Import state: DOWN + Export state: DOWN + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel ipv6 + State: DOWN + Import state: DOWN + Export state: DOWN + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel peers + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 1 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: + Pending 0 attribute sets with total 0 prefixes to send + +bgp_peer<XXX> BGP --- up <removed> Established + Created: <removed> + BGP state: Established + Neighbor address: fe80::88c6:96ff:feb5:9492%ve1 + Neighbor AS: 65002 + Local AS: 65001 + Neighbor ID: 0.0.0.2 + Local capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Neighbor capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Session: external AS4 + Source address: fe80::c8f0:65ff:febc:62b4 + Hold timer: <removed>/240 + Keepalive timer: <removed>/80 + TX pending: 0 bytes + Send hold timer: <removed>/480 + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: fe80::c8f0:65ff:febc:62b4 + Pending 0 attribute sets with total 0 prefixes to send + diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-down-m2 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-down-m2 new file mode 100644 index 00000000..b7c7f675 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-down-m2 @@ -0,0 +1,121 @@ +BIRD <removed> ready. +Name Proto Table State Since Info +device1 Device --- up <removed> + Created: <removed> + +radv1 RAdv master6 up <removed> + Created: <removed> + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + Channel peer + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 1 imported, 0 exported, 1 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + +bgp1 BGP --- start <removed> Passive + Created: <removed> + BGP state: Passive + Neighbor range: fe80::/12 + Neighbor AS: 0 + Local AS: 65002 + Channel peers + State: DOWN + Import state: DOWN + Export state: DOWN + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel ipv6 + State: DOWN + Import state: DOWN + Export state: DOWN + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel peers + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 1 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: + Pending 0 attribute sets with total 0 prefixes to send + +bgp_peer<XXX> BGP --- up <removed> Established + Created: <removed> + BGP state: Established + Neighbor address: fe80::c8f0:65ff:febc:62b4%ve2 + Neighbor AS: 65001 + Local AS: 65002 + Neighbor ID: 0.0.0.1 + Local capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Neighbor capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Session: external AS4 + Source address: fe80::88c6:96ff:feb5:9492 + Hold timer: <removed>/240 + Keepalive timer: <removed>/80 + TX pending: 0 bytes + Send hold timer: <removed>/480 + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: fe80::88c6:96ff:feb5:9492 + Pending 0 attribute sets with total 0 prefixes to send + diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-up-m1 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-up-m1 new file mode 100644 index 00000000..d018ee0f --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-up-m1 @@ -0,0 +1,121 @@ +BIRD <removed> ready. +Name Proto Table State Since Info +device1 Device --- up <removed> + Created: <removed> + +radv1 RAdv master6 up <removed> + Created: <removed> + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + Channel peer + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 1 imported, 0 exported, 1 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + +bgp1 BGP --- start <removed> Passive + Created: <removed> + BGP state: Passive + Neighbor range: fe80::/12 + Neighbor AS: 0 + Local AS: 65001 + Channel peers + State: DOWN + Import state: DOWN + Export state: DOWN + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel ipv6 + State: DOWN + Import state: DOWN + Export state: DOWN + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel peers + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 1 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: + Pending 0 attribute sets with total 0 prefixes to send + +bgp_peer<XXX> BGP --- up <removed> Established + Created: <removed> + BGP state: Established + Neighbor address: fe80::88c6:96ff:feb5:9492%ve1 + Neighbor AS: 65002 + Local AS: 65001 + Neighbor ID: 0.0.0.2 + Local capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Neighbor capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Session: external AS4 + Source address: fe80::c8f0:65ff:febc:62b4 + Hold timer: <removed>/240 + Keepalive timer: <removed>/80 + TX pending: 0 bytes + Send hold timer: <removed>/480 + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: fe80::c8f0:65ff:febc:62b4 + Pending 0 attribute sets with total 0 prefixes to send + diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-up-m2 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-up-m2 new file mode 100644 index 00000000..b7c7f675 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m1-up-m2 @@ -0,0 +1,121 @@ +BIRD <removed> ready. +Name Proto Table State Since Info +device1 Device --- up <removed> + Created: <removed> + +radv1 RAdv master6 up <removed> + Created: <removed> + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + Channel peer + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 1 imported, 0 exported, 1 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + +bgp1 BGP --- start <removed> Passive + Created: <removed> + BGP state: Passive + Neighbor range: fe80::/12 + Neighbor AS: 0 + Local AS: 65002 + Channel peers + State: DOWN + Import state: DOWN + Export state: DOWN + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel ipv6 + State: DOWN + Import state: DOWN + Export state: DOWN + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel peers + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 1 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: + Pending 0 attribute sets with total 0 prefixes to send + +bgp_peer<XXX> BGP --- up <removed> Established + Created: <removed> + BGP state: Established + Neighbor address: fe80::c8f0:65ff:febc:62b4%ve2 + Neighbor AS: 65001 + Local AS: 65002 + Neighbor ID: 0.0.0.1 + Local capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Neighbor capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Session: external AS4 + Source address: fe80::88c6:96ff:feb5:9492 + Hold timer: <removed>/240 + Keepalive timer: <removed>/80 + TX pending: 0 bytes + Send hold timer: <removed>/480 + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: fe80::88c6:96ff:feb5:9492 + Pending 0 attribute sets with total 0 prefixes to send + diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2 new file mode 100644 index 00000000..b7c7f675 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2 @@ -0,0 +1,121 @@ +BIRD <removed> ready. +Name Proto Table State Since Info +device1 Device --- up <removed> + Created: <removed> + +radv1 RAdv master6 up <removed> + Created: <removed> + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + Channel peer + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 1 imported, 0 exported, 1 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + +bgp1 BGP --- start <removed> Passive + Created: <removed> + BGP state: Passive + Neighbor range: fe80::/12 + Neighbor AS: 0 + Local AS: 65002 + Channel peers + State: DOWN + Import state: DOWN + Export state: DOWN + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel ipv6 + State: DOWN + Import state: DOWN + Export state: DOWN + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel peers + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 1 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: + Pending 0 attribute sets with total 0 prefixes to send + +bgp_peer<XXX> BGP --- up <removed> Established + Created: <removed> + BGP state: Established + Neighbor address: fe80::c8f0:65ff:febc:62b4%ve2 + Neighbor AS: 65001 + Local AS: 65002 + Neighbor ID: 0.0.0.1 + Local capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Neighbor capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Session: external AS4 + Source address: fe80::88c6:96ff:feb5:9492 + Hold timer: <removed>/240 + Keepalive timer: <removed>/80 + TX pending: 0 bytes + Send hold timer: <removed>/480 + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: fe80::88c6:96ff:feb5:9492 + Pending 0 attribute sets with total 0 prefixes to send + diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-down-m1 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-down-m1 new file mode 100644 index 00000000..d018ee0f --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-down-m1 @@ -0,0 +1,121 @@ +BIRD <removed> ready. +Name Proto Table State Since Info +device1 Device --- up <removed> + Created: <removed> + +radv1 RAdv master6 up <removed> + Created: <removed> + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + Channel peer + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 1 imported, 0 exported, 1 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + +bgp1 BGP --- start <removed> Passive + Created: <removed> + BGP state: Passive + Neighbor range: fe80::/12 + Neighbor AS: 0 + Local AS: 65001 + Channel peers + State: DOWN + Import state: DOWN + Export state: DOWN + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel ipv6 + State: DOWN + Import state: DOWN + Export state: DOWN + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel peers + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 1 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: + Pending 0 attribute sets with total 0 prefixes to send + +bgp_peer<XXX> BGP --- up <removed> Established + Created: <removed> + BGP state: Established + Neighbor address: fe80::88c6:96ff:feb5:9492%ve1 + Neighbor AS: 65002 + Local AS: 65001 + Neighbor ID: 0.0.0.2 + Local capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Neighbor capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Session: external AS4 + Source address: fe80::c8f0:65ff:febc:62b4 + Hold timer: <removed>/240 + Keepalive timer: <removed>/80 + TX pending: 0 bytes + Send hold timer: <removed>/480 + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: fe80::c8f0:65ff:febc:62b4 + Pending 0 attribute sets with total 0 prefixes to send + diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-up-m1 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-up-m1 new file mode 100644 index 00000000..d018ee0f --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-up-m1 @@ -0,0 +1,121 @@ +BIRD <removed> ready. +Name Proto Table State Since Info +device1 Device --- up <removed> + Created: <removed> + +radv1 RAdv master6 up <removed> + Created: <removed> + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + Channel peer + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 1 imported, 0 exported, 1 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + +bgp1 BGP --- start <removed> Passive + Created: <removed> + BGP state: Passive + Neighbor range: fe80::/12 + Neighbor AS: 0 + Local AS: 65001 + Channel peers + State: DOWN + Import state: DOWN + Export state: DOWN + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel ipv6 + State: DOWN + Import state: DOWN + Export state: DOWN + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel peers + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 1 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: + Pending 0 attribute sets with total 0 prefixes to send + +bgp_peer<XXX> BGP --- up <removed> Established + Created: <removed> + BGP state: Established + Neighbor address: fe80::88c6:96ff:feb5:9492%ve1 + Neighbor AS: 65002 + Local AS: 65001 + Neighbor ID: 0.0.0.2 + Local capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Neighbor capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Session: external AS4 + Source address: fe80::c8f0:65ff:febc:62b4 + Hold timer: <removed>/240 + Keepalive timer: <removed>/80 + TX pending: 0 bytes + Send hold timer: <removed>/480 + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: fe80::c8f0:65ff:febc:62b4 + Pending 0 attribute sets with total 0 prefixes to send + diff --git a/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-up-m2 b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-up-m2 new file mode 100644 index 00000000..b7c7f675 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/data-v2/proto-m2-up-m2 @@ -0,0 +1,121 @@ +BIRD <removed> ready. +Name Proto Table State Since Info +device1 Device --- up <removed> + Created: <removed> + +radv1 RAdv master6 up <removed> + Created: <removed> + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + Channel peer + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 0 + Input filter: ACCEPT + Output filter: REJECT + Routes: 1 imported, 0 exported, 1 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + +bgp1 BGP --- start <removed> Passive + Created: <removed> + BGP state: Passive + Neighbor range: fe80::/12 + Neighbor AS: 0 + Local AS: 65002 + Channel peers + State: DOWN + Import state: DOWN + Export state: DOWN + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel ipv6 + State: DOWN + Import state: DOWN + Export state: DOWN + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Channel peers + State: UP + Import state: UP + Export state: READY + Table: nd_peers + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 1 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: + Pending 0 attribute sets with total 0 prefixes to send + +bgp_peer<XXX> BGP --- up <removed> Established + Created: <removed> + BGP state: Established + Neighbor address: fe80::c8f0:65ff:febc:62b4%ve2 + Neighbor AS: 65001 + Local AS: 65002 + Neighbor ID: 0.0.0.1 + Local capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Neighbor capabilities + Multiprotocol + AF announced: ipv6 + Route refresh + Graceful restart + 4-octet AS numbers + Enhanced refresh + Long-lived graceful restart + Session: external AS4 + Source address: fe80::88c6:96ff:feb5:9492 + Hold timer: <removed>/240 + Keepalive timer: <removed>/80 + TX pending: 0 bytes + Send hold timer: <removed>/480 + Channel ipv6 + State: UP + Import state: UP + Export state: READY + Table: master6 + Preference: 100 + Input filter: ACCEPT + Output filter: ACCEPT + Routes: 0 imported, 0 exported, 0 preferred + Route change stats: received rejected filtered ignored RX limit limit accepted + Import updates: <removed> + Import withdraws: <removed> + Export updates: <removed> + Export withdraws: <removed> + BGP Next hop: :: fe80::88c6:96ff:feb5:9492 + Pending 0 attribute sets with total 0 prefixes to send + diff --git a/netlab/cf-bgp-unnumbered-autopeering/test-cf-bgp-unnumbered-autopeering.py b/netlab/cf-bgp-unnumbered-autopeering/test-cf-bgp-unnumbered-autopeering.py new file mode 100644 index 00000000..278308a0 --- /dev/null +++ b/netlab/cf-bgp-unnumbered-autopeering/test-cf-bgp-unnumbered-autopeering.py @@ -0,0 +1,101 @@ +import pytest + +import tests.kernel as tk +import tests.config as cf + +LIMIT = 100 +EXPECTED_DEVICES = ( + "m1", + "m2", +) + + +def test_wait(): + """Wait until the time (limit) runs out""" + tk.wait(LIMIT) + +@pytest.mark.parametrize("exp_devs", EXPECTED_DEVICES) +def test_nd_peers_table(exp_devs: str): + """Check the peer table nd_peers for discovered neighbors""" + tk.test_bird_routes("nd_peers", exp_devs, "nd_peers") + +@pytest.mark.parametrize("exp_devs", EXPECTED_DEVICES) +def test_bgp_protocols(exp_devs: str): + """Check that dynamic BGP protocols are spawned and established""" + tk.test_protocols(key="proto", dev=exp_devs, opts="all") + +@pytest.mark.skipif(False, reason="") +def test_stop_m2(): + """Stop the m2 node to test peers persist behavior""" + tk.kill_node("m2", "STOP") + +def test_wait_after_stop(): + """Wait for BGP sessions to detect peer is down""" + tk.wait(10) + +@pytest.mark.parametrize("exp_devs", ("m1",)) +def test_bgp_protocols_after_m2_down(exp_devs: str): + """Check that dynamic BGP protocols are removed when peer is down (peers persist no)""" + tk.test_protocols(key="proto-m2-down", dev=exp_devs, opts="all") + +@pytest.mark.parametrize("exp_devs", ("m1",)) +def test_nd_peers_table_after_m2_down(exp_devs: str): + """Check the peer table after m2 is down""" + tk.test_bird_routes("nd_peers-m2-down", exp_devs, "nd_peers") + +@pytest.mark.skipif(False, reason="") +def test_cont_m2(): + """Continue the m2 node""" + tk.kill_node("m2", "CONT") + +def test_wait_after_cont(): + """Wait for BGP sessions to re-establish""" + tk.wait(LIMIT) + +@pytest.mark.parametrize("exp_devs", EXPECTED_DEVICES) +def test_bgp_protocols_after_m2_up(exp_devs: str): + """Check that dynamic BGP protocols are re-spawned after m2 comes back""" + tk.test_protocols(key="proto-m2-up", dev=exp_devs, opts="all") + +@pytest.mark.parametrize("exp_devs", EXPECTED_DEVICES) +def test_nd_peers_table_after_m2_up(exp_devs: str): + """Check the peer table after m2 comes back up""" + tk.test_bird_routes("nd_peers-m2-up", exp_devs, "nd_peers") + +@pytest.mark.skipif(False, reason="") +def test_stop_m1(): + """Stop the m1 node to test peers persist yes on m2""" + tk.kill_node("m1", "STOP") + +def test_wait_after_m1_stop(): + """Wait for BGP sessions to detect m1 is down""" + tk.wait(10) + +@pytest.mark.parametrize("exp_devs", ("m2",)) +def test_bgp_protocols_after_m1_down(exp_devs: str): + """Check that dynamic BGP protocols remain (in down state) on m2 (peers persist yes)""" + tk.test_protocols(key="proto-m1-down", dev=exp_devs, opts="all") + +@pytest.mark.parametrize("exp_devs", ("m2",)) +def test_nd_peers_table_after_m1_down(exp_devs: str): + """Check the peer table on m2 after m1 is down""" + tk.test_bird_routes("nd_peers-m1-down", exp_devs, "nd_peers") + +@pytest.mark.skipif(False, reason="") +def test_cont_m1(): + """Continue the m1 node""" + tk.kill_node("m1", "CONT") + +def test_wait_after_m1_cont(): + """Wait for BGP sessions to re-establish""" + tk.wait(LIMIT) + +@pytest.mark.parametrize("exp_devs", EXPECTED_DEVICES) +def test_bgp_protocols_after_m1_up(exp_devs: str): + """Check that BGP sessions are re-established after m1 comes back""" + tk.test_protocols(key="proto-m1-up", dev=exp_devs, opts="all") + +@pytest.mark.parametrize("exp_devs", EXPECTED_DEVICES) +def test_nd_peers_table_after_m1_up(exp_devs: str): + """Check the peer table after m1 comes back up""" + tk.test_bird_routes("nd_peers-m1-up", exp_devs, "nd_peers") diff --git a/netlab/tests/get_stdout_bird b/netlab/tests/get_stdout_bird index c9f8bbc3..3d5d5bf7 100755 --- a/netlab/tests/get_stdout_bird +++ b/netlab/tests/get_stdout_bird @@ -6,4 +6,5 @@ cd $1 && ./birdc -l show route $2 $3 \ | strip_timestamp \ | strip_internal_route_handling_values \ | strip_mpls \ + | strip_radv_expires \ | ../tests/sort_show_route diff --git a/netlab/tests/get_stdout_protocols b/netlab/tests/get_stdout_protocols index 17cbce1e..d778de4e 100755 --- a/netlab/tests/get_stdout_protocols +++ b/netlab/tests/get_stdout_protocols @@ -6,4 +6,5 @@ cd $1 && ./birdc -l show proto "$2" \ | strip_imexport_stats \ | strip_version \ | strip_timestamp \ - | strip_timer + | strip_timer \ + | strip_dynamic_bgp_names diff --git a/netlab/tests/strip_output b/netlab/tests/strip_output index 195dc9f2..9dae4ef9 100755 --- a/netlab/tests/strip_output +++ b/netlab/tests/strip_output @@ -75,3 +75,13 @@ strip_ecmp_pref() { # workaround for change in Linux kernel behavior sed -E 's/(::\/?[0-9]* proto.*) pref medium/\1/' } + +strip_radv_expires() { + # remove the radv_expires_at timestamp value + sed -E 's/(radv_expires_at: )[0-9]+/\1<removed>/' +} + +strip_dynamic_bgp_names() { + # normalize dynamic BGP protocol names (bgp_peer001, bgp_peer002, etc. -> bgp_peer_XXX) + sed -E 's/(bgp_peer)[0-9]{3}/\1<XXX>/g' +} -- 2.43.0
Good morning, I just wanted to make sure you received the v2 of the patch correctly. I just realized that maybe something went wrong when I sent the email since it included changes on two different repositories and I sent it as a response to the same thread as the original PR. In that case, I will promptly resend the updated version with any changes needed. I also understand that you are very busy with the new releases, so do not worry if you need more time for the review. Best Regards, Matteo On Thu, 11 Dec 2025 at 10:16, Matteo Perin <matteo.perin@canonical.com> wrote:
The v2 of the patch changes the following:
- The peer route type now contains information on the interface on which the peer was discovered.
- The change above allowed to remove the need to configure the "from" option on the BGP peers channel, so it was removed.
- pxlen of net_add_peer has been set to 0, since the field is unused.
- RAdv neighbor discovery option has been renamed to router discovery.
- The peers channel in BGP has been made a regular channel (instead of a bgp_channel).
- The new BGP channel persist option has been made a protocol-wide option and it now also dictates the behavior of spawed dynamic BGP sessions.
- Documentation on changes on both RAdv and BGP has been added.
- Added testcase for the bird-tools testing suite.
Thank you Ondrej for the review, your insights (especially the advice on changing the channel type) allowed me to refactor how the implementation works a bit and make it tidier (I think). My response was delayed by a few day so I could also provide some added documentation work and finish implementing automated testing for the bird-tools project.
Let me know if you have any other suggestion on changes needed.
Best Regards, Matteo
Matteo Perin (4): Nest: Add net_peer route type to track discovered peers RAdv: Add neighbor discovery based on incoming RAs to RAdv proto BGP: Move postponed socket reloop logic from start to start_locked to achieve locking requirements BGP: Add peers channel and sessions spawn on export to achieve automatic peering
doc/bird.sgml | 65 +++++++++++++ lib/net.c | 16 ++++ lib/net.h | 32 ++++++- nest/config.Y | 3 +- nest/proto.c | 2 +- nest/protocol.h | 3 + proto/bgp/attrs.c | 51 ++++++++++ proto/bgp/bgp.c | 216 +++++++++++++++++++++++++++++++++++++++---- proto/bgp/bgp.h | 27 +++++- proto/bgp/config.Y | 12 ++- proto/bgp/packets.c | 11 +++ proto/radv/config.Y | 3 +- proto/radv/packets.c | 41 +++++++- proto/radv/radv.c | 165 ++++++++++++++++++++++++++++++++- proto/radv/radv.h | 4 + 15 files changed, 622 insertions(+), 29 deletions(-)
-- 2.43.0
On Mon, Jan 12, 2026 at 11:40:43AM +0100, Matteo Perin via Bird-users wrote:
Good morning,
I just wanted to make sure you received the v2 of the patch correctly.
Hello Yes, we received it and i am in the middle or reviewing it. Thanks for the patches. -- Elen sila lumenn' omentielvo Ondrej 'Santiago' Zajicek (email: santiago@crfreenet.org) "To err is human -- to blame it on a computer is even more so."
On Mon, Jan 12, 2026 at 11:40:43AM +0100, Matteo Perin via Bird-users wrote:
Good morning,
I just wanted to make sure you received the v2 of the patch correctly. I just realized that maybe something went wrong when I sent the email since it included changes on two different repositories and I sent it as a response to the same thread as the original PR. In that case, I will promptly resend the updated version with any changes needed.
Hello I finally got to finish reviewing and merging this patchset, sorry for delay. Here are the modified versions: https://gitlab.nic.cz/labs/bird/-/commits/dynamic-nbrs-2 https://gitlab.nic.cz/labs/bird/-/commit/4327fabff1db9b78e5e80bb8d22dc848fbf... https://gitlab.nic.cz/labs/bird/-/commit/7eda3e381414f97caed5b2cd02d6b4b3b45... https://gitlab.nic.cz/labs/bird/-/commit/0ee9f93bd076c5cc425ceaec9acedbbb7c9... (They are backported to BIRD v2, i will port them to BIRD v3 soon) The most visible change is naming, using neighbor instead of peer. That is consistent with how equivalent structures are named in other protocols in peer. Otherwise in the first two patches (Nest and RADv) there are just minor changes: Nest: - Neighbor entry formatting change - Parser grammar for neighbor entries - Filter tests - Minor fixes - Some documentation RAdv: - Moving neighbor prune to separate timer unrelated to ifaces - More idiomatic log messages, incluing TBF for warning - Reusing existing 'lifetime' attribute for neighbor entries (it still stores expire time but plan to change it in the future) With the BGP patch the situation was more complicated, i noticed several bugs in our dynamic BGP code in BIRD 3 and some parts of your patch looked like a workaround for some of these issues. - Refactoring BGP spawning code to avoid duplication - No disable and re-spawn of existing spawned protocols, this should not be necessary. We just claim existing sessions in this case. - I removed the 'persist' option. We just keep track whether the session is claimed (there is existing neighbor entry) or unclaimed. This is just makeshift now, there is a good question how to properly handle unclaimed sessions, but i think it should be consistent with how we handle dynamic BPG sessions from incoming connections, so i would prefer to keep it simple now. - Add some implicit neighbor filtering in preexport hook - Simplified some parts of channel handling - The patch is for BIRD 2 so there is no inter-thread messaging code or locking. Please take a look at these patches and reply if you are okay with them after my modifications. If you have any questions, feel free to ask. -- Elen sila lumenn' omentielvo Ondrej 'Santiago' Zajicek (email: santiago@crfreenet.org) "To err is human -- to blame it on a computer is even more so."
On Mon, 2 Mar 2026 at 06:54, Ondrej Zajicek <santiago@crfreenet.org> wrote:
Hello
I finally got to finish reviewing and merging this patchset, sorry for delay.
Here are the modified versions:
https://gitlab.nic.cz/labs/bird/-/commits/dynamic-nbrs-2
https://gitlab.nic.cz/labs/bird/-/commit/4327fabff1db9b78e5e80bb8d22dc848fbf...
https://gitlab.nic.cz/labs/bird/-/commit/7eda3e381414f97caed5b2cd02d6b4b3b45...
https://gitlab.nic.cz/labs/bird/-/commit/0ee9f93bd076c5cc425ceaec9acedbbb7c9...
(They are backported to BIRD v2, i will port them to BIRD v3 soon)
Hi Ondrej, First of all, thank you very much for taking on this very thorough rework, I really appreciate it. On Mon, 2 Mar 2026 at 06:54, Ondrej Zajicek <santiago@crfreenet.org> wrote:
Please take a look at these patches and reply if you are okay with them after my modifications. If you have any questions, feel free to ask.
Overall, I have no objections to the changes to my original patches. I think the added consistency with the existing pieces is a clear improvement, so thank you for that. On Mon, 2 Mar 2026 at 06:54, Ondrej Zajicek <santiago@crfreenet.org> wrote:
The most visible change is naming, using neighbor instead of peer. That is consistent with how equivalent structures are named in other protocols in peer.
I went back and forth on the naming for a while, at the start of the implementation, so I am entirely fine settling for 'neighbor'. On Mon, 2 Mar 2026 at 06:54, Ondrej Zajicek <santiago@crfreenet.org> wrote:
Otherwise in the first two patches (Nest and RADv) there are just minor changes:
Nest: - Neighbor entry formatting change - Parser grammar for neighbor entries - Filter tests - Minor fixes - Some documentation
Sorry for missing on adding the filter tests in the first place, it is really something I should have included myself. On Mon, 2 Mar 2026 at 06:54, Ondrej Zajicek <santiago@crfreenet.org> wrote:
RAdv: - Moving neighbor prune to separate timer unrelated to ifaces - More idiomatic log messages, incluing TBF for warning - Reusing existing 'lifetime' attribute for neighbor entries (it still stores expire time but plan to change it in the future)
Moving neighbor prune to separate timer is a good idea, my original reuse of the timer was a little too "hacky", in hindsight. On Mon, 2 Mar 2026 at 06:54, Ondrej Zajicek <santiago@crfreenet.org> wrote:
With the BGP patch the situation was more complicated, i noticed several bugs in our dynamic BGP code in BIRD 3 and some parts of your patch looked like a workaround for some of these issues.
- Refactoring BGP spawning code to avoid duplication
- No disable and re-spawn of existing spawned protocols, this should not be necessary. We just claim existing sessions in this case.
- Simplified some parts of channel handling
Yes, I had to workaround some of the original dynamic BGP behavior (especially when it came to session duplication), so my implementation came out a lot more complicated than I would have liked. Thank you very much for reworking the spawning behavior, I have seen the patch and the automatic peering implementation is much cleaner now. On Mon, 2 Mar 2026 at 06:54, Ondrej Zajicek <santiago@crfreenet.org> wrote:
- I removed the 'persist' option. We just keep track whether the session is claimed (there is existing neighbor entry) or unclaimed. This is just makeshift now, there is a good question how to properly handle unclaimed sessions, but i think it should be consistent with how we handle dynamic BPG sessions from incoming connections, so i would prefer to keep it simple now.
I am ok with dropping the persist option. The main intent was always to keep the behavior aligned with the dynamic BGP one (this is why I set persist=on as the default option). The automatic teardown was a cool option but not essential to the feature and I can see the benefit of keeping things as simple as possible. Thanks again for the work on this. Let me know if you need anything or have further comments for the BIRD 3 port. Best Regards, Matteo
participants (2)
-
Matteo Perin -
Ondrej Zajicek