From 01da2d26911c72023e71579bf4feeb707087ef50 Mon Sep 17 00:00:00 2001 From: Donatas Abraitis Date: Fri, 19 Aug 2022 13:15:15 +0300 Subject: [PATCH] bgpd: Add `neighbor soo` command BGP SoO is a tag that is appended on BGP updates to allow a peer to mark a particular peer as belonging to a particular site. In certain MPLS L3 VPN configurations, the BGP AS-Path may not provide the granularity needed prevent a loop in the control-plane. With this in mind, BGP SoO is designed to fill this gap and prevent a routing loop that may occur. If we configure for example, `neighbor soo 65000:1` at PEs, routes won't be announced between CPEs if soo matches. This is especially needed when using as-override or allowas-in. Also, this is the automated way of the same behavior as configuring route-maps for each peer like: ``` bgp extcommunity-list cpe permit soo 65000:1 ! route-map cpe permit 10 set extcommunity soo 65000:1 ... route-map cpe deny 10 match extcommunity cpe route-map cpe permit 20 ... ``` Signed-off-by: Donatas Abraitis --- bgpd/bgp_fsm.c | 5 +++ bgpd/bgp_route.c | 47 ++++++++++++++++++++++++++ bgpd/bgp_updgrp.c | 12 +++++++ bgpd/bgp_vty.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++ bgpd/bgpd.c | 12 +++++++ bgpd/bgpd.h | 4 +++ doc/user/bgp.rst | 13 +++++++ 7 files changed, 179 insertions(+) diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c index b570c84d8b..a9f25cdb81 100644 --- a/bgpd/bgp_fsm.c +++ b/bgpd/bgp_fsm.c @@ -300,6 +300,11 @@ static struct peer *peer_xfer_conn(struct peer *from_peer) peer->afc_recv[afi][safi] = from_peer->afc_recv[afi][safi]; peer->orf_plist[afi][safi] = from_peer->orf_plist[afi][safi]; peer->llgr[afi][safi] = from_peer->llgr[afi][safi]; + if (from_peer->soo[afi][safi]) { + ecommunity_free(&peer->soo[afi][safi]); + peer->soo[afi][safi] = + ecommunity_dup(from_peer->soo[afi][safi]); + } } if (bgp_getsockname(peer) < 0) { diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 179d1d5079..4cd8f419a1 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -2316,6 +2316,29 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, if (aspath_check_as_sets(attr->aspath)) return false; + /* If neighbor sso is configured, then check if the route has + * SoO extended community and validate against the configured + * one. If they match, do not announce, to prevent routing + * loops. + */ + if ((attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) && + peer->soo[afi][safi]) { + struct ecommunity *ecomm_soo = peer->soo[afi][safi]; + struct ecommunity *ecomm = bgp_attr_get_ecommunity(attr); + + if ((ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_AS, + ECOMMUNITY_SITE_ORIGIN) || + ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_AS4, + ECOMMUNITY_SITE_ORIGIN)) && + ecommunity_include(ecomm, ecomm_soo)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] %pFX is filtered by SoO extcommunity '%s'", + peer, p, ecommunity_str(ecomm_soo)); + return false; + } + } + /* Codification of AS 0 Processing */ if (aspath_check_as_zero(attr->aspath)) return false; @@ -4057,6 +4080,30 @@ int bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, return -1; } + /* If neighbor soo is configured, tag all incoming routes with + * this SoO tag and then filter out advertisements in + * subgroup_announce_check() if it matches the configured SoO + * on the other peer. + */ + if (peer->soo[afi][safi]) { + struct ecommunity *old_ecomm = + bgp_attr_get_ecommunity(&new_attr); + struct ecommunity *ecomm_soo = peer->soo[afi][safi]; + struct ecommunity *new_ecomm; + + if (old_ecomm) { + new_ecomm = ecommunity_merge(ecommunity_dup(old_ecomm), + ecomm_soo); + + if (!old_ecomm->refcnt) + ecommunity_free(&old_ecomm); + } else { + new_ecomm = ecommunity_dup(ecomm_soo); + } + + bgp_attr_set_ecommunity(&new_attr, new_ecomm); + } + attr_new = bgp_attr_intern(&new_attr); /* If the update is implicit withdraw. */ diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c index f1173941a0..85594030a7 100644 --- a/bgpd/bgp_updgrp.c +++ b/bgpd/bgp_updgrp.c @@ -164,6 +164,12 @@ static void conf_copy(struct peer *dst, struct peer *src, afi_t afi, dst->change_local_as = src->change_local_as; dst->shared_network = src->shared_network; dst->local_role = src->local_role; + + if (src->soo[afi][safi]) { + ecommunity_free(&dst->soo[afi][safi]); + dst->soo[afi][safi] = ecommunity_dup(src->soo[afi][safi]); + } + memcpy(&(dst->nexthop), &(src->nexthop), sizeof(struct bgp_nexthop)); dst->group = src->group; @@ -428,6 +434,12 @@ static unsigned int updgrp_hash_key_make(const void *p) */ key = jhash_1word(peer->local_role, key); + if (peer->soo[afi][safi]) { + char *soo_str = ecommunity_str(peer->soo[afi][safi]); + + key = jhash_1word(jhash(soo_str, strlen(soo_str), SEED1), key); + } + if (bgp_debug_neighbor_events(peer)) { zlog_debug( "%pBP Update Group Hash: sort: %d UpdGrpFlags: %ju UpdGrpAFFlags: %u", diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 0eba5ea447..4616b85f0a 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -8228,6 +8228,63 @@ ALIAS_HIDDEN( "Only give warning message when limit is exceeded\n" "Force checking all received routes not only accepted\n") +/* "neighbor soo" */ +DEFPY (neighbor_soo, + neighbor_soo_cmd, + "neighbor $neighbor soo ASN:NN_OR_IP-ADDRESS:NN$soo", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set the Site-of-Origin (SoO) extended community\n" + "VPN extended community\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + struct ecommunity *ecomm_soo; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ecomm_soo = ecommunity_str2com(soo, ECOMMUNITY_SITE_ORIGIN, 0); + if (!ecomm_soo) { + vty_out(vty, "%% Malformed SoO extended community\n"); + return CMD_WARNING; + } + ecommunity_str(ecomm_soo); + + if (!ecommunity_match(peer->soo[afi][safi], ecomm_soo)) { + ecommunity_free(&peer->soo[afi][safi]); + peer->soo[afi][safi] = ecomm_soo; + peer_af_flag_unset(peer, afi, safi, PEER_FLAG_SOO); + } + + return bgp_vty_return(vty, + peer_af_flag_set(peer, afi, safi, PEER_FLAG_SOO)); +} + +DEFPY (no_neighbor_soo, + no_neighbor_soo_cmd, + "no neighbor $neighbor soo [ASN:NN_OR_IP-ADDRESS:NN$soo]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set the Site-of-Origin (SoO) extended community\n" + "VPN extended community\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ecommunity_free(&peer->soo[afi][safi]); + + return bgp_vty_return( + vty, peer_af_flag_unset(peer, afi, safi, PEER_FLAG_SOO)); +} /* "neighbor allowas-in" */ DEFUN (neighbor_allowas_in, @@ -17221,6 +17278,15 @@ static void bgp_config_write_peer_af(struct vty *vty, struct bgp *bgp, } } + /* soo */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_SOO)) { + char *soo_str = ecommunity_ecom2str( + peer->soo[afi][safi], ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + vty_out(vty, " neighbor %s soo %s\n", addr, soo_str); + XFREE(MTYPE_ECOMMUNITY_STR, soo_str); + } + /* weight */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_WEIGHT)) vty_out(vty, " neighbor %s weight %lu\n", addr, @@ -19305,6 +19371,26 @@ void bgp_vty_init(void) install_element(BGP_EVPN_NODE, &neighbor_allowas_in_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_allowas_in_cmd); + /* "neighbor soo" */ + install_element(BGP_IPV4_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_soo_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_soo_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_soo_cmd); + install_element(BGP_IPV6_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_soo_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_soo_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_soo_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_soo_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_soo_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_soo_cmd); + 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); + /* address-family commands. */ install_element(BGP_NODE, &address_family_ipv4_safi_cmd); install_element(BGP_NODE, &address_family_ipv6_safi_cmd); diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index c17bd76ad7..e4da90616a 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -1377,6 +1377,7 @@ struct peer *peer_new(struct bgp *bgp) SET_FLAG(peer->af_flags_invert[afi][safi], PEER_FLAG_SEND_LARGE_COMMUNITY); peer->addpath_type[afi][safi] = BGP_ADDPATH_NONE; + peer->soo[afi][safi] = NULL; } /* set nexthop-unchanged for l2vpn evpn by default */ @@ -1483,6 +1484,11 @@ void peer_xfer_config(struct peer *peer_dst, struct peer *peer_src) peer_dst->weight[afi][safi] = peer_src->weight[afi][safi]; peer_dst->addpath_type[afi][safi] = peer_src->addpath_type[afi][safi]; + if (peer_src->soo[afi][safi]) { + ecommunity_free(&peer_dst->soo[afi][safi]); + peer_dst->soo[afi][safi] = + ecommunity_dup(peer_src->soo[afi][safi]); + } } for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { @@ -2042,6 +2048,10 @@ static void peer_group2peer_config_copy_af(struct peer_group *group, if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_ALLOWAS_IN)) PEER_ATTR_INHERIT(peer, group, allowas_in[afi][safi]); + /* soo */ + if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_SOO)) + PEER_ATTR_INHERIT(peer, group, soo[afi][safi]); + /* weight */ if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_WEIGHT)) PEER_ATTR_INHERIT(peer, group, weight[afi][safi]); @@ -2548,6 +2558,7 @@ int peer_delete(struct peer *peer) XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); XFREE(MTYPE_ROUTE_MAP_NAME, peer->default_rmap[afi][safi].name); + ecommunity_free(&peer->soo[afi][safi]); } FOREACH_AFI_SAFI (afi, safi) @@ -4278,6 +4289,7 @@ static const struct peer_flag_action peer_af_flag_action_list[] = { {PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE, 1, peer_change_reset_out}, {PEER_FLAG_WEIGHT, 0, peer_change_reset_in}, {PEER_FLAG_DISABLE_ADDPATH_RX, 0, peer_change_reset}, + {PEER_FLAG_SOO, 0, peer_change_reset}, {0, 0, 0}}; /* Proper action set. */ diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 8348b37b8e..6a8cc3572d 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -1411,6 +1411,7 @@ struct peer { #define PEER_FLAG_MAX_PREFIX_OUT (1U << 27) /* outgoing maximum prefix */ #define PEER_FLAG_MAX_PREFIX_FORCE (1U << 28) /* maximum-prefix force */ #define PEER_FLAG_DISABLE_ADDPATH_RX (1U << 29) /* disable-addpath-rx */ +#define PEER_FLAG_SOO (1U << 30) /* soo */ enum bgp_addpath_strat addpath_type[AFI_MAX][SAFI_MAX]; @@ -1620,6 +1621,9 @@ struct peer { /* allowas-in. */ char allowas_in[AFI_MAX][SAFI_MAX]; + /* soo */ + struct ecommunity *soo[AFI_MAX][SAFI_MAX]; + /* weight */ unsigned long weight[AFI_MAX][SAFI_MAX]; diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index e31bfe7bfa..f9dd039686 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -2832,6 +2832,19 @@ of the global VPNv4/VPNv6 family. This command defaults to on and is not displayed. The `no bgp retain route-target all` form of the command is displayed. +.. clicmd:: neighbor soo EXTCOMMUNITY + +Without this command, SoO extended community attribute is configured using +an inbound route map that sets the SoO value during the update process. +With the introduction of the new BGP per-neighbor Site-of-Origin (SoO) feature, +two new commands configured in sub-modes under router configuration mode +simplify the SoO value configuration. + +If we configure SoO per neighbor at PEs, the SoO community is automatically +added for all routes from the CPEs. Routes are validated and prevented from +being sent back to the same CPE (e.g.: multi-site). This is especially needed +when using ``as-override`` or ``allowas-in`` to prevent routing loops. + .. _bgp-l3vpn-srv6: L3VPN SRv6 -- 2.39.5