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 <donatas@opensourcerouting.org>
This commit is contained in:
Donatas Abraitis 2022-08-19 13:15:15 +03:00
parent a9f3f4f526
commit 01da2d2691
7 changed files with 179 additions and 0 deletions

View File

@ -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) {

View File

@ -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. */

View File

@ -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",

View File

@ -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 <A.B.C.D|X:X::X:X|WORD>$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 <A.B.C.D|X:X::X:X|WORD>$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);

View File

@ -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. */

View File

@ -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 <num> 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];

View File

@ -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 <A.B.C.D|X:X::X:X|WORD> 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