diff --git a/doc/bird.sgml b/doc/bird.sgml index b0ac094ae..4e8ab1a6e 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -3807,6 +3807,7 @@ together with their appropriate channels follows. @ @@ -3828,6 +3829,17 @@ policies of The BGP channels have additional config options (together with the common ones): @@ -4072,6 +4084,12 @@ be used in explicit configuration. set per AFI/SAFI pair instead of per protocol. Default: set by protocol-wide option. + + Reconfiguration @@ -4338,6 +4356,42 @@ protocol bgp { } +

Example configuration for BGP unnumbered automatic peering using router discovery: + +

+# 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 + }; +} + + BMP

Prefix specific options diff --git a/lib/net.c b/lib/net.c index 64cf9e047..b5dee2355 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 24aae87d2..ed08f6dc8 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 681d0edd9..f12103d91 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 d66200658..e8d0021f7 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); diff --git a/nest/protocol.h b/nest/protocol.h index 036e91b48..7e439dd99 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 f072e4c99..5de044426 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 d3d61eafd..17fcd7ce0 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) { @@ -2648,6 +2807,30 @@ 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 (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 */ @@ -2770,15 +2953,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. @@ -2944,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))); @@ -4083,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 8f76e7a5f..c106db71a 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 c7316d861..9d1169f98 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 bdbeabd12..87a5b7776 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 * diff --git a/proto/radv/config.Y b/proto/radv/config.Y index 372081d28..758370afd 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 40f3c41e8..96da232b4 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 8ae411dae..58260c6d1 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 1a4afc555..4757d1645 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);