From e9002c9cee61a0d38e12546e6c04f324a62c19e2 Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Tue, 16 Jun 2026 20:47:17 +0900 Subject: [PATCH 1/9] lib: add AF_MUP and SAFI_MUP for BGP-MUP Add the AF_MUP address family and the MUP route-type prefix structures (struct mup_prefix and the per-route-type bodies) used to carry a BGP-MUP route key, and define SAFI_MUP=9 with wire codepoint 85. Signed-off-by: Yuya Kusakabe --- lib/iana_afi.h | 5 +++ lib/prefix.c | 73 ++++++++++++++++++++++++++++++++++++++++++++ lib/prefix.h | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/zebra.h | 3 +- 4 files changed, 162 insertions(+), 1 deletion(-) diff --git a/lib/iana_afi.h b/lib/iana_afi.h index 4d4687c4c48b..64c361290f83 100644 --- a/lib/iana_afi.h +++ b/lib/iana_afi.h @@ -37,6 +37,7 @@ typedef enum { IANA_SAFI_ENCAP = 7, IANA_SAFI_EVPN = 70, IANA_SAFI_BGP_LS = 71, /* BGP-LS per RFC 9552 */ + IANA_SAFI_MUP = 85, IANA_SAFI_MPLS_VPN = 128, IANA_SAFI_FLOWSPEC = 133 } iana_safi_t; @@ -102,6 +103,8 @@ static inline safi_t safi_iana2int(iana_safi_t safi) return SAFI_FLOWSPEC; case IANA_SAFI_BGP_LS: return SAFI_BGP_LS; + case IANA_SAFI_MUP: + return SAFI_MUP; case IANA_SAFI_RESERVED: return SAFI_MAX; } @@ -128,6 +131,8 @@ static inline iana_safi_t safi_int2iana(safi_t safi) return IANA_SAFI_FLOWSPEC; case SAFI_BGP_LS: return IANA_SAFI_BGP_LS; + case SAFI_MUP: + return IANA_SAFI_MUP; case SAFI_UNSPEC: case SAFI_MAX: return IANA_SAFI_RESERVED; diff --git a/lib/prefix.c b/lib/prefix.c index 1c41c0ef19d1..3803e7365e39 100644 --- a/lib/prefix.c +++ b/lib/prefix.c @@ -95,6 +95,8 @@ const char *family2str(int family) return "Ethernet"; case AF_EVPN: return "Evpn"; + case AF_MUP: + return "Mup"; } return "?"; } @@ -182,6 +184,8 @@ const char *safi2str(safi_t safi) return "flowspec"; case SAFI_BGP_LS: return "bgp-ls"; + case SAFI_MUP: + return "mup"; case SAFI_UNSPEC: case SAFI_MAX: return "unknown"; @@ -356,6 +360,8 @@ void prefix_copy(union prefixptr udest, union prefixconstptr usrc) dest->u.prefix_flowspec.ptr = (uintptr_t)temp; memcpy((void *)dest->u.prefix_flowspec.ptr, (void *)src->u.prefix_flowspec.ptr, len); + } else if (src->family == AF_MUP) { + memcpy(&dest->u.prefix_mup, &src->u.prefix_mup, sizeof(struct mup_prefix)); } else { flog_err(EC_LIB_DEVELOPMENT, "prefix_copy(): Unknown address family %d", @@ -445,6 +451,10 @@ int prefix_same(union prefixconstptr up1, union prefixconstptr up2) p2->u.prefix_flowspec.prefixlen)) return 1; } + if (p1->family == AF_MUP) + if (!memcmp(&p1->u.prefix_mup, &p2->u.prefix_mup, + sizeof(struct mup_prefix))) + return 1; } return 0; } @@ -545,6 +555,8 @@ int prefix_common_bits(union prefixconstptr ua, union prefixconstptr ub) length = ETH_ALEN; if (p1->family == AF_EVPN) length = 8 * sizeof(struct evpn_addr); + if (p1->family == AF_MUP) + length = sizeof(struct mup_prefix); if (p1->family != p2->family || !length) return -1; @@ -1096,6 +1108,63 @@ static const char *prefixevpn2str(const struct prefix_evpn *p, char *str, return str; } +static const char *prefixmup2str(const struct prefix_mup *p, char *str, int size) +{ + const struct mup_prefix *mp = &p->prefix; + char buf[INET6_ADDRSTRLEN]; + int family; + + switch (mp->route_type) { + case BGP_MUP_ISD_ROUTE: + family = IS_IPADDR_V4(&mp->isd_route.ip) ? AF_INET : AF_INET6; + snprintf(str, size, "[%d]:[%d]:[%d]:[%s/%u]", mp->arch_type, mp->route_type, + mp->length, inet_ntop(family, &mp->isd_route.ip.ip.addr, buf, sizeof(buf)), + mp->isd_route.ip_prefix_length); + break; + case BGP_MUP_DSD_ROUTE: + family = IS_IPADDR_V4(&mp->dsd_route.ip) ? AF_INET : AF_INET6; + snprintf(str, size, "[%d]:[%d]:[%d]:[%s]", mp->arch_type, mp->route_type, + mp->length, + inet_ntop(family, &mp->dsd_route.ip.ip.addr, buf, sizeof(buf))); + break; + case BGP_MUP_T1ST_ROUTE: { + const struct mup_t1st_3gpp_5g *e = &mp->t1st_route.t1st_3gpp_5g; + char buf2[INET6_ADDRSTRLEN]; + int ep_family; + int len; + + family = IS_IPADDR_V4(&mp->t1st_route.ip) ? AF_INET : AF_INET6; + ep_family = IS_IPADDR_V4(&e->endpoint_address) ? AF_INET : AF_INET6; + len = snprintf(str, size, "[%d]:[%d]:[%d]:[%s/%u]:[teid=%u][qfi=%u][ep=%s]", + mp->arch_type, mp->route_type, mp->length, + inet_ntop(family, &mp->t1st_route.ip.ip.addr, buf, sizeof(buf)), + mp->t1st_route.ip_prefix_length, e->teid, e->qfi, + inet_ntop(ep_family, &e->endpoint_address.ip.addr, buf2, + sizeof(buf2))); + if (e->source_address_length && len > 0 && len < size) { + int src_family = IS_IPADDR_V4(&e->source_address) ? AF_INET : AF_INET6; + + snprintf(str + len, size - len, "[src=%s]", + inet_ntop(src_family, &e->source_address.ip.addr, buf2, + sizeof(buf2))); + } + break; + } + case BGP_MUP_T2ST_ROUTE: + family = IS_IPADDR_V4(&mp->t2st_route.endpoint_address) ? AF_INET : AF_INET6; + snprintf(str, size, "[%d]:[%d]:[%d]:[%u/%s][teid=%u]", mp->arch_type, + mp->route_type, mp->length, mp->t2st_route.endpoint_address_length, + inet_ntop(family, &mp->t2st_route.endpoint_address.ip.addr, buf, + sizeof(buf)), + mp->t2st_route.teid); + break; + default: + snprintf(str, size, "Unsupported MUP prefix"); + break; + } + return str; +} + /* Helper function to format the prefix length in the format /xx */ static size_t format_prefixlen(char *buf, size_t l, int prefixlen, size_t buf_size) { @@ -1185,6 +1254,10 @@ const char *prefix2str(union prefixconstptr pu, char *str, int size) strlcpy(str, "FS prefix", size); break; + case AF_MUP: + prefixmup2str((const struct prefix_mup *)p, str, size); + break; + default: strlcpy(str, "UNK prefix", size); break; diff --git a/lib/prefix.h b/lib/prefix.h index f9066aaf9c4a..b78defabfd0e 100644 --- a/lib/prefix.h +++ b/lib/prefix.h @@ -158,12 +158,84 @@ struct evpn_addr { #define AF_FLOWSPEC (AF_MAX + 2) #endif +#if !defined(AF_MUP) +#define AF_MUP (AF_MAX + 3) +#endif + struct flowspec_prefix { uint8_t family; uint16_t prefixlen; /* length in bytes */ uintptr_t ptr; }; +/* MUP Architecture types (draft-ietf-bess-mup-safi 3.1). */ +enum bgp_mup_architecture_type { + BGP_MUP_ARCH_3GPP_5G = 1, +}; + +/* MUP route types (draft-ietf-bess-mup-safi 3.1). */ +enum bgp_mup_route_type { + BGP_MUP_ISD_ROUTE = 1, /* Interwork Segment Discovery */ + BGP_MUP_DSD_ROUTE = 2, /* Direct Segment Discovery */ + BGP_MUP_T1ST_ROUTE = 3, /* Type 1 Session Transformed */ + BGP_MUP_T2ST_ROUTE = 4, /* Type 2 Session Transformed */ +}; + +/* Interwork Segment Discovery route (draft 3.1.1). */ +struct mup_isd_route { + uint8_t ip_prefix_length; + struct ipaddr ip; +}; + +/* Direct Segment Discovery route (draft 3.1.2). */ +struct mup_dsd_route { + struct ipaddr ip; +}; + +/* 3gpp-5g Type 1 ST extensions (draft 3.1.3.1). */ +struct mup_t1st_3gpp_5g { + uint32_t teid; + uint8_t qfi; + uint8_t endpoint_address_length; + struct ipaddr endpoint_address; + uint8_t source_address_length; + struct ipaddr source_address; +}; + +/* Type 1 Session Transformed route (draft 3.1.3). */ +struct mup_t1st_route { + uint8_t ip_prefix_length; + struct ipaddr ip; + struct mup_t1st_3gpp_5g t1st_3gpp_5g; +}; + +/* Type 2 Session Transformed route (draft 3.1.4). */ +struct mup_t2st_route { + uint8_t endpoint_address_length; /* includes trailing TEID bits */ + struct ipaddr endpoint_address; + uint32_t teid; /* left-aligned partial TEID */ +}; + +/* MUP NLRI: the RD is the first 8 octets of every route-type payload, so it + * is part of the route key rather than carried by a parent bgp_dest. + */ +struct mup_prefix { + uint8_t arch_type; /* enum bgp_mup_architecture_type */ + uint16_t route_type; /* enum bgp_mup_route_type */ + uint8_t length; + uint8_t rd[8]; + union { + struct mup_isd_route _isd_route; + struct mup_dsd_route _dsd_route; + struct mup_t1st_route _t1st_route; + struct mup_t2st_route _t2st_route; + } u; +#define isd_route u._isd_route +#define dsd_route u._dsd_route +#define t1st_route u._t1st_route +#define t2st_route u._t2st_route +}; + /* FRR generic prefix structure. */ struct prefix { uint8_t family; @@ -182,6 +254,7 @@ struct prefix { uintptr_t ptr; struct evpn_addr prefix_evpn; /* AF_EVPN */ struct flowspec_prefix prefix_flowspec; /* AF_FLOWSPEC */ + struct mup_prefix prefix_mup; /* AF_MUP */ } u __attribute__((aligned(8))); }; @@ -279,6 +352,13 @@ struct prefix_fs { struct flowspec_prefix prefix __attribute__((aligned(8))); }; +/* MUP prefix (draft-ietf-bess-mup-safi). */ +struct prefix_mup { + uint8_t family; + uint16_t prefixlen; + struct mup_prefix prefix __attribute__((aligned(8))); +}; + struct prefix_sg { uint8_t family; uint16_t prefixlen; @@ -294,6 +374,7 @@ union prefixptr { uniontype(prefixptr, struct prefix_evpn, evp) uniontype(prefixptr, struct prefix_fs, fs) uniontype(prefixptr, struct prefix_rd, rd) + uniontype(prefixptr, struct prefix_mup, mup) } TRANSPARENT_UNION; union prefixconstptr { @@ -303,6 +384,7 @@ union prefixconstptr { uniontype(prefixconstptr, const struct prefix_evpn, evp) uniontype(prefixconstptr, const struct prefix_fs, fs) uniontype(prefixconstptr, const struct prefix_rd, rd) + uniontype(prefixconstptr, const struct prefix_mup, mup) } TRANSPARENT_UNION; /* clang-format on */ diff --git a/lib/zebra.h b/lib/zebra.h index 3985b80f78fb..f8f0c761cf00 100644 --- a/lib/zebra.h +++ b/lib/zebra.h @@ -184,7 +184,8 @@ typedef enum { SAFI_LABELED_UNICAST = 6, SAFI_FLOWSPEC = 7, SAFI_BGP_LS = 8, /* BGP-LS (RFC 9552) */ - SAFI_MAX = 9 + SAFI_MUP = 9, /* BGP-MUP (draft-ietf-bess-mup-safi) */ + SAFI_MAX = 10 } safi_t; #define FOREACH_AFI_SAFI(afi, safi) \ From ef8faba4f59ee075603de0603e591edae03eebbd Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Tue, 16 Jun 2026 20:47:55 +0900 Subject: [PATCH 2/9] bgpd: register SAFI_MUP for BGP-MUP Add SAFI_MUP arms to the existing safi_t switches to keep -Wswitch-enum clean. Protocol-specific logic is marked with TODO placeholders and implemented in subsequent commits. Signed-off-by: Yuya Kusakabe --- bgpd/bgp_attr.c | 13 +++++++++++- bgpd/bgp_open.c | 35 ++++++++++++++++++------------- bgpd/bgp_packet.c | 3 +++ bgpd/bgp_route.c | 1 + bgpd/bgp_vty.c | 6 ++++++ bgpd/bgpd.c | 43 +++++++++++++++----------------------- bgpd/bgpd.h | 8 +++++++ bgpd/rfapi/rfapi_import.c | 3 +++ bgpd/rfapi/rfapi_monitor.c | 2 ++ 9 files changed, 73 insertions(+), 41 deletions(-) diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index bd965c7c4953..ebdaf5d06c06 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -4959,6 +4959,9 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, stream_putc(s, IPV4_MAX_BYTELEN); stream_put_ipv4(s, attr->mp_nexthop_global_in.s_addr); break; + case SAFI_MUP: + /* TODO: implemented in subsequent commit */ + break; case SAFI_UNSPEC: case SAFI_MAX: assert(!"SAFI's UNSPEC or MAX being specified are a DEV ESCAPE"); @@ -4970,7 +4973,8 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, case SAFI_UNICAST: case SAFI_MULTICAST: case SAFI_LABELED_UNICAST: - case SAFI_EVPN: { + case SAFI_EVPN: + case SAFI_MUP: { if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { stream_putc(s, @@ -5220,6 +5224,9 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, const st case SAFI_ENCAP: assert(!"Please add proper encoding of SAFI_ENCAP"); break; + case SAFI_MUP: + /* TODO: implemented in subsequent commit */ + break; } } @@ -5263,6 +5270,10 @@ size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, /* TODO: add explaination */ size = 0; break; + case SAFI_MUP: + /* TODO: implemented in subsequent commit */ + size = 0; + break; } return size; diff --git a/bgpd/bgp_open.c b/bgpd/bgp_open.c index adcdec68b629..16da11bcae58 100644 --- a/bgpd/bgp_open.c +++ b/bgpd/bgp_open.c @@ -219,6 +219,11 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, "capabilityErrorMultiProtocolSafi", "BGP-LS"); break; + case SAFI_MUP: + json_object_string_add(json_cap, + "capabilityErrorMultiProtocolSafi", + "BGP-MUP"); + break; case SAFI_UNSPEC: case SAFI_MAX: json_object_int_add( @@ -274,6 +279,9 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, case SAFI_BGP_LS: vty_out(vty, "SAFI BGP-LS"); break; + case SAFI_MUP: + vty_out(vty, "SAFI BGP-MUP"); + break; case SAFI_UNSPEC: case SAFI_MAX: vty_out(vty, "SAFI Unknown %d ", @@ -1544,20 +1552,19 @@ int bgp_open_option_parse(struct peer_connection *connection, uint16_t length, i error. */ if (*mp_capability && !CHECK_FLAG(peer->flags, PEER_FLAG_OVERRIDE_CAPABILITY)) { - if (!peer->afc_nego[AFI_IP][SAFI_UNICAST] - && !peer->afc_nego[AFI_IP][SAFI_MULTICAST] - && !peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] - && !peer->afc_nego[AFI_IP][SAFI_MPLS_VPN] - && !peer->afc_nego[AFI_IP][SAFI_ENCAP] - && !peer->afc_nego[AFI_IP][SAFI_FLOWSPEC] - && !peer->afc_nego[AFI_IP6][SAFI_UNICAST] - && !peer->afc_nego[AFI_IP6][SAFI_MULTICAST] - && !peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] - && !peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] - && !peer->afc_nego[AFI_IP6][SAFI_ENCAP] - && !peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] - && !peer->afc_nego[AFI_L2VPN][SAFI_EVPN] - && !peer->afc_nego[AFI_BGP_LS][SAFI_BGP_LS]) { + if (!peer->afc_nego[AFI_IP][SAFI_UNICAST] && + !peer->afc_nego[AFI_IP][SAFI_MULTICAST] && + !peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] && + !peer->afc_nego[AFI_IP][SAFI_MPLS_VPN] && !peer->afc_nego[AFI_IP][SAFI_ENCAP] && + !peer->afc_nego[AFI_IP][SAFI_FLOWSPEC] && !peer->afc_nego[AFI_IP][SAFI_MUP] && + !peer->afc_nego[AFI_IP6][SAFI_UNICAST] && + !peer->afc_nego[AFI_IP6][SAFI_MULTICAST] && + !peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] && + !peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] && + !peer->afc_nego[AFI_IP6][SAFI_ENCAP] && + !peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] && + !peer->afc_nego[AFI_IP6][SAFI_MUP] && !peer->afc_nego[AFI_L2VPN][SAFI_EVPN] && + !peer->afc_nego[AFI_BGP_LS][SAFI_BGP_LS]) { flog_err(EC_BGP_PKT_OPEN, "%s [Error] Configured AFI/SAFIs do not overlap with received MP capabilities", peer->host); diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index aa88d5cd625f..3ba08060f36b 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -330,6 +330,9 @@ int bgp_nlri_parse(struct peer *peer, struct attr *attr, return bgp_nlri_parse_flowspec(peer, attr, packet, mp_withdraw); case SAFI_BGP_LS: return bgp_nlri_parse_ls(peer, mp_withdraw ? NULL : attr, packet); + case SAFI_MUP: + /* TODO: implemented in subsequent commit */ + return BGP_NLRI_PARSE_ERROR; } return BGP_NLRI_PARSE_ERROR; } diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index c5a8a9d3248c..8372f5f4bfd8 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -14876,6 +14876,7 @@ const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, case SAFI_EVPN: return (struct prefix_rd *)(bgp_dest_get_prefix(dest)); case SAFI_BGP_LS: + case SAFI_MUP: case SAFI_UNSPEC: case SAFI_UNICAST: case SAFI_MULTICAST: diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 068b25d682b3..ecbe705469a2 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -183,6 +183,7 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) case SAFI_UNSPEC: case SAFI_ENCAP: case SAFI_EVPN: + case SAFI_MUP: case SAFI_MAX: /* not expected */ return BGP_IPV4_NODE; @@ -204,6 +205,7 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) case SAFI_UNSPEC: case SAFI_ENCAP: case SAFI_EVPN: + case SAFI_MUP: case SAFI_MAX: /* not expected and the return value seems wrong */ return BGP_IPV4_NODE; @@ -628,6 +630,7 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) case SAFI_BGP_LS: case SAFI_UNSPEC: case SAFI_EVPN: + case SAFI_MUP: case SAFI_MAX: return "unknown-afi/safi"; } @@ -649,6 +652,7 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) case SAFI_BGP_LS: case SAFI_UNSPEC: case SAFI_EVPN: + case SAFI_MUP: case SAFI_MAX: return "unknown-afi/safi"; } @@ -664,6 +668,7 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) case SAFI_ENCAP: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_MUP: case SAFI_UNSPEC: case SAFI_MAX: return "unknown-afi/safi"; @@ -680,6 +685,7 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: case SAFI_EVPN: + case SAFI_MUP: case SAFI_UNSPEC: case SAFI_MAX: return "unknown-afi/safi"; diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index ea28e8cf4e12..5d346e9dbc27 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -5183,18 +5183,14 @@ enum bgp_peer_active peer_active(struct peer_connection *connection) } } - if (peer->afc[AFI_IP][SAFI_UNICAST] || peer->afc[AFI_IP][SAFI_MULTICAST] - || peer->afc[AFI_IP][SAFI_LABELED_UNICAST] - || peer->afc[AFI_IP][SAFI_MPLS_VPN] || peer->afc[AFI_IP][SAFI_ENCAP] - || peer->afc[AFI_IP][SAFI_FLOWSPEC] - || peer->afc[AFI_IP6][SAFI_UNICAST] - || peer->afc[AFI_IP6][SAFI_MULTICAST] - || peer->afc[AFI_IP6][SAFI_LABELED_UNICAST] - || peer->afc[AFI_IP6][SAFI_MPLS_VPN] - || peer->afc[AFI_IP6][SAFI_ENCAP] - || peer->afc[AFI_IP6][SAFI_FLOWSPEC] - || peer->afc[AFI_L2VPN][SAFI_EVPN] - || peer->afc[AFI_BGP_LS][SAFI_BGP_LS]) + if (peer->afc[AFI_IP][SAFI_UNICAST] || peer->afc[AFI_IP][SAFI_MULTICAST] || + peer->afc[AFI_IP][SAFI_LABELED_UNICAST] || peer->afc[AFI_IP][SAFI_MPLS_VPN] || + peer->afc[AFI_IP][SAFI_ENCAP] || peer->afc[AFI_IP][SAFI_FLOWSPEC] || + peer->afc[AFI_IP][SAFI_MUP] || peer->afc[AFI_IP6][SAFI_UNICAST] || + peer->afc[AFI_IP6][SAFI_MULTICAST] || peer->afc[AFI_IP6][SAFI_LABELED_UNICAST] || + peer->afc[AFI_IP6][SAFI_MPLS_VPN] || peer->afc[AFI_IP6][SAFI_ENCAP] || + peer->afc[AFI_IP6][SAFI_FLOWSPEC] || peer->afc[AFI_IP6][SAFI_MUP] || + peer->afc[AFI_L2VPN][SAFI_EVPN] || peer->afc[AFI_BGP_LS][SAFI_BGP_LS]) return BGP_PEER_ACTIVE; return BGP_PEER_AF_UNCONFIGURED; @@ -5203,20 +5199,15 @@ enum bgp_peer_active peer_active(struct peer_connection *connection) /* If peer is negotiated at least one address family return 1. */ bool peer_active_nego(struct peer *peer) { - if (peer->afc_nego[AFI_IP][SAFI_UNICAST] - || peer->afc_nego[AFI_IP][SAFI_MULTICAST] - || peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] - || peer->afc_nego[AFI_IP][SAFI_MPLS_VPN] - || peer->afc_nego[AFI_IP][SAFI_ENCAP] - || peer->afc_nego[AFI_IP][SAFI_FLOWSPEC] - || peer->afc_nego[AFI_IP6][SAFI_UNICAST] - || peer->afc_nego[AFI_IP6][SAFI_MULTICAST] - || peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] - || peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] - || peer->afc_nego[AFI_IP6][SAFI_ENCAP] - || peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] - || peer->afc_nego[AFI_L2VPN][SAFI_EVPN] - || peer->afc_nego[AFI_BGP_LS][SAFI_BGP_LS]) + if (peer->afc_nego[AFI_IP][SAFI_UNICAST] || peer->afc_nego[AFI_IP][SAFI_MULTICAST] || + peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] || + peer->afc_nego[AFI_IP][SAFI_MPLS_VPN] || peer->afc_nego[AFI_IP][SAFI_ENCAP] || + peer->afc_nego[AFI_IP][SAFI_FLOWSPEC] || peer->afc_nego[AFI_IP][SAFI_MUP] || + peer->afc_nego[AFI_IP6][SAFI_UNICAST] || peer->afc_nego[AFI_IP6][SAFI_MULTICAST] || + peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] || + peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] || peer->afc_nego[AFI_IP6][SAFI_ENCAP] || + peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] || peer->afc_nego[AFI_IP6][SAFI_MUP] || + peer->afc_nego[AFI_L2VPN][SAFI_EVPN] || peer->afc_nego[AFI_BGP_LS][SAFI_BGP_LS]) return true; return false; } diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index feb4fe70e984..67691aa0a341 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -107,6 +107,8 @@ enum bgp_af_index { BGP_AF_IPV4_FLOWSPEC, BGP_AF_IPV6_FLOWSPEC, BGP_AF_BGP_LS, + BGP_AF_IPV4_MUP, + BGP_AF_IPV6_MUP, BGP_AF_MAX }; @@ -3010,6 +3012,8 @@ static inline int afindex(afi_t afi, safi_t safi) return BGP_AF_IPV4_ENCAP; case SAFI_FLOWSPEC: return BGP_AF_IPV4_FLOWSPEC; + case SAFI_MUP: + return BGP_AF_IPV4_MUP; case SAFI_BGP_LS: case SAFI_EVPN: case SAFI_UNSPEC: @@ -3031,6 +3035,8 @@ static inline int afindex(afi_t afi, safi_t safi) return BGP_AF_IPV6_ENCAP; case SAFI_FLOWSPEC: return BGP_AF_IPV6_FLOWSPEC; + case SAFI_MUP: + return BGP_AF_IPV6_MUP; case SAFI_BGP_LS: case SAFI_EVPN: case SAFI_UNSPEC: @@ -3049,6 +3055,7 @@ static inline int afindex(afi_t afi, safi_t safi) case SAFI_MPLS_VPN: case SAFI_ENCAP: case SAFI_FLOWSPEC: + case SAFI_MUP: case SAFI_UNSPEC: case SAFI_MAX: return BGP_AF_MAX; @@ -3065,6 +3072,7 @@ static inline int afindex(afi_t afi, safi_t safi) case SAFI_ENCAP: case SAFI_EVPN: case SAFI_FLOWSPEC: + case SAFI_MUP: case SAFI_UNSPEC: case SAFI_MAX: return BGP_AF_MAX; diff --git a/bgpd/rfapi/rfapi_import.c b/bgpd/rfapi/rfapi_import.c index ddf6e769e97b..56e6f9547171 100644 --- a/bgpd/rfapi/rfapi_import.c +++ b/bgpd/rfapi/rfapi_import.c @@ -240,6 +240,7 @@ void rfapiCheckRefcount(struct agg_node *rn, safi_t safi, int lockoffset) case SAFI_EVPN: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_MUP: case SAFI_MAX: assert(!"Passed in safi should be impossible"); } @@ -3833,6 +3834,7 @@ rfapiBgpInfoFilteredImportFunction(safi_t safi) case SAFI_EVPN: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_MUP: case SAFI_MAX: /* not expected */ flog_err(EC_LIB_DEVELOPMENT, "%s: bad safi %d", __func__, safi); @@ -4079,6 +4081,7 @@ static void rfapiProcessPeerDownRt(struct peer *peer, case SAFI_EVPN: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_MUP: case SAFI_MAX: /* Suppress uninitialized variable warning */ rt = NULL; diff --git a/bgpd/rfapi/rfapi_monitor.c b/bgpd/rfapi/rfapi_monitor.c index 45ada522029b..8fe1022ffccd 100644 --- a/bgpd/rfapi/rfapi_monitor.c +++ b/bgpd/rfapi/rfapi_monitor.c @@ -238,6 +238,7 @@ void rfapiMonitorExtraFlush(safi_t safi, struct agg_node *rn) case SAFI_EVPN: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_MUP: case SAFI_MAX: assert(0); } @@ -307,6 +308,7 @@ void rfapiMonitorExtraPrune(safi_t safi, struct agg_node *rn) case SAFI_EVPN: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_MUP: case SAFI_MAX: assert(0); } From 9fc6f65209c330e36395a6cebe078e221a31b1d8 Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Mon, 4 May 2026 22:35:39 +0900 Subject: [PATCH 3/9] bgpd: parse and encode BGP-MUP NLRI Introduce bgp_mup.{c,h} with the encode/decode for the four draft-ietf-bess-mup-safi route types (3gpp-5g architecture). A malformed NLRI of a known route type is treated as withdraw per RFC 7606 and skipped; unknown architecture or route types are ignored. Only a corrupt outer header resets the session. Signed-off-by: Yuya Kusakabe --- bgpd/bgp_attr.c | 18 +- bgpd/bgp_errors.c | 7 + bgpd/bgp_errors.h | 1 + bgpd/bgp_mup.c | 500 ++++++++++++++++++++++++++++++++++++++++++++++ bgpd/bgp_mup.h | 38 ++++ bgpd/bgp_packet.c | 4 +- bgpd/bgp_route.h | 1 + bgpd/subdir.am | 2 + 8 files changed, 564 insertions(+), 7 deletions(-) create mode 100644 bgpd/bgp_mup.c create mode 100644 bgpd/bgp_mup.h diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index ebdaf5d06c06..6a490970c9b4 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -44,6 +44,7 @@ #include "bgp_vnc_types.h" #endif #include "bgp_evpn.h" +#include "bgp_mup.h" #include "bgp_flowspec_private.h" #include "bgp_mac.h" #include "bgpd/bgp_ls_nlri.h" @@ -4960,7 +4961,14 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, stream_put_ipv4(s, attr->mp_nexthop_global_in.s_addr); break; case SAFI_MUP: - /* TODO: implemented in subsequent commit */ + /* Keep whichever nexthop family the route carries. */ + if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV4) { + stream_putc(s, BGP_ATTR_NHLEN_IPV4); + stream_put(s, &attr->mp_nexthop_global_in, 4); + } else { + stream_putc(s, IPV6_MAX_BYTELEN); + stream_put(s, &attr->mp_nexthop_global, IPV6_MAX_BYTELEN); + } break; case SAFI_UNSPEC: case SAFI_MAX: @@ -5225,7 +5233,7 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, const st assert(!"Please add proper encoding of SAFI_ENCAP"); break; case SAFI_MUP: - /* TODO: implemented in subsequent commit */ + bgp_mup_encode_prefix(s, afi, p, prd, addpath_capable, addpath_tx_id); break; } } @@ -5271,8 +5279,7 @@ size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, size = 0; break; case SAFI_MUP: - /* TODO: implemented in subsequent commit */ - size = 0; + size = bgp_mup_prefix_size(p); break; } @@ -5803,7 +5810,8 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, struct strea if ((afi == AFI_IP || afi == AFI_IP6)) { struct bgp_attr_srv6_l3service *srv6_l3service = NULL; - if (safi == SAFI_MPLS_VPN && bgp_attr_get_srv6_l3service(attr)) + if ((safi == SAFI_MPLS_VPN || safi == SAFI_MUP) && + bgp_attr_get_srv6_l3service(attr)) srv6_l3service = bgp_attr_get_srv6_l3service(attr); else if (peer_af_flag_check(peer, afi, safi, PEER_FLAG_CONFIG_ENCAPSULATION_SRV6_RELAX) || diff --git a/bgpd/bgp_errors.c b/bgpd/bgp_errors.c index d92cbcc674b3..930fff179763 100644 --- a/bgpd/bgp_errors.c +++ b/bgpd/bgp_errors.c @@ -575,6 +575,13 @@ static struct log_ref ferr_bgp_err[] = { .description = "BGP-LS NLRI or attribute packet parsing/encoding error", .suggestion = "Check that BGP-LS peer is sending valid packets per RFC 9552. May indicate interoperability issue or malformed data.", }, + { + .code = EC_BGP_MUP_PACKET, + .title = "BGP-MUP packet error", + .description = "BGP-MUP NLRI or attribute packet parsing/encoding error", + .suggestion = + "Check that the BGP-MUP peer is sending valid packets per draft-ietf-bess-mup-safi. May indicate an interoperability issue or malformed data.", + }, { .code = END_FERR, } diff --git a/bgpd/bgp_errors.h b/bgpd/bgp_errors.h index 0d12fc0c5fa5..3aa2b0347ccb 100644 --- a/bgpd/bgp_errors.h +++ b/bgpd/bgp_errors.h @@ -107,6 +107,7 @@ enum bgp_log_refs { EC_BGP_LABEL_POOL_INSERT_FAIL, EC_BGP_TTL_SECURITY_FAIL, EC_BGP_LS_PACKET, + EC_BGP_MUP_PACKET, }; extern void bgp_error_init(void); diff --git a/bgpd/bgp_mup.c b/bgpd/bgp_mup.c new file mode 100644 index 000000000000..854614267f2a --- /dev/null +++ b/bgpd/bgp_mup.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP-MUP NLRI handling for SAFI=MUP (draft-ietf-bess-mup-safi). + * Copyright (C) 2026 Yuya Kusakabe + */ +#include + +#include "prefix.h" +#include "stream.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_mup.h" +#include "bgpd/bgp_rd.h" +#include "bgpd/bgp_route.h" + +/* On-wire size of one BGP-MUP NLRI: fixed header plus the route body. */ +size_t bgp_mup_prefix_size(const struct prefix *p) +{ + const struct prefix_mup *mp = (const struct prefix_mup *)p; + + return BGP_MUP_HDR_BYTES + mp->prefix.length; +} + +/* Encode a BGP-MUP prefix into an MP_REACH/MP_UNREACH NLRI stream. */ +void bgp_mup_encode_prefix(struct stream *s, afi_t afi, const struct prefix *p, + const struct prefix_rd *prd, bool addpath_capable, + uint32_t addpath_tx_id) +{ + const struct prefix_mup *pm = (const struct prefix_mup *)p; + const struct mup_prefix *mp = &pm->prefix; + uint8_t prefix_octets; + uint8_t addr_octets; + uint8_t total_len = 0; + size_t len_pos; + + /* prd is unused: the RD lives inside struct mup_prefix. */ + if (addpath_capable) + stream_putl(s, addpath_tx_id); + + stream_putc(s, mp->arch_type); + stream_putw(s, mp->route_type); + + /* Patch the Length octet once the route body length is known. */ + len_pos = stream_get_endp(s); + stream_putc(s, 0); + + switch (mp->route_type) { + case BGP_MUP_ISD_ROUTE: + /* RD + Prefix Length + Prefix. */ + prefix_octets = PSIZE(mp->isd_route.ip_prefix_length); + stream_put(s, mp->rd, RD_BYTES); + stream_putc(s, mp->isd_route.ip_prefix_length); + stream_put(s, &mp->isd_route.ip.ip.addr, prefix_octets); + total_len = RD_BYTES + BGP_MUP_PREFIX_LEN_BYTES + prefix_octets; + break; + + case BGP_MUP_DSD_ROUTE: + /* RD + Address. */ + addr_octets = (afi == AFI_IP) ? IPV4_MAX_BYTELEN : IPV6_MAX_BYTELEN; + stream_put(s, mp->rd, RD_BYTES); + stream_put(s, &mp->dsd_route.ip.ip.addr, addr_octets); + total_len = RD_BYTES + addr_octets; + break; + + case BGP_MUP_T1ST_ROUTE: { + /* RD + Prefix Length + Prefix + TEID + QFI + Endpoint Length + * + Endpoint + Source Length [+ Source]. + */ + const struct mup_t1st_3gpp_5g *e = &mp->t1st_route.t1st_3gpp_5g; + uint8_t ep_octets; + uint8_t src_octets; + + prefix_octets = PSIZE(mp->t1st_route.ip_prefix_length); + ep_octets = e->endpoint_address_length / 8; + src_octets = e->source_address_length / 8; + + stream_put(s, mp->rd, RD_BYTES); + stream_putc(s, mp->t1st_route.ip_prefix_length); + stream_put(s, &mp->t1st_route.ip.ip.addr, prefix_octets); + stream_putl(s, e->teid); + stream_putc(s, e->qfi); + stream_putc(s, e->endpoint_address_length); + stream_put(s, &e->endpoint_address.ip.addr, ep_octets); + stream_putc(s, e->source_address_length); + if (src_octets) + stream_put(s, &e->source_address.ip.addr, src_octets); + + total_len = RD_BYTES + BGP_MUP_PREFIX_LEN_BYTES + prefix_octets + + BGP_MUP_TEID_BYTES + BGP_MUP_QFI_BYTES + BGP_MUP_ADDR_LEN_BYTES + + ep_octets + BGP_MUP_ADDR_LEN_BYTES + src_octets; + break; + } + + case BGP_MUP_T2ST_ROUTE: { + /* RD + Endpoint Length + Endpoint + TEID (0..4 trailing octets). */ + uint8_t teid_bits; + uint8_t teid_octets; + uint32_t teid_be; + + addr_octets = (afi == AFI_IP) ? IPV4_MAX_BYTELEN : IPV6_MAX_BYTELEN; + teid_bits = (mp->t2st_route.endpoint_address_length > addr_octets * 8) + ? mp->t2st_route.endpoint_address_length - (addr_octets * 8) + : 0; + /* draft 3.1.4.1: the TEID field is at most 4 octets. */ + if (teid_bits > BGP_MUP_TEID_BYTES * 8) + teid_bits = BGP_MUP_TEID_BYTES * 8; + teid_octets = (teid_bits + 7) / 8; + teid_be = htonl(mp->t2st_route.teid); + + stream_put(s, mp->rd, RD_BYTES); + stream_putc(s, mp->t2st_route.endpoint_address_length); + stream_put(s, &mp->t2st_route.endpoint_address.ip.addr, addr_octets); + if (teid_octets) + stream_put(s, &teid_be, teid_octets); + + total_len = RD_BYTES + BGP_MUP_ADDR_LEN_BYTES + addr_octets + teid_octets; + break; + } + + default: + break; + } + + stream_putc_at(s, len_pos, total_len); +} + +/* Fill in the common prefix_mup fields for a parsed BGP-MUP NLRI. */ +static inline void bgp_mup_prefix_init(struct prefix_mup *p, uint16_t route_type, int psize) +{ + p->family = AF_MUP; + p->prefixlen = BGP_MUP_ROUTE_PREFIXLEN; + p->prefix.arch_type = BGP_MUP_ARCH_3GPP_5G; + p->prefix.route_type = route_type; + p->prefix.length = psize; +} + +static int bgp_mup_process_isd_route(struct peer *peer, afi_t afi, safi_t safi, struct attr *attr, + uint8_t *pfx, int psize, uint32_t addpath_id) +{ + struct prefix_rd prd = {}; + struct prefix_mup p = {}; + uint8_t prefix_len; + uint8_t prefix_octets; + + if (psize < RD_BYTES + BGP_MUP_PREFIX_LEN_BYTES) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP ISD NLRI invalid length %d", + peer->bgp->vrf_id, peer->host, psize); + return -1; + } + + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(prd.val, pfx, RD_BYTES); + + prefix_len = pfx[RD_BYTES]; + if ((afi == AFI_IP && prefix_len > IPV4_MAX_BITLEN) || + (afi == AFI_IP6 && prefix_len > IPV6_MAX_BITLEN)) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP ISD NLRI bad prefix length %u", + peer->bgp->vrf_id, peer->host, prefix_len); + return -1; + } + + prefix_octets = PSIZE(prefix_len); + if (psize - (RD_BYTES + BGP_MUP_PREFIX_LEN_BYTES) != prefix_octets) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP ISD NLRI prefix length mismatch", + peer->bgp->vrf_id, peer->host); + return -1; + } + + bgp_mup_prefix_init(&p, BGP_MUP_ISD_ROUTE, psize); + memcpy(p.prefix.rd, prd.val, RD_BYTES); + p.prefix.isd_route.ip_prefix_length = prefix_len; + p.prefix.isd_route.ip.ipa_type = (afi == AFI_IP) ? IPADDR_V4 : IPADDR_V6; + memcpy(&p.prefix.isd_route.ip.ip.addr, pfx + RD_BYTES + BGP_MUP_PREFIX_LEN_BYTES, + prefix_octets); + + if (attr) + bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, NULL, 0, 0, NULL); + else + bgp_withdraw(peer, (struct prefix *)&p, addpath_id, afi, safi, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, NULL, 0); + return 0; +} + +static int bgp_mup_process_dsd_route(struct peer *peer, afi_t afi, safi_t safi, struct attr *attr, + uint8_t *pfx, int psize, uint32_t addpath_id) +{ + struct prefix_rd prd = {}; + struct prefix_mup p = {}; + uint8_t addr_octets; + + if ((afi == AFI_IP && psize != RD_BYTES + IPV4_MAX_BYTELEN) || + (afi == AFI_IP6 && psize != RD_BYTES + IPV6_MAX_BYTELEN)) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP DSD NLRI invalid length %d", + peer->bgp->vrf_id, peer->host, psize); + return -1; + } + + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(prd.val, pfx, RD_BYTES); + + bgp_mup_prefix_init(&p, BGP_MUP_DSD_ROUTE, psize); + memcpy(p.prefix.rd, prd.val, RD_BYTES); + + if (afi == AFI_IP) { + addr_octets = IPV4_MAX_BYTELEN; + p.prefix.dsd_route.ip.ipa_type = IPADDR_V4; + } else { + addr_octets = IPV6_MAX_BYTELEN; + p.prefix.dsd_route.ip.ipa_type = IPADDR_V6; + } + memcpy(&p.prefix.dsd_route.ip.ip.addr, pfx + RD_BYTES, addr_octets); + + if (attr) + bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, NULL, 0, 0, NULL); + else + bgp_withdraw(peer, (struct prefix *)&p, addpath_id, afi, safi, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, NULL, 0); + return 0; +} + +static int bgp_mup_process_t1st_route(struct peer *peer, afi_t afi, safi_t safi, struct attr *attr, + uint8_t *pfx, int psize, uint32_t addpath_id) +{ + struct prefix_rd prd = {}; + struct prefix_mup p = {}; + struct mup_t1st_3gpp_5g *ext; + uint8_t prefix_len; + uint8_t prefix_octets; + uint8_t ep_len, src_len; + uint8_t ep_octets, src_octets; + int off; + + /* Minimum: RD + Prefix Length + TEID + QFI + Endpoint Length + + * Source Length, before any prefix or endpoint address bytes. + */ + if (psize < RD_BYTES + BGP_MUP_PREFIX_LEN_BYTES + BGP_MUP_TEID_BYTES + BGP_MUP_QFI_BYTES + + BGP_MUP_ADDR_LEN_BYTES + BGP_MUP_ADDR_LEN_BYTES) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T1ST NLRI invalid length %d", + peer->bgp->vrf_id, peer->host, psize); + return -1; + } + + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(prd.val, pfx, RD_BYTES); + off = RD_BYTES; + + prefix_len = pfx[off++]; + if ((afi == AFI_IP && prefix_len > IPV4_MAX_BITLEN) || + (afi == AFI_IP6 && prefix_len > IPV6_MAX_BITLEN)) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T1ST NLRI bad prefix length %u", + peer->bgp->vrf_id, peer->host, prefix_len); + return -1; + } + prefix_octets = PSIZE(prefix_len); + + bgp_mup_prefix_init(&p, BGP_MUP_T1ST_ROUTE, psize); + memcpy(p.prefix.rd, prd.val, RD_BYTES); + p.prefix.t1st_route.ip_prefix_length = prefix_len; + p.prefix.t1st_route.ip.ipa_type = (afi == AFI_IP) ? IPADDR_V4 : IPADDR_V6; + + if (off + prefix_octets + BGP_MUP_TEID_BYTES + BGP_MUP_QFI_BYTES + BGP_MUP_ADDR_LEN_BYTES > + psize) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T1ST NLRI truncated", + peer->bgp->vrf_id, peer->host); + return -1; + } + memcpy(&p.prefix.t1st_route.ip.ip.addr, pfx + off, prefix_octets); + off += prefix_octets; + + ext = &p.prefix.t1st_route.t1st_3gpp_5g; + memcpy(&ext->teid, pfx + off, BGP_MUP_TEID_BYTES); + ext->teid = ntohl(ext->teid); + off += BGP_MUP_TEID_BYTES; + /* draft 3.1.3.1: TEID MUST NOT be 0. */ + if (ext->teid == 0) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T1ST NLRI TEID=0", + peer->bgp->vrf_id, peer->host); + return -1; + } + ext->qfi = pfx[off++]; + /* draft 3.1.3.1: Endpoint Length must be a full host address. */ + ep_len = pfx[off++]; + if ((afi == AFI_IP && ep_len != IPV4_MAX_BITLEN) || + (afi == AFI_IP6 && ep_len != IPV6_MAX_BITLEN)) { + flog_err(EC_BGP_MUP_PACKET, + "%u:%s - Rx BGP-MUP T1ST NLRI invalid endpoint length %u for AFI %u", + peer->bgp->vrf_id, peer->host, ep_len, afi); + return -1; + } + ext->endpoint_address_length = ep_len; + ep_octets = ep_len / 8; + if (off + ep_octets + BGP_MUP_ADDR_LEN_BYTES > psize) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T1ST NLRI truncated endpoint", + peer->bgp->vrf_id, peer->host); + return -1; + } + ext->endpoint_address.ipa_type = (afi == AFI_IP) ? IPADDR_V4 : IPADDR_V6; + memcpy(&ext->endpoint_address.ip.addr, pfx + off, ep_octets); + off += ep_octets; + + /* Source Length 0 means no Source Address follows. */ + src_len = pfx[off++]; + if (src_len != 0 && ((afi == AFI_IP && src_len != IPV4_MAX_BITLEN) || + (afi == AFI_IP6 && src_len != IPV6_MAX_BITLEN))) { + flog_err(EC_BGP_MUP_PACKET, + "%u:%s - Rx BGP-MUP T1ST NLRI invalid source length %u for AFI %u", + peer->bgp->vrf_id, peer->host, src_len, afi); + return -1; + } + ext->source_address_length = src_len; + if (src_len) { + src_octets = src_len / 8; + if (off + src_octets > psize) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T1ST NLRI truncated source", + peer->bgp->vrf_id, peer->host); + return -1; + } + ext->source_address.ipa_type = (afi == AFI_IP) ? IPADDR_V4 : IPADDR_V6; + memcpy(&ext->source_address.ip.addr, pfx + off, src_octets); + } + + if (attr) + bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, NULL, 0, 0, NULL); + else + bgp_withdraw(peer, (struct prefix *)&p, addpath_id, afi, safi, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, NULL, 0); + return 0; +} + +static int bgp_mup_process_t2st_route(struct peer *peer, afi_t afi, safi_t safi, struct attr *attr, + uint8_t *pfx, int psize, uint32_t addpath_id) +{ + struct prefix_rd prd = {}; + struct prefix_mup p = {}; + uint8_t addr_octets; + uint8_t teid_bits; + uint8_t teid_octets; + uint32_t teid_be = 0; + uint8_t ea_len; + + if (psize < RD_BYTES + BGP_MUP_ADDR_LEN_BYTES + IPV4_MAX_BYTELEN) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T2ST NLRI invalid length %d", + peer->bgp->vrf_id, peer->host, psize); + return -1; + } + + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(prd.val, pfx, RD_BYTES); + + ea_len = pfx[RD_BYTES]; + if ((afi == AFI_IP && ea_len > IPV4_MAX_BITLEN + BGP_MUP_TEID_BYTES * 8) || + (afi == AFI_IP6 && ea_len > IPV6_MAX_BITLEN + BGP_MUP_TEID_BYTES * 8)) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T2ST NLRI bad endpoint length %u", + peer->bgp->vrf_id, peer->host, ea_len); + return -1; + } + + addr_octets = (afi == AFI_IP) ? IPV4_MAX_BYTELEN : IPV6_MAX_BYTELEN; + if (RD_BYTES + BGP_MUP_ADDR_LEN_BYTES + addr_octets > psize) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T2ST NLRI truncated endpoint", + peer->bgp->vrf_id, peer->host); + return -1; + } + + teid_bits = (ea_len > addr_octets * 8) ? ea_len - addr_octets * 8 : 0; + teid_octets = (teid_bits + 7) / 8; + if (RD_BYTES + BGP_MUP_ADDR_LEN_BYTES + addr_octets + teid_octets > psize) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T2ST NLRI truncated TEID", + peer->bgp->vrf_id, peer->host); + return -1; + } + + bgp_mup_prefix_init(&p, BGP_MUP_T2ST_ROUTE, psize); + memcpy(p.prefix.rd, prd.val, RD_BYTES); + p.prefix.t2st_route.endpoint_address_length = ea_len; + p.prefix.t2st_route.endpoint_address.ipa_type = (afi == AFI_IP) ? IPADDR_V4 : IPADDR_V6; + memcpy(&p.prefix.t2st_route.endpoint_address.ip.addr, + pfx + RD_BYTES + BGP_MUP_ADDR_LEN_BYTES, addr_octets); + if (teid_octets) { + memcpy(&teid_be, pfx + RD_BYTES + BGP_MUP_ADDR_LEN_BYTES + addr_octets, + teid_octets); + /* Mask the sub-octet padding bits so they do not enter the + * route key or pass the TEID=0 check below. + */ + p.prefix.t2st_route.teid = ntohl(teid_be) & + (0xffffffffU << (BGP_MUP_TEID_BYTES * 8 - teid_bits)); + } + /* draft 3.1.4.1: a TEID field that is present MUST NOT be 0; an + * endpoint-level aggregate carries no TEID field and is valid. + */ + if (teid_octets && p.prefix.t2st_route.teid == 0) { + flog_err(EC_BGP_MUP_PACKET, "%u:%s - Rx BGP-MUP T2ST NLRI TEID=0", + peer->bgp->vrf_id, peer->host); + return -1; + } + + if (attr) + bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, safi, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, NULL, 0, 0, NULL); + else + bgp_withdraw(peer, (struct prefix *)&p, addpath_id, afi, safi, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, NULL, 0); + return 0; +} + +int bgp_nlri_parse_mup(struct peer *peer, struct attr *attr, struct bgp_nlri *packet, bool withdraw) +{ + int ret; + uint8_t *pnt; + uint8_t *lim; + afi_t afi; + safi_t safi; + uint32_t addpath_id; + bool addpath_capable; + int psize = 0; + uint8_t arch_type; + uint16_t route_type; + + pnt = packet->nlri; + lim = pnt + packet->length; + afi = packet->afi; + safi = packet->safi; + addpath_id = 0; + + addpath_capable = bgp_addpath_encode_rx(peer, afi, safi); + + for (; pnt < lim; pnt += psize) { + if (addpath_capable) { + if (pnt + BGP_ADDPATH_ID_LEN > lim) + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + memcpy(&addpath_id, pnt, BGP_ADDPATH_ID_LEN); + addpath_id = ntohl(addpath_id); + pnt += BGP_ADDPATH_ID_LEN; + } + + /* Architecture Type + Route Type + Length. */ + if (pnt + BGP_MUP_HDR_BYTES > lim) + return BGP_NLRI_PARSE_ERROR_MUP_MISSING_TYPE; + + arch_type = pnt[0]; + memcpy(&route_type, pnt + BGP_MUP_ARCH_TYPE_BYTES, BGP_MUP_ROUTE_TYPE_BYTES); + route_type = ntohs(route_type); + psize = pnt[BGP_MUP_ARCH_TYPE_BYTES + BGP_MUP_ROUTE_TYPE_BYTES]; + pnt += BGP_MUP_HDR_BYTES; + + if (pnt + psize > lim) + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + + /* draft 3.1 only defines 3gpp-5g; skip other architectures. */ + if (arch_type != BGP_MUP_ARCH_3GPP_5G) + continue; + + switch (route_type) { + case BGP_MUP_ISD_ROUTE: + ret = bgp_mup_process_isd_route(peer, afi, safi, withdraw ? NULL : attr, + pnt, psize, addpath_id); + break; + + case BGP_MUP_DSD_ROUTE: + ret = bgp_mup_process_dsd_route(peer, afi, safi, withdraw ? NULL : attr, + pnt, psize, addpath_id); + break; + + case BGP_MUP_T1ST_ROUTE: + ret = bgp_mup_process_t1st_route(peer, afi, safi, withdraw ? NULL : attr, + pnt, psize, addpath_id); + break; + + case BGP_MUP_T2ST_ROUTE: + ret = bgp_mup_process_t2st_route(peer, afi, safi, withdraw ? NULL : attr, + pnt, psize, addpath_id); + break; + + default: + /* Unknown route type: silently ignore (draft 3.1). */ + ret = BGP_NLRI_PARSE_OK; + break; + } + + /* draft 3.1.x: a malformed NLRI is treat-as-withdraw + * (RFC 7606) -- skip it and keep parsing the UPDATE. + */ + if (ret < 0) + continue; + } + + if (pnt != lim) + return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + + return BGP_NLRI_PARSE_OK; +} diff --git a/bgpd/bgp_mup.h b/bgpd/bgp_mup.h new file mode 100644 index 000000000000..a5904a99242c --- /dev/null +++ b/bgpd/bgp_mup.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP-MUP NLRI handling for SAFI=MUP (draft-ietf-bess-mup-safi). + * Copyright (C) 2026 Yuya Kusakabe + */ +#ifndef _FRR_BGP_MUP_H +#define _FRR_BGP_MUP_H + +#include "stream.h" + +#include "bgpd/bgpd.h" + +/* prefix_mup carries the entire MUP route key in struct mup_prefix. */ +#define BGP_MUP_ROUTE_PREFIXLEN (sizeof(struct mup_prefix) * 8) + +/* On-wire field widths (draft-ietf-bess-mup-safi 3.1). */ +#define BGP_MUP_ARCH_TYPE_BYTES 1 +#define BGP_MUP_ROUTE_TYPE_BYTES 2 +#define BGP_MUP_LEN_BYTES 1 +#define BGP_MUP_HDR_BYTES (BGP_MUP_ARCH_TYPE_BYTES + BGP_MUP_ROUTE_TYPE_BYTES + BGP_MUP_LEN_BYTES) +#define BGP_MUP_TEID_BYTES 4 +#define BGP_MUP_QFI_BYTES 1 +#define BGP_MUP_PREFIX_LEN_BYTES 1 +#define BGP_MUP_ADDR_LEN_BYTES 1 + +/* Encoded size on the wire of one BGP-MUP NLRI. */ +extern size_t bgp_mup_prefix_size(const struct prefix *p); + +/* Encode a BGP-MUP prefix into an MP_REACH/MP_UNREACH NLRI stream. */ +extern void bgp_mup_encode_prefix(struct stream *s, afi_t afi, const struct prefix *p, + const struct prefix_rd *prd, bool addpath_capable, + uint32_t addpath_tx_id); + +/* Parse all BGP-MUP NLRIs in an MP_REACH/MP_UNREACH attribute. */ +extern int bgp_nlri_parse_mup(struct peer *peer, struct attr *attr, struct bgp_nlri *packet, + bool withdraw); + +#endif /* _FRR_BGP_MUP_H */ diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index 3ba08060f36b..8b75b916525f 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -51,6 +51,7 @@ #include "bgpd/bgp_flowspec.h" #include "bgpd/bgp_trace.h" #include "bgpd/bgp_ls.h" +#include "bgpd/bgp_mup.h" DEFINE_HOOK(bgp_packet_dump, (struct peer *peer, uint8_t type, bgp_size_t size, @@ -331,8 +332,7 @@ int bgp_nlri_parse(struct peer *peer, struct attr *attr, case SAFI_BGP_LS: return bgp_nlri_parse_ls(peer, mp_withdraw ? NULL : attr, packet); case SAFI_MUP: - /* TODO: implemented in subsequent commit */ - return BGP_NLRI_PARSE_ERROR; + return bgp_nlri_parse_mup(peer, attr, packet, mp_withdraw); } return BGP_NLRI_PARSE_ERROR; } diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h index 5594210f7d7b..e2662f3e00b2 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h @@ -104,6 +104,7 @@ enum bgp_show_adj_route_type { #define BGP_NLRI_PARSE_ERROR_FLOWSPEC_BAD_FORMAT -13 #define BGP_NLRI_PARSE_ERROR_ADDRESS_FAMILY -14 #define BGP_NLRI_PARSE_ERROR_EVPN_TYPE1_SIZE -15 +#define BGP_NLRI_PARSE_ERROR_MUP_MISSING_TYPE -16 #define BGP_NLRI_PARSE_ERROR -32 /* 1. local MAC-IP/type-2 paths in the VNI routing table are linked to the diff --git a/bgpd/subdir.am b/bgpd/subdir.am index 3e179075d6e4..8eec712a596f 100644 --- a/bgpd/subdir.am +++ b/bgpd/subdir.am @@ -58,6 +58,7 @@ bgpd_libbgp_a_SOURCES = \ bgpd/bgp_memory.c \ bgpd/bgp_mpath.c \ bgpd/bgp_mplsvpn.c \ + bgpd/bgp_mup.c \ bgpd/bgp_network.c \ bgpd/bgp_nexthop.c \ bgpd/bgp_nht.c \ @@ -147,6 +148,7 @@ noinst_HEADERS += \ bgpd/bgp_mpath.h \ bgpd/bgp_mplsvpn.h \ bgpd/bgp_mplsvpn_snmp.h \ + bgpd/bgp_mup.h \ bgpd/bgp_network.h \ bgpd/bgp_nexthop.h \ bgpd/bgp_nht.h \ From fd51d160441a6f5e29d0391d502eb3cc182d82cb Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Sat, 9 May 2026 23:31:33 +0900 Subject: [PATCH 4/9] tests: add BGP-MUP NLRI parser unit test Cover the BGP-MUP NLRI codec: parse of all four route types for both AFIs, the malformed-NLRI treat-as-withdraw and skip paths, and an encode/parse round-trip byte-compared against the same vectors. Signed-off-by: Yuya Kusakabe --- tests/bgpd/subdir.am | 10 + tests/bgpd/test_bgp_mup_nlri.c | 895 ++++++++++++++++++++++++++++++++ tests/bgpd/test_bgp_mup_nlri.py | 102 ++++ 3 files changed, 1007 insertions(+) create mode 100644 tests/bgpd/test_bgp_mup_nlri.c create mode 100644 tests/bgpd/test_bgp_mup_nlri.py diff --git a/tests/bgpd/subdir.am b/tests/bgpd/subdir.am index 20e9fbb33c2c..822459326c7d 100644 --- a/tests/bgpd/subdir.am +++ b/tests/bgpd/subdir.am @@ -14,6 +14,16 @@ tests_bgpd_test_aspath_SOURCES = tests/bgpd/test_aspath.c EXTRA_DIST += tests/bgpd/test_aspath.py +if BGPD +check_PROGRAMS += tests/bgpd/test_bgp_mup_nlri +endif +tests_bgpd_test_bgp_mup_nlri_CFLAGS = $(TESTS_CFLAGS) +tests_bgpd_test_bgp_mup_nlri_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_bgpd_test_bgp_mup_nlri_LDADD = $(BGP_TEST_LDADD) +tests_bgpd_test_bgp_mup_nlri_SOURCES = tests/bgpd/test_bgp_mup_nlri.c +EXTRA_DIST += tests/bgpd/test_bgp_mup_nlri.py + + if BGPD check_PROGRAMS += tests/bgpd/test_bgp_table endif diff --git a/tests/bgpd/test_bgp_mup_nlri.c b/tests/bgpd/test_bgp_mup_nlri.c new file mode 100644 index 000000000000..a1de6a10e30f --- /dev/null +++ b/tests/bgpd/test_bgp_mup_nlri.c @@ -0,0 +1,895 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for bgp_nlri_parse_mup (draft-ietf-bess-mup-safi route types 1..4). + * Copyright (C) 2026 Yuya Kusakabe + */ + +#include + +#include "qobj.h" +#include "vty.h" +#include "stream.h" +#include "privs.h" +#include "memory.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_mup.h" + +struct zebra_privs_t bgpd_privs = {}; +struct event_loop *master; + +static int failed; + +/* + * Wire format: arch_type(1) | route_type(2 BE) | length(1) | body(length) + * + * AFI for all tests: AFI_IP unless noted. + */ + +#define MUP_ARCH 0x01 /* BGP_MUP_ARCH_3GPP_5G */ +#define MUP_ISD_RT 0x00, 0x01 /* BGP_MUP_ISD_ROUTE big-endian */ +#define MUP_DSD_RT 0x00, 0x02 /* BGP_MUP_DSD_ROUTE big-endian */ +#define MUP_T1ST_RT 0x00, 0x03 /* BGP_MUP_T1ST_ROUTE big-endian */ +#define MUP_T2ST_RT 0x00, 0x04 /* BGP_MUP_T2ST_ROUTE big-endian */ + +/* RD type 0 (AS:value): 0x00 0x00 */ +#define RD0 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01 + +struct nlri_test { + const char *name; + const char *desc; + const uint8_t data[256]; + int len; + afi_t afi; + int expect; /* expected bgp_nlri_parse_mup return code */ +}; + +/* clang-format off */ +static struct nlri_test mup_segments[] = { + /* ------------------------------------------------------------------ */ + /* Happy path: one well-formed PDU per route type */ + /* ------------------------------------------------------------------ */ + { + "isd-v4-ok", + "ISD IPv4, /24 prefix, well-formed", + { + /* arch | route-type | length */ + MUP_ARCH, MUP_ISD_RT, + /* body length = RD(8) + pfxlen(1) + pfx-octets(3) = 12 */ + 12, + /* RD type-0: AS 100 : value 1 */ + RD0, + /* prefix length (bits) */ + 24, + /* prefix 192.168.1.0/24 */ + 192, 168, 1, + }, + .len = 4 + 12, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "dsd-v4-ok", + "DSD IPv4, PE address 10.0.0.1, well-formed", + { + MUP_ARCH, MUP_DSD_RT, + /* body = RD(8) + IPv4(4) = 12 */ + 12, + RD0, + /* PE address 10.0.0.1 */ + 10, 0, 0, 1, + }, + .len = 4 + 12, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "t1st-v4-ok", + "T1ST IPv4, /0 UE prefix, TEID 1, QFI 9, EP 10.1.2.3/32, no SA, well-formed", + { + MUP_ARCH, MUP_T1ST_RT, + /* + * body = RD(8) + pfxlen(1) + pfx-octets(0) + + * TEID(4) + QFI(1) + ep_len(1) + ep(4) + + * src_len(1) = 20 + */ + 20, + RD0, + /* UE prefix length 0 (no bytes follow) */ + 0, + /* TEID = 1 (non-zero, big-endian) */ + 0x00, 0x00, 0x00, 0x01, + /* QFI */ + 9, + /* endpoint address length in bits = 32 */ + 32, + /* endpoint 10.1.2.3 */ + 10, 1, 2, 3, + /* source address length = 0 (absent) */ + 0, + }, + .len = 4 + 20, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "t2st-v4-ok", + "T2ST IPv4, EP 10.2.0.1, TEID 2, ea_len=64, well-formed", + { + MUP_ARCH, MUP_T2ST_RT, + /* + * body = RD(8) + ea_len(1) + addr(4) + teid_octets(4) + * ea_len=64: 32 addr bits + 32 TEID bits => teid_octets=4 + * total = 8+1+4+4 = 17 + */ + 17, + RD0, + /* ea_len = 64 bits */ + 64, + /* endpoint address 10.2.0.1 */ + 10, 2, 0, 1, + /* TEID = 2 (non-zero, big-endian, packed in 4 bytes) */ + 0x00, 0x00, 0x00, 0x02, + }, + .len = 4 + 17, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* Negative: outer header too short -- returns MUP_MISSING_TYPE */ + /* ------------------------------------------------------------------ */ + { + "truncated-header", + "NLRI truncated before 4-byte header completes", + { + /* Only 3 bytes: arch + route_type(2 bytes) -- no length */ + MUP_ARCH, MUP_ISD_RT, + }, + .len = 3, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_ERROR_MUP_MISSING_TYPE, + }, + /* ------------------------------------------------------------------ */ + /* Negative: body length overflows outer NLRI -- returns PACKET_OVERFLOW */ + /* ------------------------------------------------------------------ */ + { + "body-overflow", + "Route-type length field claims more bytes than the NLRI holds", + { + MUP_ARCH, MUP_ISD_RT, + /* claim 20 bytes in body, but only 12 follow */ + 20, + RD0, + 24, 192, 168, 1, + }, + .len = 4 + 12, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW, + }, + /* ------------------------------------------------------------------ */ + /* Negative: leftover bytes after consuming NLRI */ + /* */ + /* A single trailing byte looks like the start of a new header; the */ + /* loop's 4-byte-header check fires first -> MUP_MISSING_TYPE. */ + /* PACKET_LENGTH would require pnt to step *past* lim, which cannot */ + /* happen because psize is always <= (lim - pnt) after the overflow */ + /* guard. Test the reachable path. */ + /* ------------------------------------------------------------------ */ + { + "trailing-garbage", + "Single trailing byte re-enters header check, returns MUP_MISSING_TYPE", + { + MUP_ARCH, MUP_ISD_RT, + 12, + RD0, + 24, 192, 168, 1, + /* extra garbage byte -- triggers partial-header path */ + 0xff, + }, + .len = 4 + 12 + 1, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_ERROR_MUP_MISSING_TYPE, + }, + /* ------------------------------------------------------------------ */ + /* Negative (inner): prefix length > AFI max -- outer returns OK */ + /* (inner decoder logs and skips the NLRI per RFC 7606 treat-as-withdraw) */ + /* ------------------------------------------------------------------ */ + { + "isd-prefix-len-overflow", + "ISD IPv4, prefix_len=33 (>32) -- skipped (treat-as-withdraw)", + { + MUP_ARCH, MUP_ISD_RT, + /* body: RD(8) + pfxlen(1) + pfx-octets for /33 = PSIZE(33)=5 */ + 14, + RD0, + /* prefix length 33 -- exceeds IPv4 max */ + 33, + 192, 168, 1, 0, 0, + }, + .len = 4 + 14, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* Negative (inner): T1ST with TEID=0 -- inner skips, outer returns OK */ + /* ------------------------------------------------------------------ */ + { + "t1st-teid-zero", + "T1ST IPv4, TEID=0 -- skipped (treat-as-withdraw)", + { + MUP_ARCH, MUP_T1ST_RT, + 20, + RD0, + 0, + /* TEID = 0 (forbidden) */ + 0x00, 0x00, 0x00, 0x00, + 9, + 32, + 10, 1, 2, 3, + 0, + }, + .len = 4 + 20, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* Negative (inner): T1ST endpoint length > AFI max -- inner skips */ + /* ------------------------------------------------------------------ */ + { + "t1st-ep-len-overflow", + "T1ST IPv4, endpoint_length=33 (not 32) -- skipped (treat-as-withdraw)", + { + MUP_ARCH, MUP_T1ST_RT, + /* + * body: RD(8)+pfxlen(1)+teid(4)+qfi(1)+ep_len(1) = 15, + * plus 1 spare byte so psize reaches the 16-octet + * minimum. The parser rejects any ep_len other than + * 32 for AFI_IP before reading the endpoint bytes. + */ + 16, + RD0, + 0, + 0x00, 0x00, 0x00, 0x01, + 9, + /* endpoint_length = 33 bits -- exceeds IPv4 max */ + 33, + }, + .len = 4 + 16, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* Negative (inner): T2ST with TEID=0 -- inner skips, outer returns OK */ + /* ------------------------------------------------------------------ */ + { + "t2st-teid-zero", + "T2ST IPv4, TEID=0 -- skipped (treat-as-withdraw)", + { + MUP_ARCH, MUP_T2ST_RT, + 17, + RD0, + 64, + 10, 2, 0, 1, + /* TEID = 0 (forbidden) */ + 0x00, 0x00, 0x00, 0x00, + }, + .len = 4 + 17, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* Negative (inner): DSD with wrong body size for AFI -- inner skips */ + /* ------------------------------------------------------------------ */ + { + "dsd-wrong-size", + "DSD IPv4, body=13 (not 12) -- skipped (treat-as-withdraw)", + { + MUP_ARCH, MUP_DSD_RT, + 13, + RD0, + 10, 0, 0, 1, + /* one extra byte makes psize 13 != 12 for AFI_IP */ + 0x00, + }, + .len = 4 + 13, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* Unknown arch_type -- outer silently skips, returns OK */ + /* ------------------------------------------------------------------ */ + { + "unknown-arch-type", + "Unknown arch_type=0xFF -- outer loop skips, returns OK", + { + /* arch_type = 0xFF (undefined) */ + 0xFF, MUP_ISD_RT, + 12, + RD0, + 24, 192, 168, 1, + }, + .len = 4 + 12, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* IPv6 (AFI=2) happy path: one well-formed PDU per route type */ + /* ------------------------------------------------------------------ */ + { + "isd-v6-ok", + "ISD IPv6, /64 prefix, well-formed", + { + MUP_ARCH, MUP_ISD_RT, + /* body = RD(8) + pfxlen(1) + pfx-octets(8) = 17 */ + 17, + RD0, + 64, + 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, + }, + .len = 4 + 17, + .afi = AFI_IP6, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "dsd-v6-ok", + "DSD IPv6, PE address 2001:db8::1, well-formed", + { + MUP_ARCH, MUP_DSD_RT, + /* body = RD(8) + IPv6(16) = 24 */ + 24, + RD0, + 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + }, + .len = 4 + 24, + .afi = AFI_IP6, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "t1st-v6-ok", + "T1ST IPv6, /0 UE prefix, TEID 1, QFI 9, EP 2001:db8::5/128, no SA, well-formed", + { + MUP_ARCH, MUP_T1ST_RT, + /* + * body = RD(8) + pfxlen(1) + TEID(4) + QFI(1) + + * ep_len(1) + ep(16) + src_len(1) = 32 + */ + 32, + RD0, + 0, + 0x00, 0x00, 0x00, 0x01, + 9, + 128, + 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, + 0, + }, + .len = 4 + 32, + .afi = AFI_IP6, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "t2st-v6-ok", + "T2ST IPv6, EP 2001:db8::9, TEID 2, ea_len=160, well-formed", + { + MUP_ARCH, MUP_T2ST_RT, + /* body = RD(8) + ea_len(1) + addr(16) + teid(4) = 29 */ + 29, + RD0, + 160, + 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, + 0x00, 0x00, 0x00, 0x02, + }, + .len = 4 + 29, + .afi = AFI_IP6, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* T2ST endpoint-level aggregate: Endpoint Length of exactly 32/128 */ + /* means the TEID field is absent (0 octets) -- valid */ + /* ------------------------------------------------------------------ */ + { + "t2st-v4-ea32-no-teid", + "T2ST IPv4, ea_len=32, no TEID field (endpoint aggregate), well-formed", + { + MUP_ARCH, MUP_T2ST_RT, + /* body = RD(8) + ea_len(1) + addr(4) = 13 */ + 13, + RD0, + 32, + 10, 2, 0, 1, + }, + .len = 4 + 13, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "t2st-v6-ea128-no-teid", + "T2ST IPv6, ea_len=128, no TEID field (endpoint aggregate), well-formed", + { + MUP_ARCH, MUP_T2ST_RT, + /* body = RD(8) + ea_len(1) + addr(16) = 25 */ + 25, + RD0, + 128, + 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, + }, + .len = 4 + 25, + .afi = AFI_IP6, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* T2ST partial TEID: ea_len=35 leaves 3 TEID bits in one trailing */ + /* octet; the low 5 bits of that octet are padding. Padding must not */ + /* reach the route key nor defeat the TEID=0 check (see */ + /* test_t2st_teid_padding for the table-level assertions). */ + /* ------------------------------------------------------------------ */ + { + "t2st-v4-teid-padded", + "T2ST IPv4, ea_len=35, TEID octet 0xff -- significant bits 0b111, padding masked", + { + MUP_ARCH, MUP_T2ST_RT, + /* body = RD(8) + ea_len(1) + addr(4) + teid(1) = 14 */ + 14, + RD0, + 35, + 10, 2, 0, 1, + 0xff, + }, + .len = 4 + 14, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "t2st-v4-teid-pad-only", + "T2ST IPv4, ea_len=35, TEID octet 0x1f -- significant bits 0, skipped as TEID=0", + { + MUP_ARCH, MUP_T2ST_RT, + 14, + RD0, + 35, + 10, 2, 0, 1, + 0x1f, + }, + .len = 4 + 14, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* IPv6 inner malformed -- skipped (treat-as-withdraw) */ + /* ------------------------------------------------------------------ */ + { + "isd-v6-prefix-len-overflow", + "ISD IPv6, prefix_len=129 (>128) -- skipped (treat-as-withdraw)", + { + MUP_ARCH, MUP_ISD_RT, + /* body = RD(8) + pfxlen(1) + PSIZE(129)=17 -> 26 */ + 26, + RD0, + 129, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + .len = 4 + 26, + .afi = AFI_IP6, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "dsd-v6-wrong-size", + "DSD IPv6, body=25 (not 24) -- skipped (treat-as-withdraw)", + { + MUP_ARCH, MUP_DSD_RT, + 25, + RD0, + 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0x00, + }, + .len = 4 + 25, + .afi = AFI_IP6, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* T1ST with a Source Address */ + /* ------------------------------------------------------------------ */ + { + "t1st-v4-with-sa", + "T1ST IPv4 with SA 10.9.9.9/32, well-formed", + { + MUP_ARCH, MUP_T1ST_RT, + /* + * body = RD(8) + pfxlen(1) + TEID(4) + QFI(1) + + * ep_len(1) + ep(4) + src_len(1) + src(4) = 24 + */ + 24, + RD0, + 0, + 0x00, 0x00, 0x00, 0x01, + 9, + 32, + 10, 1, 2, 3, + 32, + 10, 9, 9, 9, + }, + .len = 4 + 24, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "t1st-src-truncated", + "T1ST IPv4, src_len=32 but only 2 SA bytes -- skipped (treat-as-withdraw)", + { + MUP_ARCH, MUP_T1ST_RT, + /* body = 20 + 2 = 22; src_len demands 4 more bytes */ + 22, + RD0, + 0, + 0x00, 0x00, 0x00, 0x01, + 9, + 32, + 10, 1, 2, 3, + 32, + 10, 9, + }, + .len = 4 + 22, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* Endpoint/Source Address Length must be exactly 32 or 128 */ + /* ------------------------------------------------------------------ */ + { + "t1st-ep-len-24", + "T1ST IPv4, endpoint_length=24 (not 32) -- skipped (treat-as-withdraw)", + { + MUP_ARCH, MUP_T1ST_RT, + /* body = 15 + 1 pad = 16; parser rejects at ep_len */ + 16, + RD0, + 0, + 0x00, 0x00, 0x00, 0x01, + 9, + 24, + 0x00, + }, + .len = 4 + 16, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "t1st-src-len-24", + "T1ST IPv4, source_length=24 (not 0 or 32) -- skipped (treat-as-withdraw)", + { + MUP_ARCH, MUP_T1ST_RT, + /* body = 20 + 3 = 23; parser rejects at src_len */ + 23, + RD0, + 0, + 0x00, 0x00, 0x00, 0x01, + 9, + 32, + 10, 1, 2, 3, + 24, + 10, 9, 9, + }, + .len = 4 + 23, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* Unknown route type for 3gpp-5g -- silently ignored (draft sec 3.1) */ + /* ------------------------------------------------------------------ */ + { + "unknown-route-type", + "Unknown route_type=5 -- outer loop skips, returns OK", + { + MUP_ARCH, 0x00, 0x05, + 4, + 0xde, 0xad, 0xbe, 0xef, + }, + .len = 4 + 4, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* ------------------------------------------------------------------ */ + /* Multiple NLRIs in one attribute */ + /* ------------------------------------------------------------------ */ + { + "two-valid-nlris", + "ISD + DSD concatenated -- both parsed, returns OK", + { + MUP_ARCH, MUP_ISD_RT, + 12, + RD0, + 24, 192, 168, 1, + MUP_ARCH, MUP_DSD_RT, + 12, + RD0, + 10, 0, 0, 1, + }, + .len = 16 + 16, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + { + "malformed-then-valid", + "T1ST TEID=0 then valid ISD -- malformed one skipped, rest processed", + { + MUP_ARCH, MUP_T1ST_RT, + 20, + RD0, + 0, + 0x00, 0x00, 0x00, 0x00, + 9, + 32, + 10, 1, 2, 3, + 0, + MUP_ARCH, MUP_ISD_RT, + 12, + RD0, + 24, 192, 168, 1, + }, + .len = 24 + 16, + .afi = AFI_IP, + .expect = BGP_NLRI_PARSE_OK, + }, + /* sentinel */ + { NULL, NULL, { 0 }, 0, 0, 0 }, +}; + +/* clang-format on */ + +static void run_test(struct peer *peer, struct nlri_test *t) +{ + struct bgp_nlri packet = {}; + int ret; + + packet.afi = t->afi; + packet.safi = SAFI_MUP; + packet.nlri = (uint8_t *)t->data; + packet.length = t->len; + + ret = bgp_nlri_parse_mup(peer, NULL, &packet, 0); + + printf("%s: %s\n", t->name, t->desc); + printf(" got=%d expected=%d\n", ret, t->expect); + if (ret == t->expect) { + printf("OK\n"); + } else { + printf("failed\n"); + failed++; + } + printf("\n"); +} + +static const struct nlri_test *find_segment(const char *name) +{ + int i; + + for (i = 0; mup_segments[i].name; i++) + if (strcmp(mup_segments[i].name, name) == 0) + return &mup_segments[i]; + assert(!"unknown segment name"); + return NULL; +} + +/* Encode `p` and require an exact byte match against the parse-test + * wire vector of the same name, keeping encode and parse inverse + * operations for the same NLRI. + */ +static void encode_and_check(const char *name, afi_t afi, const struct prefix_mup *p) +{ + const struct nlri_test *t = find_segment(name); + struct stream *s = stream_new(BGP_MAX_PACKET_SIZE); + + bgp_mup_encode_prefix(s, afi, (const struct prefix *)p, NULL, false, 0); + + printf("encode-%s: encode matches the %s parse vector\n", name, name); + if (stream_get_endp(s) == (size_t)t->len && memcmp(STREAM_DATA(s), t->data, t->len) == 0) { + printf("OK\n"); + } else { + printf(" endp=%zu expected=%d\n", stream_get_endp(s), t->len); + printf("failed\n"); + failed++; + } + printf("\n"); + stream_free(s); +} + +static void test_encode(void) +{ + static const uint8_t rd0[8] = { RD0 }; + struct prefix_mup p; + + memset(&p, 0, sizeof(p)); + p.prefix.arch_type = BGP_MUP_ARCH_3GPP_5G; + p.prefix.route_type = BGP_MUP_ISD_ROUTE; + p.prefix.length = 12; + memcpy(p.prefix.rd, rd0, 8); + p.prefix.isd_route.ip_prefix_length = 24; + p.prefix.isd_route.ip.ipa_type = IPADDR_V4; + inet_pton(AF_INET, "192.168.1.0", &p.prefix.isd_route.ip.ip.addr); + encode_and_check("isd-v4-ok", AFI_IP, &p); + + memset(&p, 0, sizeof(p)); + p.prefix.arch_type = BGP_MUP_ARCH_3GPP_5G; + p.prefix.route_type = BGP_MUP_ISD_ROUTE; + p.prefix.length = 17; + memcpy(p.prefix.rd, rd0, 8); + p.prefix.isd_route.ip_prefix_length = 64; + p.prefix.isd_route.ip.ipa_type = IPADDR_V6; + inet_pton(AF_INET6, "2001:db8::", &p.prefix.isd_route.ip.ip.addr); + encode_and_check("isd-v6-ok", AFI_IP6, &p); + + memset(&p, 0, sizeof(p)); + p.prefix.arch_type = BGP_MUP_ARCH_3GPP_5G; + p.prefix.route_type = BGP_MUP_T1ST_ROUTE; + p.prefix.length = 24; + memcpy(p.prefix.rd, rd0, 8); + p.prefix.t1st_route.ip_prefix_length = 0; + p.prefix.t1st_route.ip.ipa_type = IPADDR_V4; + p.prefix.t1st_route.t1st_3gpp_5g.teid = 1; + p.prefix.t1st_route.t1st_3gpp_5g.qfi = 9; + p.prefix.t1st_route.t1st_3gpp_5g.endpoint_address_length = 32; + p.prefix.t1st_route.t1st_3gpp_5g.endpoint_address.ipa_type = IPADDR_V4; + inet_pton(AF_INET, "10.1.2.3", &p.prefix.t1st_route.t1st_3gpp_5g.endpoint_address.ip.addr); + p.prefix.t1st_route.t1st_3gpp_5g.source_address_length = 32; + p.prefix.t1st_route.t1st_3gpp_5g.source_address.ipa_type = IPADDR_V4; + inet_pton(AF_INET, "10.9.9.9", &p.prefix.t1st_route.t1st_3gpp_5g.source_address.ip.addr); + encode_and_check("t1st-v4-with-sa", AFI_IP, &p); + + memset(&p, 0, sizeof(p)); + p.prefix.arch_type = BGP_MUP_ARCH_3GPP_5G; + p.prefix.route_type = BGP_MUP_T2ST_ROUTE; + p.prefix.length = 17; + memcpy(p.prefix.rd, rd0, 8); + p.prefix.t2st_route.endpoint_address_length = 64; + p.prefix.t2st_route.endpoint_address.ipa_type = IPADDR_V4; + inet_pton(AF_INET, "10.2.0.1", &p.prefix.t2st_route.endpoint_address.ip.addr); + p.prefix.t2st_route.teid = 2; + encode_and_check("t2st-v4-ok", AFI_IP, &p); + + memset(&p, 0, sizeof(p)); + p.prefix.arch_type = BGP_MUP_ARCH_3GPP_5G; + p.prefix.route_type = BGP_MUP_T2ST_ROUTE; + p.prefix.length = 13; + memcpy(p.prefix.rd, rd0, 8); + p.prefix.t2st_route.endpoint_address_length = 32; + p.prefix.t2st_route.endpoint_address.ipa_type = IPADDR_V4; + inet_pton(AF_INET, "10.2.0.1", &p.prefix.t2st_route.endpoint_address.ip.addr); + encode_and_check("t2st-v4-ea32-no-teid", AFI_IP, &p); + + memset(&p, 0, sizeof(p)); + p.prefix.arch_type = BGP_MUP_ARCH_3GPP_5G; + p.prefix.route_type = BGP_MUP_T2ST_ROUTE; + p.prefix.length = 29; + memcpy(p.prefix.rd, rd0, 8); + p.prefix.t2st_route.endpoint_address_length = 160; + p.prefix.t2st_route.endpoint_address.ipa_type = IPADDR_V6; + inet_pton(AF_INET6, "2001:db8::9", &p.prefix.t2st_route.endpoint_address.ip.addr); + p.prefix.t2st_route.teid = 2; + encode_and_check("t2st-v6-ok", AFI_IP6, &p); +} + +static struct bgp *bgp; +static as_t asn = 100; + +/* Parse `name`'s wire vector with a minimal valid attr so an accepted + * T2ST route is actually installed, then check the TEID that made it + * into the route key. This is what catches unmasked padding bits: + * the outer return code is BGP_NLRI_PARSE_OK whether the NLRI is + * accepted or skipped as malformed, so run_test cannot tell the two + * apart. + */ +static void parse_and_check_t2st_teid(struct peer *peer, const char *name, bool want_installed, + uint32_t want_teid) +{ + const struct nlri_test *t = find_segment(name); + struct bgp_nlri packet = {}; + struct attr attr = {}; + struct bgp_dest *dest; + struct bgp_path_info *pi; + uint32_t got_teid = 0; + bool installed = false; + + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_IGP); + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + inet_pton(AF_INET6, "2001:db8::1", &attr.mp_nexthop_global); + + packet.afi = t->afi; + packet.safi = SAFI_MUP; + packet.nlri = (uint8_t *)t->data; + packet.length = t->len; + + bgp_nlri_parse_mup(peer, &attr, &packet, 0); + + /* A withdrawn path lingers with BGP_PATH_REMOVED set because the + * deferred deletion in bgp_process() never runs here; only paths + * still valid count as installed. + */ + for (dest = bgp_table_top(bgp->rib[t->afi][SAFI_MUP]); dest; dest = bgp_route_next(dest)) { + const struct prefix_mup *p = (const struct prefix_mup *)bgp_dest_get_prefix(dest); + + if (p->prefix.route_type != BGP_MUP_T2ST_ROUTE) + continue; + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) + continue; + installed = true; + got_teid = p->prefix.t2st_route.teid; + } + } + + printf("teid-padding-%s: route key TEID after parse with attr\n", name); + printf(" got installed=%d teid=0x%08x, expected installed=%d teid=0x%08x\n", installed, + got_teid, want_installed, want_teid); + if (installed == want_installed && (!installed || got_teid == want_teid)) { + printf("OK\n"); + } else { + printf("failed\n"); + failed++; + } + printf("\n"); + + /* Withdraw so every case starts from an empty MUP table. */ + bgp_nlri_parse_mup(peer, NULL, &packet, 0); +} + +static void test_t2st_teid_padding(struct peer *peer) +{ + /* TEID octet 0xff: top 3 bits are TEID, low 5 are padding. */ + parse_and_check_t2st_teid(peer, "t2st-v4-teid-padded", true, 0xe0000000); + /* TEID octet 0x1f: all TEID bits zero -- draft section 3.1.4.1 + * malformed (TEID=0), must be skipped despite non-zero padding. + */ + parse_and_check_t2st_teid(peer, "t2st-v4-teid-pad-only", false, 0); +} + +int main(void) +{ + struct interface ifp; + struct peer *peer; + int i; + + qobj_init(); + cmd_init(0); + bgp_vty_init(); + master = event_master_create("test bgp mup nlri"); + bgp_master_init(master, BGP_SOCKET_SNDBUF_SIZE, list_new()); + vrf_init(NULL, NULL, NULL, NULL); + bgp_option_set(BGP_OPT_NO_LISTEN); + bgp_attr_init(); + bgp_labels_init(); + + if (bgp_get(&bgp, &asn, NULL, BGP_INSTANCE_TYPE_DEFAULT, NULL, ASNOTATION_PLAIN) < 0) + return 1; + + peer = peer_create_accept(bgp, NULL); + peer->host = (char *)"test-peer"; + peer->connection = bgp_peer_connection_new(peer, NULL, UNKNOWN); + peer->connection->status = Established; + peer->connection->curr = stream_new(BGP_MAX_PACKET_SIZE); + + ifp.ifindex = 0; + peer->nexthop.ifp = &ifp; + + for (i = AFI_IP; i < AFI_MAX; i++) { + peer->afc[i][SAFI_MUP] = 1; + peer->afc_adv[i][SAFI_MUP] = 1; + peer->afc_nego[i][SAFI_MUP] = 1; + } + + i = 0; + while (mup_segments[i].name) + run_test(peer, &mup_segments[i++]); + + test_encode(); + test_t2st_teid_padding(peer); + + printf("failures: %d\n", failed); + return failed; +} diff --git a/tests/bgpd/test_bgp_mup_nlri.py b/tests/bgpd/test_bgp_mup_nlri.py new file mode 100644 index 000000000000..d98a40620e7c --- /dev/null +++ b/tests/bgpd/test_bgp_mup_nlri.py @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +import frrtest + + +class TestBgpMupNlri(frrtest.TestMultiOut): + program = "./test_bgp_mup_nlri" + + +TestBgpMupNlri.okfail("isd-v4-ok: ISD IPv4, /24 prefix, well-formed") +TestBgpMupNlri.okfail("dsd-v4-ok: DSD IPv4, PE address 10.0.0.1, well-formed") +TestBgpMupNlri.okfail( + "t1st-v4-ok: T1ST IPv4, /0 UE prefix, TEID 1, QFI 9, EP 10.1.2.3/32, no SA, well-formed" +) +TestBgpMupNlri.okfail( + "t2st-v4-ok: T2ST IPv4, EP 10.2.0.1, TEID 2, ea_len=64, well-formed" +) +TestBgpMupNlri.okfail("truncated-header: NLRI truncated before 4-byte header completes") +TestBgpMupNlri.okfail( + "body-overflow: Route-type length field claims more bytes than the NLRI holds" +) +TestBgpMupNlri.okfail( + "trailing-garbage: Single trailing byte re-enters header check, returns MUP_MISSING_TYPE" +) +TestBgpMupNlri.okfail( + "isd-prefix-len-overflow: ISD IPv4, prefix_len=33 (>32) -- skipped (treat-as-withdraw)" +) +TestBgpMupNlri.okfail( + "t1st-teid-zero: T1ST IPv4, TEID=0 -- skipped (treat-as-withdraw)" +) +TestBgpMupNlri.okfail( + "t1st-ep-len-overflow: T1ST IPv4, endpoint_length=33 (not 32) -- skipped (treat-as-withdraw)" +) +TestBgpMupNlri.okfail( + "t2st-teid-zero: T2ST IPv4, TEID=0 -- skipped (treat-as-withdraw)" +) +TestBgpMupNlri.okfail( + "dsd-wrong-size: DSD IPv4, body=13 (not 12) -- skipped (treat-as-withdraw)" +) +TestBgpMupNlri.okfail( + "unknown-arch-type: Unknown arch_type=0xFF -- outer loop skips, returns OK" +) +TestBgpMupNlri.okfail("isd-v6-ok: ISD IPv6, /64 prefix, well-formed") +TestBgpMupNlri.okfail("dsd-v6-ok: DSD IPv6, PE address 2001:db8::1, well-formed") +TestBgpMupNlri.okfail( + "t1st-v6-ok: T1ST IPv6, /0 UE prefix, TEID 1, QFI 9, EP 2001:db8::5/128, no SA, well-formed" +) +TestBgpMupNlri.okfail( + "t2st-v6-ok: T2ST IPv6, EP 2001:db8::9, TEID 2, ea_len=160, well-formed" +) +TestBgpMupNlri.okfail( + "t2st-v4-ea32-no-teid: T2ST IPv4, ea_len=32, no TEID field (endpoint aggregate), well-formed" +) +TestBgpMupNlri.okfail( + "t2st-v6-ea128-no-teid: T2ST IPv6, ea_len=128, no TEID field (endpoint aggregate), well-formed" +) +TestBgpMupNlri.okfail( + "t2st-v4-teid-padded: T2ST IPv4, ea_len=35, TEID octet 0xff -- significant bits 0b111, padding masked" +) +TestBgpMupNlri.okfail( + "t2st-v4-teid-pad-only: T2ST IPv4, ea_len=35, TEID octet 0x1f -- significant bits 0, skipped as TEID=0" +) +TestBgpMupNlri.okfail( + "isd-v6-prefix-len-overflow: ISD IPv6, prefix_len=129 (>128) -- skipped (treat-as-withdraw)" +) +TestBgpMupNlri.okfail( + "dsd-v6-wrong-size: DSD IPv6, body=25 (not 24) -- skipped (treat-as-withdraw)" +) +TestBgpMupNlri.okfail("t1st-v4-with-sa: T1ST IPv4 with SA 10.9.9.9/32, well-formed") +TestBgpMupNlri.okfail( + "t1st-src-truncated: T1ST IPv4, src_len=32 but only 2 SA bytes -- skipped (treat-as-withdraw)" +) +TestBgpMupNlri.okfail( + "t1st-ep-len-24: T1ST IPv4, endpoint_length=24 (not 32) -- skipped (treat-as-withdraw)" +) +TestBgpMupNlri.okfail( + "t1st-src-len-24: T1ST IPv4, source_length=24 (not 0 or 32) -- skipped (treat-as-withdraw)" +) +TestBgpMupNlri.okfail( + "unknown-route-type: Unknown route_type=5 -- outer loop skips, returns OK" +) +TestBgpMupNlri.okfail( + "two-valid-nlris: ISD + DSD concatenated -- both parsed, returns OK" +) +TestBgpMupNlri.okfail( + "malformed-then-valid: T1ST TEID=0 then valid ISD -- malformed one skipped, rest processed" +) +TestBgpMupNlri.okfail("encode-isd-v4-ok: encode matches the isd-v4-ok parse vector") +TestBgpMupNlri.okfail("encode-isd-v6-ok: encode matches the isd-v6-ok parse vector") +TestBgpMupNlri.okfail( + "encode-t1st-v4-with-sa: encode matches the t1st-v4-with-sa parse vector" +) +TestBgpMupNlri.okfail("encode-t2st-v4-ok: encode matches the t2st-v4-ok parse vector") +TestBgpMupNlri.okfail( + "encode-t2st-v4-ea32-no-teid: encode matches the t2st-v4-ea32-no-teid parse vector" +) +TestBgpMupNlri.okfail("encode-t2st-v6-ok: encode matches the t2st-v6-ok parse vector") +TestBgpMupNlri.okfail( + "teid-padding-t2st-v4-teid-padded: route key TEID after parse with attr" +) +TestBgpMupNlri.okfail( + "teid-padding-t2st-v4-teid-pad-only: route key TEID after parse with attr" +) From caeb60f0e8946090a675dbd32969df8696f8a2cf Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Mon, 4 May 2026 22:37:32 +0900 Subject: [PATCH 5/9] bgpd: add BGP-MUP extended community draft-ietf-bess-mup-safi 3.2 defines a transitive extended community carrying a Direct-Type Segment Identifier for Type 2 ST routes. The type codepoint 0x0c is the IANA-assigned "SRv6 MUP Extended Community"; the sub-type 0x00 follows existing implementations. Signed-off-by: Yuya Kusakabe --- bgpd/bgp_ecommunity.c | 18 ++++++++++++++++++ bgpd/bgp_ecommunity.h | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/bgpd/bgp_ecommunity.c b/bgpd/bgp_ecommunity.c index 097f444bcc7f..05c27ef9319c 100644 --- a/bgpd/bgp_ecommunity.c +++ b/bgpd/bgp_ecommunity.c @@ -1390,6 +1390,24 @@ static char *_ecommunity_ecom2str(struct ecommunity *ecom, int format, int filte l2mtu); } else unk_ecom = true; + } else if (type == ECOMMUNITY_ENCODE_MUP) { + sub_type = *pnt++; + if (sub_type == ECOMMUNITY_MUP_SUBTYPE_DIRECT_SEG_ID) { + /* draft-ietf-bess-mup-safi section 3.2: + * 6-byte segment identifier; display it as a + * uint16:uint32 pair like the "AS:NN" RD form. + */ + uint16_t sid_hi; + uint32_t sid_lo; + + memcpy(&sid_hi, pnt, 2); + sid_hi = ntohs(sid_hi); + memcpy(&sid_lo, pnt + 2, 4); + sid_lo = ntohl(sid_lo); + snprintf(encbuf, sizeof(encbuf), "MUP:%u:%u", sid_hi, sid_lo); + } else { + unk_ecom = true; + } } else if (type == ECOMMUNITY_ENCODE_REDIRECT_IP_NH) { sub_type = *pnt++; if (sub_type == ECOMMUNITY_REDIRECT_IP_NH) { diff --git a/bgpd/bgp_ecommunity.h b/bgpd/bgp_ecommunity.h index 95097e152fc6..4586a091d69f 100644 --- a/bgpd/bgp_ecommunity.h +++ b/bgpd/bgp_ecommunity.h @@ -24,6 +24,7 @@ #define ECOMMUNITY_ENCODE_OPAQUE 0x03 #define ECOMMUNITY_ENCODE_EVPN 0x06 #define ECOMMUNITY_ENCODE_REDIRECT_IP_NH 0x08 /* Flow Spec */ +#define ECOMMUNITY_ENCODE_MUP 0x0c /* Generic Transitive Experimental */ #define ECOMMUNITY_ENCODE_TRANS_EXP 0x80 @@ -93,6 +94,9 @@ #define ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP 0x0c #define ECOMMUNITY_OPAQUE_SUBTYPE_COLOR 0x0b +/* Sub-Types for ECOMMUNITY_ENCODE_MUP (draft 3.2). */ +#define ECOMMUNITY_MUP_SUBTYPE_DIRECT_SEG_ID 0x00 + /* Extended communities attribute string format. */ #define ECOMMUNITY_FORMAT_ROUTE_MAP 0 #define ECOMMUNITY_FORMAT_COMMUNITY_LIST 1 From 247bb04e0effa1a42a647144127fc90edda53ba1 Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Mon, 4 May 2026 22:40:49 +0900 Subject: [PATCH 6/9] bgpd: add BGP-MUP IPv4/IPv6 nodes and address-family commands Register BGP_MUPV4_NODE / BGP_MUPV6_NODE entered through "address-family ipv4|ipv6 mup"; draft-ietf-bess-mup-safi uses AFI=1 and AFI=2 so each AFI gets its own VTY node. The nodes accept "neighbor activate" and the per-neighbor policy commands so peering can be enabled and filtered per-AFI. Signed-off-by: Yuya Kusakabe --- bgpd/bgp_vty.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++--- bgpd/bgp_vty.h | 6 +- lib/command.h | 2 + vtysh/vtysh.c | 63 +++++++++++++-- 4 files changed, 260 insertions(+), 24 deletions(-) diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index ecbe705469a2..1a836bbf3f71 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -179,11 +179,12 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) return BGP_VPNV4_NODE; case SAFI_FLOWSPEC: return BGP_FLOWSPECV4_NODE; + case SAFI_MUP: + return BGP_MUPV4_NODE; case SAFI_BGP_LS: case SAFI_UNSPEC: case SAFI_ENCAP: case SAFI_EVPN: - case SAFI_MUP: case SAFI_MAX: /* not expected */ return BGP_IPV4_NODE; @@ -201,11 +202,12 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) return BGP_VPNV6_NODE; case SAFI_FLOWSPEC: return BGP_FLOWSPECV6_NODE; + case SAFI_MUP: + return BGP_MUPV6_NODE; case SAFI_BGP_LS: case SAFI_UNSPEC: case SAFI_ENCAP: case SAFI_EVPN: - case SAFI_MUP: case SAFI_MAX: /* not expected and the return value seems wrong */ return BGP_IPV4_NODE; @@ -240,6 +242,8 @@ static const char *get_afi_safi_vty_str(afi_t afi, safi_t safi) return "IPv4 Encap"; if (safi == SAFI_FLOWSPEC) return "IPv4 Flowspec"; + if (safi == SAFI_MUP) + return "IPv4 MUP"; } else if (afi == AFI_IP6) { if (safi == SAFI_UNICAST) return "IPv6 Unicast"; @@ -253,6 +257,8 @@ static const char *get_afi_safi_vty_str(afi_t afi, safi_t safi) return "IPv6 Encap"; if (safi == SAFI_FLOWSPEC) return "IPv6 Flowspec"; + if (safi == SAFI_MUP) + return "IPv6 MUP"; } else if (afi == AFI_L2VPN) { if (safi == SAFI_EVPN) return "L2VPN EVPN"; @@ -285,6 +291,8 @@ static const char *get_afi_safi_json_str(afi_t afi, safi_t safi) return "ipv4Encap"; if (safi == SAFI_FLOWSPEC) return "ipv4Flowspec"; + if (safi == SAFI_MUP) + return "ipv4Mup"; } else if (afi == AFI_IP6) { if (safi == SAFI_UNICAST) return "ipv6Unicast"; @@ -298,6 +306,8 @@ static const char *get_afi_safi_json_str(afi_t afi, safi_t safi) return "ipv6Encap"; if (safi == SAFI_FLOWSPEC) return "ipv6Flowspec"; + if (safi == SAFI_MUP) + return "ipv6Mup"; } else if (afi == AFI_L2VPN) { if (safi == SAFI_EVPN) return "l2VpnEvpn"; @@ -462,6 +472,12 @@ afi_t bgp_node_afi(struct vty *vty) case BGP_LS_NODE: afi = AFI_BGP_LS; break; + case BGP_MUPV4_NODE: + afi = AFI_IP; + break; + case BGP_MUPV6_NODE: + afi = AFI_IP6; + break; default: afi = AFI_IP; break; @@ -497,6 +513,10 @@ safi_t bgp_node_safi(struct vty *vty) case BGP_LS_NODE: safi = SAFI_BGP_LS; break; + case BGP_MUPV4_NODE: + case BGP_MUPV6_NODE: + safi = SAFI_MUP; + break; default: safi = SAFI_UNICAST; break; @@ -561,6 +581,8 @@ safi_t bgp_vty_safi_from_str(const char *safi_str) safi = SAFI_LABELED_UNICAST; else if (strmatch(safi_str, "flowspec")) safi = SAFI_FLOWSPEC; + else if (strmatch(safi_str, "mup")) + safi = SAFI_MUP; return safi; } @@ -592,6 +614,10 @@ int argv_find_and_parse_safi(struct cmd_token **argv, int argc, int *index, ret = 1; if (safi) *safi = SAFI_FLOWSPEC; + } else if (argv_find(argv, argc, "mup", index)) { + ret = 1; + if (safi) + *safi = SAFI_MUP; } return ret; } @@ -11678,7 +11704,7 @@ DEFPY (af_routetarget_import, DEFUN_NOSH (address_family_ipv4_safi, address_family_ipv4_safi_cmd, - "address-family ipv4 []", + "address-family ipv4 []", "Enter Address Family command mode\n" BGP_AF_STR BGP_SAFI_WITH_LABEL_HELP_STR) @@ -11703,7 +11729,7 @@ DEFUN_NOSH (address_family_ipv4_safi, DEFUN_NOSH (address_family_ipv6_safi, address_family_ipv6_safi_cmd, - "address-family ipv6 []", + "address-family ipv6 []", "Enter Address Family command mode\n" BGP_AF_STR BGP_SAFI_WITH_LABEL_HELP_STR) @@ -12005,14 +12031,13 @@ DEFUN_NOSH (exit_address_family, "exit-address-family", "Exit from Address Family configuration mode\n") { - if (vty->node == BGP_IPV4_NODE || vty->node == BGP_IPV4M_NODE - || vty->node == BGP_IPV4L_NODE || vty->node == BGP_VPNV4_NODE - || vty->node == BGP_IPV6_NODE || vty->node == BGP_IPV6M_NODE - || vty->node == BGP_IPV6L_NODE || vty->node == BGP_VPNV6_NODE - || vty->node == BGP_EVPN_NODE - || vty->node == BGP_FLOWSPECV4_NODE - || vty->node == BGP_FLOWSPECV6_NODE - || vty->node == BGP_LS_NODE) + if (vty->node == BGP_IPV4_NODE || vty->node == BGP_IPV4M_NODE || + vty->node == BGP_IPV4L_NODE || vty->node == BGP_VPNV4_NODE || + vty->node == BGP_IPV6_NODE || vty->node == BGP_IPV6M_NODE || + vty->node == BGP_IPV6L_NODE || vty->node == BGP_VPNV6_NODE || + vty->node == BGP_EVPN_NODE || vty->node == BGP_FLOWSPECV4_NODE || + vty->node == BGP_FLOWSPECV6_NODE || vty->node == BGP_LS_NODE || + vty->node == BGP_MUPV4_NODE || vty->node == BGP_MUPV6_NODE) vty->node = BGP_NODE; return CMD_SUCCESS; } @@ -12104,7 +12129,7 @@ static int bgp_clear_prefix(struct vty *vty, const char *view_name, /* one clear bgp command to rule them all */ DEFUN (clear_ip_bgp_all, clear_ip_bgp_all_cmd, - "clear [ip] bgp [ VIEWVRFNAME] [ []] <*|A.B.C.D$neighbor|X:X::X:X$neighbor|WORD$neighbor|ASNUM|external|peer-group PGNAME> []|in [prefix-filter]|out|message-stats|capabilities>]", + "clear [ip] bgp [ VIEWVRFNAME] [ []] <*|A.B.C.D$neighbor|X:X::X:X$neighbor|WORD$neighbor|ASNUM|external|peer-group PGNAME> []|in [prefix-filter]|out|message-stats|capabilities>]", CLEAR_STR IP_STR BGP_STR @@ -22003,6 +22028,8 @@ static void bgp_config_write_family(struct vty *vty, struct bgp *bgp, afi_t afi, vty_frame(vty, "ipv4 encap"); else if (safi == SAFI_FLOWSPEC) vty_frame(vty, "ipv4 flowspec"); + else if (safi == SAFI_MUP) + vty_frame(vty, "ipv4 mup"); } else if (afi == AFI_IP6) { if (safi == SAFI_UNICAST) vty_frame(vty, "ipv6 unicast"); @@ -22016,6 +22043,8 @@ static void bgp_config_write_family(struct vty *vty, struct bgp *bgp, afi_t afi, vty_frame(vty, "ipv6 encap"); else if (safi == SAFI_FLOWSPEC) vty_frame(vty, "ipv6 flowspec"); + else if (safi == SAFI_MUP) + vty_frame(vty, "ipv6 mup"); } else if (afi == AFI_L2VPN) { if (safi == SAFI_EVPN) vty_frame(vty, "l2vpn evpn"); @@ -22720,6 +22749,10 @@ int bgp_config_write(struct vty *vty) /* BGP-LS configuration. */ bgp_config_write_family(vty, bgp, AFI_BGP_LS, SAFI_BGP_LS); + /* MUP IPv4/IPv6 configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP, SAFI_MUP); + bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_MUP); + hook_call(bgp_inst_config_write, bgp, vty); #ifdef ENABLE_BGP_VNC @@ -22852,6 +22885,22 @@ static struct cmd_node bgp_ls_node = { .no_xpath = true, }; +static struct cmd_node bgp_mupv4_node = { + .name = "bgp ipv4 mup", + .node = BGP_MUPV4_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_mupv6_node = { + .name = "bgp ipv6 mup", + .node = BGP_MUPV6_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", + .no_xpath = true, +}; + static void community_list_vty(void); static void bgp_ac_peergroup(vector comps, struct cmd_token *token) @@ -23172,6 +23221,8 @@ void bgp_vty_init(void) install_node(&bgp_flowspecv6_node); install_node(&bgp_srv6_node); install_node(&bgp_ls_node); + install_node(&bgp_mupv4_node); + install_node(&bgp_mupv6_node); /* Install default VTY commands to new nodes. */ install_default(BGP_NODE); @@ -23189,6 +23240,8 @@ void bgp_vty_init(void) install_default(BGP_EVPN_VNI_NODE); install_default(BGP_SRV6_NODE); install_default(BGP_LS_NODE); + install_default(BGP_MUPV4_NODE); + install_default(BGP_MUPV6_NODE); /* "global bgp inq-limit command */ install_element(CONFIG_NODE, &bgp_inq_limit_cmd); @@ -23594,6 +23647,8 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV6_NODE, &neighbor_activate_cmd); install_element(BGP_EVPN_NODE, &neighbor_activate_cmd); install_element(BGP_LS_NODE, &neighbor_activate_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_activate_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_activate_cmd); /* "no neighbor activate" commands. */ install_element(BGP_NODE, &no_neighbor_activate_hidden_cmd); @@ -23609,6 +23664,8 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_activate_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_activate_cmd); install_element(BGP_LS_NODE, &no_neighbor_activate_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_activate_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_activate_cmd); /* "neighbor peer-group" set commands. */ install_element(BGP_NODE, &neighbor_set_peer_group_cmd); @@ -23623,6 +23680,8 @@ void bgp_vty_init(void) &neighbor_set_peer_group_hidden_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_set_peer_group_hidden_cmd); /* "no neighbor peer-group unset" commands. */ install_element(BGP_NODE, &no_neighbor_set_peer_group_cmd); @@ -23637,6 +23696,8 @@ void bgp_vty_init(void) &no_neighbor_set_peer_group_hidden_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_set_peer_group_hidden_cmd); /* "neighbor softreconfiguration inbound" commands.*/ install_element(BGP_NODE, &neighbor_soft_reconfiguration_hidden_cmd); @@ -23667,6 +23728,10 @@ void bgp_vty_init(void) &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_EVPN_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_soft_reconfiguration_cmd); /* "neighbor attribute-unchanged" commands. */ install_element(BGP_NODE, &neighbor_attr_unchanged_hidden_cmd); @@ -23695,6 +23760,10 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_attr_unchanged_cmd); /* "nexthop-local unchanged" commands */ install_element(BGP_IPV6_NODE, &neighbor_nexthop_local_unchanged_cmd); @@ -23722,6 +23791,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &no_neighbor_nexthop_self_cmd); install_element(BGP_EVPN_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_nexthop_self_cmd); /* "neighbor next-hop-self force" commands. */ install_element(BGP_NODE, &neighbor_nexthop_self_force_hidden_cmd); @@ -23770,6 +23843,14 @@ void bgp_vty_init(void) &no_neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_EVPN_NODE, &neighbor_nexthop_self_force_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); /* "neighbor as-override" commands. */ install_element(BGP_NODE, &neighbor_as_override_hidden_cmd); @@ -23790,6 +23871,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV4_NODE, &no_neighbor_as_override_cmd); install_element(BGP_VPNV6_NODE, &neighbor_as_override_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_as_override_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_as_override_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_as_override_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_as_override_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_as_override_cmd); /* "neighbor remove-private-AS" commands. */ install_element(BGP_NODE, &neighbor_remove_private_as_hidden_cmd); @@ -23902,6 +23987,22 @@ void bgp_vty_init(void) &neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); /* "neighbor send-community" commands.*/ install_element(BGP_NODE, &neighbor_send_community_hidden_cmd); @@ -23940,6 +24041,14 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &neighbor_send_community_type_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_send_community_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_send_community_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_send_community_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_send_community_type_cmd); install_element(BGP_NODE, &neighbor_ecommunity_rpki_cmd); install_element(BGP_IPV4_NODE, &neighbor_ecommunity_rpki_cmd); install_element(BGP_IPV4M_NODE, &neighbor_ecommunity_rpki_cmd); @@ -23986,6 +24095,10 @@ void bgp_vty_init(void) &no_neighbor_route_reflector_client_cmd); install_element(BGP_EVPN_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_route_reflector_client_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_route_reflector_client_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_route_reflector_client_cmd); /* "neighbor route-server" commands.*/ install_element(BGP_NODE, &neighbor_route_server_client_hidden_cmd); @@ -24034,6 +24147,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &no_neighbor_disable_addpath_rx_cmd); install_element(BGP_EVPN_NODE, &neighbor_disable_addpath_rx_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_disable_addpath_rx_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_disable_addpath_rx_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_disable_addpath_rx_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_disable_addpath_rx_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_disable_addpath_rx_cmd); /* "neighbor addpath-tx-all-paths" commands.*/ install_element(BGP_NODE, &neighbor_addpath_tx_all_paths_hidden_cmd); @@ -24056,6 +24173,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &no_neighbor_addpath_tx_all_paths_cmd); install_element(BGP_EVPN_NODE, &neighbor_addpath_tx_all_paths_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_addpath_tx_all_paths_cmd); /* "neighbor addpath-tx-best-selected" commands.*/ install_element(BGP_IPV4_NODE, @@ -24092,6 +24213,10 @@ void bgp_vty_init(void) &no_neighbor_addpath_tx_best_selected_paths_cmd); install_element(BGP_EVPN_NODE, &neighbor_addpath_tx_best_selected_paths_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_addpath_tx_best_selected_paths_cmd); /* "neighbor addpath-tx-bestpath-per-AS" commands.*/ install_element(BGP_NODE, @@ -24132,6 +24257,10 @@ void bgp_vty_init(void) &no_neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_EVPN_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_addpath_tx_bestpath_per_as_cmd); /* "neighbor addpath-rx-paths-limit" commands.*/ install_element(BGP_NODE, &neighbor_addpath_paths_limit_cmd); @@ -24154,6 +24283,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &no_neighbor_addpath_paths_limit_cmd); install_element(BGP_EVPN_NODE, &neighbor_addpath_paths_limit_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_addpath_paths_limit_cmd); /* "neighbor sender-as-path-loop-detection" commands. */ install_element(BGP_NODE, &neighbor_aspath_loop_detection_cmd); @@ -24307,6 +24440,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV4_NODE, &no_neighbor_weight_cmd); install_element(BGP_VPNV6_NODE, &neighbor_weight_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_weight_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_weight_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_weight_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_weight_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_weight_cmd); /* "neighbor encapsulation-srv6|encapsulation-mpls" commands. */ install_element(BGP_VPNV4_NODE, &neighbor_encapsulation_srv6_or_mpls_cmd); @@ -24359,6 +24496,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV4_NODE, &no_neighbor_distribute_list_cmd); install_element(BGP_VPNV6_NODE, &neighbor_distribute_list_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_distribute_list_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_distribute_list_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_distribute_list_cmd); /* "neighbor prefix-list" commands. */ install_element(BGP_NODE, &neighbor_prefix_list_hidden_cmd); @@ -24383,6 +24524,10 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_prefix_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_prefix_list_cmd); /* "neighbor filter-list" commands. */ install_element(BGP_NODE, &neighbor_filter_list_hidden_cmd); @@ -24407,6 +24552,10 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_filter_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_filter_list_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_filter_list_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_filter_list_cmd); /* "neighbor route-map" commands. */ install_element(BGP_NODE, &neighbor_route_map_hidden_cmd); @@ -24435,6 +24584,10 @@ void bgp_vty_init(void) install_element(BGP_EVPN_NODE, &no_neighbor_route_map_cmd); install_element(BGP_LS_NODE, &neighbor_route_map_cmd); install_element(BGP_LS_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_route_map_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_route_map_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_route_map_cmd); /* "neighbor unsuppress-map" commands. */ install_element(BGP_NODE, &neighbor_unsuppress_map_hidden_cmd); @@ -24455,6 +24608,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV4_NODE, &no_neighbor_unsuppress_map_cmd); install_element(BGP_VPNV6_NODE, &neighbor_unsuppress_map_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_unsuppress_map_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_unsuppress_map_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_unsuppress_map_cmd); /* "neighbor advertise-map" commands. */ install_element(BGP_NODE, &bgp_condadv_period_cmd); @@ -24467,6 +24624,8 @@ void bgp_vty_init(void) install_element(BGP_IPV6L_NODE, &neighbor_advertise_map_cmd); install_element(BGP_VPNV4_NODE, &neighbor_advertise_map_cmd); install_element(BGP_VPNV6_NODE, &neighbor_advertise_map_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_advertise_map_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_advertise_map_cmd); /* bgp default-originate timer */ install_element(BGP_NODE, &bgp_def_originate_eval_cmd); @@ -24490,6 +24649,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV4_NODE, &no_neighbor_maximum_prefix_out_cmd); install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_out_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_maximum_prefix_out_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_maximum_prefix_out_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_maximum_prefix_out_cmd); /* "neighbor maximum-prefix" commands. */ install_element(BGP_NODE, &neighbor_maximum_prefix_hidden_cmd); @@ -24583,6 +24746,20 @@ void bgp_vty_init(void) install_element(BGP_EVPN_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_maximum_prefix_cmd); /* "neighbor allowas-in" */ install_element(BGP_NODE, &neighbor_allowas_in_hidden_cmd); @@ -24605,6 +24782,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &no_neighbor_allowas_in_cmd); install_element(BGP_EVPN_NODE, &neighbor_allowas_in_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_allowas_in_cmd); /* neighbor accept-own */ install_element(BGP_VPNV4_NODE, &neighbor_accept_own_cmd); @@ -24629,6 +24810,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &no_neighbor_soo_cmd); install_element(BGP_EVPN_NODE, &neighbor_soo_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_soo_cmd); + install_element(BGP_MUPV4_NODE, &neighbor_soo_cmd); + install_element(BGP_MUPV4_NODE, &no_neighbor_soo_cmd); + install_element(BGP_MUPV6_NODE, &neighbor_soo_cmd); + install_element(BGP_MUPV6_NODE, &no_neighbor_soo_cmd); /* "neighbor dampening" commands. */ install_element(BGP_NODE, &neighbor_damp_cmd); @@ -24671,6 +24856,8 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV6_NODE, &exit_address_family_cmd); install_element(BGP_EVPN_NODE, &exit_address_family_cmd); install_element(BGP_LS_NODE, &exit_address_family_cmd); + install_element(BGP_MUPV4_NODE, &exit_address_family_cmd); + install_element(BGP_MUPV6_NODE, &exit_address_family_cmd); /* BGP retain all route-target */ install_element(BGP_VPNV4_NODE, &bgp_retain_route_target_cmd); diff --git a/bgpd/bgp_vty.h b/bgpd/bgp_vty.h index 8ad0edd7631e..fd97f13d6755 100644 --- a/bgpd/bgp_vty.h +++ b/bgpd/bgp_vty.h @@ -34,9 +34,9 @@ FRR_CFG_DEFAULT_ULONG(BGP_CONNECT_RETRY, #define BGP_AFI_SAFI_CMD_STR BGP_AFI_CMD_STR" "BGP_SAFI_CMD_STR #define BGP_AFI_SAFI_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_HELP_STR -#define BGP_SAFI_WITH_LABEL_CMD_STR "" -#define BGP_SAFI_WITH_LABEL_HELP_STR \ - BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR \ +#define BGP_SAFI_WITH_LABEL_CMD_STR "" +#define BGP_SAFI_WITH_LABEL_HELP_STR \ + BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR \ BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR #define BGP_SELF_ORIG_CMD_STR "self-originate" diff --git a/lib/command.h b/lib/command.h index ce9d894855f0..d9f9da4d4808 100644 --- a/lib/command.h +++ b/lib/command.h @@ -121,6 +121,8 @@ enum node_type { BGP_EVPN_NODE, /* BGP EVPN node. */ BGP_SRV6_NODE, /* BGP SRv6 node. */ BGP_LS_NODE, /* BGP Link-State node. */ + BGP_MUPV4_NODE, /* BGP-MUP IPv4 (draft-ietf-bess-mup-safi). */ + BGP_MUPV6_NODE, /* BGP-MUP IPv6 (draft-ietf-bess-mup-safi). */ OSPF_NODE, /* OSPF protocol mode */ OSPF6_NODE, /* OSPF protocol for IPv6 mode */ LDP_NODE, /* LDP protocol mode */ diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 677529ee005b..ad633c41ac5b 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -1524,6 +1524,20 @@ static struct cmd_node bgp_ls_node = { .prompt = "%s(config-router-af)# ", }; +static struct cmd_node bgp_mupv4_node = { + .name = "bgp ipv4 mup", + .node = BGP_MUPV4_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", +}; + +static struct cmd_node bgp_mupv6_node = { + .name = "bgp ipv6 mup", + .node = BGP_MUPV6_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", +}; + static struct cmd_node ospf_node = { .name = "ospf", .node = OSPF_NODE, @@ -1854,6 +1868,26 @@ DEFUNSH(VTYSH_BGPD, address_family_flowspecv6, address_family_flowspecv6_cmd, return CMD_SUCCESS; } +DEFUNSH(VTYSH_BGPD, address_family_mupv4, address_family_mupv4_cmd, + "address-family ipv4 mup", + "Enter Address Family command mode\n" + BGP_AF_STR + BGP_AF_MODIFIER_STR) +{ + vty->node = BGP_MUPV4_NODE; + return CMD_SUCCESS; +} + +DEFUNSH(VTYSH_BGPD, address_family_mupv6, address_family_mupv6_cmd, + "address-family ipv6 mup", + "Enter Address Family command mode\n" + BGP_AF_STR + BGP_AF_MODIFIER_STR) +{ + vty->node = BGP_MUPV6_NODE; + return CMD_SUCCESS; +} + DEFUNSH(VTYSH_BGPD, address_family_ipv4_multicast, address_family_ipv4_multicast_cmd, "address-family ipv4 multicast", "Enter Address Family command mode\n" @@ -2609,14 +2643,13 @@ DEFUNSH(VTYSH_REALLYALL, vtysh_quit_all, vtysh_quit_all_cmd, "quit", DEFUNSH(VTYSH_BGPD, exit_address_family, exit_address_family_cmd, "exit-address-family", "Exit from Address Family configuration mode\n") { - if (vty->node == BGP_IPV4_NODE || vty->node == BGP_IPV4M_NODE - || vty->node == BGP_IPV4L_NODE || vty->node == BGP_VPNV4_NODE - || vty->node == BGP_VPNV6_NODE || vty->node == BGP_IPV6_NODE - || vty->node == BGP_IPV6L_NODE || vty->node == BGP_IPV6M_NODE - || vty->node == BGP_EVPN_NODE - || vty->node == BGP_FLOWSPECV4_NODE - || vty->node == BGP_FLOWSPECV6_NODE - || vty->node == BGP_LS_NODE) + if (vty->node == BGP_IPV4_NODE || vty->node == BGP_IPV4M_NODE || + vty->node == BGP_IPV4L_NODE || vty->node == BGP_VPNV4_NODE || + vty->node == BGP_VPNV6_NODE || vty->node == BGP_IPV6_NODE || + vty->node == BGP_IPV6L_NODE || vty->node == BGP_IPV6M_NODE || + vty->node == BGP_EVPN_NODE || vty->node == BGP_FLOWSPECV4_NODE || + vty->node == BGP_FLOWSPECV6_NODE || vty->node == BGP_LS_NODE || + vty->node == BGP_MUPV4_NODE || vty->node == BGP_MUPV6_NODE) vty->node = BGP_NODE; return CMD_SUCCESS; } @@ -5317,6 +5350,8 @@ void vtysh_init_vty(void) install_node(&bmp_node); install_node(&bgp_srv6_node); install_node(&bgp_ls_node); + install_node(&bgp_mupv4_node); + install_node(&bgp_mupv6_node); install_node(&rip_node); install_node(&ripng_node); install_node(&ospf_node); @@ -5503,6 +5538,18 @@ void vtysh_init_vty(void) install_element(BGP_LS_NODE, &vtysh_exit_bgpd_cmd); install_element(BGP_LS_NODE, &vtysh_end_all_cmd); install_element(BGP_LS_NODE, &exit_address_family_cmd); + + install_element(BGP_NODE, &address_family_mupv4_cmd); + install_element(BGP_MUPV4_NODE, &vtysh_quit_bgpd_cmd); + install_element(BGP_MUPV4_NODE, &vtysh_exit_bgpd_cmd); + install_element(BGP_MUPV4_NODE, &vtysh_end_all_cmd); + install_element(BGP_MUPV4_NODE, &exit_address_family_cmd); + + install_element(BGP_NODE, &address_family_mupv6_cmd); + install_element(BGP_MUPV6_NODE, &vtysh_quit_bgpd_cmd); + install_element(BGP_MUPV6_NODE, &vtysh_exit_bgpd_cmd); + install_element(BGP_MUPV6_NODE, &vtysh_end_all_cmd); + install_element(BGP_MUPV6_NODE, &exit_address_family_cmd); #endif /* HAVE_BGPD */ /* ripd */ From 31155d2e83262df5e38ce2095f6b9701c1c75010 Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Thu, 4 Jun 2026 21:32:55 +0900 Subject: [PATCH 7/9] bgpd: render received BGP-MUP routes in show output route_vty_out_route() renders prefixes via inet_ntop(), which has no AF_MUP formatter; use the MUP %pFX printer instead, and decompose the NLRI into structured fields via bgp_mup_route2json() for JSON output. Signed-off-by: Yuya Kusakabe --- bgpd/bgp_mup.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ bgpd/bgp_mup.h | 3 +++ bgpd/bgp_route.c | 13 ++++++++++++ 3 files changed, 69 insertions(+) diff --git a/bgpd/bgp_mup.c b/bgpd/bgp_mup.c index 854614267f2a..878e9e4bb8d7 100644 --- a/bgpd/bgp_mup.c +++ b/bgpd/bgp_mup.c @@ -15,6 +15,59 @@ #include "bgpd/bgp_rd.h" #include "bgpd/bgp_route.h" +/* Add the decomposed MUP NLRI fields to a json object for show output. */ +void bgp_mup_route2json(const struct prefix_mup *pm, struct json_object *json) +{ + const struct mup_prefix *mp = &pm->prefix; + struct prefix_rd prd = {}; + int family; + + if (!mp || !json) + return; + + json_object_int_add(json, "archType", mp->arch_type); + json_object_int_add(json, "routeType", mp->route_type); + + memcpy(prd.val, mp->rd, sizeof(prd.val)); + json_object_string_addf(json, "rd", "%pRDP", &prd); + + switch (mp->route_type) { + case BGP_MUP_ISD_ROUTE: + family = IS_IPADDR_V4(&mp->isd_route.ip) ? AF_INET : AF_INET6; + json_object_string_add(json, "ipFamily", family == AF_INET ? "ipv4" : "ipv6"); + json_object_string_addf(json, "ip", "%pIA", &mp->isd_route.ip); + json_object_int_add(json, "ipLen", mp->isd_route.ip_prefix_length); + break; + case BGP_MUP_DSD_ROUTE: + family = IS_IPADDR_V4(&mp->dsd_route.ip) ? AF_INET : AF_INET6; + json_object_string_add(json, "ipFamily", family == AF_INET ? "ipv4" : "ipv6"); + json_object_string_addf(json, "ip", "%pIA", &mp->dsd_route.ip); + break; + case BGP_MUP_T1ST_ROUTE: + family = IS_IPADDR_V4(&mp->t1st_route.ip) ? AF_INET : AF_INET6; + json_object_string_add(json, "ipFamily", family == AF_INET ? "ipv4" : "ipv6"); + json_object_string_addf(json, "ip", "%pIA", &mp->t1st_route.ip); + json_object_int_add(json, "ipLen", mp->t1st_route.ip_prefix_length); + json_object_int_add(json, "teid", mp->t1st_route.t1st_3gpp_5g.teid); + json_object_int_add(json, "qfi", mp->t1st_route.t1st_3gpp_5g.qfi); + if (mp->t1st_route.t1st_3gpp_5g.endpoint_address_length) + json_object_string_addf(json, "endpointAddress", "%pIA", + &mp->t1st_route.t1st_3gpp_5g.endpoint_address); + if (mp->t1st_route.t1st_3gpp_5g.source_address_length) + json_object_string_addf(json, "sourceAddress", "%pIA", + &mp->t1st_route.t1st_3gpp_5g.source_address); + break; + case BGP_MUP_T2ST_ROUTE: + family = IS_IPADDR_V4(&mp->t2st_route.endpoint_address) ? AF_INET : AF_INET6; + json_object_string_add(json, "endpointAddressFamily", + family == AF_INET ? "ipv4" : "ipv6"); + json_object_string_addf(json, "endpointAddress", "%pIA", + &mp->t2st_route.endpoint_address); + json_object_int_add(json, "teid", mp->t2st_route.teid); + break; + } +} + /* On-wire size of one BGP-MUP NLRI: fixed header plus the route body. */ size_t bgp_mup_prefix_size(const struct prefix *p) { diff --git a/bgpd/bgp_mup.h b/bgpd/bgp_mup.h index a5904a99242c..a794e126765f 100644 --- a/bgpd/bgp_mup.h +++ b/bgpd/bgp_mup.h @@ -35,4 +35,7 @@ extern void bgp_mup_encode_prefix(struct stream *s, afi_t afi, const struct pref extern int bgp_nlri_parse_mup(struct peer *peer, struct attr *attr, struct bgp_nlri *packet, bool withdraw); +/* Add the decomposed MUP NLRI fields to a json object for show output. */ +extern void bgp_mup_route2json(const struct prefix_mup *pm, struct json_object *json); + #endif /* _FRR_BGP_MUP_H */ diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 8372f5f4bfd8..57eeedcdb335 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -62,6 +62,7 @@ #include "bgpd/bgp_label.h" #include "bgpd/bgp_addpath.h" #include "bgpd/bgp_mac.h" +#include "bgpd/bgp_mup.h" #include "bgpd/bgp_network.h" #include "bgpd/bgp_trace.h" #include "bgpd/bgp_rpki.h" @@ -11132,6 +11133,18 @@ static void route_vty_out_route(struct bgp_dest *dest, const struct prefix *p, s json_object_string_add(json, "nlriStr", nlri_str); json_object_object_add(json, "nlri", json_nlri); } + } else if (p->family == AF_MUP) { + if (!json) { + len = vty_out(vty, "%pFX", p); + } else { + const struct prefix_mup *pm = (const struct prefix_mup *)p; + + json_object_string_addf(json, "prefix", "%pFX", p); + json_object_int_add(json, "prefixLen", p->prefixlen); + json_object_string_addf(json, "network", "%pFX", p); + json_object_int_add(json, "version", dest->version); + bgp_mup_route2json(pm, json); + } } else { if (!json) len = vty_out(vty, "%pFX", p); From 005e0e170e0405cdcbf12ae2cf0082f3a0f0b206 Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Wed, 10 Jun 2026 11:03:40 +0900 Subject: [PATCH 8/9] doc: add BGP-MUP SAFI user guide section Document the BGP-MUP SAFI (draft-ietf-bess-mup-safi): the four MUP route types, how "address-family ipv4|ipv6 mup" activates the sub-AFIs on a peering, what the speaker does with received routes, and the show commands for inspecting them. Signed-off-by: Yuya Kusakabe --- doc/user/bgp.rst | 51 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index 146c198e67e4..a3b3f591cf1f 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -4457,6 +4457,55 @@ This makes it possible to separate not only layer 3 networks like VRF-lite netwo Also, VRF netns based make possible to separate layer 2 networks on separate VRF instances. +.. _bgp-mup: + +BGP Mobile User Plane (MUP) SAFI +-------------------------------- + +FRR supports the BGP Mobile User Plane SAFI defined in +`draft-ietf-bess-mup-safi +`_, +which lets BGP advertise the session state required to bridge GTP-U +and SRv6 forwarding planes in an SRv6 Mobile User Plane deployment. +The SAFI carries four route types: Type 1 (Interwork Segment +Discovery), Type 2 (Direct Segment Discovery), Type 3 (Type 1 +Session Transformed) and Type 4 (Type 2 Session Transformed). The +routes complement the SRv6 Mobile User Plane endpoint behaviors +specified in :rfc:`9433`. + +The IPv4 and IPv6 sub-AFIs are activated independently on each +peering: + +.. code-block:: frr + + router bgp 65000 + neighbor 2001:db8::1 remote-as 65000 + ! + address-family ipv4 mup + neighbor 2001:db8::1 activate + exit-address-family + ! + address-family ipv6 mup + neighbor 2001:db8::1 activate + exit-address-family + +Activating these address families causes the local speaker to +advertise the BGP Multiprotocol Extensions capability for IPv4 and +IPv6 Mobile User Plane, parse all four route types, and run best-path +selection and re-advertisement over them, so an FRR speaker can act +as a route reflector or route server for the SAFI. The generic +per-neighbor policy commands such as ``route-map`` and +``route-reflector-client`` are available under both address families. + +Received routes are inspected with the following command: + +.. clicmd:: show bgp mup [all] [json] + + Display the BGP-MUP RIB for the selected sub-AFI. + +Locally originating Mobile User Plane routes and installing +received routes into the forwarding plane are not yet supported. + .. _bgp-conditional-advertisement: BGP Conditional Advertisement @@ -5036,7 +5085,7 @@ incoming/outgoing directions. Total number of VRFs (including default): 3 -.. clicmd:: show bgp [ | l2vpn evpn] +.. clicmd:: show bgp [ | l2vpn evpn] These commands display BGP routes for the specific routing table indicated by the selected afi and the selected safi. If no afi and no safi value is given, From c03010f646f37feee82f31e76e4899ab6136a3a5 Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Wed, 10 Jun 2026 11:03:40 +0900 Subject: [PATCH 9/9] tests: add BGP-MUP topotest covering capability and NLRI codec Verify the BGP-MUP control plane over a live topology: capability negotiation for both AFIs, parse of all four route types announced by an ExaBGP peer, show output, and re-advertisement and withdraw to a second FRR speaker that exercise the encode and withdraw paths on the wire. Signed-off-by: Yuya Kusakabe --- tests/topotests/bgp_mup/__init__.py | 0 tests/topotests/bgp_mup/exabgp.env | 53 +++ tests/topotests/bgp_mup/peer1/exabgp.cfg | 65 ++++ .../topotests/bgp_mup/r1/bgp_capability.json | 14 + tests/topotests/bgp_mup/r1/bgp_neighbor.json | 7 + tests/topotests/bgp_mup/r1/frr.conf | 22 ++ .../topotests/bgp_mup/r2/bgp_capability.json | 14 + tests/topotests/bgp_mup/r2/bgp_neighbor.json | 7 + tests/topotests/bgp_mup/r2/frr.conf | 30 ++ tests/topotests/bgp_mup/test_bgp_mup.py | 336 ++++++++++++++++++ 10 files changed, 548 insertions(+) create mode 100644 tests/topotests/bgp_mup/__init__.py create mode 100644 tests/topotests/bgp_mup/exabgp.env create mode 100644 tests/topotests/bgp_mup/peer1/exabgp.cfg create mode 100644 tests/topotests/bgp_mup/r1/bgp_capability.json create mode 100644 tests/topotests/bgp_mup/r1/bgp_neighbor.json create mode 100644 tests/topotests/bgp_mup/r1/frr.conf create mode 100644 tests/topotests/bgp_mup/r2/bgp_capability.json create mode 100644 tests/topotests/bgp_mup/r2/bgp_neighbor.json create mode 100644 tests/topotests/bgp_mup/r2/frr.conf create mode 100644 tests/topotests/bgp_mup/test_bgp_mup.py diff --git a/tests/topotests/bgp_mup/__init__.py b/tests/topotests/bgp_mup/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/topotests/bgp_mup/exabgp.env b/tests/topotests/bgp_mup/exabgp.env new file mode 100644 index 000000000000..6c554f5fa8af --- /dev/null +++ b/tests/topotests/bgp_mup/exabgp.env @@ -0,0 +1,53 @@ + +[exabgp.api] +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_mup/peer1/exabgp.cfg b/tests/topotests/bgp_mup/peer1/exabgp.cfg new file mode 100644 index 000000000000..f888ed3147c8 --- /dev/null +++ b/tests/topotests/bgp_mup/peer1/exabgp.cfg @@ -0,0 +1,65 @@ +neighbor 10.0.2.1 { + router-id 10.0.2.105; + local-address 10.0.2.105; + local-as 65003; + peer-as 65002; + + family { + ipv4 mup; + ipv6 mup; + } + announce { + ipv4 { + # ISD: gNB-facing prefix reachable through an + # interworking SRv6 endpoint. + mup \ + mup-isd 10.99.0.0/24 rd 100:100 \ + next-hop 10.0.2.105 \ + extended-community [ target:65001:1 ]; + + # DSD: direct-mode SRv6 endpoint, advertised with the + # MUP Extended Community Direct-Type Segment Identifier. + mup \ + mup-dsd 10.0.0.250 rd 300:300 \ + next-hop 10.0.2.105 \ + extended-community [ target:65001:1 mup:65001:10 ]; + + # T1ST: one GTP-U session toward UE prefix + # 192.168.1.5/32 behind endpoint 10.99.0.5. + mup \ + mup-t1st 192.168.1.5/32 rd 100:100 teid 12345 qfi 9 \ + endpoint 10.99.0.5 source 10.0.2.105 \ + next-hop 10.0.2.105 \ + extended-community [ target:65001:1 ]; + + # T2ST: GTP-U endpoint 10.0.0.250, TEID 12345. + mup \ + mup-t2st 10.0.0.250 rd 100:100 teid 12345/32 \ + next-hop 10.0.2.105 \ + extended-community [ target:65001:1 mup:65001:10 ]; + } + ipv6 { + # One route of each MUP type on the IPv6 sub-AFI. + mup \ + mup-isd 2001:db8:99::/64 rd 200:200 \ + next-hop 2001:db8:2::105 \ + extended-community [ target:65001:2 ]; + + mup \ + mup-dsd 2001:db8:99::250 rd 300:300 \ + next-hop 2001:db8:2::105 \ + extended-community [ target:65001:2 mup:65001:10 ]; + + mup \ + mup-t1st 2001:db8:aa::/64 rd 200:200 teid 12345 qfi 9 \ + endpoint 2001:db8:99::5 source 2001:db8:2::105 \ + next-hop 2001:db8:2::105 \ + extended-community [ target:65001:2 ]; + + mup \ + mup-t2st 2001:db8:99::250 rd 200:200 teid 12345/32 \ + next-hop 2001:db8:2::105 \ + extended-community [ target:65001:2 mup:65001:10 ]; + } + } +} diff --git a/tests/topotests/bgp_mup/r1/bgp_capability.json b/tests/topotests/bgp_mup/r1/bgp_capability.json new file mode 100644 index 000000000000..780a5280f72d --- /dev/null +++ b/tests/topotests/bgp_mup/r1/bgp_capability.json @@ -0,0 +1,14 @@ +{ + "2001:db8::2": { + "neighborCapabilities": { + "multiprotocolExtensions": { + "ipv4Mup": { + "advertisedAndReceived": true + }, + "ipv6Mup": { + "advertisedAndReceived": true + } + } + } + } +} diff --git a/tests/topotests/bgp_mup/r1/bgp_neighbor.json b/tests/topotests/bgp_mup/r1/bgp_neighbor.json new file mode 100644 index 000000000000..74da80ca070a --- /dev/null +++ b/tests/topotests/bgp_mup/r1/bgp_neighbor.json @@ -0,0 +1,7 @@ +{ + "2001:db8::2": { + "remoteAs": 65002, + "localAs": 65001, + "bgpState": "Established" + } +} diff --git a/tests/topotests/bgp_mup/r1/frr.conf b/tests/topotests/bgp_mup/r1/frr.conf new file mode 100644 index 000000000000..d4c5850658e5 --- /dev/null +++ b/tests/topotests/bgp_mup/r1/frr.conf @@ -0,0 +1,22 @@ +! +hostname r1 +! +interface r1-eth0 + ipv6 address 2001:db8::1/64 +exit +! +router bgp 65001 + bgp router-id 1.1.1.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001:db8::2 remote-as 65002 + ! + address-family ipv4 mup + neighbor 2001:db8::2 activate + exit-address-family + ! + address-family ipv6 mup + neighbor 2001:db8::2 activate + exit-address-family +exit +! diff --git a/tests/topotests/bgp_mup/r2/bgp_capability.json b/tests/topotests/bgp_mup/r2/bgp_capability.json new file mode 100644 index 000000000000..8c1b9214607c --- /dev/null +++ b/tests/topotests/bgp_mup/r2/bgp_capability.json @@ -0,0 +1,14 @@ +{ + "2001:db8::1": { + "neighborCapabilities": { + "multiprotocolExtensions": { + "ipv4Mup": { + "advertisedAndReceived": true + }, + "ipv6Mup": { + "advertisedAndReceived": true + } + } + } + } +} diff --git a/tests/topotests/bgp_mup/r2/bgp_neighbor.json b/tests/topotests/bgp_mup/r2/bgp_neighbor.json new file mode 100644 index 000000000000..7dbac47941e4 --- /dev/null +++ b/tests/topotests/bgp_mup/r2/bgp_neighbor.json @@ -0,0 +1,7 @@ +{ + "2001:db8::1": { + "remoteAs": 65001, + "localAs": 65002, + "bgpState": "Established" + } +} diff --git a/tests/topotests/bgp_mup/r2/frr.conf b/tests/topotests/bgp_mup/r2/frr.conf new file mode 100644 index 000000000000..4979bea608a1 --- /dev/null +++ b/tests/topotests/bgp_mup/r2/frr.conf @@ -0,0 +1,30 @@ +! +hostname r2 +! +interface r2-eth0 + ipv6 address 2001:db8::2/64 +exit +! +interface r2-eth1 + ip address 10.0.2.1/24 + ipv6 address 2001:db8:2::1/64 +exit +! +router bgp 65002 + bgp router-id 2.2.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001:db8::1 remote-as 65001 + neighbor 10.0.2.105 remote-as 65003 + ! + address-family ipv4 mup + neighbor 2001:db8::1 activate + neighbor 10.0.2.105 activate + exit-address-family + ! + address-family ipv6 mup + neighbor 2001:db8::1 activate + neighbor 10.0.2.105 activate + exit-address-family +exit +! diff --git a/tests/topotests/bgp_mup/test_bgp_mup.py b/tests/topotests/bgp_mup/test_bgp_mup.py new file mode 100644 index 000000000000..06ae1098f9df --- /dev/null +++ b/tests/topotests/bgp_mup/test_bgp_mup.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2026 by Yuya Kusakabe +# +r""" +Test BGP-MUP SAFI (draft-ietf-bess-mup-safi) control-plane basics: + +- BGP session establishment over BGP-MUP-only peerings +- BGP-MUP capability negotiation for both AFI=IPv4 and AFI=IPv6 +- NLRI parse of all four route types (ISD / DSD / T1ST / T2ST) + received from an ExaBGP peer +- Structured `show bgp ipv[46] mup all json` rendering +- Re-advertisement of the received routes to a second FRR speaker, + which exercises the encode side of the NLRI codec on the wire +- The BGP-MUP extended community rendering in detailed show output +- Withdraw of every route when the announcing peer goes away, which + exercises the withdraw side of the codec on the wire + +Topology: + + +-----+ +-----+ +-------+ + | r1 |---------| r2 |---------| peer1 | + +-----+ +-----+ +-------+ + AS 65001 AS 65002 AS 65003 + eBGP (v6) eBGP (v4) ExaBGP + +peer1 (ExaBGP) announces one route of each MUP type to r2 on both +the IPv4 and IPv6 sub-AFIs. r2 must parse and display them, and +re-advertise them to r1. +""" + +import os +import sys +import json +import functools + +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + +# (afi, routeType, ip, expected NLRI fields) for every route peer1 +# announces; checked on r2 (receive) and r1 (re-advertisement). +EXPECTED_MUP_ROUTES = [ + ( + "ipv4", + 1, + "10.99.0.0", + {"archType": 1, "ipFamily": "ipv4", "ipLen": 24, "rd": "100:100"}, + ), + ("ipv4", 2, "10.0.0.250", {"ipFamily": "ipv4", "rd": "300:300"}), + ( + "ipv4", + 3, + "192.168.1.5", + { + "ipLen": 32, + "teid": 12345, + "qfi": 9, + "endpointAddress": "10.99.0.5", + "sourceAddress": "10.0.2.105", + }, + ), + ("ipv4", 4, "10.0.0.250", {"teid": 12345, "endpointAddressFamily": "ipv4"}), + ( + "ipv6", + 1, + "2001:db8:99::", + {"ipFamily": "ipv6", "ipLen": 64, "rd": "200:200"}, + ), + ("ipv6", 2, "2001:db8:99::250", {"ipFamily": "ipv6", "rd": "300:300"}), + ( + "ipv6", + 3, + "2001:db8:aa::", + { + "ipLen": 64, + "teid": 12345, + "qfi": 9, + "endpointAddress": "2001:db8:99::5", + "sourceAddress": "2001:db8:2::105", + }, + ), + ( + "ipv6", + 4, + "2001:db8:99::250", + {"teid": 12345, "endpointAddressFamily": "ipv6"}, + ), +] + + +def build_topo(tgen): + """r1 <--s1--> r2 <--s2--> peer1 (ExaBGP).""" + for i in (1, 2): + tgen.add_router("r{}".format(i)) + + sw = tgen.add_switch("s1") + sw.add_link(tgen.gears["r1"]) + sw.add_link(tgen.gears["r2"]) + + sw2 = tgen.add_switch("s2") + sw2.add_link(tgen.gears["r2"]) + peer = tgen.add_exabgp_peer( + "peer1", ip="10.0.2.105/24", defaultRoute="via 10.0.2.1" + ) + sw2.add_link(peer) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + # The routes announced on the IPv6 sub-AFI carry an IPv6 next-hop; + # put it on peer1's interface so r2 can validate it as on-link. + tgen.gears["peer1"].run("ip addr add 2001:db8:2::105/64 dev peer1-eth0") + + for rname, router in tgen.routers().items(): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + # Start ExaBGP after FRR is up so the BGP session establishes + # against an already-listening r2. + for pname, peer in tgen.exabgp_peers().items(): + peer_dir = os.path.join(CWD, pname) + env_file = os.path.join(CWD, "exabgp.env") + peer.start(peer_dir, env_file) + logger.info("started %s", pname) + + +def teardown_module(mod): + get_topogen().stop_topology() + + +def _open_json_file(path): + try: + with open(path, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(path) + + +def _check_router_json(router, command, reffile, label): + expected = _open_json_file(reffile) + test_func = functools.partial(topotest.router_json_cmp, router, command, expected) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, '"{}" {} JSON output mismatches'.format(router.name, label) + + +def _check_show_contains(router, command, pattern): + "Return None if `pattern` shows up in `command` output, else an error." + if pattern in router.vtysh_cmd(command): + return None + return "'{}' not found in `{}`".format(pattern, command) + + +def _find_mup_route(routes, route_type, ip): + """Walk a `routes` dict from `show bgp ipv* mup all json` and + return the first path whose (routeType, ip) matches. Keys are + human-readable NLRI strings (e.g. + `[1]:[3]:[28]:[192.168.1.5/32]:[teid=12345][qfi=9][ep=10.99.0.5] + [src=10.0.2.105]`); we can't rely on exact key form, so scan each + value list instead. + """ + for paths in (routes or {}).values(): + if not isinstance(paths, list): + continue + for path in paths: + if path.get("routeType") != route_type: + continue + if path.get("ip") == ip or path.get("endpointAddress") == ip: + return path + return None + + +def _mup_json_routes(router, afi): + out = router.vtysh_cmd("show bgp {} mup all json".format(afi), isjson=True) + key = "ipv4Mup" if afi == "ipv4" else "ipv6Mup" + if not isinstance(out, dict) or key not in out: + return None + return out[key].get("routes", {}) + + +def _check_all_mup_routes(router): + """Return None when every EXPECTED_MUP_ROUTES entry is present with + the expected structured NLRI fields, else an error string.""" + for afi, route_type, ip, fields in EXPECTED_MUP_ROUTES: + routes = _mup_json_routes(router, afi) + if routes is None: + return "{} mup json output missing".format(afi) + path = _find_mup_route(routes, route_type, ip) + if path is None: + return "type-{} route {} missing from {} mup json".format( + route_type, ip, afi + ) + for key, want in fields.items(): + if path.get(key) != want: + return "type-{} route {}: {}={!r}, want {!r}".format( + route_type, ip, key, path.get(key), want + ) + return None + + +def test_bgp_session_established(): + """The BGP session over the MUP-only AFs must reach Established.""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Verifying BGP session establishes over BGP-MUP") + + for rname in ("r1", "r2"): + _check_router_json( + tgen.gears[rname], + "show bgp neighbor json", + os.path.join(CWD, rname, "bgp_neighbor.json"), + "neighbor", + ) + + +def test_bgp_mup_capability(): + """ipv4Mup AND ipv6Mup must show advertisedAndReceived on both sides.""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Verifying BGP-MUP multiprotocol capability negotiation") + + for rname in ("r1", "r2"): + _check_router_json( + tgen.gears[rname], + "show bgp neighbor json", + os.path.join(CWD, rname, "bgp_capability.json"), + "MUP capability", + ) + + +def test_mup_routes_received_on_r2(): + """All routes announced by peer1 must reach r2's BGP-MUP RIB.""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Verifying peer1's MUP routes are received on r2") + r2 = tgen.gears["r2"] + + for afi, route_type, ip, _ in EXPECTED_MUP_ROUTES: + test_func = functools.partial( + _check_show_contains, r2, "show bgp {} mup all".format(afi), ip + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "type-{} route on r2: {}".format(route_type, result) + + +def test_show_bgp_mup_json(): + """`show bgp ipv[46] mup all json` on r2 must decompose each NLRI + into structured fields.""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + test_func = functools.partial(_check_all_mup_routes, tgen.gears["r2"]) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "r2 mup json: {}".format(result) + + +def test_mup_routes_propagated_to_r1(): + """r2 must re-advertise the routes to r1 with the NLRI intact; + this exercises the encode path of the codec on a live session.""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Verifying MUP routes are re-advertised to r1") + + test_func = functools.partial(_check_all_mup_routes, tgen.gears["r1"]) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "r1 mup json: {}".format(result) + + +def test_mup_extended_community(): + """The BGP-MUP extended community carried on the DSD/T2ST routes + must render as MUP:: in detailed show output.""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + test_func = functools.partial( + _check_show_contains, + tgen.gears["r2"], + "show bgp ipv4 mup all detail-routes", + "MUP:65001:10", + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, result + + +def _check_no_mup_routes(router): + "Return None when both MUP RIBs are empty, else an error string." + for afi in ("ipv4", "ipv6"): + if _mup_json_routes(router, afi): + return "{} still has {} mup routes".format(router.name, afi) + return None + + +def test_mup_routes_withdrawn_on_peer_down(): + """Stopping ExaBGP must withdraw everything: r2 purges the routes on + session down and sends MP_UNREACH to r1, which exercises the + withdraw side of the codec on the wire. Must run last.""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Stopping peer1 and verifying MUP routes are withdrawn") + peer = tgen.gears["peer1"] + peer.stop() + peer.run("pkill -f exabgp") + + for rname in ("r2", "r1"): + test_func = functools.partial(_check_no_mup_routes, tgen.gears[rname]) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, result + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args))