diff options
53 files changed, 1577 insertions, 452 deletions
diff --git a/.github/workflows/conflicts.yml b/.github/workflows/conflicts.yml new file mode 100644 index 0000000000..18a8c0d15d --- /dev/null +++ b/.github/workflows/conflicts.yml @@ -0,0 +1,21 @@ +name: Add a conflict label is PR needs to rebase + +on: +  push: +  pull_request_target: +    types: [synchronize] + +jobs: +  conflicts: +    runs-on: ubuntu-latest +    permissions: +      contents: read +      pull-requests: write +    steps: +      - name: Check if PRs need a rebase (have some conflicts) +        uses: eps1lon/actions-label-merge-conflict@releases/2.x +        with: +          dirtyLabel: "conflicts" +          removeOnDirtyLabel: "no_conflicts" +          repoToken: "${{ secrets.GITHUB_TOKEN }}" +          commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." diff --git a/bgpd/bgp_evpn.c b/bgpd/bgp_evpn.c index 58f5e9a226..0a97a8b7bc 100644 --- a/bgpd/bgp_evpn.c +++ b/bgpd/bgp_evpn.c @@ -62,6 +62,7 @@  DEFINE_QOBJ_TYPE(bgpevpn);  DEFINE_QOBJ_TYPE(bgp_evpn_es); +DEFINE_MTYPE_STATIC(BGPD, VRF_ROUTE_TARGET, "L3 Route Target");  /*   * Static function declarations @@ -350,6 +351,15 @@ int bgp_evpn_route_target_cmp(struct ecommunity *ecom1,  	return strcmp(ecom1->str, ecom2->str);  } +/* + * Compare L3 Route Targets. + */ +static int evpn_vrf_route_target_cmp(struct vrf_route_target *rt1, +				     struct vrf_route_target *rt2) +{ +	return bgp_evpn_route_target_cmp(rt1->ecom, rt2->ecom); +} +  void bgp_evpn_xxport_delete_ecomm(void *val)  {  	struct ecommunity *ecomm = val; @@ -357,11 +367,37 @@ void bgp_evpn_xxport_delete_ecomm(void *val)  }  /* + * Delete l3 Route Target. + */ +static void evpn_vrf_rt_del(void *val) +{ +	struct vrf_route_target *l3rt = val; + +	ecommunity_free(&l3rt->ecom); + +	XFREE(MTYPE_VRF_ROUTE_TARGET, l3rt); +} + +/* + * Allocate a new l3 Route Target. + */ +static struct vrf_route_target *evpn_vrf_rt_new(struct ecommunity *ecom) +{ +	struct vrf_route_target *l3rt; + +	l3rt = XCALLOC(MTYPE_VRF_ROUTE_TARGET, sizeof(struct vrf_route_target)); + +	l3rt->ecom = ecom; + +	return l3rt; +} + +/*   * Mask off global-admin field of specified extended community (RT),   * just retain the local-admin field.   */  static inline void mask_ecom_global_admin(struct ecommunity_val *dst, -					  struct ecommunity_val *src) +					  const struct ecommunity_val *src)  {  	uint8_t type; @@ -377,33 +413,55 @@ static inline void mask_ecom_global_admin(struct ecommunity_val *dst,  }  /* - * Map one RT to specified VRF. - * bgp_vrf = BGP vrf instance + * Converts the RT to Ecommunity Value and adjusts masking based + * on flags set for RT.   */ -static void map_vrf_to_rt(struct bgp *bgp_vrf, struct ecommunity_val *eval) +static void vrf_rt2ecom_val(struct ecommunity_val *to_eval, +			    const struct vrf_route_target *l3rt, int iter)  { -	struct vrf_irt_node *irt = NULL; -	struct ecommunity_val eval_tmp; +	const struct ecommunity_val *eval; -	/* If using "automatic" RT, +	eval = (const struct ecommunity_val *)(l3rt->ecom->val + +					       (iter * ECOMMUNITY_SIZE)); +	/* If using "automatic" or "wildcard *" RT,  	 * we only care about the local-admin sub-field.  	 * This is to facilitate using L3VNI(VRF-VNI) -	 * as the RT for EBGP peering too. +	 * as the RT for EBGP peering too and simplify +	 * configurations by allowing any ASN via '*'.  	 */ -	memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); -	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) -		mask_ecom_global_admin(&eval_tmp, eval); +	memcpy(to_eval, eval, ECOMMUNITY_SIZE); -	irt = lookup_vrf_import_rt(&eval_tmp); -	if (irt && is_vrf_present_in_irt_vrfs(irt->vrfs, bgp_vrf)) -		/* Already mapped. */ -		return; +	if (CHECK_FLAG(l3rt->flags, BGP_VRF_RT_AUTO) || +	    CHECK_FLAG(l3rt->flags, BGP_VRF_RT_WILD)) +		mask_ecom_global_admin(to_eval, eval); +} -	if (!irt) -		irt = vrf_import_rt_new(&eval_tmp); +/* + * Map one RT to specified VRF. + * bgp_vrf = BGP vrf instance + */ +static void map_vrf_to_rt(struct bgp *bgp_vrf, struct vrf_route_target *l3rt) +{ +	uint32_t i = 0; + +	for (i = 0; i < l3rt->ecom->size; i++) { +		struct vrf_irt_node *irt = NULL; +		struct ecommunity_val eval_tmp; + +		/* Adjust masking for value */ +		vrf_rt2ecom_val(&eval_tmp, l3rt, i); + +		irt = lookup_vrf_import_rt(&eval_tmp); -	/* Add VRF to the list for this RT. */ -	listnode_add(irt->vrfs, bgp_vrf); +		if (irt && is_vrf_present_in_irt_vrfs(irt->vrfs, bgp_vrf)) +			return; /* Already mapped. */ + +		if (!irt) +			irt = vrf_import_rt_new(&eval_tmp); + +		/* Add VRF to the list for this RT. */ +		listnode_add(irt->vrfs, bgp_vrf); +	}  }  /* @@ -411,12 +469,28 @@ static void map_vrf_to_rt(struct bgp *bgp_vrf, struct ecommunity_val *eval)   * VRFs for this RT, then the RT hash is deleted.   * bgp_vrf: BGP VRF specific instance   */ -static void unmap_vrf_from_rt(struct bgp *bgp_vrf, struct vrf_irt_node *irt) +static void unmap_vrf_from_rt(struct bgp *bgp_vrf, +			      struct vrf_route_target *l3rt)  { -	/* Delete VRF from list for this RT. */ -	listnode_delete(irt->vrfs, bgp_vrf); -	if (!listnode_head(irt->vrfs)) { -		vrf_import_rt_free(irt); +	uint32_t i; + +	for (i = 0; i < l3rt->ecom->size; i++) { +		struct vrf_irt_node *irt; +		struct ecommunity_val eval_tmp; + +		/* Adjust masking for value */ +		vrf_rt2ecom_val(&eval_tmp, l3rt, i); + +		irt = lookup_vrf_import_rt(&eval_tmp); + +		if (!irt) +			return; /* Not mapped */ + +		/* Delete VRF from list for this RT. */ +		listnode_delete(irt->vrfs, bgp_vrf); + +		if (!listnode_head(irt->vrfs)) +			vrf_import_rt_free(irt);  	}  } @@ -505,10 +579,14 @@ static void bgp_evpn_get_rmac_nexthop(struct bgpevpn *vpn,   * VNIs but the same across routers (in the same AS) for a particular   * VNI.   */ -static void form_auto_rt(struct bgp *bgp, vni_t vni, struct list *rtl) +static void form_auto_rt(struct bgp *bgp, vni_t vni, struct list *rtl, +			 bool is_l3)  {  	struct ecommunity_val eval; -	struct ecommunity *ecomadd, *ecom; +	struct ecommunity *ecomadd; +	struct ecommunity *ecom; +	struct vrf_route_target *l3rt; +	struct vrf_route_target *newrt;  	bool ecom_found = false;  	struct listnode *node; @@ -518,15 +596,30 @@ static void form_auto_rt(struct bgp *bgp, vni_t vni, struct list *rtl)  	ecomadd = ecommunity_new();  	ecommunity_add_val(ecomadd, &eval, false, false); -	for (ALL_LIST_ELEMENTS_RO(rtl, node, ecom)) -		if (ecommunity_cmp(ecomadd, ecom)) { -			ecom_found = true; -			break; -		} -	if (!ecom_found) -		listnode_add_sort(rtl, ecomadd); -	else +	if (is_l3) { +		for (ALL_LIST_ELEMENTS_RO(rtl, node, l3rt)) +			if (ecommunity_cmp(ecomadd, l3rt->ecom)) { +				ecom_found = true; +				break; +			} +	} else { +		for (ALL_LIST_ELEMENTS_RO(rtl, node, ecom)) +			if (ecommunity_cmp(ecomadd, ecom)) { +				ecom_found = true; +				break; +			} +	} + +	if (!ecom_found) { +		if (is_l3) { +			newrt = evpn_vrf_rt_new(ecomadd); +			/* Label it as autoderived */ +			SET_FLAG(newrt->flags, BGP_VRF_RT_AUTO); +			listnode_add_sort(rtl, newrt); +		} else +			listnode_add_sort(rtl, ecomadd); +	} else  		ecommunity_free(&ecomadd);  } @@ -724,8 +817,9 @@ static void build_evpn_type5_route_extcomm(struct bgp *bgp_vrf,  	struct ecommunity_val eval_rmac;  	bgp_encap_types tnl_type;  	struct listnode *node, *nnode; -	struct ecommunity *ecom; +	struct vrf_route_target *l3rt;  	struct ecommunity *old_ecom; +	struct ecommunity *ecom;  	struct list *vrf_export_rtl = NULL;  	/* Encap */ @@ -749,10 +843,10 @@ static void build_evpn_type5_route_extcomm(struct bgp *bgp_vrf,  	/* Add the export RTs for L3VNI/VRF */  	vrf_export_rtl = bgp_vrf->vrf_export_rtl; -	for (ALL_LIST_ELEMENTS(vrf_export_rtl, node, nnode, ecom)) +	for (ALL_LIST_ELEMENTS(vrf_export_rtl, node, nnode, l3rt))  		bgp_attr_set_ecommunity( -			attr, -			ecommunity_merge(bgp_attr_get_ecommunity(attr), ecom)); +			attr, ecommunity_merge(bgp_attr_get_ecommunity(attr), +					       l3rt->ecom));  	/* add the router mac extended community */  	if (!is_zero_mac(&attr->rmac)) { @@ -789,6 +883,7 @@ static void build_evpn_route_extcomm(struct bgpevpn *vpn, struct attr *attr,  	bgp_encap_types tnl_type;  	struct listnode *node, *nnode;  	struct ecommunity *ecom; +	struct vrf_route_target *l3rt;  	uint32_t seqnum;  	struct list *vrf_export_rtl = NULL; @@ -817,12 +912,12 @@ static void build_evpn_route_extcomm(struct bgpevpn *vpn, struct attr *attr,  		vrf_export_rtl = bgpevpn_get_vrf_export_rtl(vpn);  		if (vrf_export_rtl && !list_isempty(vrf_export_rtl)) {  			for (ALL_LIST_ELEMENTS(vrf_export_rtl, node, nnode, -					       ecom)) +					       l3rt))  				bgp_attr_set_ecommunity(  					attr,  					ecommunity_merge(  						bgp_attr_get_ecommunity(attr), -						ecom)); +						l3rt->ecom));  		}  	} @@ -4263,13 +4358,14 @@ static void evpn_auto_rt_import_add_for_vrf(struct bgp *bgp_vrf)  {  	struct bgp *bgp_evpn = NULL; -	form_auto_rt(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_import_rtl); -	UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD); +	form_auto_rt(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_import_rtl, true);  	/* Map RT to VRF */  	bgp_evpn = bgp_get_evpn(); +  	if (!bgp_evpn)  		return; +  	bgp_evpn_map_vrf_to_its_rts(bgp_vrf);  } @@ -4278,7 +4374,8 @@ static void evpn_auto_rt_import_add_for_vrf(struct bgp *bgp_vrf)   */  static void evpn_auto_rt_import_delete_for_vrf(struct bgp *bgp_vrf)  { -	evpn_rt_delete_auto(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_import_rtl); +	evpn_rt_delete_auto(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_import_rtl, +			    true);  }  /* @@ -4286,8 +4383,7 @@ static void evpn_auto_rt_import_delete_for_vrf(struct bgp *bgp_vrf)   */  static void evpn_auto_rt_export_add_for_vrf(struct bgp *bgp_vrf)  { -	UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD); -	form_auto_rt(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_export_rtl); +	form_auto_rt(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_export_rtl, true);  }  /* @@ -4295,7 +4391,8 @@ static void evpn_auto_rt_export_add_for_vrf(struct bgp *bgp_vrf)   */  static void evpn_auto_rt_export_delete_for_vrf(struct bgp *bgp_vrf)  { -	evpn_rt_delete_auto(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_export_rtl); +	evpn_rt_delete_auto(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_export_rtl, +			    true);  }  static void bgp_evpn_handle_export_rt_change_for_vrf(struct bgp *bgp_vrf) @@ -4538,36 +4635,65 @@ void bgp_evpn_advertise_type5_routes(struct bgp *bgp_vrf, afi_t afi,  	}  } -void evpn_rt_delete_auto(struct bgp *bgp, vni_t vni, struct list *rtl) +static void rt_list_remove_node(struct list *rt_list, +				struct ecommunity *ecomdel, bool is_l3)  { -	struct listnode *node, *nnode, *node_to_del; -	struct ecommunity *ecom, *ecom_auto; +	struct listnode *node = NULL, *nnode = NULL, *node_to_del = NULL; +	struct vrf_route_target *l3rt = NULL; +	struct ecommunity *ecom = NULL; + +	if (is_l3) { +		for (ALL_LIST_ELEMENTS(rt_list, node, nnode, l3rt)) { +			if (ecommunity_match(l3rt->ecom, ecomdel)) { +				evpn_vrf_rt_del(l3rt); +				node_to_del = node; +				break; +			} +		} +	} else { +		for (ALL_LIST_ELEMENTS(rt_list, node, nnode, ecom)) { +			if (ecommunity_match(ecom, ecomdel)) { +				ecommunity_free(&ecom); +				node_to_del = node; +				break; +			} +		} +	} + + +	if (node_to_del) +		list_delete_node(rt_list, node_to_del); +} + +void evpn_rt_delete_auto(struct bgp *bgp, vni_t vni, struct list *rtl, +			 bool is_l3) +{ +	struct ecommunity *ecom_auto;  	struct ecommunity_val eval;  	if (bgp->advertise_autort_rfc8365)  		vni |= EVPN_AUTORT_VXLAN; +  	encode_route_target_as((bgp->as & 0xFFFF), vni, &eval);  	ecom_auto = ecommunity_new();  	ecommunity_add_val(ecom_auto, &eval, false, false); -	node_to_del = NULL; -	for (ALL_LIST_ELEMENTS(rtl, node, nnode, ecom)) { -		if (ecommunity_match(ecom, ecom_auto)) { -			ecommunity_free(&ecom); -			node_to_del = node; -			break; -		} -	} - -	if (node_to_del) -		list_delete_node(rtl, node_to_del); +	rt_list_remove_node(rtl, ecom_auto, is_l3);  	ecommunity_free(&ecom_auto);  } -void bgp_evpn_configure_import_rt_for_vrf(struct bgp *bgp_vrf, -					  struct ecommunity *ecomadd) +static void evpn_vrf_rt_routes_map(struct bgp *bgp_vrf) +{ +	/* map VRFs to its RTs and install routes matching this new RT */ +	if (is_l3vni_live(bgp_vrf)) { +		bgp_evpn_map_vrf_to_its_rts(bgp_vrf); +		install_routes_for_vrf(bgp_vrf); +	} +} + +static void evpn_vrf_rt_routes_unmap(struct bgp *bgp_vrf)  {  	/* uninstall routes from vrf */  	if (is_l3vni_live(bgp_vrf)) @@ -4575,112 +4701,189 @@ void bgp_evpn_configure_import_rt_for_vrf(struct bgp *bgp_vrf,  	/* Cleanup the RT to VRF mapping */  	bgp_evpn_unmap_vrf_from_its_rts(bgp_vrf); +} -	/* Remove auto generated RT */ -	evpn_auto_rt_import_delete_for_vrf(bgp_vrf); +static bool rt_list_has_cfgd_rt(struct list *rt_list) +{ +	struct listnode *node = NULL, *nnode = NULL; +	struct vrf_route_target *l3rt = NULL; + +	for (ALL_LIST_ELEMENTS(rt_list, node, nnode, l3rt)) { +		if (!CHECK_FLAG(l3rt->flags, BGP_VRF_RT_AUTO)) +			return true; +	} + +	return false; +} + +static void unconfigure_import_rt_for_vrf_fini(struct bgp *bgp_vrf) +{ +	if (!bgp_vrf->vrf_import_rtl) +		return; /* this should never fail */ + +	if (!is_l3vni_live(bgp_vrf)) +		return; /* Nothing to do if no vni */ + +	/* fall back to auto-generated RT if this was the last RT */ +	if (list_isempty(bgp_vrf->vrf_import_rtl)) +		evpn_auto_rt_import_add_for_vrf(bgp_vrf); +} + +static void unconfigure_export_rt_for_vrf_fini(struct bgp *bgp_vrf) +{ + +	if (!bgp_vrf->vrf_export_rtl) +		return; /* this should never fail */ + +	if (!is_l3vni_live(bgp_vrf)) +		return; /* Nothing to do if no vni */ + +	/* fall back to auto-generated RT if this was the last RT */ +	if (list_isempty(bgp_vrf->vrf_export_rtl)) +		evpn_auto_rt_export_add_for_vrf(bgp_vrf); + +	bgp_evpn_handle_export_rt_change_for_vrf(bgp_vrf); +} + +void bgp_evpn_configure_import_rt_for_vrf(struct bgp *bgp_vrf, +					  struct ecommunity *ecomadd, +					  bool is_wildcard) +{ +	struct vrf_route_target *newrt; + +	newrt = evpn_vrf_rt_new(ecomadd); + +	if (is_wildcard) +		SET_FLAG(newrt->flags, BGP_VRF_RT_WILD); + +	evpn_vrf_rt_routes_unmap(bgp_vrf); + +	/* Remove auto generated RT if not configured */ +	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) +		evpn_auto_rt_import_delete_for_vrf(bgp_vrf);  	/* Add the newly configured RT to RT list */ -	listnode_add_sort(bgp_vrf->vrf_import_rtl, ecomadd); +	listnode_add_sort(bgp_vrf->vrf_import_rtl, newrt); +  	SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD); -	/* map VRF to its RTs and install routes matching the new RTs */ -	if (is_l3vni_live(bgp_vrf)) { -		bgp_evpn_map_vrf_to_its_rts(bgp_vrf); -		install_routes_for_vrf(bgp_vrf); -	} +	evpn_vrf_rt_routes_map(bgp_vrf); +} + +void bgp_evpn_configure_import_auto_rt_for_vrf(struct bgp *bgp_vrf) +{ +	if (CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) +		return; /* Already configured */ + +	SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD); + +	if (!is_l3vni_live(bgp_vrf)) +		return; /* Wait for VNI before adding rts */ + +	evpn_vrf_rt_routes_unmap(bgp_vrf); + +	evpn_auto_rt_import_add_for_vrf(bgp_vrf); + +	evpn_vrf_rt_routes_map(bgp_vrf);  }  void bgp_evpn_unconfigure_import_rt_for_vrf(struct bgp *bgp_vrf,  					    struct ecommunity *ecomdel)  { -	struct listnode *node = NULL, *nnode = NULL, *node_to_del = NULL; -	struct ecommunity *ecom = NULL; +	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) +		return; /* Already un-configured */ -	/* uninstall routes from vrf */ -	if (is_l3vni_live(bgp_vrf)) -		uninstall_routes_for_vrf(bgp_vrf); +	evpn_vrf_rt_routes_unmap(bgp_vrf); -	/* Cleanup the RT to VRF mapping */ -	bgp_evpn_unmap_vrf_from_its_rts(bgp_vrf); +	/* Remove rt */ +	rt_list_remove_node(bgp_vrf->vrf_import_rtl, ecomdel, true); -	/* remove the RT from the RT list */ -	for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, ecom)) { -		if (ecommunity_match(ecom, ecomdel)) { -			ecommunity_free(&ecom); -			node_to_del = node; -			break; -		} -	} +	if (!rt_list_has_cfgd_rt(bgp_vrf->vrf_import_rtl)) +		UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD); -	if (node_to_del) -		list_delete_node(bgp_vrf->vrf_import_rtl, node_to_del); +	unconfigure_import_rt_for_vrf_fini(bgp_vrf); -	assert(bgp_vrf->vrf_import_rtl); -	/* fallback to auto import rt, if this was the last RT */ -	if (bgp_vrf->vrf_import_rtl && list_isempty(bgp_vrf->vrf_import_rtl)) { -		UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD); -		if (is_l3vni_live(bgp_vrf)) -			evpn_auto_rt_import_add_for_vrf(bgp_vrf); -	} +	evpn_vrf_rt_routes_map(bgp_vrf); +} -	/* map VRFs to its RTs and install routes matching this new RT */ -	if (is_l3vni_live(bgp_vrf)) { -		bgp_evpn_map_vrf_to_its_rts(bgp_vrf); -		install_routes_for_vrf(bgp_vrf); -	} +void bgp_evpn_unconfigure_import_auto_rt_for_vrf(struct bgp *bgp_vrf) +{ +	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) +		return; /* Already un-configured */ + +	evpn_vrf_rt_routes_unmap(bgp_vrf); + +	/* remove auto-generated RT */ +	evpn_auto_rt_import_delete_for_vrf(bgp_vrf); + +	UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD); + +	unconfigure_import_rt_for_vrf_fini(bgp_vrf); + +	evpn_vrf_rt_routes_map(bgp_vrf);  }  void bgp_evpn_configure_export_rt_for_vrf(struct bgp *bgp_vrf,  					  struct ecommunity *ecomadd)  { -	/* remove auto-generated RT */ -	evpn_auto_rt_export_delete_for_vrf(bgp_vrf); +	struct vrf_route_target *newrt; + +	newrt = evpn_vrf_rt_new(ecomadd); + +	/* Remove auto generated RT if not configured */ +	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) +		evpn_auto_rt_export_delete_for_vrf(bgp_vrf);  	/* Add the new RT to the RT list */ -	listnode_add_sort(bgp_vrf->vrf_export_rtl, ecomadd); +	listnode_add_sort(bgp_vrf->vrf_export_rtl, newrt); +  	SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD);  	if (is_l3vni_live(bgp_vrf))  		bgp_evpn_handle_export_rt_change_for_vrf(bgp_vrf);  } +void bgp_evpn_configure_export_auto_rt_for_vrf(struct bgp *bgp_vrf) +{ +	if (CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) +		return; /* Already configured */ + +	SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD); + +	if (!is_l3vni_live(bgp_vrf)) +		return; /* Wait for VNI before adding rts */ + +	evpn_auto_rt_export_add_for_vrf(bgp_vrf); + +	bgp_evpn_handle_export_rt_change_for_vrf(bgp_vrf); +} +  void bgp_evpn_unconfigure_export_rt_for_vrf(struct bgp *bgp_vrf,  					    struct ecommunity *ecomdel)  { -	struct listnode *node = NULL, *nnode = NULL, *node_to_del = NULL; -	struct ecommunity *ecom = NULL; +	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) +		return; /* Already un-configured */ -	/* Remove the RT from the RT list */ -	for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_export_rtl, node, nnode, ecom)) { -		if (ecommunity_match(ecom, ecomdel)) { -			ecommunity_free(&ecom); -			node_to_del = node; -			break; -		} -	} +	/* Remove rt */ +	rt_list_remove_node(bgp_vrf->vrf_export_rtl, ecomdel, true); -	if (node_to_del) -		list_delete_node(bgp_vrf->vrf_export_rtl, node_to_del); +	if (!rt_list_has_cfgd_rt(bgp_vrf->vrf_export_rtl)) +		UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD); -	/* -	 * Temporary assert to make SA happy. -	 * The ALL_LIST_ELEMENTS macro above has a NULL check -	 * which means that SA is going to complain about -	 * the list_isempty call, which doesn't NULL check. -	 * So until we get this situation cleaned up, here -	 * we are. -	 */ -	assert(bgp_vrf->vrf_export_rtl); +	unconfigure_export_rt_for_vrf_fini(bgp_vrf); +} -	/* fall back to auto-generated RT if this was the last RT */ -	if (list_isempty(bgp_vrf->vrf_export_rtl)) { -		UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD); -		if (is_l3vni_live(bgp_vrf)) -			evpn_auto_rt_export_add_for_vrf(bgp_vrf); -	} +void bgp_evpn_unconfigure_export_auto_rt_for_vrf(struct bgp *bgp_vrf) +{ +	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) +		return; /* Already un-configured */ -	if (is_l3vni_live(bgp_vrf)) -		bgp_evpn_handle_export_rt_change_for_vrf(bgp_vrf); +	/* remove auto-generated RT */ +	evpn_auto_rt_export_delete_for_vrf(bgp_vrf); + +	UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD); + +	unconfigure_export_rt_for_vrf_fini(bgp_vrf);  }  /* @@ -5109,19 +5312,11 @@ int bgp_nlri_parse_evpn(struct peer *peer, struct attr *attr,   */  void bgp_evpn_map_vrf_to_its_rts(struct bgp *bgp_vrf)  { -	uint32_t i = 0; -	struct ecommunity_val *eval = NULL; -	struct listnode *node = NULL, *nnode = NULL; -	struct ecommunity *ecom = NULL; +	struct listnode *node, *nnode; +	struct vrf_route_target *l3rt; -	for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, ecom)) { -		for (i = 0; i < ecom->size; i++) { -			eval = (struct ecommunity_val *)(ecom->val -							 + (i -							    * ECOMMUNITY_SIZE)); -			map_vrf_to_rt(bgp_vrf, eval); -		} -	} +	for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, l3rt)) +		map_vrf_to_rt(bgp_vrf, l3rt);  }  /* @@ -5129,37 +5324,13 @@ void bgp_evpn_map_vrf_to_its_rts(struct bgp *bgp_vrf)   */  void bgp_evpn_unmap_vrf_from_its_rts(struct bgp *bgp_vrf)  { -	uint32_t i; -	struct ecommunity_val *eval;  	struct listnode *node, *nnode; -	struct ecommunity *ecom; - -	for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, ecom)) { -		for (i = 0; i < ecom->size; i++) { -			struct vrf_irt_node *irt; -			struct ecommunity_val eval_tmp; - -			eval = (struct ecommunity_val *)(ecom->val -							 + (i -							    * ECOMMUNITY_SIZE)); -			/* If using "automatic" RT, we only care about the -			 * local-admin sub-field. -			 * This is to facilitate using VNI as the RT for EBGP -			 * peering too. -			 */ -			memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); -			if (!CHECK_FLAG(bgp_vrf->vrf_flags, -					BGP_VRF_IMPORT_RT_CFGD)) -				mask_ecom_global_admin(&eval_tmp, eval); +	struct vrf_route_target *l3rt; -			irt = lookup_vrf_import_rt(&eval_tmp); -			if (irt) -				unmap_vrf_from_rt(bgp_vrf, irt); -		} -	} +	for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, l3rt)) +		unmap_vrf_from_rt(bgp_vrf, l3rt);  } -  /*   * Map the RTs (configured or automatically derived) of a VNI to the VNI.   * The mapping will be used during route processing. @@ -5221,7 +5392,7 @@ void bgp_evpn_unmap_vni_from_its_rts(struct bgp *bgp, struct bgpevpn *vpn)   */  void bgp_evpn_derive_auto_rt_import(struct bgp *bgp, struct bgpevpn *vpn)  { -	form_auto_rt(bgp, vpn->vni, vpn->import_rtl); +	form_auto_rt(bgp, vpn->vni, vpn->import_rtl, false);  	UNSET_FLAG(vpn->flags, VNI_FLAG_IMPRT_CFGD);  	/* Map RT to VNI */ @@ -5233,7 +5404,7 @@ void bgp_evpn_derive_auto_rt_import(struct bgp *bgp, struct bgpevpn *vpn)   */  void bgp_evpn_derive_auto_rt_export(struct bgp *bgp, struct bgpevpn *vpn)  { -	form_auto_rt(bgp, vpn->vni, vpn->export_rtl); +	form_auto_rt(bgp, vpn->vni, vpn->export_rtl, false);  	UNSET_FLAG(vpn->flags, VNI_FLAG_EXPRT_CFGD);  } @@ -5643,12 +5814,14 @@ int bgp_evpn_local_l3vni_add(vni_t l3vni, vrf_id_t vrf_id,  	}  	/* Map auto derive or configured RTs */ -	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) +	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD) || +	    CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD))  		evpn_auto_rt_import_add_for_vrf(bgp_vrf);  	else  		bgp_evpn_map_vrf_to_its_rts(bgp_vrf); -	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) +	if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD) || +	    CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD))  		evpn_auto_rt_export_add_for_vrf(bgp_vrf);  	/* auto derive RD */ @@ -6021,12 +6194,12 @@ void bgp_evpn_init(struct bgp *bgp)  			    "BGP VRF Import RT Hash");  	bgp->vrf_import_rtl = list_new();  	bgp->vrf_import_rtl->cmp = -		(int (*)(void *, void *))bgp_evpn_route_target_cmp; -	bgp->vrf_import_rtl->del = bgp_evpn_xxport_delete_ecomm; +		(int (*)(void *, void *))evpn_vrf_route_target_cmp; +	bgp->vrf_import_rtl->del = evpn_vrf_rt_del;  	bgp->vrf_export_rtl = list_new();  	bgp->vrf_export_rtl->cmp = -		(int (*)(void *, void *))bgp_evpn_route_target_cmp; -	bgp->vrf_export_rtl->del = bgp_evpn_xxport_delete_ecomm; +		(int (*)(void *, void *))evpn_vrf_route_target_cmp; +	bgp->vrf_export_rtl->del = evpn_vrf_rt_del;  	bgp->l2vnis = list_new();  	bgp->l2vnis->cmp = vni_list_cmp;  	/* By default Duplicate Address Dection is enabled. diff --git a/bgpd/bgp_evpn_private.h b/bgpd/bgp_evpn_private.h index 64fdc29704..fdbffa95dd 100644 --- a/bgpd/bgp_evpn_private.h +++ b/bgpd/bgp_evpn_private.h @@ -194,6 +194,18 @@ struct evpn_remote_ip {  	struct list *macip_path_list;  }; +/* + * Wrapper struct for l3 RT's + */ +struct vrf_route_target { +	/* flags based on config to determine how RTs are handled */ +	uint8_t flags; +#define BGP_VRF_RT_AUTO (1 << 0) +#define BGP_VRF_RT_WILD (1 << 1) + +	struct ecommunity *ecom; +}; +  static inline int is_vrf_rd_configured(struct bgp *bgp_vrf)  {  	return (CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_RD_CFGD)); @@ -590,15 +602,21 @@ extern struct zclient *zclient;  extern void bgp_evpn_install_uninstall_default_route(struct bgp *bgp_vrf,  						     afi_t afi, safi_t safi,  						     bool add); -extern void evpn_rt_delete_auto(struct bgp *, vni_t, struct list *); +extern void evpn_rt_delete_auto(struct bgp *bgp, vni_t vni, struct list *rtl, +				bool is_l3);  extern void bgp_evpn_configure_export_rt_for_vrf(struct bgp *bgp_vrf,  						 struct ecommunity *ecomadd); +extern void bgp_evpn_configure_export_auto_rt_for_vrf(struct bgp *bgp_vrf);  extern void bgp_evpn_unconfigure_export_rt_for_vrf(struct bgp *bgp_vrf,  						   struct ecommunity *ecomdel); +extern void bgp_evpn_unconfigure_export_auto_rt_for_vrf(struct bgp *bgp_vrf);  extern void bgp_evpn_configure_import_rt_for_vrf(struct bgp *bgp_vrf, -						 struct ecommunity *ecomadd); +						 struct ecommunity *ecomadd, +						 bool is_wildcard); +extern void bgp_evpn_configure_import_auto_rt_for_vrf(struct bgp *bgp_vrf);  extern void bgp_evpn_unconfigure_import_rt_for_vrf(struct bgp *bgp_vrf,  						   struct ecommunity *ecomdel); +extern void bgp_evpn_unconfigure_import_auto_rt_for_vrf(struct bgp *bgp_vrf);  extern int bgp_evpn_handle_export_rt_change(struct bgp *bgp,  					    struct bgpevpn *vpn);  extern void bgp_evpn_handle_autort_change(struct bgp *bgp); diff --git a/bgpd/bgp_evpn_vty.c b/bgpd/bgp_evpn_vty.c index 5ad5cf8bff..f920a783b3 100644 --- a/bgpd/bgp_evpn_vty.c +++ b/bgpd/bgp_evpn_vty.c @@ -373,7 +373,7 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf,  	char buf1[INET6_ADDRSTRLEN];  	char *ecom_str;  	struct listnode *node, *nnode; -	struct ecommunity *ecom; +	struct vrf_route_target *l3rt;  	json_object *json_import_rtl = NULL;  	json_object *json_export_rtl = NULL;  	char buf2[ETHER_ADDR_STRLEN]; @@ -431,8 +431,8 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf,  	if (!json)  		vty_out(vty, "  Import Route Target:\n"); -	for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, ecom)) { -		ecom_str = ecommunity_ecom2str(ecom, +	for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, l3rt)) { +		ecom_str = ecommunity_ecom2str(l3rt->ecom,  					       ECOMMUNITY_FORMAT_ROUTE_MAP, 0);  		if (json) @@ -449,8 +449,8 @@ static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf,  	else  		vty_out(vty, "  Export Route Target:\n"); -	for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_export_rtl, node, nnode, ecom)) { -		ecom_str = ecommunity_ecom2str(ecom, +	for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_export_rtl, node, nnode, l3rt)) { +		ecom_str = ecommunity_ecom2str(l3rt->ecom,  					       ECOMMUNITY_FORMAT_ROUTE_MAP, 0);  		if (json) @@ -913,7 +913,7 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp,  	char rt_buf[25];  	char *ecom_str;  	struct listnode *node, *nnode; -	struct ecommunity *ecom; +	struct vrf_route_target *l3rt;  	if (!bgp->l3vni)  		return; @@ -954,8 +954,8 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp,  			&bgp->vrf_prd);  	} -	for (ALL_LIST_ELEMENTS(bgp->vrf_import_rtl, node, nnode, ecom)) { -		ecom_str = ecommunity_ecom2str(ecom, +	for (ALL_LIST_ELEMENTS(bgp->vrf_import_rtl, node, nnode, l3rt)) { +		ecom_str = ecommunity_ecom2str(l3rt->ecom,  					       ECOMMUNITY_FORMAT_ROUTE_MAP, 0);  		if (json) { @@ -982,8 +982,8 @@ static void show_l3vni_entry(struct vty *vty, struct bgp *bgp,  	if (json)  		json_object_object_add(json_vni, "importRTs", json_import_rtl); -	for (ALL_LIST_ELEMENTS(bgp->vrf_export_rtl, node, nnode, ecom)) { -		ecom_str = ecommunity_ecom2str(ecom, +	for (ALL_LIST_ELEMENTS(bgp->vrf_export_rtl, node, nnode, l3rt)) { +		ecom_str = ecommunity_ecom2str(l3rt->ecom,  					       ECOMMUNITY_FORMAT_ROUTE_MAP, 0);  		if (json) { @@ -1984,12 +1984,12 @@ DEFUN(no_evpnrt5_network,  static void evpn_import_rt_delete_auto(struct bgp *bgp, struct bgpevpn *vpn)  { -	evpn_rt_delete_auto(bgp, vpn->vni, vpn->import_rtl); +	evpn_rt_delete_auto(bgp, vpn->vni, vpn->import_rtl, false);  }  static void evpn_export_rt_delete_auto(struct bgp *bgp, struct bgpevpn *vpn)  { -	evpn_rt_delete_auto(bgp, vpn->vni, vpn->export_rtl); +	evpn_rt_delete_auto(bgp, vpn->vni, vpn->export_rtl, false);  }  /* @@ -3980,11 +3980,13 @@ DEFUN (bgp_evpn_advertise_type5,  DEFUN (no_bgp_evpn_advertise_type5,         no_bgp_evpn_advertise_type5_cmd, -       "no advertise " BGP_AFI_CMD_STR "" BGP_SAFI_CMD_STR, +       "no advertise " BGP_AFI_CMD_STR "" BGP_SAFI_CMD_STR " [route-map WORD]",         NO_STR         "Advertise prefix routes\n"         BGP_AFI_HELP_STR -       BGP_SAFI_HELP_STR) +       BGP_SAFI_HELP_STR +       "route-map for filtering specific routes\n" +       "Name of the route map\n")  {  	struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); /* bgp vrf instance */  	int idx_afi = 0; @@ -5654,18 +5656,35 @@ DEFUN (no_bgp_evpn_vni_rd_without_val,   * Loop over all extended-communities in the route-target list rtl and   * return 1 if we find ecomtarget   */ -static int bgp_evpn_rt_matches_existing(struct list *rtl, -					struct ecommunity *ecomtarget) +static bool bgp_evpn_rt_matches_existing(struct list *rtl, +					 struct ecommunity *ecomtarget)  { -	struct listnode *node, *nnode; +	struct listnode *node;  	struct ecommunity *ecom; -	for (ALL_LIST_ELEMENTS(rtl, node, nnode, ecom)) { +	for (ALL_LIST_ELEMENTS_RO(rtl, node, ecom)) {  		if (ecommunity_match(ecom, ecomtarget)) -			return 1; +			return true;  	} -	return 0; +	return false; +} + +/* + * L3 RT version of above. + */ +static bool bgp_evpn_vrf_rt_matches_existing(struct list *rtl, +					     struct ecommunity *ecomtarget) +{ +	struct listnode *node; +	struct vrf_route_target *l3rt; + +	for (ALL_LIST_ELEMENTS_RO(rtl, node, l3rt)) { +		if (ecommunity_match(l3rt->ecom, ecomtarget)) +			return true; +	} + +	return false;  }  /* display L3VNI related info for a VRF instance */ @@ -5685,7 +5704,7 @@ DEFUN (show_bgp_vrf_l3vni_info,  	struct bgp *bgp = NULL;  	struct listnode *node = NULL;  	struct bgpevpn *vpn = NULL; -	struct ecommunity *ecom = NULL; +	struct vrf_route_target *l3rt;  	json_object *json = NULL;  	json_object *json_vnis = NULL;  	json_object *json_export_rts = NULL; @@ -5735,13 +5754,13 @@ DEFUN (show_bgp_vrf_l3vni_info,  		vty_out(vty, "\n");  		vty_out(vty, "  Export-RTs:\n");  		vty_out(vty, "    "); -		for (ALL_LIST_ELEMENTS_RO(bgp->vrf_export_rtl, node, ecom)) -			vty_out(vty, "%s  ", ecommunity_str(ecom)); +		for (ALL_LIST_ELEMENTS_RO(bgp->vrf_export_rtl, node, l3rt)) +			vty_out(vty, "%s  ", ecommunity_str(l3rt->ecom));  		vty_out(vty, "\n");  		vty_out(vty, "  Import-RTs:\n");  		vty_out(vty, "    "); -		for (ALL_LIST_ELEMENTS_RO(bgp->vrf_import_rtl, node, ecom)) -			vty_out(vty, "%s  ", ecommunity_str(ecom)); +		for (ALL_LIST_ELEMENTS_RO(bgp->vrf_import_rtl, node, l3rt)) +			vty_out(vty, "%s  ", ecommunity_str(l3rt->ecom));  		vty_out(vty, "\n");  		vty_out(vty, "  RD: %pRD\n", &bgp->vrf_prd);  	} else { @@ -5765,17 +5784,19 @@ DEFUN (show_bgp_vrf_l3vni_info,  		json_object_object_add(json, "l2vnis", json_vnis);  		/* export rts */ -		for (ALL_LIST_ELEMENTS_RO(bgp->vrf_export_rtl, node, ecom)) +		for (ALL_LIST_ELEMENTS_RO(bgp->vrf_export_rtl, node, l3rt))  			json_object_array_add(  				json_export_rts, -				json_object_new_string(ecommunity_str(ecom))); +				json_object_new_string( +					ecommunity_str(l3rt->ecom)));  		json_object_object_add(json, "export-rts", json_export_rts);  		/* import rts */ -		for (ALL_LIST_ELEMENTS_RO(bgp->vrf_import_rtl, node, ecom)) +		for (ALL_LIST_ELEMENTS_RO(bgp->vrf_import_rtl, node, l3rt))  			json_object_array_add(  				json_import_rts, -				json_object_new_string(ecommunity_str(ecom))); +				json_object_new_string( +					ecommunity_str(l3rt->ecom)));  		json_object_object_add(json, "import-rts", json_import_rts);  		json_object_string_addf(json, "rd", "%pRD", &bgp->vrf_prd);  	} @@ -5785,22 +5806,133 @@ DEFUN (show_bgp_vrf_l3vni_info,  	return CMD_SUCCESS;  } +static int add_rt(struct bgp *bgp, struct ecommunity *ecom, bool is_import, +		  bool is_wildcard) +{ +	/* Do nothing if we already have this route-target */ +	if (is_import) { +		if (!bgp_evpn_vrf_rt_matches_existing(bgp->vrf_import_rtl, +						      ecom)) +			bgp_evpn_configure_import_rt_for_vrf(bgp, ecom, +							     is_wildcard); +		else +			return -1; +	} else { +		if (!bgp_evpn_vrf_rt_matches_existing(bgp->vrf_export_rtl, +						      ecom)) +			bgp_evpn_configure_export_rt_for_vrf(bgp, ecom); +		else +			return -1; +	} + +	return 0; +} + +static int del_rt(struct bgp *bgp, struct ecommunity *ecom, bool is_import) +{ +	/* Verify we already have this route-target */ +	if (is_import) { +		if (!bgp_evpn_vrf_rt_matches_existing(bgp->vrf_import_rtl, +						      ecom)) +			return -1; + +		bgp_evpn_unconfigure_import_rt_for_vrf(bgp, ecom); +	} else { +		if (!bgp_evpn_vrf_rt_matches_existing(bgp->vrf_export_rtl, +						      ecom)) +			return -1; + +		bgp_evpn_unconfigure_export_rt_for_vrf(bgp, ecom); +	} + +	return 0; +} + +static int parse_rtlist(struct bgp *bgp, struct vty *vty, int argc, +			struct cmd_token **argv, int rt_idx, bool is_add, +			bool is_import) +{ +	int ret = CMD_SUCCESS; +	bool is_wildcard = false; +	struct ecommunity *ecom = NULL; + +	for (int i = rt_idx; i < argc; i++) { +		is_wildcard = false; + +		/* +		 * Special handling for wildcard '*' here. +		 * +		 * Let's just convert it to 0 here so we dont have to modify +		 * the ecommunity parser. +		 */ +		if ((argv[i]->arg)[0] == '*') { +			if (!is_import) { +				vty_out(vty, +					"%% Wildcard '*' only applicable for import\n"); +				ret = CMD_WARNING; +				continue; +			} + +			(argv[i]->arg)[0] = '0'; +			is_wildcard = true; +		} + +		ecom = ecommunity_str2com(argv[i]->arg, ECOMMUNITY_ROUTE_TARGET, +					  0); + +		/* Put it back as was */ +		if (is_wildcard) +			(argv[i]->arg)[0] = '*'; + +		if (!ecom) { +			vty_out(vty, "%% Malformed Route Target list\n"); +			ret = CMD_WARNING; +			continue; +		} + +		ecommunity_str(ecom); + +		if (is_add) { +			if (add_rt(bgp, ecom, is_import, is_wildcard) != 0) { +				vty_out(vty, +					"%% RT specified already configured for this VRF: %s\n", +					argv[i]->arg); +				ecommunity_free(&ecom); +				ret = CMD_WARNING; +			} + +		} else { +			if (del_rt(bgp, ecom, is_import) != 0) { +				vty_out(vty, +					"%% RT specified does not match configuration for this VRF: %s\n", +					argv[i]->arg); +				ret = CMD_WARNING; +			} + +			ecommunity_free(&ecom); +		} +	} + +	return ret; +} +  /* import/export rt for l3vni-vrf */  DEFUN (bgp_evpn_vrf_rt,         bgp_evpn_vrf_rt_cmd, -       "route-target <both|import|export> RT", +       "route-target <both|import|export> RTLIST...",         "Route Target\n"         "import and export\n"         "import\n"         "export\n" -       "Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +       "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN|*:OPQR|*:MN)\n")  { +	int ret = CMD_SUCCESS; +	int tmp_ret = CMD_SUCCESS;  	int rt_type;  	struct bgp *bgp = VTY_GET_CONTEXT(bgp); -	struct ecommunity *ecomadd = NULL;  	if (!bgp) -		return CMD_WARNING; +		return CMD_WARNING_CONFIG_FAILED;  	if (!strcmp(argv[1]->arg, "import"))  		rt_type = RT_TYPE_IMPORT; @@ -5810,49 +5942,82 @@ DEFUN (bgp_evpn_vrf_rt,  		rt_type = RT_TYPE_BOTH;  	else {  		vty_out(vty, "%% Invalid Route Target type\n"); -		return CMD_WARNING; +		return CMD_WARNING_CONFIG_FAILED;  	} -	ecomadd = ecommunity_str2com(argv[2]->arg, ECOMMUNITY_ROUTE_TARGET, 0); -	if (!ecomadd) { -		vty_out(vty, "%% Malformed Route Target list\n"); -		return CMD_WARNING; +	if (strmatch(argv[2]->arg, "auto")) { +		vty_out(vty, "%% `auto` cannot be configured via list\n"); +		return CMD_WARNING_CONFIG_FAILED;  	} -	ecommunity_str(ecomadd);  	/* Add/update the import route-target */ -	if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) { -		/* Do nothing if we already have this import route-target */ -		if (!bgp_evpn_rt_matches_existing(bgp->vrf_import_rtl, ecomadd)) -			bgp_evpn_configure_import_rt_for_vrf(bgp, ecomadd); -	} +	if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) +		tmp_ret = parse_rtlist(bgp, vty, argc, argv, 2, true, true); -	/* Add/update the export route-target */ -	if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) { -		/* Do nothing if we already have this export route-target */ -		if (!bgp_evpn_rt_matches_existing(bgp->vrf_export_rtl, ecomadd)) -			bgp_evpn_configure_export_rt_for_vrf(bgp, ecomadd); +	if (ret == CMD_SUCCESS && tmp_ret != CMD_SUCCESS) +		ret = tmp_ret; + +	if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) +		tmp_ret = parse_rtlist(bgp, vty, argc, argv, 2, true, false); + +	if (ret == CMD_SUCCESS && tmp_ret != CMD_SUCCESS) +		ret = tmp_ret; + +	return ret; +} + +DEFPY (bgp_evpn_vrf_rt_auto, +       bgp_evpn_vrf_rt_auto_cmd, +       "route-target <both|import|export>$type auto", +       "Route Target\n" +       "import and export\n" +       "import\n" +       "export\n" +       "Automatically derive route target\n") +{ +	struct bgp *bgp = VTY_GET_CONTEXT(bgp); +	int rt_type; + +	if (!bgp) +		return CMD_WARNING_CONFIG_FAILED; + +	if (strmatch(type, "import")) +		rt_type = RT_TYPE_IMPORT; +	else if (strmatch(type, "export")) +		rt_type = RT_TYPE_EXPORT; +	else if (strmatch(type, "both")) +		rt_type = RT_TYPE_BOTH; +	else { +		vty_out(vty, "%% Invalid Route Target type\n"); +		return CMD_WARNING_CONFIG_FAILED;  	} +	if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) +		bgp_evpn_configure_import_auto_rt_for_vrf(bgp); + +	if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) +		bgp_evpn_configure_export_auto_rt_for_vrf(bgp); +  	return CMD_SUCCESS;  }  DEFUN (no_bgp_evpn_vrf_rt,         no_bgp_evpn_vrf_rt_cmd, -       "no route-target <both|import|export> RT", +       "no route-target <both|import|export> RTLIST...",         NO_STR         "Route Target\n"         "import and export\n"         "import\n"         "export\n" -       EVPN_ASN_IP_HELP_STR) +       "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n")  {  	struct bgp *bgp = VTY_GET_CONTEXT(bgp); -	int rt_type, found_ecomdel; -	struct ecommunity *ecomdel = NULL; +	int ret = CMD_SUCCESS; +	int tmp_ret = CMD_SUCCESS; +	int rt_type;  	if (!bgp) -		return CMD_WARNING; +		return CMD_WARNING_CONFIG_FAILED;  	if (!strcmp(argv[2]->arg, "import"))  		rt_type = RT_TYPE_IMPORT; @@ -5862,79 +6027,104 @@ DEFUN (no_bgp_evpn_vrf_rt,  		rt_type = RT_TYPE_BOTH;  	else {  		vty_out(vty, "%% Invalid Route Target type\n"); -		return CMD_WARNING; +		return CMD_WARNING_CONFIG_FAILED; +	} + +	if (!strcmp(argv[3]->arg, "auto")) { +		vty_out(vty, "%% `auto` cannot be unconfigured via list\n"); +		return CMD_WARNING_CONFIG_FAILED;  	}  	if (rt_type == RT_TYPE_IMPORT) {  		if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) {  			vty_out(vty,  				"%% Import RT is not configured for this VRF\n"); -			return CMD_WARNING; +			return CMD_WARNING_CONFIG_FAILED;  		}  	} else if (rt_type == RT_TYPE_EXPORT) {  		if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) {  			vty_out(vty,  				"%% Export RT is not configured for this VRF\n"); -			return CMD_WARNING; +			return CMD_WARNING_CONFIG_FAILED;  		}  	} else if (rt_type == RT_TYPE_BOTH) {  		if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)  		    && !CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) {  			vty_out(vty,  				"%% Import/Export RT is not configured for this VRF\n"); -			return CMD_WARNING; +			return CMD_WARNING_CONFIG_FAILED;  		}  	} -	ecomdel = ecommunity_str2com(argv[3]->arg, ECOMMUNITY_ROUTE_TARGET, 0); -	if (!ecomdel) { -		vty_out(vty, "%% Malformed Route Target list\n"); -		return CMD_WARNING; +	if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) +		tmp_ret = parse_rtlist(bgp, vty, argc, argv, 3, false, true); + +	if (ret == CMD_SUCCESS && tmp_ret != CMD_SUCCESS) +		ret = tmp_ret; + +	if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) +		tmp_ret = parse_rtlist(bgp, vty, argc, argv, 3, false, false); + +	if (ret == CMD_SUCCESS && tmp_ret != CMD_SUCCESS) +		ret = tmp_ret; + +	return ret; +} + +DEFPY (no_bgp_evpn_vrf_rt_auto, +       no_bgp_evpn_vrf_rt_auto_cmd, +       "no route-target <both|import|export>$type auto", +       NO_STR +       "Route Target\n" +       "import and export\n" +       "import\n" +       "export\n" +       "Automatically derive route target\n") +{ +	struct bgp *bgp = VTY_GET_CONTEXT(bgp); +	int rt_type; + +	if (!bgp) +		return CMD_WARNING_CONFIG_FAILED; + +	if (strmatch(type, "import")) +		rt_type = RT_TYPE_IMPORT; +	else if (strmatch(type, "export")) +		rt_type = RT_TYPE_EXPORT; +	else if (strmatch(type, "both")) +		rt_type = RT_TYPE_BOTH; +	else { +		vty_out(vty, "%% Invalid Route Target type\n"); +		return CMD_WARNING_CONFIG_FAILED;  	} -	ecommunity_str(ecomdel);  	if (rt_type == RT_TYPE_IMPORT) { -		if (!bgp_evpn_rt_matches_existing(bgp->vrf_import_rtl, -						  ecomdel)) { -			ecommunity_free(&ecomdel); +		if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) {  			vty_out(vty, -				"%% RT specified does not match configuration for this VRF\n"); -			return CMD_WARNING; +				"%% Import AUTO RT is not configured for this VRF\n"); +			return CMD_WARNING_CONFIG_FAILED;  		} -		bgp_evpn_unconfigure_import_rt_for_vrf(bgp, ecomdel);  	} else if (rt_type == RT_TYPE_EXPORT) { -		if (!bgp_evpn_rt_matches_existing(bgp->vrf_export_rtl, -						  ecomdel)) { -			ecommunity_free(&ecomdel); +		if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) {  			vty_out(vty, -				"%% RT specified does not match configuration for this VRF\n"); -			return CMD_WARNING; +				"%% Export AUTO RT is not configured for this VRF\n"); +			return CMD_WARNING_CONFIG_FAILED;  		} -		bgp_evpn_unconfigure_export_rt_for_vrf(bgp, ecomdel);  	} else if (rt_type == RT_TYPE_BOTH) { -		found_ecomdel = 0; - -		if (bgp_evpn_rt_matches_existing(bgp->vrf_import_rtl, -						 ecomdel)) { -			bgp_evpn_unconfigure_import_rt_for_vrf(bgp, ecomdel); -			found_ecomdel = 1; -		} - -		if (bgp_evpn_rt_matches_existing(bgp->vrf_export_rtl, -						 ecomdel)) { -			bgp_evpn_unconfigure_export_rt_for_vrf(bgp, ecomdel); -			found_ecomdel = 1; -		} - -		if (!found_ecomdel) { -			ecommunity_free(&ecomdel); +		if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD) && +		    !CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) {  			vty_out(vty, -				"%% RT specified does not match configuration for this VRF\n"); -			return CMD_WARNING; +				"%% Import/Export AUTO RT is not configured for this VRF\n"); +			return CMD_WARNING_CONFIG_FAILED;  		}  	} -	ecommunity_free(&ecomdel); +	if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) +		bgp_evpn_unconfigure_import_auto_rt_for_vrf(bgp); + +	if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) +		bgp_evpn_unconfigure_export_auto_rt_for_vrf(bgp); +  	return CMD_SUCCESS;  } @@ -6421,31 +6611,62 @@ void bgp_config_write_evpn_info(struct vty *vty, struct bgp *bgp, afi_t afi,  	if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) {  		char *ecom_str;  		struct listnode *node, *nnode; -		struct ecommunity *ecom; +		struct vrf_route_target *l3rt;  		for (ALL_LIST_ELEMENTS(bgp->vrf_import_rtl, node, nnode, -				       ecom)) { +				       l3rt)) { + +			if (CHECK_FLAG(l3rt->flags, BGP_VRF_RT_AUTO)) +				continue; +  			ecom_str = ecommunity_ecom2str( -				ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); -			vty_out(vty, "  route-target import %s\n", ecom_str); +				l3rt->ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + +			if (CHECK_FLAG(l3rt->flags, BGP_VRF_RT_WILD)) { +				char *vni_str = NULL; + +				vni_str = strchr(ecom_str, ':') + 1; + +				if (!vni_str) +					continue; /* This should never happen */ + +				vty_out(vty, "  route-target import *:%s\n", +					vni_str); + +			} else +				vty_out(vty, "  route-target import %s\n", +					ecom_str); +  			XFREE(MTYPE_ECOMMUNITY_STR, ecom_str);  		}  	} +	/* import route-target auto */ +	if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) +		vty_out(vty, "  route-target import auto\n"); +  	/* export route-target */  	if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) {  		char *ecom_str;  		struct listnode *node, *nnode; -		struct ecommunity *ecom; +		struct vrf_route_target *l3rt;  		for (ALL_LIST_ELEMENTS(bgp->vrf_export_rtl, node, nnode, -				       ecom)) { +				       l3rt)) { + +			if (CHECK_FLAG(l3rt->flags, BGP_VRF_RT_AUTO)) +				continue; +  			ecom_str = ecommunity_ecom2str( -				ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); +				l3rt->ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0);  			vty_out(vty, "  route-target export %s\n", ecom_str);  			XFREE(MTYPE_ECOMMUNITY_STR, ecom_str);  		}  	} + +	/* export route-target auto */ +	if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) +		vty_out(vty, "  route-target export auto\n");  }  void bgp_ethernetvpn_init(void) @@ -6547,6 +6768,8 @@ void bgp_ethernetvpn_init(void)  	install_element(BGP_NODE, &no_bgp_evpn_vrf_rd_without_val_cmd);  	install_element(BGP_EVPN_NODE, &bgp_evpn_vrf_rt_cmd);  	install_element(BGP_EVPN_NODE, &no_bgp_evpn_vrf_rt_cmd); +	install_element(BGP_EVPN_NODE, &bgp_evpn_vrf_rt_auto_cmd); +	install_element(BGP_EVPN_NODE, &no_bgp_evpn_vrf_rt_auto_cmd);  	install_element(BGP_EVPN_NODE, &bgp_evpn_ead_es_rt_cmd);  	install_element(BGP_EVPN_NODE, &no_bgp_evpn_ead_es_rt_cmd);  	install_element(BGP_EVPN_NODE, &bgp_evpn_ead_es_frag_evi_limit_cmd); diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c index 66eef1aa52..f1ffa7b840 100644 --- a/bgpd/bgp_mplsvpn.c +++ b/bgpd/bgp_mplsvpn.c @@ -656,8 +656,10 @@ void ensure_vrf_tovpn_sid(struct bgp *bgp_vpn, struct bgp *bgp_vrf, afi_t afi)  						  tovpn_sid_locator, tovpn_sid);  	if (tovpn_sid_transpose_label == 0) { -		zlog_debug("%s: not allocated new sid for vrf %s: afi %s", -			   __func__, bgp_vrf->name_pretty, afi2str(afi)); +		if (debug) +			zlog_debug( +				"%s: not allocated new sid for vrf %s: afi %s", +				__func__, bgp_vrf->name_pretty, afi2str(afi));  		srv6_locator_chunk_free(tovpn_sid_locator);  		XFREE(MTYPE_BGP_SRV6_SID, tovpn_sid);  		return; diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h index 22d28ecd00..7c6e60bd92 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h @@ -609,19 +609,35 @@ static inline bool bgp_check_advertise(struct bgp *bgp, struct bgp_dest *dest)   */  static inline bool bgp_check_withdrawal(struct bgp *bgp, struct bgp_dest *dest)  { -	struct bgp_path_info *pi; +	struct bgp_path_info *pi, *selected = NULL;  	if (!BGP_SUPPRESS_FIB_ENABLED(bgp))  		return false;  	for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { -		if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) +		if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { +			selected = pi;  			continue; +		}  		if (pi->sub_type != BGP_ROUTE_NORMAL)  			return true;  	} +	/* +	 * pi is selected and bgp is dealing with a static route +	 * ( ie a network statement of some sort ).  FIB installed +	 * is irrelevant +	 * +	 * I am not sure what the above for loop is wanted in this +	 * manner at this point.  But I do know that if I have +	 * a static route that is selected and it's the one +	 * being checked for should I withdrawal we do not +	 * want to withdraw the route on installation :) +	 */ +	if (selected && selected->sub_type == BGP_ROUTE_STATIC) +		return false; +  	if (CHECK_FLAG(dest->flags, BGP_NODE_FIB_INSTALLED))  		return false; diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index ded47028a6..233fd55ef0 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -643,6 +643,20 @@ route_match_prefix_list_flowspec(afi_t afi, struct prefix_list *plist,  }  static enum route_map_cmd_result_t +route_match_prefix_list_evpn(afi_t afi, struct prefix_list *plist, +			     const struct prefix *p) +{ +	/* Convert to match a general plist */ +	struct prefix new; + +	if (evpn_prefix2prefix(p, &new)) +		return RMAP_NOMATCH; + +	return (prefix_list_apply(plist, &new) == PREFIX_DENY ? RMAP_NOMATCH +							      : RMAP_MATCH); +} + +static enum route_map_cmd_result_t  route_match_address_prefix_list(void *rule, afi_t afi,  				const struct prefix *prefix, void *object)  { @@ -655,6 +669,10 @@ route_match_address_prefix_list(void *rule, afi_t afi,  	if (prefix->family == AF_FLOWSPEC)  		return route_match_prefix_list_flowspec(afi, plist,  							prefix); + +	else if (prefix->family == AF_EVPN) +		return route_match_prefix_list_evpn(afi, plist, prefix); +  	return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH  								: RMAP_MATCH);  } diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index b85f3707b6..e574ae7805 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -5816,7 +5816,7 @@ ALIAS_HIDDEN(neighbor_remove_private_as_all,  	     "neighbor <A.B.C.D|X:X::X:X|WORD> remove-private-AS all",  	     NEIGHBOR_STR NEIGHBOR_ADDR_STR2  	     "Remove private ASNs in outbound updates\n" -	     "Apply to all AS numbers") +	     "Apply to all AS numbers\n")  DEFUN (neighbor_remove_private_as_replace_as,         neighbor_remove_private_as_replace_as_cmd, diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 44e225b043..64d87f946c 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -755,8 +755,10 @@ struct bgp {  #define BGP_VRF_AUTO                        (1 << 0)  #define BGP_VRF_IMPORT_RT_CFGD              (1 << 1)  #define BGP_VRF_EXPORT_RT_CFGD              (1 << 2) -#define BGP_VRF_RD_CFGD                     (1 << 3) -#define BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY    (1 << 4) +#define BGP_VRF_IMPORT_AUTO_RT_CFGD         (1 << 3) /* retain auto when cfgd */ +#define BGP_VRF_EXPORT_AUTO_RT_CFGD         (1 << 4) /* retain auto when cfgd */ +#define BGP_VRF_RD_CFGD                     (1 << 5) +#define BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY    (1 << 6)  	/* unique ID for auto derivation of RD for this vrf */  	uint16_t vrf_rd_id; diff --git a/configure.ac b/configure.ac index 4e1080045e..4cbdfe0fcc 100644 --- a/configure.ac +++ b/configure.ac @@ -2631,6 +2631,7 @@ AC_DEFINE_UNQUOTED([ZEBRA_SERV_PATH], ["$frr_statedir%s%s/zserv.api"], [zebra ap  AC_DEFINE_UNQUOTED([BFDD_CONTROL_SOCKET], ["$frr_statedir%s%s/bfdd.sock"], [bfdd control socket])  AC_DEFINE_UNQUOTED([OSPFD_GR_STATE], ["$frr_statedir%s/ospfd-gr.json"], [ospfd GR state information])  AC_DEFINE_UNQUOTED([OSPF6D_GR_STATE], ["$frr_statedir/ospf6d-gr.json"], [ospf6d GR state information]) +AC_DEFINE_UNQUOTED([ISISD_RESTART], ["$frr_statedir%s/isid-restart.json"], [isisd restart information])  AC_DEFINE_UNQUOTED([OSPF6_AUTH_SEQ_NUM_FILE], ["$frr_statedir/ospf6d-at-seq-no.dat"], [ospf6d AT Sequence number information])  AC_DEFINE_UNQUOTED([DAEMON_VTY_DIR], ["$frr_statedir%s%s"], [daemon vty directory])  AC_DEFINE_UNQUOTED([DAEMON_DB_DIR], ["$frr_statedir"], [daemon database directory]) diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index 8a16c57e6c..e32a84f80c 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -2944,6 +2944,20 @@ sysctl configurations:  For more information, see ``man 7 arp``. +.. _bgp-evpn-l3-route-targets: + +EVPN L3 Route-Targets +^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: route-target <import|export|both> <RTLIST|auto> + +Modify the route-target set for EVPN advertised type-2/type-5 routes. +RTLIST is a list of any of matching +``(A.B.C.D:MN|EF:OPQR|GHJK:MN|*:OPQR|*:MN)`` where ``*`` indicates wildcard +matching for the AS number. It will be set to match any AS number. This is +useful in datacenter deployments with Downstream VNI. ``auto`` is used to +retain the autoconfigure that is default behavior for L3 RTs. +  .. _bgp-evpn-advertise-pip:  EVPN advertise-PIP diff --git a/doc/user/isisd.rst b/doc/user/isisd.rst index 9ccb5ba4b5..2b114ad127 100644 --- a/doc/user/isisd.rst +++ b/doc/user/isisd.rst @@ -83,6 +83,10 @@ writing, *isisd* does not support multiple ISIS processes.     Set overload bit to avoid any transit traffic. +.. clicmd:: set-overload-bit on-startup (0-86400) + +   Set overload bit on startup for the specified duration, in seconds. Reference: :rfc:`3277` +  .. clicmd:: purge-originator     Enable or disable :rfc:`6232` purge originator identification. diff --git a/isisd/isis_cli.c b/isisd/isis_cli.c index a673cb8c1e..9db867e2c0 100644 --- a/isisd/isis_cli.c +++ b/isisd/isis_cli.c @@ -404,7 +404,7 @@ DEFPY_YANG(set_overload_bit, set_overload_bit_cmd, "[no] set-overload-bit",        "Reset overload bit to accept transit traffic\n"        "Set overload bit to avoid any transit traffic\n")  { -	nb_cli_enqueue_change(vty, "./overload", NB_OP_MODIFY, +	nb_cli_enqueue_change(vty, "./overload/enabled", NB_OP_MODIFY,  			      no ? "false" : "true");  	return nb_cli_apply_changes(vty, NULL); @@ -419,6 +419,42 @@ void cli_show_isis_overload(struct vty *vty, const struct lyd_node *dnode,  }  /* + * XPath: /frr-isisd:isis/instance/overload/on-startup + */ +DEFPY_YANG(set_overload_bit_on_startup, set_overload_bit_on_startup_cmd, +	   "set-overload-bit on-startup (0-86400)$val", +	   "Set overload bit to avoid any transit traffic\n" +	   "Set overload bit on startup\n" +	   "Set overload time in seconds\n") +{ +	nb_cli_enqueue_change(vty, "./overload/on-startup", NB_OP_MODIFY, +			      val_str); + +	return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_set_overload_bit_on_startup, no_set_overload_bit_on_startup_cmd, +	   "no set-overload-bit on-startup [(0-86400)$val]", +	   NO_STR +	   "Reset overload bit to accept transit traffic\n" +	   "Set overload bit on startup\n" +	   "Set overload time in seconds\n") +{ +	nb_cli_enqueue_change(vty, "./overload/on-startup", NB_OP_DESTROY, +			      NULL); + +	return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_overload_on_startup(struct vty *vty, +				       const struct lyd_node *dnode, +				       bool show_defaults) +{ +	vty_out(vty, " set-overload-bit on-startup %s\n", +		yang_dnode_get_string(dnode, NULL)); +} + +/*   * XPath: /frr-isisd:isis/instance/attach-send   */  DEFPY_YANG(attached_bit_send, attached_bit_send_cmd, "[no] attached-bit send", @@ -3107,6 +3143,9 @@ void isis_cli_init(void)  	install_element(ISIS_NODE, &dynamic_hostname_cmd);  	install_element(ISIS_NODE, &set_overload_bit_cmd); +	install_element(ISIS_NODE, &set_overload_bit_on_startup_cmd); +	install_element(ISIS_NODE, &no_set_overload_bit_on_startup_cmd); +  	install_element(ISIS_NODE, &attached_bit_send_cmd);  	install_element(ISIS_NODE, &attached_bit_receive_ignore_cmd); diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c index 5387f37039..63b4edb1e1 100644 --- a/isisd/isis_lsp.c +++ b/isisd/isis_lsp.c @@ -68,6 +68,8 @@ static void lsp_l2_refresh_pseudo(struct thread *thread);  static void lsp_destroy(struct isis_lsp *lsp); +static bool device_startup; +  int lsp_id_cmp(uint8_t *id1, uint8_t *id2)  {  	return memcmp(id1, id2, ISIS_SYS_ID_LEN + 2); @@ -437,6 +439,21 @@ bool isis_level2_adj_up(struct isis_area *area)  	return false;  } +/* + * Unset the overload bit after the timer expires + */ +void set_overload_on_start_timer(struct thread *thread) +{ +	struct isis_area *area = THREAD_ARG(thread); +	assert(area); + +	area->t_overload_on_startup_timer = NULL; + +	/* Check if set-overload-bit is not currently configured */ +	if (!area->overload_configured) +		isis_area_overload_bit_set(area, false); +} +  static void isis_reset_attach_bit(struct isis_adjacency *adj)  {  	struct isis_area *area = adj->circuit->area; @@ -1355,6 +1372,7 @@ int lsp_generate(struct isis_area *area, int level)  	uint32_t seq_num = 0;  	uint8_t lspid[ISIS_SYS_ID_LEN + 2];  	uint16_t rem_lifetime, refresh_time; +	uint32_t overload_time;  	if ((area == NULL) || (area->is_type & level) != level)  		return ISIS_ERROR; @@ -1363,6 +1381,18 @@ int lsp_generate(struct isis_area *area, int level)  	memcpy(&lspid, area->isis->sysid, ISIS_SYS_ID_LEN); +	/* Check if device should be overloaded on startup */ +	if (device_startup) { +		overload_time = isis_restart_read_overload_time(area); +		if (overload_time > 0) { +			isis_area_overload_bit_set(area, true); +			thread_add_timer(master, set_overload_on_start_timer, +					 area, overload_time, +					 &area->t_overload_on_startup_timer); +		} +		device_startup = false; +	} +  	/* only builds the lsp if the area shares the level */  	oldlsp = lsp_search(&area->lspdb[level - 1], lspid);  	if (oldlsp) { @@ -2373,6 +2403,7 @@ int isis_lsp_iterate_is_reach(struct isis_lsp *lsp, uint16_t mtid,  void lsp_init(void)  { +	device_startup = true;  	hook_register(isis_adj_state_change_hook,  		      lsp_handle_adj_state_change);  } diff --git a/isisd/isis_lsp.h b/isisd/isis_lsp.h index b13b2a35e6..d7762324d9 100644 --- a/isisd/isis_lsp.h +++ b/isisd/isis_lsp.h @@ -66,6 +66,7 @@ DECLARE_RBTREE_UNIQ(lspdb, struct isis_lsp, dbe, lspdb_compare);  void lsp_db_init(struct lspdb_head *head);  void lsp_db_fini(struct lspdb_head *head);  void lsp_tick(struct thread *thread); +void set_overload_on_start_timer(struct thread *thread);  int lsp_generate(struct isis_area *area, int level);  #define lsp_regenerate_schedule(area, level, all_pseudo) \ diff --git a/isisd/isis_nb.c b/isisd/isis_nb.c index a2ba33d078..4f4e6dc730 100644 --- a/isisd/isis_nb.c +++ b/isisd/isis_nb.c @@ -81,11 +81,18 @@ const struct frr_yang_module_info frr_isisd_info = {  			},  		},  		{ -			.xpath = "/frr-isisd:isis/instance/overload", +			.xpath = "/frr-isisd:isis/instance/overload/enabled",  			.cbs = {  				.cli_show = cli_show_isis_overload, -				.modify = isis_instance_overload_modify, -			}, +				.modify = isis_instance_overload_enabled_modify, +			} +		}, +		{ +			.xpath = "/frr-isisd:isis/instance/overload/on-startup", +			.cbs = { +				.cli_show = cli_show_isis_overload_on_startup, +				.modify = isis_instance_overload_on_startup_modify, +			}  		},  		{  			.xpath = "/frr-isisd:isis/instance/metric-style", diff --git a/isisd/isis_nb.h b/isisd/isis_nb.h index 00ca8be3b0..a9f2eaea95 100644 --- a/isisd/isis_nb.h +++ b/isisd/isis_nb.h @@ -37,7 +37,8 @@ int isis_instance_dynamic_hostname_modify(struct nb_cb_modify_args *args);  int isis_instance_attached_send_modify(struct nb_cb_modify_args *args);  int isis_instance_attached_receive_modify(struct nb_cb_modify_args *args);  int isis_instance_attached_modify(struct nb_cb_modify_args *args); -int isis_instance_overload_modify(struct nb_cb_modify_args *args); +int isis_instance_overload_enabled_modify(struct nb_cb_modify_args *args); +int isis_instance_overload_on_startup_modify(struct nb_cb_modify_args *args);  int isis_instance_metric_style_modify(struct nb_cb_modify_args *args);  int isis_instance_purge_originator_modify(struct nb_cb_modify_args *args);  int isis_instance_lsp_mtu_modify(struct nb_cb_modify_args *args); @@ -442,6 +443,9 @@ void cli_show_isis_attached_receive(struct vty *vty,  				    bool show_defaults);  void cli_show_isis_overload(struct vty *vty, const struct lyd_node *dnode,  			    bool show_defaults); +void cli_show_isis_overload_on_startup(struct vty *vty, +				       const struct lyd_node *dnode, +				       bool show_defaults);  void cli_show_isis_metric_style(struct vty *vty, const struct lyd_node *dnode,  				bool show_defaults);  void cli_show_isis_area_pwd(struct vty *vty, const struct lyd_node *dnode, diff --git a/isisd/isis_nb_config.c b/isisd/isis_nb_config.c index e0decf48f2..1b7663fcfd 100644 --- a/isisd/isis_nb_config.c +++ b/isisd/isis_nb_config.c @@ -336,9 +336,9 @@ int isis_instance_attached_modify(struct nb_cb_modify_args *args)  }  /* - * XPath: /frr-isisd:isis/instance/overload + * XPath: /frr-isisd:isis/instance/overload/enabled   */ -int isis_instance_overload_modify(struct nb_cb_modify_args *args) +int isis_instance_overload_enabled_modify(struct nb_cb_modify_args *args)  {  	struct isis_area *area;  	bool overload; @@ -348,12 +348,32 @@ int isis_instance_overload_modify(struct nb_cb_modify_args *args)  	area = nb_running_get_entry(args->dnode, NULL, true);  	overload = yang_dnode_get_bool(args->dnode, NULL); +	area->overload_configured = overload; +  	isis_area_overload_bit_set(area, overload);  	return NB_OK;  }  /* + * XPath: /frr-isisd:isis/instance/overload/on-startup + */ +int isis_instance_overload_on_startup_modify(struct nb_cb_modify_args *args) +{ +	struct isis_area *area; +	uint32_t overload_time; + +	if (args->event != NB_EV_APPLY) +		return NB_OK; + +	overload_time = yang_dnode_get_uint32(args->dnode, NULL); +	area = nb_running_get_entry(args->dnode, NULL, true); +	isis_area_overload_on_startup_set(area, overload_time); + +	return NB_OK; +} + +/*   * XPath: /frr-isisd:isis/instance/metric-style   */  int isis_instance_metric_style_modify(struct nb_cb_modify_args *args) diff --git a/isisd/isisd.c b/isisd/isisd.c index 0ff31df0f8..efea1e5d5e 100644 --- a/isisd/isisd.c +++ b/isisd/isisd.c @@ -3198,9 +3198,15 @@ void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit)  	if (new_overload_bit != area->overload_bit) {  		area->overload_bit = new_overload_bit; - -		if (new_overload_bit) +		if (new_overload_bit) {  			area->overload_counter++; +		} else { +			/* Cancel overload on startup timer if it's running */ +			if (area->t_overload_on_startup_timer) { +				THREAD_OFF(area->t_overload_on_startup_timer); +				area->t_overload_on_startup_timer = NULL; +			} +		}  #ifndef FABRICD  		hook_call(isis_hook_db_overload, area); @@ -3213,6 +3219,109 @@ void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit)  #endif /* ifndef FABRICD */  } +void isis_area_overload_on_startup_set(struct isis_area *area, +				       uint32_t startup_time) +{ +	if (area->overload_on_startup_time != startup_time) { +		area->overload_on_startup_time = startup_time; +		isis_restart_write_overload_time(area, startup_time); +	} +} + +/* + * Returns the path of the file (non-volatile memory) that contains restart + * information. + */ +char *isis_restart_filepath() +{ +	static char filepath[MAXPATHLEN]; +	snprintf(filepath, sizeof(filepath), ISISD_RESTART, ""); +	return filepath; +} + +/* + * Record in non-volatile memory the overload on startup time. + */ +void isis_restart_write_overload_time(struct isis_area *isis_area, +				      uint32_t overload_time) +{ +	char *filepath; +	const char *area_name; +	json_object *json; +	json_object *json_areas; +	json_object *json_area; + +	filepath = isis_restart_filepath(); +	area_name = isis_area->area_tag; + +	json = json_object_from_file(filepath); +	if (json == NULL) +		json = json_object_new_object(); + +	json_object_object_get_ex(json, "areas", &json_areas); +	if (!json_areas) { +		json_areas = json_object_new_object(); +		json_object_object_add(json, "areas", json_areas); +	} + +	json_object_object_get_ex(json_areas, area_name, &json_area); +	if (!json_area) { +		json_area = json_object_new_object(); +		json_object_object_add(json_areas, area_name, json_area); +	} + +	json_object_int_add(json_area, "overload_time", +			    isis_area->overload_on_startup_time); +	json_object_to_file_ext(filepath, json, JSON_C_TO_STRING_PRETTY); +	json_object_free(json); +} + +/* + * Fetch from non-volatile memory the overload on startup time. + */ +uint32_t isis_restart_read_overload_time(struct isis_area *isis_area) +{ +	char *filepath; +	const char *area_name; +	json_object *json; +	json_object *json_areas; +	json_object *json_area; +	json_object *json_overload_time; +	uint32_t overload_time = 0; + +	filepath = isis_restart_filepath(); +	area_name = isis_area->area_tag; + +	json = json_object_from_file(filepath); +	if (json == NULL) +		json = json_object_new_object(); + +	json_object_object_get_ex(json, "areas", &json_areas); +	if (!json_areas) { +		json_areas = json_object_new_object(); +		json_object_object_add(json, "areas", json_areas); +	} + +	json_object_object_get_ex(json_areas, area_name, &json_area); +	if (!json_area) { +		json_area = json_object_new_object(); +		json_object_object_add(json_areas, area_name, json_area); +	} + +	json_object_object_get_ex(json_area, "overload_time", +				  &json_overload_time); +	if (json_overload_time) { +		overload_time = json_object_get_int(json_overload_time); +	} + +	json_object_object_del(json_areas, area_name); + +	json_object_to_file_ext(filepath, json, JSON_C_TO_STRING_PRETTY); +	json_object_free(json); + +	return overload_time; +} +  void isis_area_attached_bit_send_set(struct isis_area *area, bool attached_bit)  { diff --git a/isisd/isisd.h b/isisd/isisd.h index 4951e5809b..a9c1d60439 100644 --- a/isisd/isisd.h +++ b/isisd/isisd.h @@ -142,6 +142,7 @@ struct isis_area {  	struct flags flags;  	struct thread *t_tick; /* LSP walker */  	struct thread *t_lsp_refresh[ISIS_LEVELS]; +	struct thread *t_overload_on_startup_timer;  	struct timeval last_lsp_refresh_event[ISIS_LEVELS];  	struct thread *t_rlfa_rib_update;  	/* t_lsp_refresh is used in two ways: @@ -180,7 +181,9 @@ struct isis_area {  	char is_type; /* level-1 level-1-2 or level-2-only */  	/* are we overloaded? */  	char overload_bit; +	bool overload_configured;  	uint32_t overload_counter; +	uint32_t overload_on_startup_time;  	/* L1/L2 router identifier for inter-area traffic */  	char attached_bit_send;  	char attached_bit_rcv_ignore; @@ -290,6 +293,8 @@ void isis_area_invalidate_routes(struct isis_area *area, int levels);  void isis_area_verify_routes(struct isis_area *area);  void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit); +void isis_area_overload_on_startup_set(struct isis_area *area, +				       uint32_t startup_time);  void isis_area_attached_bit_send_set(struct isis_area *area, bool attached_bit);  void isis_area_attached_bit_receive_set(struct isis_area *area,  					bool attached_bit); @@ -315,7 +320,10 @@ void show_isis_database_lspdb_json(struct json_object *json,  void show_isis_database_lspdb_vty(struct vty *vty, struct isis_area *area,  				  int level, struct lspdb_head *lspdb,  				  const char *argv, int ui_level); - +char *isis_restart_filepath(void); +void isis_restart_write_overload_time(struct isis_area *isis_area, +				      uint32_t overload_time); +uint32_t isis_restart_read_overload_time(struct isis_area *isis_area);  /* YANG paths */  #define ISIS_INSTANCE	"/frr-isisd:isis/instance"  #define ISIS_SR		"/frr-isisd:isis/instance/segment-routing" diff --git a/ldpd/ldp_vty_cmds.c b/ldpd/ldp_vty_cmds.c index 3795cdaf38..3d11d3137a 100644 --- a/ldpd/ldp_vty_cmds.c +++ b/ldpd/ldp_vty_cmds.c @@ -246,7 +246,7 @@ DEFPY  (ldp_allow_broken_lsps,  	"[no] install allow-broken-lsps",  	NO_STR  	"install lsps\n" -	"if no remote-label install with imp-null") +	"if no remote-label install with imp-null\n")  {  	return (ldp_vty_allow_broken_lsp(vty, no));  } diff --git a/lib/command.c b/lib/command.c index 7e171cb309..1fae32a04a 100644 --- a/lib/command.c +++ b/lib/command.c @@ -265,8 +265,7 @@ void install_node(struct cmd_node *node)  	node->cmdgraph = graph_new();  	node->cmd_vector = vector_init(VECTOR_MIN_SIZE);  	// add start node -	struct cmd_token *token = -		cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); +	struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);  	graph_new_node(node->cmdgraph, token,  		       (void (*)(void *)) & cmd_token_del); @@ -326,7 +325,7 @@ void _install_element(enum node_type ntype, const struct cmd_element *cmd)  	if (cnode->graph_built || !defer_cli_tree) {  		struct graph *graph = graph_new();  		struct cmd_token *token = -			cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); +			cmd_token_new(START_TKN, 0, NULL, NULL);  		graph_new_node(graph, token,  			       (void (*)(void *)) & cmd_token_del); @@ -349,8 +348,7 @@ static void cmd_finalize_iter(struct hash_bucket *hb, void *arg)  	struct cmd_node *cnode = arg;  	const struct cmd_element *cmd = hb->data;  	struct graph *graph = graph_new(); -	struct cmd_token *token = -		cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); +	struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);  	graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); @@ -405,7 +403,7 @@ void uninstall_element(enum node_type ntype, const struct cmd_element *cmd)  	if (cnode->graph_built) {  		struct graph *graph = graph_new();  		struct cmd_token *token = -			cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); +			cmd_token_new(START_TKN, 0, NULL, NULL);  		graph_new_node(graph, token,  			       (void (*)(void *)) & cmd_token_del); @@ -991,7 +989,7 @@ static int cmd_execute_command_real(vector vline, enum cmd_filter_type filter,  			 * Perform pending commit (if any) before executing  			 * non-YANG command.  			 */ -			if (matched_element->attr != CMD_ATTR_YANG) +			if (!(matched_element->attr & CMD_ATTR_YANG))  				(void)nb_cli_pending_commit_check(vty);  		} @@ -1472,8 +1470,7 @@ static void permute(struct graph_node *start, struct vty *vty)  	for (unsigned int i = 0; i < vector_active(start->to); i++) {  		struct graph_node *gn = vector_slot(start->to, i);  		struct cmd_token *tok = gn->data; -		if (tok->attr == CMD_ATTR_HIDDEN -		    || tok->attr == CMD_ATTR_DEPRECATED) +		if (tok->attr & CMD_ATTR_HIDDEN)  			continue;  		else if (tok->type == END_TKN || gn == start) {  			vty_out(vty, " "); @@ -1562,9 +1559,8 @@ int cmd_list_cmds(struct vty *vty, int do_permute)  		const struct cmd_element *element = NULL;  		for (unsigned int i = 0; i < vector_active(node->cmd_vector);  		     i++) -			if ((element = vector_slot(node->cmd_vector, i)) -			    && element->attr != CMD_ATTR_DEPRECATED -			    && element->attr != CMD_ATTR_HIDDEN) { +			if ((element = vector_slot(node->cmd_vector, i)) && +			    !(element->attr & CMD_ATTR_HIDDEN)) {  				vty_out(vty, "    ");  				print_cmd(vty, element->string);  			} diff --git a/lib/command.h b/lib/command.h index 42b4406212..f4168dedd7 100644 --- a/lib/command.h +++ b/lib/command.h @@ -280,17 +280,18 @@ struct cmd_node {  			    int argc __attribute__((unused)),                  \  			    struct cmd_token *argv[] __attribute__((unused))) -#define DEFPY(funcname, cmdname, cmdstr, helpstr)                              \ -	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)            \ -	funcdecl_##funcname - -#define DEFPY_NOSH(funcname, cmdname, cmdstr, helpstr)                         \ -	DEFPY(funcname, cmdname, cmdstr, helpstr) +/* DEFPY variants */  #define DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, attr)                   \  	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0)         \  	funcdecl_##funcname +#define DEFPY(funcname, cmdname, cmdstr, helpstr)                              \ +	DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, 0) + +#define DEFPY_NOSH(funcname, cmdname, cmdstr, helpstr)                         \ +	DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_NOSH) +  #define DEFPY_HIDDEN(funcname, cmdname, cmdstr, helpstr)                       \  	DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) @@ -298,18 +299,19 @@ struct cmd_node {  	DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG)  #define DEFPY_YANG_NOSH(funcname, cmdname, cmdstr, helpstr)                    \ -	DEFPY_YANG(funcname, cmdname, cmdstr, helpstr) +	DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr,                         \ +		   CMD_ATTR_YANG | CMD_ATTR_NOSH) -#define DEFUN(funcname, cmdname, cmdstr, helpstr)                              \ -	DEFUN_CMD_FUNC_DECL(funcname)                                          \ -	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0)            \ -	DEFUN_CMD_FUNC_TEXT(funcname) +/* DEFUN variants */  #define DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr)                   \  	DEFUN_CMD_FUNC_DECL(funcname)                                          \  	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0)         \  	DEFUN_CMD_FUNC_TEXT(funcname) +#define DEFUN(funcname, cmdname, cmdstr, helpstr)                              \ +	DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, 0) +  #define DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr)                       \  	DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) @@ -318,73 +320,55 @@ struct cmd_node {  /* DEFUN_NOSH for commands that vtysh should ignore */  #define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr)                         \ -	DEFUN(funcname, cmdname, cmdstr, helpstr) +	DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_NOSH)  #define DEFUN_YANG_NOSH(funcname, cmdname, cmdstr, helpstr)                    \ -	DEFUN_YANG(funcname, cmdname, cmdstr, helpstr) +	DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr,                         \ +		   CMD_ATTR_YANG | CMD_ATTR_NOSH)  /* DEFSH for vtysh. */ +#define DEFSH_ATTR(daemon, cmdname, cmdstr, helpstr, attr)                     \ +	DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, attr, daemon) +  #define DEFSH(daemon, cmdname, cmdstr, helpstr)                                \ -	DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, 0, daemon) +	DEFSH_ATTR(daemon, cmdname, cmdstr, helpstr, 0)  #define DEFSH_HIDDEN(daemon, cmdname, cmdstr, helpstr)                         \ -	DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN,     \ -			  daemon) - -#define DEFSH_YANG(daemon, cmdname, cmdstr, helpstr)                           \ -	DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, CMD_ATTR_YANG, daemon) +	DEFSH_ATTR(daemon, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN)  /* DEFUN + DEFSH */ -#define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr)                    \ -	DEFUN_CMD_FUNC_DECL(funcname)                                          \ -	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon)       \ -	DEFUN_CMD_FUNC_TEXT(funcname) - -/* DEFUN + DEFSH with attributes */  #define DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, attr)         \  	DEFUN_CMD_FUNC_DECL(funcname)                                          \  	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, daemon)    \  	DEFUN_CMD_FUNC_TEXT(funcname) +#define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr)                    \ +	DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, 0) +  #define DEFUNSH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr)             \  	DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr,               \  		     CMD_ATTR_HIDDEN) -#define DEFUNSH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr)         \ -	DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr,               \ -		     CMD_ATTR_DEPRECATED) - -#define DEFUNSH_YANG(daemon, funcname, cmdname, cmdstr, helpstr)               \ -	DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG) -  /* ALIAS macro which define existing command's alias. */ -#define ALIAS(funcname, cmdname, cmdstr, helpstr)                              \ -	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) -  #define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr)                   \  	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) +#define ALIAS(funcname, cmdname, cmdstr, helpstr)                              \ +	ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, 0) +  #define ALIAS_HIDDEN(funcname, cmdname, cmdstr, helpstr)                       \ -	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, \ -			  0) +	ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) +/* note: DEPRECATED implies HIDDEN, and other than that there is currently no + * difference.  It's purely for expressing intent in the source code - a + * DEPRECATED command is supposed to go away, a HIDDEN one is likely to stay. + */  #define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr)                   \ -	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr,                  \ -			  CMD_ATTR_DEPRECATED, 0) +	ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr,                         \ +		   CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN)  #define ALIAS_YANG(funcname, cmdname, cmdstr, helpstr)                         \ -	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG, 0) - -#define ALIAS_SH(daemon, funcname, cmdname, cmdstr, helpstr)                   \ -	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) - -#define ALIAS_SH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr)            \ -	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, \ -			  daemon) - -#define ALIAS_SH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr)        \ -	DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr,                  \ -			  CMD_ATTR_DEPRECATED, daemon) +	ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG)  #endif /* VTYSH_EXTRACT_PL */ diff --git a/lib/command_graph.c b/lib/command_graph.c index 09d802e796..e940685250 100644 --- a/lib/command_graph.c +++ b/lib/command_graph.c @@ -494,9 +494,10 @@ void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf)  	snprintf(nbuf, sizeof(nbuf), "<b>%s</b>",  		 lookup_msg(tokennames, tok->type, NULL));  	buffer_putstr(buf, nbuf); -	if (tok->attr == CMD_ATTR_DEPRECATED) +	if (tok->attr & CMD_ATTR_DEPRECATED)  		buffer_putstr(buf, " (d)"); -	else if (tok->attr == CMD_ATTR_HIDDEN) +	/* DEPRECATED implies HIDDEN, don't print both */ +	else if (tok->attr & CMD_ATTR_HIDDEN)  		buffer_putstr(buf, " (h)");  	if (tok->text) {  		if (tok->type == WORD_TKN) diff --git a/lib/command_graph.h b/lib/command_graph.h index ed4da6aa4c..b8c7a9c72c 100644 --- a/lib/command_graph.h +++ b/lib/command_graph.h @@ -73,10 +73,11 @@ enum cmd_token_type {  #define IS_VARYING_TOKEN(x) ((x) >= VARIABLE_TKN && (x) < FORK_TKN)  /* Command attributes */ -enum { CMD_ATTR_NORMAL, -       CMD_ATTR_DEPRECATED, -       CMD_ATTR_HIDDEN, -       CMD_ATTR_YANG, +enum { +	CMD_ATTR_YANG = (1 << 0), +	CMD_ATTR_HIDDEN = (1 << 1), +	CMD_ATTR_DEPRECATED = (1 << 2), +	CMD_ATTR_NOSH = (1 << 3),  };  enum varname_src { diff --git a/lib/command_match.c b/lib/command_match.c index f221e0a02c..ce2dbc9528 100644 --- a/lib/command_match.c +++ b/lib/command_match.c @@ -395,8 +395,7 @@ enum matcher_rv command_complete(struct graph *graph, vector vline,  		for (ALL_LIST_ELEMENTS_RO(current, node, gstack)) {  			struct cmd_token *token = gstack[0]->data; -			if (token->attr == CMD_ATTR_HIDDEN -			    || token->attr == CMD_ATTR_DEPRECATED) +			if (token->attr & CMD_ATTR_HIDDEN)  				continue;  			enum match_type minmatch = min_match_level(token->type); diff --git a/lib/command_py.c b/lib/command_py.c index 6301eec5e8..cce9542e30 100644 --- a/lib/command_py.c +++ b/lib/command_py.c @@ -226,8 +226,8 @@ static PyObject *graph_to_pyobj(struct wrap_graph *wgraph,  			wrap->type = "???";  		} -		wrap->deprecated = (tok->attr == CMD_ATTR_DEPRECATED); -		wrap->hidden = (tok->attr == CMD_ATTR_HIDDEN); +		wrap->deprecated = !!(tok->attr & CMD_ATTR_DEPRECATED); +		wrap->hidden = !!(tok->attr & CMD_ATTR_HIDDEN);  		wrap->text = tok->text;  		wrap->desc = tok->desc;  		wrap->varname = tok->varname; @@ -353,6 +353,12 @@ PyMODINIT_FUNC command_py_init(void)  	if (!pymod)  		initret(NULL); +	if (PyModule_AddIntMacro(pymod, CMD_ATTR_YANG) +	    || PyModule_AddIntMacro(pymod, CMD_ATTR_HIDDEN) +	    || PyModule_AddIntMacro(pymod, CMD_ATTR_DEPRECATED) +	    || PyModule_AddIntMacro(pymod, CMD_ATTR_NOSH)) +		initret(NULL); +  	Py_INCREF(&typeobj_graph_node);  	PyModule_AddObject(pymod, "GraphNode", (PyObject *)&typeobj_graph_node);  	Py_INCREF(&typeobj_graph); diff --git a/lib/grammar_sandbox.c b/lib/grammar_sandbox.c index f9778c5d4c..8fa47c053b 100644 --- a/lib/grammar_sandbox.c +++ b/lib/grammar_sandbox.c @@ -76,8 +76,7 @@ DEFUN (grammar_test,  	// parse the command and install it into the command graph  	struct graph *graph = graph_new(); -	struct cmd_token *token = -		cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); +	struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);  	graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);  	cmd_graph_parse(graph, cmd); diff --git a/lib/log_vty.c b/lib/log_vty.c index 81280f302f..c9268734c4 100644 --- a/lib/log_vty.c +++ b/lib/log_vty.c @@ -761,8 +761,8 @@ DEFPY (log_immediate_mode,         log_immediate_mode_cmd,         "[no] log immediate-mode",         NO_STR -       "Logging control" -       "Output immediately, without buffering") +       "Logging control\n" +       "Output immediately, without buffering\n")  {  	zlog_set_immediate(!no);  	return CMD_SUCCESS; diff --git a/lib/prefix.c b/lib/prefix.c index e64b10bf24..4642f14d35 100644 --- a/lib/prefix.c +++ b/lib/prefix.c @@ -1404,6 +1404,63 @@ bool ipv4_unicast_valid(const struct in_addr *addr)  	return true;  } +static int ipaddr2prefix(const struct ipaddr *ip, uint16_t prefixlen, +			 struct prefix *p) +{ +	switch (ip->ipa_type) { +	case (IPADDR_V4): +		p->family = AF_INET; +		p->u.prefix4 = ip->ipaddr_v4; +		p->prefixlen = prefixlen; +		break; +	case (IPADDR_V6): +		p->family = AF_INET6; +		p->u.prefix6 = ip->ipaddr_v6; +		p->prefixlen = prefixlen; +		break; +	case (IPADDR_NONE): +		p->family = AF_UNSPEC; +		break; +	} + +	return 0; +} + +/* + * Convert type-2 and type-5 evpn route prefixes into the more + * general ipv4/ipv6 prefix types so we can match prefix lists + * and such. + */ +int evpn_prefix2prefix(const struct prefix *evpn, struct prefix *to) +{ +	const struct evpn_addr *addr; + +	if (evpn->family != AF_EVPN) +		return -1; + +	addr = &evpn->u.prefix_evpn; + +	switch (addr->route_type) { +	case BGP_EVPN_MAC_IP_ROUTE: +		if (IS_IPADDR_V4(&addr->macip_addr.ip)) +			ipaddr2prefix(&addr->macip_addr.ip, 32, to); +		else if (IS_IPADDR_V6(&addr->macip_addr.ip)) +			ipaddr2prefix(&addr->macip_addr.ip, 128, to); +		else +			return -1; /* mac only? */ + +		break; +	case BGP_EVPN_IP_PREFIX_ROUTE: +		ipaddr2prefix(&addr->prefix_addr.ip, +			      addr->prefix_addr.ip_prefix_length, to); +		break; +	default: +		return -1; +	} + +	return 0; +} +  printfrr_ext_autoreg_p("EA", printfrr_ea);  static ssize_t printfrr_ea(struct fbuf *buf, struct printfrr_eargs *ea,  			   const void *ptr) diff --git a/lib/prefix.h b/lib/prefix.h index b904311539..c67656cfd1 100644 --- a/lib/prefix.h +++ b/lib/prefix.h @@ -510,6 +510,7 @@ extern char *esi_to_str(const esi_t *esi, char *buf, int size);  extern char *evpn_es_df_alg2str(uint8_t df_alg, char *buf, int buf_len);  extern void prefix_evpn_hexdump(const struct prefix_evpn *p);  extern bool ipv4_unicast_valid(const struct in_addr *addr); +extern int evpn_prefix2prefix(const struct prefix *evpn, struct prefix *to);  static inline int ipv6_martian(const struct in6_addr *addr)  { diff --git a/lib/routemap.c b/lib/routemap.c index 3a92799991..dcaecd8085 100644 --- a/lib/routemap.c +++ b/lib/routemap.c @@ -1815,7 +1815,24 @@ route_map_get_index(struct route_map *map, const struct prefix *prefix,  	struct route_map_index *index = NULL, *best_index = NULL;  	struct route_map_index *head_index = NULL;  	struct route_table *table = NULL; -	unsigned char family = prefix->family; +	struct prefix conv; +	unsigned char family; + +	/* +	 * Handling for matching evpn_routes in the prefix table. +	 * +	 * We convert type2/5 prefix to ipv4/6 prefix to do longest +	 * prefix matching on. +	 */ +	if (prefix->family == AF_EVPN) { +		if (evpn_prefix2prefix(prefix, &conv) != 0) +			return NULL; + +		prefix = &conv; +	} + + +	family = prefix->family;  	if (family == AF_INET)  		table = map->ipv4_prefix_table; diff --git a/ospfd/ospf_interface.c b/ospfd/ospf_interface.c index 646d318362..a0b14e73ee 100644 --- a/ospfd/ospf_interface.c +++ b/ospfd/ospf_interface.c @@ -461,13 +461,13 @@ struct ospf_interface *ospf_if_lookup_recv_if(struct ospf *ospf,  {  	struct route_node *rn;  	struct prefix_ipv4 addr; -	struct ospf_interface *oi, *match; +	struct ospf_interface *oi, *match, *unnumbered_match;  	addr.family = AF_INET;  	addr.prefix = src;  	addr.prefixlen = IPV4_MAX_BITLEN; -	match = NULL; +	match = unnumbered_match = NULL;  	for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) {  		oi = rn->info; @@ -482,7 +482,7 @@ struct ospf_interface *ospf_if_lookup_recv_if(struct ospf *ospf,  			continue;  		if (CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED)) -			match = oi; +			unnumbered_match = oi;  		else if (prefix_match(CONNECTED_PREFIX(oi->connected),  				      (struct prefix *)&addr)) {  			if ((match == NULL) || (match->address->prefixlen @@ -491,7 +491,10 @@ struct ospf_interface *ospf_if_lookup_recv_if(struct ospf *ospf,  		}  	} -	return match; +	if (match) +		return match; + +	return unnumbered_match;  }  void ospf_interface_fifo_flush(struct ospf_interface *oi) diff --git a/pimd/pim6_mld.c b/pimd/pim6_mld.c index 23042ef14e..c34c782969 100644 --- a/pimd/pim6_mld.c +++ b/pimd/pim6_mld.c @@ -2997,9 +2997,9 @@ DEFPY(gm_debug_show,        "debug show mld interface IFNAME",        DEBUG_STR        SHOW_STR -      "MLD" +      MLD_STR        INTERFACE_STR -      "interface name") +      "interface name\n")  {  	struct interface *ifp;  	struct pim_interface *pim_ifp; diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index 1ac22f38a3..ab0689a156 100644 --- a/pimd/pim_cmd.c +++ b/pimd/pim_cmd.c @@ -5438,7 +5438,7 @@ DEFPY(no_ip_msdp_mesh_group,        IP_STR        CFG_MSDP_STR        "Delete MSDP mesh-group\n" -      "Mesh group name") +      "Mesh group name\n")  {  	const char *vrfname;  	char xpath_value[XPATH_MAXLEN]; diff --git a/python/clippy/__init__.py b/python/clippy/__init__.py index 344a1c91ee..281e2bb3c6 100644 --- a/python/clippy/__init__.py +++ b/python/clippy/__init__.py @@ -17,8 +17,23 @@  # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA  import os, stat + +try: +    from enum import IntFlag as _IntFlag +except ImportError: +    # python <3.6 +    from enum import IntEnum as _IntFlag  # type: ignore +  import _clippy -from _clippy import parse, Graph, GraphNode +from _clippy import ( +    parse, +    Graph, +    GraphNode, +    CMD_ATTR_YANG, +    CMD_ATTR_HIDDEN, +    CMD_ATTR_DEPRECATED, +    CMD_ATTR_NOSH, +)  frr_top_src = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -78,3 +93,10 @@ def wrdiff(filename, buf, reffiles=[]):      with open(newname, "w") as out:          out.write(buf)      os.rename(newname, filename) + + +class CmdAttr(_IntFlag): +    YANG = CMD_ATTR_YANG +    HIDDEN = CMD_ATTR_HIDDEN +    DEPRECATED = CMD_ATTR_DEPRECATED +    NOSH = CMD_ATTR_NOSH diff --git a/python/makefile.py b/python/makefile.py index afc993b5b9..bd897b7508 100644 --- a/python/makefile.py +++ b/python/makefile.py @@ -160,6 +160,9 @@ for clippy_file in clippy_scan:  # combine daemon .xref files into frr.xref  out_lines.append("") +xref_targets = [ +    target for target in xref_targets if target not in ["tools/ssd", "vtysh/vtysh"] +]  out_lines.append(      "xrefs = %s" % (" ".join(["%s.xref" % target for target in xref_targets]))  ) diff --git a/python/xrelfo.py b/python/xrelfo.py index 17262da8d9..09455ea9b4 100644 --- a/python/xrelfo.py +++ b/python/xrelfo.py @@ -21,12 +21,21 @@ import os  import struct  import re  import traceback -import json + +json_dump_args = {} + +try: +    import ujson as json + +    json_dump_args["escape_forward_slashes"] = False +except ImportError: +    import json +  import argparse  from clippy.uidhash import uidhash  from clippy.elf import * -from clippy import frr_top_src +from clippy import frr_top_src, CmdAttr  from tiabwarfo import FieldApplicator  try: @@ -196,8 +205,6 @@ Xref.containers[XREFT_LOGMSG] = XrefLogmsg  class CmdElement(ELFDissectStruct, XrelfoJson):      struct = 'cmd_element' -    cmd_attrs = { 0: None, 1: 'deprecated', 2: 'hidden'} -      def __init__(self, *args, **kwargs):          super().__init__(*args, **kwargs) @@ -207,10 +214,14 @@ class CmdElement(ELFDissectStruct, XrelfoJson):          jsobj.update({              'string': self.string,              'doc': self.doc, -            'attr': self.cmd_attrs.get(self.attr, self.attr),          }) -        if jsobj['attr'] is None: -            del jsobj['attr'] +        if self.attr: +            jsobj['attr'] = attr = self.attr +            for attrname in CmdAttr.__members__: +                val = CmdAttr[attrname] +                if attr & val: +                    jsobj.setdefault('attrs', []).append(attrname.lower()) +                    attr &= ~val          jsobj['defun'] = dict([(i, getattr(self.xref, i)) for i in ['file', 'line', 'func']]) @@ -416,12 +427,12 @@ def _main(args):      if args.output:          with open(args.output + '.tmp', 'w') as fd: -            json.dump(out, fd, indent=2, sort_keys=True) +            json.dump(out, fd, indent=2, sort_keys=True, **json_dump_args)          os.rename(args.output + '.tmp', args.output)      if args.out_by_file:          with open(args.out_by_file + '.tmp', 'w') as fd: -            json.dump(outbyfile, fd, indent=2, sort_keys=True) +            json.dump(outbyfile, fd, indent=2, sort_keys=True, **json_dump_args)          os.rename(args.out_by_file + '.tmp', args.out_by_file)  if __name__ == '__main__': diff --git a/snapcraft/snapcraft.yaml.in b/snapcraft/snapcraft.yaml.in index 9729be7b92..f634b59c75 100644 --- a/snapcraft/snapcraft.yaml.in +++ b/snapcraft/snapcraft.yaml.in @@ -272,7 +272,7 @@ parts:             - zlib1g          prime:             - lib/librtr.so* -           - usr/lib/x86_64-linux-gnu/libssh.so* +           - usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libssh.so*          source: https://github.com/rtrlib/rtrlib.git          source-type: git          source-tag: v0.8.0 diff --git a/tests/topotests/bgp_suppress_fib/r1/bgp_ipv4_allowas.json b/tests/topotests/bgp_suppress_fib/r1/bgp_ipv4_allowas.json index bc4d0f4796..1a5ede276d 100644 --- a/tests/topotests/bgp_suppress_fib/r1/bgp_ipv4_allowas.json +++ b/tests/topotests/bgp_suppress_fib/r1/bgp_ipv4_allowas.json @@ -32,7 +32,7 @@        ],        "peer":{          "peerId":"10.0.0.2", -        "routerId":"10.0.0.9", +        "routerId":"60.0.0.1",          "type":"external"        }      } diff --git a/tests/topotests/bgp_suppress_fib/r2/bgp_ipv4_allowas.json b/tests/topotests/bgp_suppress_fib/r2/bgp_ipv4_allowas.json index 16561ce837..4a35abfd6f 100644 --- a/tests/topotests/bgp_suppress_fib/r2/bgp_ipv4_allowas.json +++ b/tests/topotests/bgp_suppress_fib/r2/bgp_ipv4_allowas.json @@ -61,7 +61,7 @@        ],        "peer":{          "peerId":"0.0.0.0", -        "routerId":"10.0.0.9" +        "routerId":"60.0.0.1"        }      }    ] diff --git a/tests/topotests/bgp_suppress_fib/r2/bgpd.conf b/tests/topotests/bgp_suppress_fib/r2/bgpd.conf index ebef2012a8..010e86aad7 100644 --- a/tests/topotests/bgp_suppress_fib/r2/bgpd.conf +++ b/tests/topotests/bgp_suppress_fib/r2/bgpd.conf @@ -7,3 +7,5 @@ router bgp 2    bgp suppress-fib-pending    neighbor 10.0.0.1 remote-as 1    neighbor 10.0.0.10 remote-as 3 +  address-family ipv4 uni +     network 60.0.0.0/24
\ No newline at end of file diff --git a/tests/topotests/bgp_suppress_fib/r2/zebra.conf b/tests/topotests/bgp_suppress_fib/r2/zebra.conf index 443fffc703..6e8bce0450 100644 --- a/tests/topotests/bgp_suppress_fib/r2/zebra.conf +++ b/tests/topotests/bgp_suppress_fib/r2/zebra.conf @@ -1,4 +1,7 @@  ! +interface lo + ip address 60.0.0.1/24 +!  interface r2-eth0   ip address 10.0.0.2/30  ! diff --git a/tests/topotests/bgp_suppress_fib/r3/v4_route3.json b/tests/topotests/bgp_suppress_fib/r3/v4_route3.json new file mode 100644 index 0000000000..ab8c3aa5e5 --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r3/v4_route3.json @@ -0,0 +1,23 @@ +{ +  "60.0.0.0/24":[ +    { +      "prefix":"60.0.0.0/24", +      "protocol":"bgp", +      "selected":true, +      "destSelected":true, +      "distance":20, +      "metric":0, +      "installed":true, +      "table":254, +      "nexthops":[ +        { +          "fib":true, +          "ip":"10.0.0.9", +          "afi":"ipv4", +          "interfaceName":"r3-eth0", +          "active":true +        } +      ] +    } +  ] +} diff --git a/tests/topotests/bgp_suppress_fib/test_bgp_suppress_fib.py b/tests/topotests/bgp_suppress_fib/test_bgp_suppress_fib.py index 2c87d9d7b0..96a294cae3 100644 --- a/tests/topotests/bgp_suppress_fib/test_bgp_suppress_fib.py +++ b/tests/topotests/bgp_suppress_fib/test_bgp_suppress_fib.py @@ -84,8 +84,6 @@ def test_bgp_route():      r3 = tgen.gears["r3"] -    sleep(5) -      json_file = "{}/r3/v4_route.json".format(CWD)      expected = json.loads(open(json_file).read()) @@ -95,7 +93,7 @@ def test_bgp_route():          "show ip route 40.0.0.0 json",          expected,      ) -    _, result = topotest.run_and_expect(test_func, None, count=2, wait=0.5) +    _, result = topotest.run_and_expect(test_func, None, count=20, wait=0.5)      assertmsg = '"r3" JSON output mismatches'      assert result is None, assertmsg @@ -112,6 +110,16 @@ def test_bgp_route():      assertmsg = '"r3" JSON output mismatches'      assert result is None, assertmsg +    json_file = "{}/r3/v4_route3.json".format(CWD) +    expected = json.loads(open(json_file).read()) + +    test_func = partial( +        topotest.router_json_cmp, +        r3, +        "show ip route 10.0.0.3 json", +        expected, +        ) +    _, result = topotest.run_and_expect(test_func, None, count=3, wait=0.5)  def test_bgp_better_admin_won():      "A better Admin distance protocol may come along and knock us out" diff --git a/tests/topotests/isis_topo1/test_isis_topo1.py b/tests/topotests/isis_topo1/test_isis_topo1.py index 014722387f..8b6c3772b8 100644 --- a/tests/topotests/isis_topo1/test_isis_topo1.py +++ b/tests/topotests/isis_topo1/test_isis_topo1.py @@ -25,7 +25,7 @@  """  test_isis_topo1.py: Test ISIS topology.  """ - +import datetime  import functools  import json  import os @@ -38,6 +38,11 @@ sys.path.append(os.path.join(CWD, "../"))  # pylint: disable=C0413  from lib import topotest +from lib.common_config import ( +    retry, +    stop_router, +    start_router, +)  from lib.topogen import Topogen, TopoRouter, get_topogen  from lib.topolog import logger @@ -248,10 +253,12 @@ def test_isis_summary_json():      for rname, router in tgen.routers().items():          logger.info("Checking router %s", rname)          json_output = tgen.gears[rname].vtysh_cmd("show isis summary json", isjson=True) -        assertmsg = "Test isis summary json failed in '{}' data '{}'".format(rname, json_output) -        assert json_output['vrf'] == "default", assertmsg -        assert json_output['areas'][0]['area'] == "1", assertmsg -        assert json_output['areas'][0]['levels'][0]['id'] != '3', assertmsg +        assertmsg = "Test isis summary json failed in '{}' data '{}'".format( +            rname, json_output +        ) +        assert json_output["vrf"] == "default", assertmsg +        assert json_output["areas"][0]["area"] == "1", assertmsg +        assert json_output["areas"][0]["levels"][0]["id"] != "3", assertmsg  def test_isis_interface_json(): @@ -265,15 +272,29 @@ def test_isis_interface_json():      logger.info("Checking 'show isis interface json'")      for rname, router in tgen.routers().items():          logger.info("Checking router %s", rname) -        json_output = tgen.gears[rname].vtysh_cmd("show isis interface json", isjson=True) -        assertmsg = "Test isis interface json failed in '{}' data '{}'".format(rname, json_output) -        assert json_output['areas'][0]['circuits'][0]['interface']['name'] == rname+"-eth0", assertmsg +        json_output = tgen.gears[rname].vtysh_cmd( +            "show isis interface json", isjson=True +        ) +        assertmsg = "Test isis interface json failed in '{}' data '{}'".format( +            rname, json_output +        ) +        assert ( +            json_output["areas"][0]["circuits"][0]["interface"]["name"] +            == rname + "-eth0" +        ), assertmsg      for rname, router in tgen.routers().items():          logger.info("Checking router %s", rname) -        json_output = tgen.gears[rname].vtysh_cmd("show isis interface detail json", isjson=True) -        assertmsg = "Test isis interface json failed in '{}' data '{}'".format(rname, json_output) -        assert json_output['areas'][0]['circuits'][0]['interface']['name'] == rname+"-eth0", assertmsg +        json_output = tgen.gears[rname].vtysh_cmd( +            "show isis interface detail json", isjson=True +        ) +        assertmsg = "Test isis interface json failed in '{}' data '{}'".format( +            rname, json_output +        ) +        assert ( +            json_output["areas"][0]["circuits"][0]["interface"]["name"] +            == rname + "-eth0" +        ), assertmsg  def test_isis_neighbor_json(): @@ -284,19 +305,32 @@ def test_isis_neighbor_json():      if tgen.routers_have_failure():          pytest.skip(tgen.errors) -    #tgen.mininet_cli() +    # tgen.mininet_cli()      logger.info("Checking 'show isis neighbor json'")      for rname, router in tgen.routers().items():          logger.info("Checking router %s", rname) -        json_output = tgen.gears[rname].vtysh_cmd("show isis neighbor json", isjson=True) -        assertmsg = "Test isis neighbor json failed in '{}' data '{}'".format(rname, json_output) -        assert json_output['areas'][0]['circuits'][0]['interface'] == rname+"-eth0", assertmsg +        json_output = tgen.gears[rname].vtysh_cmd( +            "show isis neighbor json", isjson=True +        ) +        assertmsg = "Test isis neighbor json failed in '{}' data '{}'".format( +            rname, json_output +        ) +        assert ( +            json_output["areas"][0]["circuits"][0]["interface"] == rname + "-eth0" +        ), assertmsg      for rname, router in tgen.routers().items():          logger.info("Checking router %s", rname) -        json_output = tgen.gears[rname].vtysh_cmd("show isis neighbor detail json", isjson=True) -        assertmsg = "Test isis neighbor json failed in '{}' data '{}'".format(rname, json_output) -        assert json_output['areas'][0]['circuits'][0]['interface']['name'] == rname+"-eth0", assertmsg +        json_output = tgen.gears[rname].vtysh_cmd( +            "show isis neighbor detail json", isjson=True +        ) +        assertmsg = "Test isis neighbor json failed in '{}' data '{}'".format( +            rname, json_output +        ) +        assert ( +            json_output["areas"][0]["circuits"][0]["interface"]["name"] +            == rname + "-eth0" +        ), assertmsg  def test_isis_database_json(): @@ -307,21 +341,246 @@ def test_isis_database_json():      if tgen.routers_have_failure():          pytest.skip(tgen.errors) -    #tgen.mininet_cli() +    # tgen.mininet_cli()      logger.info("Checking 'show isis database json'")      for rname, router in tgen.routers().items():          logger.info("Checking router %s", rname) -        json_output = tgen.gears[rname].vtysh_cmd("show isis database json", isjson=True) -        assertmsg = "Test isis database json failed in '{}' data '{}'".format(rname, json_output) -        assert json_output['areas'][0]['area']['name'] == "1", assertmsg -        assert json_output['areas'][0]['levels'][0]['id'] != '3', assertmsg +        json_output = tgen.gears[rname].vtysh_cmd( +            "show isis database json", isjson=True +        ) +        assertmsg = "Test isis database json failed in '{}' data '{}'".format( +            rname, json_output +        ) +        assert json_output["areas"][0]["area"]["name"] == "1", assertmsg +        assert json_output["areas"][0]["levels"][0]["id"] != "3", assertmsg      for rname, router in tgen.routers().items():          logger.info("Checking router %s", rname) -        json_output = tgen.gears[rname].vtysh_cmd("show isis database detail json", isjson=True) -        assertmsg = "Test isis database json failed in '{}' data '{}'".format(rname, json_output) -        assert json_output['areas'][0]['area']['name'] == "1", assertmsg -        assert json_output['areas'][0]['levels'][0]['id'] != '3', assertmsg +        json_output = tgen.gears[rname].vtysh_cmd( +            "show isis database detail json", isjson=True +        ) +        assertmsg = "Test isis database json failed in '{}' data '{}'".format( +            rname, json_output +        ) +        assert json_output["areas"][0]["area"]["name"] == "1", assertmsg +        assert json_output["areas"][0]["levels"][0]["id"] != "3", assertmsg + + +def test_isis_overload_on_startup(): +    "Check that overload on startup behaves as expected" + +    tgen = get_topogen() +    net = get_topogen().net +    overload_time = 120 + +    # Don't run this test if we have any failure. +    if tgen.routers_have_failure(): +        pytest.skip(tgen.errors) + +    logger.info("Testing overload on startup behavior") + +    # Configure set-overload-bit on-startup on r3 +    r3 = tgen.gears["r3"] +    r3.vtysh_cmd( +        f""" +          configure +            router isis 1 +              set-overload-bit on-startup {overload_time} +        """ +    ) +    # Restart r3 +    logger.info("Stop router") +    stop_router(tgen, "r3") +    logger.info("Start router") + +    tstamp_before_start_router = datetime.datetime.now() +    start_router(tgen, "r3") +    tstamp_after_start_router = datetime.datetime.now() +    startup_router_time = ( +        tstamp_after_start_router - tstamp_before_start_router +    ).total_seconds() + +    # Check that the overload bit is set in r3's LSP +    check_lsp_overload_bit("r3", "r3.00-00", "0/0/1") +    check_lsp_overload_bit("r1", "r3.00-00", "0/0/1") + +    # Attempt to unset overload bit while timer is still running +    r3.vtysh_cmd( +        """ +          configure +            router isis 1 +              no set-overload-bit on-startup +              no set-overload-bit +        """ +    ) + +    # Check overload bit is still set +    check_lsp_overload_bit("r1", "r3.00-00", "0/0/1") + +    # Check that overload bit is unset after timer completes +    check_lsp_overload_bit("r3", "r3.00-00", "0/0/0") +    tstamp_after_bit_unset = datetime.datetime.now() +    check_lsp_overload_bit("r1", "r3.00-00", "0/0/0") + +    # Collect time overloaded +    time_overloaded = ( +        tstamp_after_bit_unset - tstamp_after_start_router +    ).total_seconds() +    logger.info(f"Time Overloaded: {time_overloaded}") + +    # Use time it took to startup router as lower bound +    logger.info( +        f"Assert that overload time falls in range: {overload_time - startup_router_time} < {time_overloaded} <= {overload_time}" +    ) +    result = overload_time - startup_router_time < time_overloaded <= overload_time +    assert result + + +def test_isis_overload_on_startup_cancel_timer(): +    "Check that overload on startup timer is cancelled when overload bit is set/unset" + +    tgen = get_topogen() +    net = get_topogen().net +    overload_time = 90 + +    # Don't run this test if we have any failure. +    if tgen.routers_have_failure(): +        pytest.skip(tgen.errors) + +    logger.info( +        "Testing overload on startup behavior with set overload bit: cancel timer" +    ) + +    # Configure set-overload-bit on-startup on r3 +    r3 = tgen.gears["r3"] +    r3.vtysh_cmd( +        f""" +          configure +            router isis 1 +              set-overload-bit on-startup {overload_time} +              set-overload-bit +        """ +    ) +    # Restart r3 +    logger.info("Stop router") +    stop_router(tgen, "r3") +    logger.info("Start router") +    start_router(tgen, "r3") + +    # Check that the overload bit is set in r3's LSP +    check_lsp_overload_bit("r3", "r3.00-00", "0/0/1") + +    # Check that overload timer is running +    check_overload_timer("r3", True) + +    # Unset overload bit while timer is running +    r3.vtysh_cmd( +        """ +          configure +            router isis 1 +              no set-overload-bit +        """ +    ) + +    # Check that overload timer is cancelled +    check_overload_timer("r3", False) + +    # Check overload bit is unset +    check_lsp_overload_bit("r3", "r3.00-00", "0/0/0") + + +def test_isis_overload_on_startup_override_timer(): +    "Check that overload bit remains set after overload timer expires if overload bit is configured" + +    tgen = get_topogen() +    net = get_topogen().net +    overload_time = 60 + +    # Don't run this test if we have any failure. +    if tgen.routers_have_failure(): +        pytest.skip(tgen.errors) + +    logger.info( +        "Testing overload on startup behavior with set overload bit: override timer" +    ) + +    # Configure set-overload-bit on-startup on r3 +    r3 = tgen.gears["r3"] +    r3.vtysh_cmd( +        f""" +          configure +            router isis 1 +              set-overload-bit on-startup {overload_time} +              set-overload-bit +        """ +    ) +    # Restart r3 +    logger.info("Stop router") +    stop_router(tgen, "r3") +    logger.info("Start router") +    start_router(tgen, "r3") + +    # Check that the overload bit is set in r3's LSP +    check_lsp_overload_bit("r3", "r3.00-00", "0/0/1") + +    # Check that overload timer is running +    check_overload_timer("r3", True) + +    # Check that overload timer expired +    check_overload_timer("r3", False) + +    # Check overload bit is still set +    check_lsp_overload_bit("r3", "r3.00-00", "0/0/1") + + +@retry(retry_timeout=200) +def _check_lsp_overload_bit(router, overloaded_router_lsp, att_p_ol_expected): +    "Verfiy overload bit in router's LSP" + +    tgen = get_topogen() +    router = tgen.gears[router] +    logger.info(f"check_overload_bit {router}") +    isis_database_output = router.vtysh_cmd( +        "show isis database {} json".format(overloaded_router_lsp) +    ) + +    database_json = json.loads(isis_database_output) +    att_p_ol = database_json["areas"][0]["levels"][1]["att-p-ol"] +    if att_p_ol == att_p_ol_expected: +        return True +    return "{} peer with expected att_p_ol {} got {} ".format( +        router.name, att_p_ol_expected, att_p_ol +    ) + + +def check_lsp_overload_bit(router, overloaded_router_lsp, att_p_ol_expected): +    "Verfiy overload bit in router's LSP" + +    assertmsg = _check_lsp_overload_bit( +        router, overloaded_router_lsp, att_p_ol_expected +    ) +    assert assertmsg is True, assertmsg + + +@retry(retry_timeout=200) +def _check_overload_timer(router, timer_expected): +    "Verfiy overload bit in router's LSP" + +    tgen = get_topogen() +    router = tgen.gears[router] +    thread_output = router.vtysh_cmd("show thread timers") + +    timer_running = "set_overload_on_start_timer" in thread_output +    if timer_running == timer_expected: +        return True +    return "Expected timer running status: {}".format(timer_expected) + + +def check_overload_timer(router, timer_expected): +    "Verfiy overload bit in router's LSP" + +    assertmsg = _check_overload_timer(router, timer_expected) +    assert assertmsg is True, assertmsg  def test_memory_leak(): diff --git a/tools/frr.in b/tools/frr.in index f0c665fdee..e9f1122834 100755 --- a/tools/frr.in +++ b/tools/frr.in @@ -524,7 +524,7 @@ convert_daemon_prios  if [ ! -d $V_PATH ]; then  	echo "Creating $V_PATH" -	install -g "$FRR_GROUP" -o "$FRR_USER" -m "$FRR_CONFIG_MODE" -d /proc "$V_PATH" +	install -g "$FRR_GROUP" -o "$FRR_USER" -m "$FRR_CONFIG_MODE" -d "$V_PATH"  	chmod gu+x "${V_PATH}"  fi diff --git a/tools/frrcommon.sh.in b/tools/frrcommon.sh.in index 469b9c5d8c..61f1abb378 100755 --- a/tools/frrcommon.sh.in +++ b/tools/frrcommon.sh.in @@ -161,7 +161,7 @@ daemon_start() {  	[ "$MAX_FDS" != "" ] && ulimit -n "$MAX_FDS" > /dev/null 2> /dev/null  	daemon_prep "$daemon" "$inst" || return 1  	if test ! -d "$V_PATH"; then -		install -g "$FRR_GROUP" -o "$FRR_USER" -m "$FRR_CONFIG_MODE" -d /proc "$V_PATH" +		install -g "$FRR_GROUP" -o "$FRR_USER" -m "$FRR_CONFIG_MODE" -d "$V_PATH"  		chmod gu+x "${V_PATH}"  	fi diff --git a/tools/permutations.c b/tools/permutations.c index b280cc15b1..a0b041f2f2 100644 --- a/tools/permutations.c +++ b/tools/permutations.c @@ -80,8 +80,7 @@ void permute(struct graph_node *start)  	for (unsigned int i = 0; i < vector_active(start->to); i++) {  		struct graph_node *gn = vector_slot(start->to, i);  		struct cmd_token *tok = gn->data; -		if (tok->attr == CMD_ATTR_HIDDEN -		    || tok->attr == CMD_ATTR_DEPRECATED) +		if (tok->attr & CMD_ATTR_HIDDEN)  			continue;  		else if (tok->type == END_TKN || gn == start) {  			fprintf(stdout, " "); diff --git a/vrrpd/vrrp_vty.c b/vrrpd/vrrp_vty.c index d2f25f2d40..aea7d9abc7 100644 --- a/vrrpd/vrrp_vty.c +++ b/vrrpd/vrrp_vty.c @@ -121,7 +121,7 @@ DEFPY_YANG(vrrp_priority,        VRRP_STR        VRRP_VRID_STR        VRRP_PRIORITY_STR -      "Priority value") +      "Priority value\n")  {  	nb_cli_enqueue_change(vty, "./priority", NB_OP_MODIFY, priority_str); @@ -138,7 +138,7 @@ DEFPY_YANG(no_vrrp_priority,        VRRP_STR        VRRP_VRID_STR        VRRP_PRIORITY_STR -      "Priority value") +      "Priority value\n")  {  	nb_cli_enqueue_change(vty, "./priority", NB_OP_MODIFY, NULL); @@ -162,7 +162,7 @@ DEFPY_YANG(vrrp_advertisement_interval,        vrrp_advertisement_interval_cmd,        "vrrp (1-255)$vrid advertisement-interval (10-40950)",        VRRP_STR VRRP_VRID_STR VRRP_ADVINT_STR -      "Advertisement interval in milliseconds; must be multiple of 10") +      "Advertisement interval in milliseconds; must be multiple of 10\n")  {  	char val[20]; @@ -183,7 +183,7 @@ DEFPY_YANG(no_vrrp_advertisement_interval,        no_vrrp_advertisement_interval_cmd,        "no vrrp (1-255)$vrid advertisement-interval [(10-40950)]",        NO_STR VRRP_STR VRRP_VRID_STR VRRP_ADVINT_STR -      "Advertisement interval in milliseconds; must be multiple of 10") +      "Advertisement interval in milliseconds; must be multiple of 10\n")  {  	nb_cli_enqueue_change(vty, "./advertisement-interval", NB_OP_MODIFY,  			      NULL); diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index f39ecf0709..48274d7170 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -2951,7 +2951,7 @@ DEFUN_HIDDEN (show_config_running,         show_config_running_cmd,         "show configuration running\            [<json|xml> [translate WORD]]\ -          [with-defaults]" DAEMONS_LIST, +          [with-defaults] " DAEMONS_LIST,         SHOW_STR         "Configuration information\n"         "Running configuration\n" @@ -2972,7 +2972,7 @@ DEFUN (show_yang_operational_data,  	   format <json|xml>\  	   |translate WORD\  	   |with-config\ -	 }]" DAEMONS_LIST, +	 }] " DAEMONS_LIST,         SHOW_STR         "YANG information\n"         "Show YANG operational data\n" diff --git a/yang/frr-isisd.yang b/yang/frr-isisd.yang index 0812c86fac..380fce3859 100644 --- a/yang/frr-isisd.yang +++ b/yang/frr-isisd.yang @@ -1066,11 +1066,25 @@ module frr-isisd {            "If true, identify as L1/L2 router for inter-area traffic.";        } -      leaf overload { -        type boolean; -        default "false"; +      container overload {          description -          "If true, avoid any transit traffic."; +          "Overload bit configuration."; +        leaf enabled { +          type boolean; +          default "false"; +          description +            "If true, avoid any transit traffic."; +        } + +        leaf on-startup { +          type uint32 { +            range "0..86400"; +          } +          units "seconds"; +          default "0"; +          description +            "The duration the overload bit should be set on startup."; +        }        }        leaf metric-style { diff --git a/zebra/zebra_fpm.c b/zebra/zebra_fpm.c index 1b2753377b..c74c692ee1 100644 --- a/zebra/zebra_fpm.c +++ b/zebra/zebra_fpm.c @@ -1841,12 +1841,15 @@ DEFUN (clear_zebra_fpm_stats,  /*   * update fpm connection information   */ -DEFUN ( fpm_remote_ip, +DEFUN (fpm_remote_ip,         fpm_remote_ip_cmd, -        "fpm connection ip A.B.C.D port (1-65535)", -        "fpm connection remote ip and port\n" -        "Remote fpm server ip A.B.C.D\n" -        "Enter ip ") +       "fpm connection ip A.B.C.D port (1-65535)", +       "Forwarding Path Manager\n" +       "Configure FPM connection\n" +       "Connect to IPv4 address\n" +       "Connect to IPv4 address\n" +       "TCP port number\n" +       "TCP port number\n")  {  	in_addr_t fpm_server; @@ -1867,13 +1870,16 @@ DEFUN ( fpm_remote_ip,  	return CMD_SUCCESS;  } -DEFUN ( no_fpm_remote_ip, +DEFUN (no_fpm_remote_ip,         no_fpm_remote_ip_cmd, -        "no fpm connection ip A.B.C.D port (1-65535)", -        "fpm connection remote ip and port\n" -        "Connection\n" -        "Remote fpm server ip A.B.C.D\n" -        "Enter ip ") +       "no fpm connection ip A.B.C.D port (1-65535)", +       NO_STR +       "Forwarding Path Manager\n" +       "Remove configured FPM connection\n" +       "Connect to IPv4 address\n" +       "Connect to IPv4 address\n" +       "TCP port number\n" +       "TCP port number\n")  {  	if (zfpm_g->fpm_server != inet_addr(argv[4]->arg)  	    || zfpm_g->fpm_port != atoi(argv[6]->arg))  | 
