bgpd: add colored extended communities support

add support of color extended community, conforming to RFC 9012.
This extended community will be added to the existing one, RT,SOO
and Node Target. The configuration will be made through the
route-map service.

find above a configuration example:

router bgp 65001
 bgp router-id 192.168.1.1
 no bgp ebgp-requires-policy
 no bgp network import-check
 neighbor 192.168.1.2 remote-as external
 neighbor 192.168.1.3 remote-as external
 neighbor 192.168.1.4 remote-as external
 address-family ipv4 unicast
  network 10.10.10.10/24 route-map rmap
  exit-address-family
!
  route-map rmap permit 10
   set extcommunity color 55555 200
  exit

Signed-off-by: Francois Dumontet <francois.dumontet@6wind.com>
This commit is contained in:
Francois Dumontet 2023-05-22 15:36:06 +02:00
parent 53a9aee618
commit b80ebc2d8c
9 changed files with 313 additions and 16 deletions

View file

@ -355,6 +355,22 @@ bool ecommunity_cmp(const void *arg1, const void *arg2)
ecom1->unit_size) == 0);
}
static void ecommunity_color_str(char *buf, size_t bufsz, uint8_t *ptr)
{
/*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | 0x03 | Sub-Type(0x0b) | Flags |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Color Value |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
uint32_t colorid;
memcpy(&colorid, ptr + 3, 4);
colorid = ntohl(colorid);
snprintf(buf, bufsz, "Color:%d", colorid);
}
/* Initialize Extended Comminities related hash. */
void ecommunity_init(void)
{
@ -373,6 +389,7 @@ enum ecommunity_token {
ecommunity_token_rt,
ecommunity_token_nt,
ecommunity_token_soo,
ecommunity_token_color,
ecommunity_token_val,
ecommunity_token_rt6,
ecommunity_token_val6,
@ -510,6 +527,9 @@ static int ecommunity_encode_internal(uint8_t type, uint8_t sub_type,
memcpy(&eval6->val[2], ip6, sizeof(struct in6_addr));
eval6->val[18] = (val >> 8) & 0xff;
eval6->val[19] = val & 0xff;
} else if (type == ECOMMUNITY_ENCODE_OPAQUE &&
sub_type == ECOMMUNITY_COLOR) {
encode_color(val, eval);
} else {
encode_route_target_as4(as, val, eval, trans);
}
@ -537,16 +557,22 @@ static const char *ecommunity_gettoken(const char *str, void *eval_ptr,
int dot = 0;
int digit = 0;
int separator = 0;
int i;
const char *p = str;
char *endptr;
struct in_addr ip;
struct in6_addr ip6;
as_t as = 0;
uint32_t val = 0;
uint8_t ecomm_type;
uint32_t val_color = 0;
uint8_t ecomm_type = 0;
uint8_t sub_type = 0;
char buf[INET_ADDRSTRLEN + 1];
struct ecommunity_val *eval = (struct ecommunity_val *)eval_ptr;
uint64_t tmp_as = 0;
static const char str_color[5] = "color";
const char *ptr_color;
bool val_color_set = false;
/* Skip white space. */
while (isspace((unsigned char)*p)) {
@ -558,7 +584,7 @@ static const char *ecommunity_gettoken(const char *str, void *eval_ptr,
if (*p == '\0')
return NULL;
/* "rt", "nt", and "soo" keyword parse. */
/* "rt", "nt", "soo", and "color" keyword parse. */
if (!isdigit((unsigned char)*p)) {
/* "rt" match check. */
if (tolower((unsigned char)*p) == 'r') {
@ -612,10 +638,33 @@ static const char *ecommunity_gettoken(const char *str, void *eval_ptr,
return p;
}
goto error;
} else if (tolower((unsigned char)*p) == 'c') {
/* "color" match check.
* 'c', 'co', 'col', 'colo' are also accepted
*/
for (i = 0; i < 5; i++) {
ptr_color = &str_color[0];
if (tolower((unsigned char)*p) == *ptr_color) {
p++;
ptr_color++;
} else if (i > 0) {
if (isspace((unsigned char)*p) ||
*p == '\0') {
*token = ecommunity_token_color;
return p;
}
goto error;
}
if (isspace((unsigned char)*p) || *p == '\0') {
*token = ecommunity_token_color;
return p;
}
goto error;
}
goto error;
}
goto error;
}
/* What a mess, there are several possibilities:
*
* a) A.B.C.D:MN
@ -716,17 +765,24 @@ static const char *ecommunity_gettoken(const char *str, void *eval_ptr,
} else {
digit = 1;
/* We're past the IP/ASN part */
/* We're past the IP/ASN part,
* or we have a color
*/
if (separator) {
val *= 10;
val += (*p - '0');
val_color_set = false;
} else {
val_color *= 10;
val_color += (*p - '0');
val_color_set = true;
}
}
p++;
}
/* Low digit part must be there. */
if (!digit || !separator)
if (!digit && (!separator || !val_color_set))
goto error;
/* Encode result into extended community. */
@ -734,9 +790,15 @@ static const char *ecommunity_gettoken(const char *str, void *eval_ptr,
ecomm_type = ECOMMUNITY_ENCODE_IP;
else if (as > BGP_AS_MAX)
ecomm_type = ECOMMUNITY_ENCODE_AS4;
else
else if (as > 0)
ecomm_type = ECOMMUNITY_ENCODE_AS;
if (ecommunity_encode(ecomm_type, type, 1, as, ip, val, eval))
else if (val_color) {
ecomm_type = ECOMMUNITY_ENCODE_OPAQUE;
sub_type = ECOMMUNITY_COLOR;
val = val_color;
}
if (ecommunity_encode(ecomm_type, sub_type, 1, as, ip, val, eval))
goto error;
*token = ecommunity_token_val;
return p;
@ -763,6 +825,7 @@ static struct ecommunity *ecommunity_str2com_internal(const char *str, int type,
case ecommunity_token_nt:
case ecommunity_token_rt6:
case ecommunity_token_soo:
case ecommunity_token_color:
if (!keyword_included || keyword) {
if (ecom)
ecommunity_free(&ecom);
@ -771,15 +834,14 @@ static struct ecommunity *ecommunity_str2com_internal(const char *str, int type,
keyword = 1;
if (token == ecommunity_token_rt ||
token == ecommunity_token_rt6) {
token == ecommunity_token_rt6)
type = ECOMMUNITY_ROUTE_TARGET;
}
if (token == ecommunity_token_soo) {
if (token == ecommunity_token_soo)
type = ECOMMUNITY_SITE_ORIGIN;
}
if (token == ecommunity_token_nt) {
if (token == ecommunity_token_nt)
type = ECOMMUNITY_NODE_TARGET;
}
if (token == ecommunity_token_color)
type = ECOMMUNITY_COLOR;
break;
case ecommunity_token_val:
if (keyword_included) {
@ -998,10 +1060,12 @@ static int ecommunity_lb_str(char *buf, size_t bufsz, const uint8_t *pnt,
"rt 100:1 100:2soo 100:3"
extcommunity-list
"rt 100:1 rt 100:2 soo 100:3show [ip] bgp" and extcommunity-list regular expression matching
"rt 100:1 rt 100:2 soo 100:3
show [ip] bgp"
and extcommunity-list regular expression matching
"RT:100:1 RT:100:2 SoO:100:3"
For each formath please use below definition for format:
For each format please use below definition for format:
ECOMMUNITY_FORMAT_ROUTE_MAP
ECOMMUNITY_FORMAT_COMMUNITY_LIST
@ -1086,6 +1150,9 @@ char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter)
} else if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_DEF_GW) {
strlcpy(encbuf, "Default Gateway",
sizeof(encbuf));
} else if (*pnt == ECOMMUNITY_COLOR) {
ecommunity_color_str(encbuf, sizeof(encbuf),
pnt);
} else {
unk_ecom = 1;
}

View file

@ -46,6 +46,8 @@
#define ECOMMUNITY_REDIRECT_VRF 0x08
#define ECOMMUNITY_TRAFFIC_MARKING 0x09
#define ECOMMUNITY_REDIRECT_IP_NH 0x00
#define ECOMMUNITY_COLOR 0x0b /* RFC9012 - color */
/* from IANA: bgp-extended-communities/bgp-extended-communities.xhtml
* 0x0c Flow-spec Redirect to IPv4 - draft-ietf-idr-flowspec-redirect
*/
@ -290,6 +292,35 @@ static inline void encode_node_target(struct in_addr *node_id,
eval->val[7] = ECOMMUNITY_NODE_TARGET_RESERVED;
}
/*
* Encode BGP Color extended community
* is's a transitive opaque Extended community (RFC 9012 4.3)
* flag is set to 0
* RFC 9012 14.10: No values have currently been registered.
* 4.3: this field MUST be set to zero by the originator
* and ignored by the receiver;
*
*/
static inline void encode_color(uint32_t color_id, struct ecommunity_val *eval)
{
/*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | 0x03 | Sub-Type(0x0b) | Flags |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Color Value |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
memset(eval, 0, sizeof(*eval));
eval->val[0] = ECOMMUNITY_ENCODE_OPAQUE;
eval->val[1] = ECOMMUNITY_COLOR;
eval->val[2] = 0x00;
eval->val[3] = 0x00;
eval->val[4] = (color_id >> 24) & 0xff;
eval->val[5] = (color_id >> 16) & 0xff;
eval->val[6] = (color_id >> 8) & 0xff;
eval->val[7] = color_id & 0xff;
}
extern void ecommunity_init(void);
extern void ecommunity_finish(void);
extern void ecommunity_free(struct ecommunity **);
@ -314,7 +345,7 @@ extern void ecommunity_strfree(char **s);
extern bool ecommunity_include(struct ecommunity *e1, struct ecommunity *e2);
extern bool ecommunity_match(const struct ecommunity *,
const struct ecommunity *);
extern char *ecommunity_str(struct ecommunity *);
extern char *ecommunity_str(struct ecommunity *ecom);
extern struct ecommunity_val *ecommunity_lookup(const struct ecommunity *,
uint8_t, uint8_t);

View file

@ -3055,6 +3055,44 @@ static void *route_set_ecommunity_lb_compile(const char *arg)
return rels;
}
static enum route_map_cmd_result_t
route_set_ecommunity_color(void *rule, const struct prefix *prefix,
void *object)
{
struct bgp_path_info *path;
path = object;
route_set_ecommunity(rule, prefix, object);
path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_SRTE_COLOR);
return RMAP_OKAY;
}
static void *route_set_ecommunity_color_compile(const char *arg)
{
struct rmap_ecom_set *rcs;
struct ecommunity *ecom;
ecom = ecommunity_str2com(arg, ECOMMUNITY_COLOR, 0);
if (!ecom)
return NULL;
rcs = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_ecom_set));
rcs->ecom = ecommunity_intern(ecom);
rcs->none = false;
return rcs;
}
static const struct route_map_rule_cmd route_set_ecommunity_color_cmd = {
"extcommunity color",
route_set_ecommunity_color,
route_set_ecommunity_color_compile,
route_set_ecommunity_free,
};
static void route_set_ecommunity_lb_free(void *rule)
{
XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
@ -6522,6 +6560,57 @@ DEFPY_YANG (no_set_ecommunity_nt,
return nb_cli_apply_changes(vty, NULL);
}
DEFPY_YANG(set_ecommunity_color, set_ecommunity_color_cmd,
"set extcommunity color RTLIST...",
SET_STR
"BGP extended community attribute\n"
"Color extended community\n"
"Color ID\n")
{
int idx_color = 3;
char *str;
int ret;
const char *xpath =
"./set-action[action='frr-bgp-route-map:set-extcommunity-color']";
char xpath_value[XPATH_MAXLEN];
nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
snprintf(xpath_value, sizeof(xpath_value),
"%s/rmap-set-action/frr-bgp-route-map:extcommunity-color",
xpath);
str = argv_concat(argv, argc, idx_color);
nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str);
ret = nb_cli_apply_changes(vty, NULL);
XFREE(MTYPE_TMP, str);
return ret;
}
DEFPY_YANG(no_set_ecommunity_color_all, no_set_ecommunity_color_all_cmd,
"no set extcommunity color",
NO_STR SET_STR
"BGP extended community attribute\n"
"Color extended community\n")
{
const char *xpath =
"./set-action[action='frr-bgp-route-map:set-extcommunity-color']";
nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
return nb_cli_apply_changes(vty, NULL);
}
DEFPY_YANG(no_set_ecommunity_color, no_set_ecommunity_color_cmd,
"no set extcommunity color RTLIST...",
NO_STR SET_STR
"BGP extended community attribute\n"
"Color extended community\n"
"Color ID\n")
{
const char *xpath =
"./set-action[action='frr-bgp-route-map:set-extcommunity-color']";
nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
return nb_cli_apply_changes(vty, NULL);
}
ALIAS_YANG (no_set_ecommunity_nt,
no_set_ecommunity_nt_short_cmd,
"no set extcommunity nt",
@ -7375,6 +7464,7 @@ void bgp_route_map_init(void)
route_map_install_set(&route_set_ecommunity_nt_cmd);
route_map_install_set(&route_set_ecommunity_soo_cmd);
route_map_install_set(&route_set_ecommunity_lb_cmd);
route_map_install_set(&route_set_ecommunity_color_cmd);
route_map_install_set(&route_set_ecommunity_none_cmd);
route_map_install_set(&route_set_tag_cmd);
route_map_install_set(&route_set_label_index_cmd);
@ -7478,6 +7568,9 @@ void bgp_route_map_init(void)
install_element(RMAP_NODE, &set_ecommunity_nt_cmd);
install_element(RMAP_NODE, &no_set_ecommunity_nt_cmd);
install_element(RMAP_NODE, &no_set_ecommunity_nt_short_cmd);
install_element(RMAP_NODE, &set_ecommunity_color_cmd);
install_element(RMAP_NODE, &no_set_ecommunity_color_cmd);
install_element(RMAP_NODE, &no_set_ecommunity_color_all_cmd);
#ifdef KEEP_OLD_VPN_COMMANDS
install_element(RMAP_NODE, &set_vpn_nexthop_cmd);
install_element(RMAP_NODE, &no_set_vpn_nexthop_cmd);

View file

@ -400,6 +400,13 @@ const struct frr_yang_module_info frr_bgp_route_map_info = {
.destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_bandwidth_destroy,
}
},
{
.xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-color",
.cbs = {
.modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_modify,
.destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_destroy,
}
},
{
.xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-lb/two-octet-as-specific",
.cbs = {

View file

@ -153,6 +153,10 @@ int lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv6_modify(
struct nb_cb_modify_args *args);
int lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv6_destroy(
struct nb_cb_destroy_args *args);
int lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_modify(
struct nb_cb_modify_args *args);
int lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_destroy(
struct nb_cb_destroy_args *args);
int lib_route_map_entry_set_action_rmap_set_action_l3vpn_nexthop_encapsulation_modify(
struct nb_cb_modify_args *args);
int lib_route_map_entry_set_action_rmap_set_action_l3vpn_nexthop_encapsulation_destroy(

View file

@ -2952,6 +2952,58 @@ lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_bandwidth_destroy
return lib_route_map_entry_set_destroy(args);
}
/*
* XPath:
* /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-color
*/
int lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_modify(
struct nb_cb_modify_args *args)
{
struct routemap_hook_context *rhc;
const char *str;
int rv;
switch (args->event) {
case NB_EV_VALIDATE:
case NB_EV_PREPARE:
case NB_EV_ABORT:
break;
case NB_EV_APPLY:
/* Add configuration. */
rhc = nb_running_get_entry(args->dnode, NULL, true);
str = yang_dnode_get_string(args->dnode, NULL);
/* Set destroy information. */
rhc->rhc_shook = generic_set_delete;
rhc->rhc_rule = "extcommunity color";
rhc->rhc_event = RMAP_EVENT_SET_DELETED;
rv = generic_set_add(rhc->rhc_rmi, "extcommunity color", str,
args->errmsg, args->errmsg_len);
if (rv != CMD_SUCCESS) {
rhc->rhc_shook = NULL;
return NB_ERR_INCONSISTENCY;
}
}
return NB_OK;
}
int lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_destroy(
struct nb_cb_destroy_args *args)
{
switch (args->event) {
case NB_EV_VALIDATE:
case NB_EV_PREPARE:
case NB_EV_ABORT:
break;
case NB_EV_APPLY:
return lib_route_map_entry_match_destroy(args);
}
return NB_OK;
}
/*
* XPath:
* /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-lb/two-octet-as-specific

View file

@ -362,6 +362,9 @@ DECLARE_QOBJ_TYPE(route_map);
(strmatch(A, "frr-bgp-route-map:set-extcommunity-soo"))
#define IS_SET_EXTCOMMUNITY_LB(A) \
(strmatch(A, "frr-bgp-route-map:set-extcommunity-lb"))
#define IS_SET_EXTCOMMUNITY_COLOR(A) \
(strmatch(A, "frr-bgp-route-map:set-extcommunity-color"))
#define IS_SET_AGGREGATOR(A) \
(strmatch(A, "frr-bgp-route-map:aggregator"))
#define IS_SET_AS_PREPEND(A) \

View file

@ -1259,6 +1259,11 @@ void route_map_action_show(struct vty *vty, const struct lyd_node *dnode,
strlcat(str, " non-transitive", sizeof(str));
vty_out(vty, " set extcommunity bandwidth %s\n", str);
} else if (IS_SET_EXTCOMMUNITY_COLOR(action)) {
vty_out(vty, " set extcommunity color %s\n",
yang_dnode_get_string(
dnode,
"./rmap-set-action/frr-bgp-route-map:extcommunity-color"));
} else if (IS_SET_EXTCOMMUNITY_NONE(action)) {
if (yang_dnode_get_bool(
dnode,

View file

@ -214,6 +214,12 @@ module frr-bgp-route-map {
"Set BGP extended community attribute";
}
identity set-extcommunity-color {
base frr-route-map:rmap-set-type;
description
"Set BGP extended community attribute";
}
identity set-ipv4-nexthop {
base frr-route-map:rmap-set-type;
description
@ -511,6 +517,22 @@ module frr-bgp-route-map {
}
}
typedef color-list {
type string {
pattern '((429496729[0-5]|42949672[0-8][0-9]|'
+ '4294967[0-1][0-9]{2}|429496[0-6][0-9]{3}|'
+ '42949[0-5][0-9]{4}|4294[0-8][0-9]{5}|'
+ '429[0-3][0-9]{6}|42[0-8][0-9]{7}|'
+ '4[0-1][0-9]{8}|[1-3][0-9]{9}|'
+ '[1-9][0-9]{0,8})(\s*))+';
}
description
"The color-list type represent a set of colors of value (1..4294967295)
values are separated by white spaces";
reference
"RFC 9012 - The BGP Tunnel Encapsulation Attribute";
}
augment "/frr-route-map:lib/frr-route-map:route-map/frr-route-map:entry/frr-route-map:match-condition/frr-route-map:rmap-match-condition/frr-route-map:match-condition" {
case local-preference {
when "derived-from-or-self(/frr-route-map:lib/frr-route-map:route-map/frr-route-map:entry/frr-route-map:match-condition/frr-route-map:condition, 'frr-bgp-route-map:match-local-preference')";
@ -852,6 +874,19 @@ module frr-bgp-route-map {
}
}
case extcommunity-color {
when "derived-from-or-self(/frr-route-map:lib/frr-route-map:route-map/frr-route-map:entry/frr-route-map:set-action/frr-route-map:action, 'frr-bgp-route-map:set-extcommunity-color')";
description
"Value of the ext-community";
leaf extcommunity-color {
type color-list;
description
"Set BGP ext-community color attribute with a list of colors";
reference
"RFC9012";
}
}
case ipv4-address {
when "derived-from-or-self(/frr-route-map:lib/frr-route-map:route-map/frr-route-map:entry/frr-route-map:set-action/frr-route-map:action, 'frr-bgp-route-map:ipv4-vpn-address')";
description