diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index bd965c7c4953..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" @@ -4959,6 +4960,16 @@ 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: + /* 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: assert(!"SAFI's UNSPEC or MAX being specified are a DEV ESCAPE"); @@ -4970,7 +4981,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 +5232,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: + bgp_mup_encode_prefix(s, afi, p, prd, addpath_capable, addpath_tx_id); + break; } } @@ -5263,6 +5278,9 @@ size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, /* TODO: add explaination */ size = 0; break; + case SAFI_MUP: + size = bgp_mup_prefix_size(p); + break; } return size; @@ -5792,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_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 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..878e9e4bb8d7 --- /dev/null +++ b/bgpd/bgp_mup.c @@ -0,0 +1,553 @@ +// 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" + +/* 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) +{ + 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..a794e126765f --- /dev/null +++ b/bgpd/bgp_mup.h @@ -0,0 +1,41 @@ +// 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); + +/* 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_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..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, @@ -330,6 +331,8 @@ 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: + return bgp_nlri_parse_mup(peer, attr, packet, mp_withdraw); } return BGP_NLRI_PARSE_ERROR; } diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index c5a8a9d3248c..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); @@ -14876,6 +14889,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_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/bgp_vty.c b/bgpd/bgp_vty.c index 068b25d682b3..1a836bbf3f71 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -179,6 +179,8 @@ 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: @@ -200,6 +202,8 @@ 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: @@ -238,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"; @@ -251,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"; @@ -283,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"; @@ -296,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"; @@ -460,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; @@ -495,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; @@ -559,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; } @@ -590,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; } @@ -628,6 +656,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 +678,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 +694,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 +711,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"; @@ -11672,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) @@ -11697,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) @@ -11999,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; } @@ -12098,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 @@ -21997,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"); @@ -22010,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"); @@ -22714,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 @@ -22846,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) @@ -23166,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); @@ -23183,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); @@ -23588,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); @@ -23603,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); @@ -23617,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); @@ -23631,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); @@ -23661,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); @@ -23689,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); @@ -23716,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); @@ -23764,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); @@ -23784,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); @@ -23896,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); @@ -23934,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); @@ -23980,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); @@ -24028,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); @@ -24050,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, @@ -24086,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, @@ -24126,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); @@ -24148,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); @@ -24301,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); @@ -24353,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); @@ -24377,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); @@ -24401,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); @@ -24429,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); @@ -24449,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); @@ -24461,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); @@ -24484,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); @@ -24577,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); @@ -24599,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); @@ -24623,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); @@ -24665,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/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); } 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 \ 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, 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/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) \ 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" +) 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)) 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 */