diff --git a/bgpd/bgp_ecommunity.c b/bgpd/bgp_ecommunity.c index dc1905868b..596c5dbe37 100644 --- a/bgpd/bgp_ecommunity.c +++ b/bgpd/bgp_ecommunity.c @@ -371,6 +371,7 @@ void ecommunity_finish(void) enum ecommunity_token { ecommunity_token_unknown = 0, ecommunity_token_rt, + ecommunity_token_nt, ecommunity_token_soo, ecommunity_token_val, ecommunity_token_rt6, @@ -415,6 +416,53 @@ static void ecommunity_origin_validation_state_str(char *buf, size_t bufsz, (void)ptr; /* consume value */ } +bool ecommunity_node_target_match(struct ecommunity *ecom, + struct in_addr *local_id) +{ + uint32_t i; + bool match = false; + + if (!ecom || !ecom->size) + return NULL; + + for (i = 0; i < ecom->size; i++) { + const uint8_t *pnt; + uint8_t type, sub_type; + + pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); + type = *pnt++; + sub_type = *pnt++; + + if (type == ECOMMUNITY_ENCODE_IP && + sub_type == ECOMMUNITY_NODE_TARGET) { + /* Node Target ID is encoded as A.B.C.D:0 */ + if (IPV4_ADDR_SAME((struct in_addr *)pnt, local_id)) + match = true; + (void)pnt; + } + } + + return match; +} + +static void ecommunity_node_target_str(char *buf, size_t bufsz, uint8_t *ptr) +{ + /* + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x01 or 0x41 | Sub-Type(0x09) | Target BGP Identifier | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Target BGP Identifier (cont.) | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + struct in_addr node_id = {}; + + IPV4_ADDR_COPY(&node_id, (struct in_addr *)ptr); + + snprintfrr(buf, bufsz, "NT:%pI4", &node_id); + + (void)ptr; /* consume value */ +} + static int ecommunity_encode_internal(uint8_t type, uint8_t sub_type, int trans, as_t as, struct in_addr *ip, @@ -453,9 +501,13 @@ static int ecommunity_encode_internal(uint8_t type, uint8_t sub_type, eval->val[6] = (val >> 8) & 0xff; eval->val[7] = val & 0xff; } else if (type == ECOMMUNITY_ENCODE_IP) { - memcpy(&eval->val[2], ip, sizeof(struct in_addr)); - eval->val[6] = (val >> 8) & 0xff; - eval->val[7] = val & 0xff; + if (sub_type == ECOMMUNITY_NODE_TARGET) { + encode_node_target(ip, eval, trans); + } else { + memcpy(&eval->val[2], ip, sizeof(struct in_addr)); + eval->val[6] = (val >> 8) & 0xff; + eval->val[7] = val & 0xff; + } } else if (type == ECOMMUNITY_ENCODE_TRANS_EXP && sub_type == ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6) { memcpy(&eval6->val[2], ip6, sizeof(struct in6_addr)); @@ -486,9 +538,8 @@ static int ecommunity_encode(uint8_t type, uint8_t sub_type, int trans, as_t as, } /* Get next Extended Communities token from the string. */ -static const char *ecommunity_gettoken(const char *str, - void *eval_ptr, - enum ecommunity_token *token) +static const char *ecommunity_gettoken(const char *str, void *eval_ptr, + enum ecommunity_token *token, int type) { int ret; int dot = 0; @@ -515,7 +566,7 @@ static const char *ecommunity_gettoken(const char *str, if (*p == '\0') return NULL; - /* "rt" and "soo" keyword parse. */ + /* "rt", "nt", and "soo" keyword parse. */ if (!isdigit((unsigned char)*p)) { /* "rt" match check. */ if (tolower((unsigned char)*p) == 'r') { @@ -534,6 +585,20 @@ static const char *ecommunity_gettoken(const char *str, } goto error; } + /* "nt" match check. */ + if (tolower((unsigned char)*p) == 'n') { + p++; + if (tolower((unsigned char)*p) == 't') { + p++; + *token = ecommunity_token_nt; + return p; + } + if (isspace((unsigned char)*p) || *p == '\0') { + *token = ecommunity_token_nt; + return p; + } + goto error; + } /* "soo" match check. */ else if (tolower((unsigned char)*p) == 's') { p++; @@ -679,7 +744,7 @@ static const char *ecommunity_gettoken(const char *str, ecomm_type = ECOMMUNITY_ENCODE_AS4; else ecomm_type = ECOMMUNITY_ENCODE_AS; - if (ecommunity_encode(ecomm_type, 0, 1, as, ip, val, eval)) + if (ecommunity_encode(ecomm_type, type, 1, as, ip, val, eval)) goto error; *token = ecommunity_token_val; return p; @@ -700,9 +765,10 @@ static struct ecommunity *ecommunity_str2com_internal(const char *str, int type, if (is_ipv6_extcomm) token = ecommunity_token_rt6; - while ((str = ecommunity_gettoken(str, (void *)&eval, &token))) { + while ((str = ecommunity_gettoken(str, (void *)&eval, &token, type))) { switch (token) { case ecommunity_token_rt: + case ecommunity_token_nt: case ecommunity_token_rt6: case ecommunity_token_soo: if (!keyword_included || keyword) { @@ -719,6 +785,9 @@ static struct ecommunity *ecommunity_str2com_internal(const char *str, int type, if (token == ecommunity_token_soo) { type = ECOMMUNITY_SITE_ORIGIN; } + if (token == ecommunity_token_nt) { + type = ECOMMUNITY_NODE_TARGET; + } break; case ecommunity_token_val: if (keyword_included) { @@ -1000,6 +1069,10 @@ char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter) ecommunity_lb_str( encbuf, sizeof(encbuf), pnt, ecom->disable_ieee_floating); + } else if (sub_type == ECOMMUNITY_NODE_TARGET && + type == ECOMMUNITY_ENCODE_IP) { + ecommunity_node_target_str( + encbuf, sizeof(encbuf), pnt); } else unk_ecom = 1; } else { @@ -1209,6 +1282,13 @@ char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter) ecom->disable_ieee_floating); else unk_ecom = 1; + } else if (type == ECOMMUNITY_ENCODE_IP_NON_TRANS) { + sub_type = *pnt++; + if (sub_type == ECOMMUNITY_NODE_TARGET) + ecommunity_node_target_str(encbuf, + sizeof(encbuf), pnt); + else + unk_ecom = 1; } else if (type == ECOMMUNITY_ENCODE_OPAQUE_NON_TRANS) { sub_type = *pnt++; if (sub_type == ECOMMUNITY_ORIGIN_VALIDATION_STATE) diff --git a/bgpd/bgp_ecommunity.h b/bgpd/bgp_ecommunity.h index 83a1584489..1efb0276d0 100644 --- a/bgpd/bgp_ecommunity.h +++ b/bgpd/bgp_ecommunity.h @@ -101,6 +101,10 @@ enum ecommunity_origin_validation_states { /* Extended Community readable string length */ #define ECOMMUNITY_STRLEN 64 +/* Node Target Extended Communities */ +#define ECOMMUNITY_NODE_TARGET 0x09 +#define ECOMMUNITY_NODE_TARGET_RESERVED 0 + /* Extended Communities attribute. */ struct ecommunity { /* Reference counter. */ @@ -257,6 +261,26 @@ static inline void encode_origin_validation_state(enum rpki_states state, eval->val[7] = ovs_state; } +static inline void encode_node_target(struct in_addr *node_id, + struct ecommunity_val *eval, bool trans) +{ + /* + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x01 or 0x41 | Sub-Type(0x09) | Target BGP Identifier | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Target BGP Identifier (cont.) | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_IP; + if (!trans) + eval->val[0] |= ECOMMUNITY_ENCODE_IP_NON_TRANS; + eval->val[1] = ECOMMUNITY_NODE_TARGET; + memcpy(&eval->val[2], node_id, sizeof(*node_id)); + eval->val[6] = ECOMMUNITY_NODE_TARGET_RESERVED; + eval->val[7] = ECOMMUNITY_NODE_TARGET_RESERVED; +} + extern void ecommunity_init(void); extern void ecommunity_finish(void); extern void ecommunity_free(struct ecommunity **); @@ -338,4 +362,9 @@ static inline void ecommunity_strip_rts(struct ecommunity *ecom) extern struct ecommunity * ecommunity_add_origin_validation_state(enum rpki_states rpki_state, struct ecommunity *ecom); +extern struct ecommunity *ecommunity_add_node_target(struct in_addr *node_id, + struct ecommunity *old, + bool non_trans); +extern bool ecommunity_node_target_match(struct ecommunity *ecomm, + struct in_addr *local_id); #endif /* _QUAGGA_BGP_ECOMMUNITY_H */ diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index a23bffd4ae..957b30794c 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -4154,6 +4154,21 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, goto filtered; } + /* If the route has Node Target Extended Communities, check + * if it's allowed to be installed locally. + */ + if ((attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) { + struct ecommunity *ecomm = bgp_attr_get_ecommunity(attr); + + if (ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_IP, + ECOMMUNITY_NODE_TARGET) && + !ecommunity_node_target_match(ecomm, &peer->local_id)) { + reason = + "Node-Target Extended Communities do not contain own BGP Identifier;"; + goto filtered; + } + } + /* RFC 8212 to prevent route leaks. * This specification intends to improve this situation by requiring the * explicit configuration of both BGP Import and Export Policies for any diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index 32ecb083be..10fc3ecda4 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -2867,6 +2867,29 @@ static const struct route_map_rule_cmd route_set_ecommunity_soo_cmd = { route_set_ecommunity_free, }; +static void *route_set_ecommunity_nt_compile(const char *arg) +{ + struct rmap_ecom_set *rcs; + struct ecommunity *ecom; + + ecom = ecommunity_str2com(arg, ECOMMUNITY_NODE_TARGET, 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_nt_cmd = { + "extcommunity nt", + route_set_ecommunity, + route_set_ecommunity_nt_compile, + route_set_ecommunity_free, +}; + /* `set extcommunity bandwidth' */ struct rmap_ecomm_lb_set { @@ -6418,6 +6441,55 @@ ALIAS_YANG (no_set_ecommunity_lb, "BGP extended community attribute\n" "Link bandwidth extended community\n") +DEFPY_YANG (set_ecommunity_nt, + set_ecommunity_nt_cmd, + "set extcommunity nt RTLIST...", + SET_STR + "BGP extended community attribute\n" + "Node Target extended community\n" + "Node Target ID\n") +{ + int idx_nt = 3; + char *str; + int ret; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-nt']"; + 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-nt", xpath); + str = argv_concat(argv, argc, idx_nt); + 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_nt, + no_set_ecommunity_nt_cmd, + "no set extcommunity nt RTLIST...", + NO_STR + SET_STR + "BGP extended community attribute\n" + "Node Target extended community\n" + "Node Target ID\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-nt']"; + 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", + NO_STR + SET_STR + "BGP extended community attribute\n" + "Node Target extended community\n") + DEFUN_YANG (set_origin, set_origin_cmd, "set origin ", @@ -7223,6 +7295,7 @@ void bgp_route_map_init(void) route_map_install_set(&route_set_vpnv6_nexthop_cmd); route_map_install_set(&route_set_originator_id_cmd); route_map_install_set(&route_set_ecommunity_rt_cmd); + 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_none_cmd); @@ -7325,6 +7398,9 @@ void bgp_route_map_init(void) install_element(RMAP_NODE, &no_set_ecommunity_lb_short_cmd); install_element(RMAP_NODE, &set_ecommunity_none_cmd); install_element(RMAP_NODE, &no_set_ecommunity_none_cmd); + 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); #ifdef KEEP_OLD_VPN_COMMANDS install_element(RMAP_NODE, &set_vpn_nexthop_cmd); install_element(RMAP_NODE, &no_set_vpn_nexthop_cmd); diff --git a/bgpd/bgp_routemap_nb.c b/bgpd/bgp_routemap_nb.c index 9188a40cc9..6e8439cc26 100644 --- a/bgpd/bgp_routemap_nb.c +++ b/bgpd/bgp_routemap_nb.c @@ -185,6 +185,13 @@ const struct frr_yang_module_info frr_bgp_route_map_info = { .destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_destroy, } }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-nt", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_destroy, + } + }, { .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-soo", .cbs = { diff --git a/bgpd/bgp_routemap_nb.h b/bgpd/bgp_routemap_nb.h index 07a1a6eb57..bcd1e837e8 100644 --- a/bgpd/bgp_routemap_nb.h +++ b/bgpd/bgp_routemap_nb.h @@ -69,6 +69,10 @@ int lib_route_map_entry_set_action_rmap_set_action_distance_modify(struct nb_cb_ int lib_route_map_entry_set_action_rmap_set_action_distance_destroy(struct nb_cb_destroy_args *args); int lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_modify(struct nb_cb_modify_args *args); int lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_destroy( + struct nb_cb_destroy_args *args); int lib_route_map_entry_set_action_rmap_set_action_extcommunity_soo_modify(struct nb_cb_modify_args *args); int lib_route_map_entry_set_action_rmap_set_action_extcommunity_soo_destroy(struct nb_cb_destroy_args *args); int lib_route_map_entry_set_action_rmap_set_action_ipv4_address_modify(struct nb_cb_modify_args *args); diff --git a/bgpd/bgp_routemap_nb_config.c b/bgpd/bgp_routemap_nb_config.c index 4db8dba2cc..938a5ec31b 100644 --- a/bgpd/bgp_routemap_nb_config.c +++ b/bgpd/bgp_routemap_nb_config.c @@ -1413,6 +1413,58 @@ lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_destroy( return NB_OK; } +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-nt + */ +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_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 nt"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "extcommunity nt", 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_nt_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-soo diff --git a/lib/routemap.h b/lib/routemap.h index 2197a49e76..fda42b069d 100644 --- a/lib/routemap.h +++ b/lib/routemap.h @@ -352,6 +352,8 @@ DECLARE_QOBJ_TYPE(route_map); (strmatch(A, "frr-bgp-route-map:set-extcommunity-none")) #define IS_SET_EXTCOMMUNITY_RT(A) \ (strmatch(A, "frr-bgp-route-map:set-extcommunity-rt")) +#define IS_SET_EXTCOMMUNITY_NT(A) \ + (strmatch(A, "frr-bgp-route-map:set-extcommunity-nt")) #define IS_SET_EXTCOMMUNITY_SOO(A) \ (strmatch(A, "frr-bgp-route-map:set-extcommunity-soo")) #define IS_SET_EXTCOMMUNITY_LB(A) \ diff --git a/lib/routemap_cli.c b/lib/routemap_cli.c index 4345b74bc0..52a604bb22 100644 --- a/lib/routemap_cli.c +++ b/lib/routemap_cli.c @@ -1140,6 +1140,11 @@ void route_map_action_show(struct vty *vty, const struct lyd_node *dnode, yang_dnode_get_string( dnode, "./rmap-set-action/frr-bgp-route-map:extcommunity-rt")); + } else if (IS_SET_EXTCOMMUNITY_NT(action)) { + vty_out(vty, " set extcommunity nt %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:extcommunity-nt")); } else if (IS_SET_EXTCOMMUNITY_SOO(action)) { vty_out(vty, " set extcommunity soo %s\n", yang_dnode_get_string( diff --git a/yang/frr-bgp-route-map.yang b/yang/frr-bgp-route-map.yang index 666f2bb235..23e5b0227c 100644 --- a/yang/frr-bgp-route-map.yang +++ b/yang/frr-bgp-route-map.yang @@ -186,6 +186,12 @@ module frr-bgp-route-map { "Set BGP extended community attribute"; } + identity set-extcommunity-nt { + base frr-route-map:rmap-set-type; + description + "Set BGP extended community attribute"; + } + identity set-extcommunity-soo { base frr-route-map:rmap-set-type; description @@ -786,6 +792,17 @@ module frr-bgp-route-map { } } + case extcommunity-nt { + 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-nt')"; + description + "Value of the ext-community"; + leaf extcommunity-nt { + type string; + description + "Set BGP ext-community node-target attribute"; + } + } + case extcommunity-soo { 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-soo')"; description