diff --git a/bgpd/bgp_ecommunity.c b/bgpd/bgp_ecommunity.c index c408edb16677..c6e1efd8ddb6 100644 --- a/bgpd/bgp_ecommunity.c +++ b/bgpd/bgp_ecommunity.c @@ -1042,6 +1042,34 @@ static int ecommunity_lb_str(char *buf, size_t bufsz, const uint8_t *pnt, return len; } +bool ecommunity_has_route_target(struct ecommunity *ecom) +{ + uint32_t i; + uint8_t *pnt; + uint8_t type = 0; + uint8_t sub_type = 0; + + if (!ecom) + return NULL; + for (i = 0; i < ecom->size; i++) { + /* Retrieve value field */ + pnt = ecom->val + (i * ecom->unit_size); + + /* High-order octet is the type */ + type = *pnt++; + + if (type == ECOMMUNITY_ENCODE_AS || + type == ECOMMUNITY_ENCODE_IP || + type == ECOMMUNITY_ENCODE_AS4) { + /* Low-order octet of type. */ + sub_type = *pnt++; + if (sub_type == ECOMMUNITY_ROUTE_TARGET) + return true; + } + } + return false; +} + /* Convert extended community attribute to string. * Due to historical reason of industry standard implementation, there * are three types of format: diff --git a/bgpd/bgp_ecommunity.h b/bgpd/bgp_ecommunity.h index 7dc04d206af9..f12d0dd3ee12 100644 --- a/bgpd/bgp_ecommunity.h +++ b/bgpd/bgp_ecommunity.h @@ -341,6 +341,7 @@ extern struct ecommunity *ecommunity_str2com(const char *, int, int); extern struct ecommunity *ecommunity_str2com_ipv6(const char *str, int type, int keyword_included); extern char *ecommunity_ecom2str(struct ecommunity *, int, int); +extern bool ecommunity_has_route_target(struct ecommunity *ecom); extern void ecommunity_strfree(char **s); extern bool ecommunity_include(struct ecommunity *e1, struct ecommunity *e2); extern bool ecommunity_match(const struct ecommunity *, diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c index 3e63823f99e4..7d416cc48236 100644 --- a/bgpd/bgp_mplsvpn.c +++ b/bgpd/bgp_mplsvpn.c @@ -300,7 +300,7 @@ void vpn_leak_zebra_vrf_label_update(struct bgp *bgp, afi_t afi) return; } - if (vpn_leak_to_vpn_active(bgp, afi, NULL)) { + if (vpn_leak_to_vpn_active(bgp, afi, NULL, false)) { label = bgp->vpn_policy[afi].tovpn_label; } @@ -1532,6 +1532,9 @@ void vpn_leak_from_vrf_update(struct bgp *to_bgp, /* to */ struct bgp_dest *bn; const char *debugmsg; int nexthop_self_flag = 0; + struct ecommunity *old_ecom; + struct ecommunity *new_ecom = NULL; + struct ecommunity *rtlist_ecom; if (debug) zlog_debug("%s: from vrf %s", __func__, from_bgp->name_pretty); @@ -1559,7 +1562,7 @@ void vpn_leak_from_vrf_update(struct bgp *to_bgp, /* to */ if (!is_route_injectable_into_vpn(path_vrf)) return; - if (!vpn_leak_to_vpn_active(from_bgp, afi, &debugmsg)) { + if (!vpn_leak_to_vpn_active(from_bgp, afi, &debugmsg, false)) { if (debug) zlog_debug("%s: %s skipping: %s", __func__, from_bgp->name, debugmsg); @@ -1608,27 +1611,37 @@ void vpn_leak_from_vrf_update(struct bgp *to_bgp, /* to */ /* * Add the vpn-policy rt-list */ - struct ecommunity *old_ecom; - struct ecommunity *new_ecom; /* Export with the 'from' instance's export RTs. */ /* If doing VRF-to-VRF leaking, strip existing RTs first. */ old_ecom = bgp_attr_get_ecommunity(&static_attr); + rtlist_ecom = from_bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN]; if (old_ecom) { new_ecom = ecommunity_dup(old_ecom); if (CHECK_FLAG(from_bgp->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_VRF_EXPORT)) ecommunity_strip_rts(new_ecom); - new_ecom = ecommunity_merge( - new_ecom, from_bgp->vpn_policy[afi] - .rtlist[BGP_VPN_POLICY_DIR_TOVPN]); + if (rtlist_ecom) + new_ecom = ecommunity_merge(new_ecom, rtlist_ecom); if (!old_ecom->refcnt) ecommunity_free(&old_ecom); - } else { - new_ecom = ecommunity_dup( - from_bgp->vpn_policy[afi] - .rtlist[BGP_VPN_POLICY_DIR_TOVPN]); + } else if (rtlist_ecom) + new_ecom = ecommunity_dup(rtlist_ecom); + else { + if (debug) + zlog_debug("%s: %s skipping: waiting for a valid export rt list.", + __func__, from_bgp->name_pretty); + return; } + + if (ecommunity_has_route_target(new_ecom) == false) { + ecommunity_free(&new_ecom); + if (debug) + zlog_debug("%s: %s skipping: waiting for a valid export rt list.", + __func__, from_bgp->name_pretty); + return; + } + bgp_attr_set_ecommunity(&static_attr, new_ecom); if (debug && bgp_attr_get_ecommunity(&static_attr)) { @@ -1884,7 +1897,7 @@ void vpn_leak_from_vrf_withdraw(struct bgp *to_bgp, /* to */ if (!is_route_injectable_into_vpn(path_vrf)) return; - if (!vpn_leak_to_vpn_active(from_bgp, afi, &debugmsg)) { + if (!vpn_leak_to_vpn_active(from_bgp, afi, &debugmsg, true)) { if (debug) zlog_debug("%s: skipping: %s", __func__, debugmsg); return; @@ -2665,7 +2678,7 @@ void vpn_handle_router_id_update(struct bgp *bgp, bool withdraw, edir = BGP_VPN_POLICY_DIR_TOVPN; for (afi = 0; afi < AFI_MAX; ++afi) { - if (!vpn_leak_to_vpn_active(bgp, afi, NULL)) + if (!vpn_leak_to_vpn_active(bgp, afi, NULL, NULL)) continue; if (withdraw) { diff --git a/bgpd/bgp_mplsvpn.h b/bgpd/bgp_mplsvpn.h index 19b6f4eb777e..bbfa115063cc 100644 --- a/bgpd/bgp_mplsvpn.h +++ b/bgpd/bgp_mplsvpn.h @@ -112,7 +112,8 @@ static inline bool is_bgp_vrf_mplsvpn(struct bgp *bgp) } static inline int vpn_leak_to_vpn_active(struct bgp *bgp_vrf, afi_t afi, - const char **pmsg) + const char **pmsg, + bool ignore_export_rt_list) { if (bgp_vrf->inst_type != BGP_INSTANCE_TYPE_VRF && bgp_vrf->inst_type != BGP_INSTANCE_TYPE_DEFAULT) { @@ -132,8 +133,21 @@ static inline int vpn_leak_to_vpn_active(struct bgp *bgp_vrf, afi_t afi, return 0; } - /* Is there an RT list set? */ - if (!bgp_vrf->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN]) { + /* Before performing withdrawal, VPN activation is checked; however, + * when the route-map modifies the export route-target (RT) list, it + * becomes challenging to determine if VPN prefixes were previously + * present, or not. The 'ignore_export_rt_list' parameter will be + * used to force the withdraw operation by not checking the possible + * route-map changes. + * Of the 'ignore_export_rt_list' is set to false, check the following: + * - Is there an RT list set? + * - Is there a route-map that sets RT communities + */ + if (ignore_export_rt_list == false && + !bgp_vrf->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN] && + (!bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN] || + !bgp_route_map_has_extcommunity_rt( + bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN]))) { if (pmsg) *pmsg = "rtlist tovpn not defined"; return 0; @@ -226,8 +240,7 @@ static inline void vpn_leak_prechange(enum vpn_policy_direction direction, vpn_leak_to_vrf_withdraw_all(bgp_vrf, afi); } if ((direction == BGP_VPN_POLICY_DIR_TOVPN) && - vpn_leak_to_vpn_active(bgp_vrf, afi, NULL)) { - + vpn_leak_to_vpn_active(bgp_vrf, afi, NULL, true)) { vpn_leak_from_vrf_withdraw_all(bgp_vpn, bgp_vrf, afi); } } diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index 274df5197e2c..3d8ce029d5c5 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -4720,6 +4720,23 @@ static void bgp_route_map_delete(const char *rmap_name) route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_DELETED); } +bool bgp_route_map_has_extcommunity_rt(const struct route_map *map) +{ + struct route_map_index *index = NULL; + struct route_map_rule *set = NULL; + + assert(map); + + for (index = map->head; index; index = index->next) { + for (set = index->set_list.head; set; set = set->next) { + if (set->cmd && set->cmd->str && + strmatch(set->cmd->str, "extcommunity rt")) + return true; + } + } + return false; +} + static void bgp_route_map_event(const char *rmap_name) { if (route_map_mark_updated(rmap_name) == 0) diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 8f78e33f6517..de64e5823825 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -2433,6 +2433,8 @@ extern enum asnotation_mode bgp_get_asnotation(struct bgp *bgp); extern void bgp_route_map_terminate(void); +extern bool bgp_route_map_has_extcommunity_rt(const struct route_map *map); + extern int peer_cmp(struct peer *p1, struct peer *p2); extern int bgp_map_afi_safi_iana2int(iana_afi_t pkt_afi, iana_safi_t pkt_safi,