diff options
author | Donatas Abraitis <donatas@opensourcerouting.org> | 2022-09-18 21:18:13 +0200 |
---|---|---|
committer | Donatas Abraitis <donatas@opensourcerouting.org> | 2022-10-12 16:48:43 +0200 |
commit | 46dbf9d0c0b99f60767793ef3b688f95175edc6e (patch) | |
tree | caa2b86397dc6c9dec7969e9ea3a91119e211293 | |
parent | Merge pull request #11159 from maduri111/bgpd-orr (diff) | |
download | frr-46dbf9d0c0b99f60767793ef3b688f95175edc6e.tar.xz frr-46dbf9d0c0b99f60767793ef3b688f95175edc6e.zip |
bgpd: Implement ACCEPT_OWN extended community
TL;DR: rfc7611.
Signed-off-by: Donatas Abraitis <donatas@opensourcerouting.org>
25 files changed, 667 insertions, 32 deletions
diff --git a/bgpd/bgp_community.h b/bgpd/bgp_community.h index 616ddb440..05a5d4486 100644 --- a/bgpd/bgp_community.h +++ b/bgpd/bgp_community.h @@ -23,6 +23,7 @@ #include "lib/json.h" #include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" /* Communities attribute. */ struct community { @@ -109,4 +110,30 @@ extern void bgp_remove_comm_from_aggregate_hash(struct bgp_aggregate *aggregate, struct community *community); extern void bgp_aggr_community_remove(void *arg); +/* This implies that when propagating routes into a VRF, the ACCEPT_OWN + * community SHOULD NOT be propagated. + */ +static inline void community_strip_accept_own(struct attr *attr) +{ + struct community *old_com = bgp_attr_get_community(attr); + struct community *new_com = NULL; + uint32_t val = COMMUNITY_ACCEPT_OWN; + + if (old_com && community_include(old_com, val)) { + new_com = community_dup(old_com); + val = htonl(val); + community_del_val(new_com, &val); + + if (!old_com->refcnt) + community_free(&old_com); + + if (!new_com->size) { + community_free(&new_com); + bgp_attr_set_community(attr, NULL); + } else { + bgp_attr_set_community(attr, new_com); + } + } +} + #endif /* _QUAGGA_BGP_COMMUNITY_H */ diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c index 08748d2eb..66eef1aa5 100644 --- a/bgpd/bgp_mplsvpn.c +++ b/bgpd/bgp_mplsvpn.c @@ -42,6 +42,7 @@ #include "bgpd/bgp_packet.h" #include "bgpd/bgp_vty.h" #include "bgpd/bgp_vpn.h" +#include "bgpd/bgp_community.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_zebra.h" #include "bgpd/bgp_nexthop.h" @@ -996,6 +997,9 @@ leak_update(struct bgp *to_bgp, struct bgp_dest *bn, if (nexthop_self_flag) bgp_path_info_set_flag(bn, bpi, BGP_PATH_ANNC_NH_SELF); + if (CHECK_FLAG(source_bpi->flags, BGP_PATH_ACCEPT_OWN)) + bgp_path_info_set_flag(bn, bpi, BGP_PATH_ACCEPT_OWN); + if (leak_update_nexthop_valid(to_bgp, bn, new_attr, afi, safi, source_bpi, bpi, bgp_orig, p, debug)) @@ -1036,6 +1040,9 @@ leak_update(struct bgp *to_bgp, struct bgp_dest *bn, if (nexthop_self_flag) bgp_path_info_set_flag(bn, new, BGP_PATH_ANNC_NH_SELF); + if (CHECK_FLAG(source_bpi->flags, BGP_PATH_ACCEPT_OWN)) + bgp_path_info_set_flag(bn, new, BGP_PATH_ACCEPT_OWN); + bgp_path_info_extra_get(new); /* @@ -1217,6 +1224,8 @@ void vpn_leak_from_vrf_update(struct bgp *to_bgp, /* to */ XFREE(MTYPE_ECOMMUNITY_STR, s); } + community_strip_accept_own(&static_attr); + /* Nexthop */ /* if policy nexthop not set, use 0 */ if (CHECK_FLAG(from_bgp->vpn_policy[afi].flags, @@ -1362,7 +1371,7 @@ void vpn_leak_from_vrf_update(struct bgp *to_bgp, /* to */ * because of loop checking. */ if (new_info) - vpn_leak_to_vrf_update(from_bgp, new_info); + vpn_leak_to_vrf_update(from_bgp, new_info, NULL); } void vpn_leak_from_vrf_withdraw(struct bgp *to_bgp, /* to */ @@ -1518,10 +1527,40 @@ void vpn_leak_from_vrf_update_all(struct bgp *to_bgp, struct bgp *from_bgp, } } -static bool -vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */ - struct bgp *from_bgp, /* from */ - struct bgp_path_info *path_vpn) /* route */ +static struct bgp *bgp_lookup_by_rd(struct bgp_path_info *bpi, + struct prefix_rd *rd, afi_t afi) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + + if (!rd) + return NULL; + + /* If ACCEPT_OWN is not enabled for this path - return. */ + if (!CHECK_FLAG(bpi->flags, BGP_PATH_ACCEPT_OWN)) + return NULL; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + if (!CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_RD_SET)) + continue; + + /* Check if we have source VRF by RD value */ + if (memcmp(&bgp->vpn_policy[afi].tovpn_rd.val, rd->val, + ECOMMUNITY_SIZE) == 0) + return bgp; + } + + return NULL; +} + +static bool vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */ + struct bgp *from_bgp, /* from */ + struct bgp_path_info *path_vpn, + struct prefix_rd *prd) { const struct prefix *p = bgp_dest_get_prefix(path_vpn->net); afi_t afi = family2afi(p->family); @@ -1558,9 +1597,22 @@ vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */ return false; } + /* A route MUST NOT ever be accepted back into its source VRF, even if + * it carries one or more RTs that match that VRF. + */ + if (prd && memcmp(&prd->val, &to_bgp->vpn_policy[afi].tovpn_rd.val, + ECOMMUNITY_SIZE) == 0) { + if (debug) + zlog_debug( + "%s: skipping import, match RD (%pRD) of src VRF (%s) and the prefix (%pFX)", + __func__, prd, to_bgp->name_pretty, p); + + return false; + } + if (debug) - zlog_debug("%s: updating %pFX to vrf %s", __func__, p, - to_bgp->name_pretty); + zlog_debug("%s: updating RD %pRD, %pFX to vrf %s", __func__, + prd, p, to_bgp->name_pretty); /* shallow copy */ static_attr = *path_vpn->attr; @@ -1585,6 +1637,8 @@ vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */ ecommunity_free(&old_ecom); } + community_strip_accept_own(&static_attr); + /* * Nexthop: stash and clear * @@ -1711,9 +1765,16 @@ vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */ /* * For VRF-2-VRF route-leaking, * the source will be the originating VRF. + * + * If ACCEPT_OWN mechanism is enabled, then we SHOULD(?) + * get the source VRF (BGP) by looking at the RD. */ + struct bgp *src_bgp = bgp_lookup_by_rd(path_vpn, prd, afi); + if (path_vpn->extra && path_vpn->extra->bgp_orig) src_vrf = path_vpn->extra->bgp_orig; + else if (src_bgp) + src_vrf = src_bgp; else src_vrf = from_bgp; @@ -1723,8 +1784,9 @@ vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */ return true; } -bool vpn_leak_to_vrf_update(struct bgp *from_bgp, /* from */ - struct bgp_path_info *path_vpn) /* route */ +bool vpn_leak_to_vrf_update(struct bgp *from_bgp, + struct bgp_path_info *path_vpn, + struct prefix_rd *prd) { struct listnode *mnode, *mnnode; struct bgp *bgp; @@ -1741,7 +1803,7 @@ bool vpn_leak_to_vrf_update(struct bgp *from_bgp, /* from */ if (!path_vpn->extra || path_vpn->extra->bgp_orig != bgp) { /* no loop */ leak_success |= vpn_leak_to_vrf_update_onevrf( - bgp, from_bgp, path_vpn); + bgp, from_bgp, path_vpn, prd); } } return leak_success; @@ -1897,7 +1959,7 @@ void vpn_leak_to_vrf_update_all(struct bgp *to_bgp, struct bgp *vpn_from, continue; vpn_leak_to_vrf_update_onevrf(to_bgp, vpn_from, - bpi); + bpi, NULL); } } } diff --git a/bgpd/bgp_mplsvpn.h b/bgpd/bgp_mplsvpn.h index 46607a38c..9af4cdf3a 100644 --- a/bgpd/bgp_mplsvpn.h +++ b/bgpd/bgp_mplsvpn.h @@ -72,7 +72,8 @@ extern void vpn_leak_to_vrf_update_all(struct bgp *to_bgp, struct bgp *from_bgp, afi_t afi); extern bool vpn_leak_to_vrf_update(struct bgp *from_bgp, - struct bgp_path_info *path_vpn); + struct bgp_path_info *path_vpn, + struct prefix_rd *prd); extern void vpn_leak_to_vrf_withdraw(struct bgp *from_bgp, struct bgp_path_info *path_vpn); diff --git a/bgpd/bgp_nht.c b/bgpd/bgp_nht.c index 7274bcdb2..7eeab373a 100644 --- a/bgpd/bgp_nht.c +++ b/bgpd/bgp_nht.c @@ -139,7 +139,8 @@ static int bgp_isvalid_nexthop_for_mpls(struct bgp_nexthop_cache *bnc, */ return (bgp_zebra_num_connects() == 0 || (bnc && (bnc->nexthop_num > 0 && - (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_LABELED_VALID) || + (CHECK_FLAG(path->flags, BGP_PATH_ACCEPT_OWN) || + CHECK_FLAG(bnc->flags, BGP_NEXTHOP_LABELED_VALID) || bnc->bgp->srv6_enabled || bgp_isvalid_nexthop_for_ebgp(bnc, path) || bgp_isvalid_nexthop_for_mplsovergre(bnc, path))))); diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 0b411d0ec..f6b6cb93d 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -103,10 +103,6 @@ DEFINE_HOOK(bgp_rpki_prefix_status, const struct prefix *prefix), (peer, attr, prefix)); -/* Render dest to prefix_rd based on safi */ -static const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, - safi_t safi); - /* Extern from bgp_dump.c */ extern const char *bgp_origin_str[]; extern const char *bgp_origin_long_str[]; @@ -881,6 +877,49 @@ static int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, return 0; } + /* If a BGP speaker supports ACCEPT_OWN and is configured for the + * extensions defined in this document, the following step is inserted + * after the LOCAL_PREF comparison step in the BGP decision process: + * When comparing a pair of routes for a BGP destination, the + * route with the ACCEPT_OWN community attached is preferred over + * the route that does not have the community. + * This extra step MUST only be invoked during the best path selection + * process of VPN-IP routes. + */ + if (safi == SAFI_MPLS_VPN && + (CHECK_FLAG(new->peer->af_flags[afi][safi], PEER_FLAG_ACCEPT_OWN) || + CHECK_FLAG(exist->peer->af_flags[afi][safi], + PEER_FLAG_ACCEPT_OWN))) { + bool new_accept_own = false; + bool exist_accept_own = false; + uint32_t accept_own = COMMUNITY_ACCEPT_OWN; + + if (newattr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) + new_accept_own = community_include( + bgp_attr_get_community(newattr), accept_own); + if (existattr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) + exist_accept_own = community_include( + bgp_attr_get_community(existattr), accept_own); + + if (new_accept_own && !exist_accept_own) { + *reason = bgp_path_selection_accept_own; + if (debug) + zlog_debug( + "%s: %s wins over %s due to accept-own", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (!new_accept_own && exist_accept_own) { + *reason = bgp_path_selection_accept_own; + if (debug) + zlog_debug( + "%s: %s loses to %s due to accept-own", + pfx_buf, new_buf, exist_buf); + return 0; + } + } + /* 3. Local route check. We prefer: * - BGP_ROUTE_STATIC * - BGP_ROUTE_AGGREGATE @@ -3883,6 +3922,60 @@ static void bgp_attr_add_no_export_community(struct attr *attr) bgp_attr_set_community(attr, new); } +static bool bgp_accept_own(struct peer *peer, afi_t afi, safi_t safi, + struct attr *attr, const struct prefix *prefix, + int *sub_type) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + bool accept_own_found = false; + + if (safi != SAFI_MPLS_VPN) + return false; + + /* Processing of the ACCEPT_OWN community is enabled by configuration */ + if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ACCEPT_OWN)) + return false; + + /* The route in question carries the ACCEPT_OWN community */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) { + struct community *comm = bgp_attr_get_community(attr); + + if (community_include(comm, COMMUNITY_ACCEPT_OWN)) + accept_own_found = true; + } + + /* The route in question is targeted to one or more destination VRFs + * on the router (as determined by inspecting the Route Target(s)). + */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + if (accept_own_found && + ecommunity_include( + bgp->vpn_policy[afi] + .rtlist[BGP_VPN_POLICY_DIR_TOVPN], + bgp_attr_get_ecommunity(attr))) { + if (bgp_debug_update(peer, prefix, NULL, 1)) + zlog_debug( + "%pBP prefix %pFX has ORIGINATOR_ID, but it's accepted due to ACCEPT_OWN", + peer, prefix); + + /* Treat this route as imported, because it's leaked + * already from another VRF, and we got an updated + * version from route-reflector with ACCEPT_OWN + * community. + */ + *sub_type = BGP_ROUTE_IMPORTED; + + return true; + } + } + + return false; +} + int bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, struct attr *attr, afi_t afi, safi_t safi, int type, int sub_type, struct prefix_rd *prd, mpls_label_t *label, @@ -3996,12 +4089,20 @@ int bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, } } - /* Route reflector originator ID check. */ + /* Route reflector originator ID check. If ACCEPT_OWN mechanism is + * enabled, then take care of that too. + */ + bool accept_own = false; + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID) && IPV4_ADDR_SAME(&bgp->router_id, &attr->originator_id)) { - peer->stat_pfx_originator_loop++; - reason = "originator is us;"; - goto filtered; + accept_own = + bgp_accept_own(peer, afi, safi, attr, p, &sub_type); + if (!accept_own) { + peer->stat_pfx_originator_loop++; + reason = "originator is us;"; + goto filtered; + } } /* Route reflector cluster ID check. */ @@ -4499,8 +4600,13 @@ int bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, bgp_path_info_unset_flag(dest, pi, BGP_PATH_VALID); } - } else + } else { + if (accept_own) + bgp_path_info_set_flag(dest, pi, + BGP_PATH_ACCEPT_OWN); + bgp_path_info_set_flag(dest, pi, BGP_PATH_VALID); + } #ifdef ENABLE_BGP_VNC if (safi == SAFI_MPLS_VPN) { @@ -4550,8 +4656,7 @@ int bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, } if ((SAFI_MPLS_VPN == safi) && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { - - leak_success = vpn_leak_to_vrf_update(bgp, pi); + leak_success = vpn_leak_to_vrf_update(bgp, pi, prd); } #ifdef ENABLE_BGP_VNC @@ -4659,8 +4764,12 @@ int bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, } bgp_path_info_unset_flag(dest, new, BGP_PATH_VALID); } - } else + } else { + if (accept_own) + bgp_path_info_set_flag(dest, new, BGP_PATH_ACCEPT_OWN); + bgp_path_info_set_flag(dest, new, BGP_PATH_VALID); + } /* Addpath ID */ new->addpath_rx_id = addpath_id; @@ -4706,7 +4815,7 @@ int bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, } if ((SAFI_MPLS_VPN == safi) && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { - leak_success = vpn_leak_to_vrf_update(bgp, new); + leak_success = vpn_leak_to_vrf_update(bgp, new, prd); } #ifdef ENABLE_BGP_VNC if (SAFI_MPLS_VPN == safi) { @@ -6427,7 +6536,8 @@ static void bgp_static_update_safi(struct bgp *bgp, const struct prefix *p, if (SAFI_MPLS_VPN == safi && bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { - vpn_leak_to_vrf_update(bgp, pi); + vpn_leak_to_vrf_update(bgp, pi, + &bgp_static->prd); } #ifdef ENABLE_BGP_VNC rfapiProcessUpdate(pi->peer, NULL, p, &bgp_static->prd, @@ -6467,7 +6577,7 @@ static void bgp_static_update_safi(struct bgp *bgp, const struct prefix *p, if (SAFI_MPLS_VPN == safi && bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { - vpn_leak_to_vrf_update(bgp, new); + vpn_leak_to_vrf_update(bgp, new, &bgp_static->prd); } #ifdef ENABLE_BGP_VNC rfapiProcessUpdate(new->peer, NULL, p, &bgp_static->prd, new->attr, afi, @@ -8826,6 +8936,8 @@ const char *bgp_path_selection_reason2str(enum bgp_path_selection_reason reason) return "Weight"; case bgp_path_selection_local_pref: return "Local Pref"; + case bgp_path_selection_accept_own: + return "Accept Own"; case bgp_path_selection_local_route: return "Local Route"; case bgp_path_selection_confed_as_path: @@ -11902,8 +12014,8 @@ static void bgp_show_path_info(const struct prefix_rd *pfx_rd, /* * Return rd based on safi */ -static const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, - safi_t safi) +const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, + safi_t safi) { switch (safi) { case SAFI_MPLS_VPN: @@ -11912,7 +12024,6 @@ static const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, return (struct prefix_rd *)(bgp_dest_get_prefix(dest)); default: return NULL; - } } diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h index ddef4ca1b..22d28ecd0 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h @@ -289,7 +289,7 @@ struct bgp_path_info { int lock; /* BGP information status. */ - uint16_t flags; + uint32_t flags; #define BGP_PATH_IGP_CHANGED (1 << 0) #define BGP_PATH_DAMPED (1 << 1) #define BGP_PATH_HISTORY (1 << 2) @@ -306,6 +306,7 @@ struct bgp_path_info { #define BGP_PATH_RIB_ATTR_CHG (1 << 13) #define BGP_PATH_ANNC_NH_SELF (1 << 14) #define BGP_PATH_LINK_BW_CHG (1 << 15) +#define BGP_PATH_ACCEPT_OWN (1 << 16) /* BGP route type. This can be static, RIP, OSPF, BGP etc. */ uint8_t type; @@ -852,4 +853,6 @@ extern void subgroup_announce_reset_nhop(uint8_t family, struct attr *attr); const char * bgp_path_selection_reason2str(enum bgp_path_selection_reason reason); extern bool bgp_addpath_encode_rx(struct peer *peer, afi_t afi, safi_t safi); +extern const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, + safi_t safi); #endif /* _QUAGGA_BGP_ROUTE_H */ diff --git a/bgpd/bgp_table.h b/bgpd/bgp_table.h index 86cd4f3da..91d92ebd6 100644 --- a/bgpd/bgp_table.h +++ b/bgpd/bgp_table.h @@ -63,6 +63,7 @@ enum bgp_path_selection_reason { bgp_path_selection_evpn_lower_ip, bgp_path_selection_weight, bgp_path_selection_local_pref, + bgp_path_selection_accept_own, bgp_path_selection_local_route, bgp_path_selection_confed_as_path, bgp_path_selection_as_path, diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 10396b8b4..b85f3707b 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -8327,6 +8327,32 @@ ALIAS_HIDDEN( "Only give warning message when limit is exceeded\n" "Force checking all received routes not only accepted\n") +/* "neighbor accept-own" */ +DEFPY (neighbor_accept_own, + neighbor_accept_own_cmd, + "[no$no] neighbor <A.B.C.D|X:X::X:X|WORD>$neighbor accept-own", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enable handling of self-originated VPN routes containing ACCEPT_OWN community\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (no) + ret = peer_af_flag_unset(peer, afi, safi, PEER_FLAG_ACCEPT_OWN); + else + ret = peer_af_flag_set(peer, afi, safi, PEER_FLAG_ACCEPT_OWN); + + return bgp_vty_return(vty, ret); +} + /* "neighbor soo" */ DEFPY (neighbor_soo, neighbor_soo_cmd, @@ -17362,6 +17388,10 @@ static void bgp_config_write_peer_af(struct vty *vty, struct bgp *bgp, } } + /* accept-own */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ACCEPT_OWN)) + vty_out(vty, " neighbor %s accept-own\n", addr); + /* soo */ if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_SOO)) { char *soo_str = ecommunity_ecom2str( @@ -19571,6 +19601,10 @@ 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 accept-own */ + install_element(BGP_VPNV4_NODE, &neighbor_accept_own_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_accept_own_cmd); + /* "neighbor soo" */ install_element(BGP_IPV4_NODE, &neighbor_soo_cmd); install_element(BGP_IPV4_NODE, &no_neighbor_soo_cmd); diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 188402d0b..40e6c90df 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -4313,6 +4313,7 @@ static const struct peer_flag_action peer_af_flag_action_list[] = { {PEER_FLAG_WEIGHT, 0, peer_change_reset_in}, {PEER_FLAG_DISABLE_ADDPATH_RX, 0, peer_change_reset}, {PEER_FLAG_SOO, 0, peer_change_reset}, + {PEER_FLAG_ACCEPT_OWN, 0, peer_change_reset}, {0, 0, 0}}; /* Proper action set. */ diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index b9d24969e..44e225b04 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -1462,6 +1462,7 @@ struct peer { #define PEER_FLAG_DISABLE_ADDPATH_RX (1ULL << 29) #define PEER_FLAG_SOO (1ULL << 30) #define PEER_FLAG_ORR_GROUP (1ULL << 31) /* Optimal-Route-Reflection */ +#define PEER_FLAG_ACCEPT_OWN (1ULL << 32) /* BGP Optimal Route Reflection Group name */ char *orr_group_name[AFI_MAX][SAFI_MAX]; diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index cd7a0adff..8a16c57e6 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -1670,6 +1670,23 @@ Configuring Peers turning on this command will allow BGP to install v4 routes with v6 nexthops if you do not have v4 configured on interfaces. +.. clicmd:: neighbor <A.B.C.D|X:X::X:X|WORD> accept-own + + Enable handling of self-originated VPN routes containing ``accept-own`` community. + + This feature allows you to handle self-originated VPN routes, which a BGP speaker + receives from a route-reflector. A 'self-originated' route is one that was + originally advertised by the speaker itself. As per :rfc:`4271`, a BGP speaker rejects + advertisements that originated the speaker itself. However, the BGP ACCEPT_OWN + mechanism enables a router to accept the prefixes it has advertised, when reflected + from a route-reflector that modifies certain attributes of the prefix. + + A special community called ``accept-own`` is attached to the prefix by the + route-reflector, which is a signal to the receiving router to bypass the ORIGINATOR_ID + and NEXTHOP/MP_REACH_NLRI check. + + Default: disabled. + .. clicmd:: bgp fast-external-failover This command causes bgp to take down ebgp peers immediately diff --git a/tests/topotests/bgp_accept_own/__init__.py b/tests/topotests/bgp_accept_own/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/topotests/bgp_accept_own/__init__.py diff --git a/tests/topotests/bgp_accept_own/ce1/bgpd.conf b/tests/topotests/bgp_accept_own/ce1/bgpd.conf new file mode 100644 index 000000000..fa53a4291 --- /dev/null +++ b/tests/topotests/bgp_accept_own/ce1/bgpd.conf @@ -0,0 +1,12 @@ +! +debug bgp updates +! +router bgp 65010 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_accept_own/ce1/zebra.conf b/tests/topotests/bgp_accept_own/ce1/zebra.conf new file mode 100644 index 000000000..7863ae165 --- /dev/null +++ b/tests/topotests/bgp_accept_own/ce1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface ce1-eth0 + ip address 192.168.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_accept_own/ce2/bgpd.conf b/tests/topotests/bgp_accept_own/ce2/bgpd.conf new file mode 100644 index 000000000..cdf8898c9 --- /dev/null +++ b/tests/topotests/bgp_accept_own/ce2/bgpd.conf @@ -0,0 +1,12 @@ +! +debug bgp updates +! +router bgp 65020 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_accept_own/ce2/zebra.conf b/tests/topotests/bgp_accept_own/ce2/zebra.conf new file mode 100644 index 000000000..829967e36 --- /dev/null +++ b/tests/topotests/bgp_accept_own/ce2/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.2/32 +! +interface ce2-eth0 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_accept_own/pe1/bgpd.conf b/tests/topotests/bgp_accept_own/pe1/bgpd.conf new file mode 100644 index 000000000..863129373 --- /dev/null +++ b/tests/topotests/bgp_accept_own/pe1/bgpd.conf @@ -0,0 +1,49 @@ +! +debug bgp updates +debug bgp vpn leak-from-vrf +debug bgp vpn leak-to-vrf +debug bgp nht +! +router bgp 65001 + bgp router-id 10.10.10.10 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 10.10.10.101 remote-as internal + neighbor 10.10.10.101 update-source 10.10.10.10 + neighbor 10.10.10.101 timers 1 3 + neighbor 10.10.10.101 timers connect 1 + address-family ipv4 vpn + neighbor 10.10.10.101 activate + neighbor 10.10.10.101 attribute-unchanged + exit-address-family +! +router bgp 65001 vrf Customer + bgp router-id 192.168.1.2 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + address-family ipv4 unicast + label vpn export 10 + rd vpn export 192.168.1.2:2 + rt vpn import 192.168.1.2:2 + rt vpn export 192.168.1.2:2 + export vpn + import vpn + exit-address-family +! +router bgp 65001 vrf Service + bgp router-id 192.168.2.2 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 + address-family ipv4 unicast + label vpn export 20 + rd vpn export 192.168.2.2:2 + rt vpn import 192.168.2.2:2 + rt vpn export 192.168.2.2:2 + export vpn + import vpn + exit-address-family +! diff --git a/tests/topotests/bgp_accept_own/pe1/ldpd.conf b/tests/topotests/bgp_accept_own/pe1/ldpd.conf new file mode 100644 index 000000000..7c1ea33a3 --- /dev/null +++ b/tests/topotests/bgp_accept_own/pe1/ldpd.conf @@ -0,0 +1,12 @@ +mpls ldp + router-id 10.10.10.10 + ! + address-family ipv4 + discovery transport-address 10.10.10.10 + ! + interface pe1-eth2 + exit + ! + exit-address-family + ! +exit diff --git a/tests/topotests/bgp_accept_own/pe1/ospfd.conf b/tests/topotests/bgp_accept_own/pe1/ospfd.conf new file mode 100644 index 000000000..1a5e1a06b --- /dev/null +++ b/tests/topotests/bgp_accept_own/pe1/ospfd.conf @@ -0,0 +1,7 @@ +interface pe1-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +! +router ospf + router-id 10.10.10.10 + network 0.0.0.0/0 area 0 diff --git a/tests/topotests/bgp_accept_own/pe1/zebra.conf b/tests/topotests/bgp_accept_own/pe1/zebra.conf new file mode 100644 index 000000000..71476d2ae --- /dev/null +++ b/tests/topotests/bgp_accept_own/pe1/zebra.conf @@ -0,0 +1,15 @@ +! +interface lo + ip address 10.10.10.10/32 +! +interface pe1-eth0 vrf Customer + ip address 192.168.1.2/24 +! +interface pe1-eth1 vrf Service + ip address 192.168.2.2/24 +! +interface pe1-eth2 + ip address 10.0.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_accept_own/rr1/bgpd.conf b/tests/topotests/bgp_accept_own/rr1/bgpd.conf new file mode 100644 index 000000000..4f0a6ab0f --- /dev/null +++ b/tests/topotests/bgp_accept_own/rr1/bgpd.conf @@ -0,0 +1,25 @@ +! +debug bgp updates +! +router bgp 65001 + bgp router-id 10.10.10.101 + bgp route-reflector allow-outbound-policy + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 10.10.10.10 remote-as internal + neighbor 10.10.10.10 update-source 10.10.10.101 + neighbor 10.10.10.10 timers 1 3 + neighbor 10.10.10.10 timers connect 1 + address-family ipv4 vpn + neighbor 10.10.10.10 activate + neighbor 10.10.10.10 route-reflector-client + neighbor 10.10.10.10 route-map pe1 out + exit-address-family +! +route-map pe1 permit 10 + set extcommunity rt 192.168.1.2:2 192.168.2.2:2 + set community 65001:111 accept-own additive + set ip next-hop unchanged +route-map pe1 permit 20 +exit +! diff --git a/tests/topotests/bgp_accept_own/rr1/ldpd.conf b/tests/topotests/bgp_accept_own/rr1/ldpd.conf new file mode 100644 index 000000000..036990186 --- /dev/null +++ b/tests/topotests/bgp_accept_own/rr1/ldpd.conf @@ -0,0 +1,12 @@ +mpls ldp + router-id 10.10.10.101 + ! + address-family ipv4 + discovery transport-address 10.10.10.101 + ! + interface rr1-eth0 + exit + ! + exit-address-family + ! +exit diff --git a/tests/topotests/bgp_accept_own/rr1/ospfd.conf b/tests/topotests/bgp_accept_own/rr1/ospfd.conf new file mode 100644 index 000000000..b598246ef --- /dev/null +++ b/tests/topotests/bgp_accept_own/rr1/ospfd.conf @@ -0,0 +1,7 @@ +interface rr1-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +! +router ospf + router-id 10.10.10.101 + network 0.0.0.0/0 area 0 diff --git a/tests/topotests/bgp_accept_own/rr1/zebra.conf b/tests/topotests/bgp_accept_own/rr1/zebra.conf new file mode 100644 index 000000000..aa3f633bf --- /dev/null +++ b/tests/topotests/bgp_accept_own/rr1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 10.10.10.101/32 +! +interface rr1-eth0 + ip address 10.0.1.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_accept_own/test_bgp_accept_own.py b/tests/topotests/bgp_accept_own/test_bgp_accept_own.py new file mode 100644 index 000000000..161530b48 --- /dev/null +++ b/tests/topotests/bgp_accept_own/test_bgp_accept_own.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2022 by +# Donatas Abraitis <donatas@opensourcerouting.org> +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" + +""" + +import os +import sys +import json +import pytest +import functools + +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, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("ce1") + tgen.add_router("ce2") + tgen.add_router("pe1") + tgen.add_router("rr1") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["ce1"]) + switch.add_link(tgen.gears["pe1"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["ce2"]) + switch.add_link(tgen.gears["pe1"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["pe1"]) + switch.add_link(tgen.gears["rr1"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + pe1 = tgen.gears["pe1"] + rr1 = tgen.gears["rr1"] + + pe1.run("ip link add Customer type vrf table 1001") + pe1.run("ip link set up dev Customer") + pe1.run("ip link set pe1-eth0 master Customer") + pe1.run("ip link add Service type vrf table 1002") + pe1.run("ip link set up dev Service") + pe1.run("ip link set pe1-eth1 master Service") + pe1.run("sysctl -w net.mpls.conf.pe1-eth2.input=1") + rr1.run("sysctl -w net.mpls.conf.rr1-eth0.input=1") + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_LDP, os.path.join(CWD, "{}/ldpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_accept_own(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["pe1"] + ce2 = tgen.gears["ce2"] + + step("Check if routes are not installed in PE1 from RR1 (due to ORIGINATOR_ID)") + + def _bgp_check_received_routes_due_originator_id(): + output = json.loads(pe1.vtysh_cmd("show bgp ipv4 vpn summary json")) + expected = {"peers": {"10.10.10.101": {"pfxRcd": 0, "pfxSnt": 4}}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_routes_due_originator_id) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "Failed, received routes from RR1 regardless ORIGINATOR_ID" + + step("Enable ACCEPT_OWN for RR1") + + pe1.vtysh_cmd( + """ + configure terminal + router bgp 65001 + address-family ipv4 vpn + neighbor 10.10.10.101 accept-own + """ + ) + + step("Check if we received routes due to ACCEPT_OWN from RR1") + + def _bgp_check_received_routes_with_modified_rts(): + output = json.loads(pe1.vtysh_cmd("show bgp ipv4 vpn summary json")) + expected = {"peers": {"10.10.10.101": {"pfxRcd": 4, "pfxSnt": 4}}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_routes_with_modified_rts) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert ( + result is None + ), "Failed, didn't receive routes from RR1 with ACCEPT_OWN enabled" + + step( + "Check if 172.16.255.1/32 is imported into vrf Service due to modified RT list at RR1" + ) + + def _bgp_check_received_routes_with_changed_rts(): + output = json.loads( + pe1.vtysh_cmd("show bgp vrf Service ipv4 unicast 172.16.255.1/32 json") + ) + expected = { + "paths": [ + { + "community": { + "string": "65001:111" + }, + "extendedCommunity": { + "string": "RT:192.168.1.2:2 RT:192.168.2.2:2" + }, + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_routes_with_changed_rts) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert ( + result is None + ), "Failed, routes are not imported from RR1 with modified RT list" + + step("Check if 172.16.255.1/32 is announced to CE2") + + def _bgp_check_received_routes_from_pe(): + output = json.loads(ce2.vtysh_cmd("show ip route 172.16.255.1/32 json")) + expected = { + "172.16.255.1/32": [ + { + "protocol": "bgp", + "installed": True, + "nexthops": [{"ip": "192.168.2.2"}], + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_routes_from_pe) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert ( + result is None + ), "Failed, didn't receive 172.16.255.1/32 from PE1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) |