summaryrefslogtreecommitdiffstats
path: root/bgpd/bgp_ecommunity.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
commite2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch)
treef0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /bgpd/bgp_ecommunity.c
parentInitial commit. (diff)
downloadfrr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz
frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'bgpd/bgp_ecommunity.c')
-rw-r--r--bgpd/bgp_ecommunity.c1861
1 files changed, 1861 insertions, 0 deletions
diff --git a/bgpd/bgp_ecommunity.c b/bgpd/bgp_ecommunity.c
new file mode 100644
index 0000000..c408edb
--- /dev/null
+++ b/bgpd/bgp_ecommunity.c
@@ -0,0 +1,1861 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* BGP Extended Communities Attribute
+ * Copyright (C) 2000 Kunihiro Ishiguro <kunihiro@zebra.org>
+ */
+
+#include <zebra.h>
+
+#include "hash.h"
+#include "memory.h"
+#include "prefix.h"
+#include "command.h"
+#include "queue.h"
+#include "filter.h"
+#include "jhash.h"
+#include "stream.h"
+
+#include "lib/printfrr.h"
+
+#include "bgpd/bgpd.h"
+#include "bgpd/bgp_ecommunity.h"
+#include "bgpd/bgp_lcommunity.h"
+#include "bgpd/bgp_aspath.h"
+#include "bgpd/bgp_flowspec_private.h"
+#include "bgpd/bgp_pbr.h"
+
+/* struct used to dump the rate contained in FS set traffic-rate EC */
+union traffic_rate {
+ float rate_float;
+ uint8_t rate_byte[4];
+};
+
+/* Hash of community attribute. */
+static struct hash *ecomhash;
+
+/* Allocate a new ecommunities. */
+struct ecommunity *ecommunity_new(void)
+{
+ struct ecommunity *ecom;
+
+ ecom = (struct ecommunity *)XCALLOC(MTYPE_ECOMMUNITY,
+ sizeof(struct ecommunity));
+ ecom->unit_size = ECOMMUNITY_SIZE;
+ return ecom;
+}
+
+void ecommunity_strfree(char **s)
+{
+ XFREE(MTYPE_ECOMMUNITY_STR, *s);
+}
+
+/* Free ecommunities. */
+void ecommunity_free(struct ecommunity **ecom)
+{
+ if (!(*ecom))
+ return;
+
+ XFREE(MTYPE_ECOMMUNITY_VAL, (*ecom)->val);
+ XFREE(MTYPE_ECOMMUNITY_STR, (*ecom)->str);
+ XFREE(MTYPE_ECOMMUNITY, *ecom);
+}
+
+static void ecommunity_hash_free(struct ecommunity *ecom)
+{
+ ecommunity_free(&ecom);
+}
+
+
+/* Add a new Extended Communities value to Extended Communities
+ Attribute structure. When the value is already exists in the
+ structure, we don't add the value. Newly added value is sorted by
+ numerical order. When the value is added to the structure return 1
+ else return 0.
+ The additional parameters 'unique' and 'overwrite' ensure a particular
+ extended community (based on type and sub-type) is present only
+ once and whether the new value should replace what is existing or
+ not.
+*/
+static bool ecommunity_add_val_internal(struct ecommunity *ecom,
+ const void *eval,
+ bool unique, bool overwrite,
+ uint8_t ecom_size)
+{
+ uint32_t c, ins_idx;
+ const struct ecommunity_val *eval4 = (struct ecommunity_val *)eval;
+ const struct ecommunity_val_ipv6 *eval6 =
+ (struct ecommunity_val_ipv6 *)eval;
+
+ /* When this is fist value, just add it. */
+ if (ecom->val == NULL) {
+ ecom->size = 1;
+ ecom->val = XMALLOC(MTYPE_ECOMMUNITY_VAL,
+ ecom_length_size(ecom, ecom_size));
+ memcpy(ecom->val, eval, ecom_size);
+ return true;
+ }
+
+ /* If the value already exists in the structure return 0. */
+ /* check also if the extended community itself exists. */
+ c = 0;
+
+ ins_idx = UINT32_MAX;
+ for (uint8_t *p = ecom->val; c < ecom->size;
+ p += ecom_size, c++) {
+ if (unique) {
+ if (ecom_size == ECOMMUNITY_SIZE) {
+ if (p[0] == eval4->val[0] &&
+ p[1] == eval4->val[1]) {
+ if (overwrite) {
+ memcpy(p, eval4->val,
+ ecom_size);
+ return true;
+ }
+ return false;
+ }
+ } else {
+ if (p[0] == eval6->val[0] &&
+ p[1] == eval6->val[1]) {
+ if (overwrite) {
+ memcpy(p, eval6->val,
+ ecom_size);
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+ int ret = memcmp(p, eval, ecom_size);
+ if (ret == 0)
+ return false;
+ if (ret > 0) {
+ if (!unique)
+ break;
+ if (ins_idx == UINT32_MAX)
+ ins_idx = c;
+ }
+ }
+
+ if (ins_idx == UINT32_MAX)
+ ins_idx = c;
+
+ /* Add the value to the structure with numerical sorting. */
+ ecom->size++;
+ ecom->val = XREALLOC(MTYPE_ECOMMUNITY_VAL, ecom->val,
+ ecom_length_size(ecom, ecom_size));
+
+ memmove(ecom->val + ((ins_idx + 1) * ecom_size),
+ ecom->val + (ins_idx * ecom_size),
+ (ecom->size - 1 - ins_idx) * ecom_size);
+ memcpy(ecom->val + (ins_idx * ecom_size),
+ eval, ecom_size);
+
+ return true;
+}
+
+/* Add a new Extended Communities value to Extended Communities
+ * Attribute structure. When the value is already exists in the
+ * structure, we don't add the value. Newly added value is sorted by
+ * numerical order. When the value is added to the structure return 1
+ * else return 0.
+ */
+bool ecommunity_add_val(struct ecommunity *ecom, struct ecommunity_val *eval,
+ bool unique, bool overwrite)
+{
+ return ecommunity_add_val_internal(ecom, (const void *)eval, unique,
+ overwrite, ECOMMUNITY_SIZE);
+}
+
+bool ecommunity_add_val_ipv6(struct ecommunity *ecom,
+ struct ecommunity_val_ipv6 *eval,
+ bool unique, bool overwrite)
+{
+ return ecommunity_add_val_internal(ecom, (const void *)eval, unique,
+ overwrite, IPV6_ECOMMUNITY_SIZE);
+}
+
+static struct ecommunity *
+ecommunity_uniq_sort_internal(struct ecommunity *ecom,
+ unsigned short ecom_size)
+{
+ uint32_t i;
+ struct ecommunity *new;
+ const void *eval;
+
+ if (!ecom)
+ return NULL;
+
+ new = ecommunity_new();
+ new->unit_size = ecom_size;
+ new->disable_ieee_floating = ecom->disable_ieee_floating;
+
+ for (i = 0; i < ecom->size; i++) {
+ eval = (void *)(ecom->val + (i * ecom_size));
+ ecommunity_add_val_internal(new, eval, false, false, ecom_size);
+ }
+ return new;
+}
+
+/* This function takes pointer to Extended Communites structure then
+ * create a new Extended Communities structure by uniq and sort each
+ * Extended Communities value.
+ */
+struct ecommunity *ecommunity_uniq_sort(struct ecommunity *ecom)
+{
+ return ecommunity_uniq_sort_internal(ecom, ECOMMUNITY_SIZE);
+}
+
+/* Parse Extended Communites Attribute in BGP packet. */
+static struct ecommunity *ecommunity_parse_internal(uint8_t *pnt,
+ unsigned short length,
+ unsigned short size_ecom,
+ bool disable_ieee_floating)
+{
+ struct ecommunity tmp;
+ struct ecommunity *new;
+
+ /* Length check. */
+ if (length % size_ecom)
+ return NULL;
+
+ /* Prepare tmporary structure for making a new Extended Communities
+ Attribute. */
+ tmp.size = length / size_ecom;
+ tmp.val = pnt;
+ tmp.disable_ieee_floating = disable_ieee_floating;
+
+ /* Create a new Extended Communities Attribute by uniq and sort each
+ Extended Communities value */
+ new = ecommunity_uniq_sort_internal(&tmp, size_ecom);
+
+ return ecommunity_intern(new);
+}
+
+struct ecommunity *ecommunity_parse(uint8_t *pnt, unsigned short length,
+ bool disable_ieee_floating)
+{
+ return ecommunity_parse_internal(pnt, length, ECOMMUNITY_SIZE,
+ disable_ieee_floating);
+}
+
+struct ecommunity *ecommunity_parse_ipv6(uint8_t *pnt, unsigned short length,
+ bool disable_ieee_floating)
+{
+ return ecommunity_parse_internal(pnt, length, IPV6_ECOMMUNITY_SIZE,
+ disable_ieee_floating);
+}
+
+/* Duplicate the Extended Communities Attribute structure. */
+struct ecommunity *ecommunity_dup(struct ecommunity *ecom)
+{
+ struct ecommunity *new;
+
+ new = XCALLOC(MTYPE_ECOMMUNITY, sizeof(struct ecommunity));
+ new->size = ecom->size;
+ new->unit_size = ecom->unit_size;
+ if (new->size) {
+ new->val = XMALLOC(MTYPE_ECOMMUNITY_VAL,
+ ecom->size * ecom->unit_size);
+ memcpy(new->val, ecom->val,
+ (size_t)ecom->size * (size_t)ecom->unit_size);
+ } else
+ new->val = NULL;
+ return new;
+}
+
+/* Return string representation of ecommunities attribute. */
+char *ecommunity_str(struct ecommunity *ecom)
+{
+ if (!ecom->str)
+ ecom->str =
+ ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_DISPLAY, 0);
+ return ecom->str;
+}
+
+/* Merge two Extended Communities Attribute structure. */
+struct ecommunity *ecommunity_merge(struct ecommunity *ecom1,
+ struct ecommunity *ecom2)
+{
+ ecom1->val = XREALLOC(MTYPE_ECOMMUNITY_VAL, ecom1->val,
+ (size_t)(ecom1->size + ecom2->size)
+ * (size_t)ecom1->unit_size);
+
+ memcpy(ecom1->val + (ecom1->size * ecom1->unit_size), ecom2->val,
+ (size_t)ecom2->size * (size_t)ecom1->unit_size);
+ ecom1->size += ecom2->size;
+
+ return ecom1;
+}
+
+/* Intern Extended Communities Attribute. */
+struct ecommunity *ecommunity_intern(struct ecommunity *ecom)
+{
+ struct ecommunity *find;
+
+ assert(ecom->refcnt == 0);
+ find = (struct ecommunity *)hash_get(ecomhash, ecom, hash_alloc_intern);
+ if (find != ecom)
+ ecommunity_free(&ecom);
+
+ find->refcnt++;
+
+ if (!find->str)
+ find->str =
+ ecommunity_ecom2str(find, ECOMMUNITY_FORMAT_DISPLAY, 0);
+
+ return find;
+}
+
+/* Unintern Extended Communities Attribute. */
+void ecommunity_unintern(struct ecommunity **ecom)
+{
+ struct ecommunity *ret;
+
+ if (!*ecom)
+ return;
+
+ if ((*ecom)->refcnt)
+ (*ecom)->refcnt--;
+
+ /* Pull off from hash. */
+ if ((*ecom)->refcnt == 0) {
+ /* Extended community must be in the hash. */
+ ret = (struct ecommunity *)hash_release(ecomhash, *ecom);
+ assert(ret != NULL);
+
+ ecommunity_free(ecom);
+ }
+}
+
+/* Utinity function to make hash key. */
+unsigned int ecommunity_hash_make(const void *arg)
+{
+ const struct ecommunity *ecom = arg;
+ int size = ecom->size * ecom->unit_size;
+
+ return jhash(ecom->val, size, 0x564321ab);
+}
+
+/* Compare two Extended Communities Attribute structure. */
+bool ecommunity_cmp(const void *arg1, const void *arg2)
+{
+ const struct ecommunity *ecom1 = arg1;
+ const struct ecommunity *ecom2 = arg2;
+
+ if (ecom1 == NULL && ecom2 == NULL)
+ return true;
+
+ if (ecom1 == NULL || ecom2 == NULL)
+ return false;
+
+ if (ecom1->unit_size != ecom2->unit_size)
+ return false;
+
+ return (ecom1->size == ecom2->size
+ && memcmp(ecom1->val, ecom2->val, ecom1->size *
+ 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)
+{
+ ecomhash = hash_create(ecommunity_hash_make, ecommunity_cmp,
+ "BGP ecommunity hash");
+}
+
+void ecommunity_finish(void)
+{
+ hash_clean_and_free(&ecomhash, (void (*)(void *))ecommunity_hash_free);
+}
+
+/* Extended Communities token enum. */
+enum ecommunity_token {
+ ecommunity_token_unknown = 0,
+ ecommunity_token_rt,
+ ecommunity_token_nt,
+ ecommunity_token_soo,
+ ecommunity_token_color,
+ ecommunity_token_val,
+ ecommunity_token_rt6,
+ ecommunity_token_val6,
+};
+
+static const char *ecommunity_origin_validation_state2str(
+ enum ecommunity_origin_validation_states state)
+{
+ switch (state) {
+ case ECOMMUNITY_ORIGIN_VALIDATION_STATE_VALID:
+ return "valid";
+ case ECOMMUNITY_ORIGIN_VALIDATION_STATE_NOTFOUND:
+ return "not-found";
+ case ECOMMUNITY_ORIGIN_VALIDATION_STATE_INVALID:
+ return "invalid";
+ case ECOMMUNITY_ORIGIN_VALIDATION_STATE_NOTUSED:
+ return "not-used";
+ }
+
+ return "ERROR";
+}
+
+static void ecommunity_origin_validation_state_str(char *buf, size_t bufsz,
+ uint8_t *ptr)
+{
+ /* Origin Validation State is encoded in the last octet
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | 0x43 | 0x00 | Reserved |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Reserved |validationstate|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ uint8_t state = *(ptr + ECOMMUNITY_SIZE - 3);
+
+ snprintf(buf, bufsz, "OVS:%s",
+ ecommunity_origin_validation_state2str(state));
+
+ (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,
+ int format)
+{
+ /*
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | 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, "%s%pI4%s",
+ format == ECOMMUNITY_FORMAT_COMMUNITY_LIST ? "nt " : "NT:",
+ &node_id,
+ format == ECOMMUNITY_FORMAT_COMMUNITY_LIST ? ":0" : "");
+
+ (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,
+ struct in6_addr *ip6,
+ uint32_t val,
+ void *eval_ptr)
+{
+ struct ecommunity_val *eval = (struct ecommunity_val *)eval_ptr;
+ struct ecommunity_val_ipv6 *eval6 =
+ (struct ecommunity_val_ipv6 *)eval_ptr;
+
+ assert(eval);
+ if (type == ECOMMUNITY_ENCODE_AS) {
+ if (as > BGP_AS_MAX)
+ return -1;
+ } else if (type == ECOMMUNITY_ENCODE_IP
+ || type == ECOMMUNITY_ENCODE_AS4) {
+ if (val > UINT16_MAX)
+ return -1;
+ } else if (type == ECOMMUNITY_ENCODE_TRANS_EXP &&
+ sub_type == ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6 &&
+ (!ip6 || val > UINT16_MAX)) {
+ return -1;
+ }
+
+ /* Fill in the values. */
+ eval->val[0] = type;
+ if (!trans)
+ eval->val[0] |= ECOMMUNITY_FLAG_NON_TRANSITIVE;
+ eval->val[1] = sub_type;
+ if (type == ECOMMUNITY_ENCODE_AS) {
+ encode_route_target_as(as, val, eval, trans);
+ } else if (type == ECOMMUNITY_ENCODE_IP) {
+ if (sub_type == ECOMMUNITY_NODE_TARGET)
+ encode_node_target(ip, eval, trans);
+ else
+ encode_route_target_ip(ip, val, eval, trans);
+ } else if (type == ECOMMUNITY_ENCODE_TRANS_EXP &&
+ sub_type == ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6) {
+ 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);
+ }
+
+ return 0;
+}
+
+/*
+ * Encode BGP extended community from passed values. Supports types
+ * defined in RFC 4360 and well-known sub-types.
+ */
+static int ecommunity_encode(uint8_t type, uint8_t sub_type, int trans, as_t as,
+ struct in_addr ip, uint32_t val,
+ struct ecommunity_val *eval)
+{
+ return ecommunity_encode_internal(type, sub_type, trans, as,
+ &ip, NULL, val, (void *)eval);
+}
+
+/* Get next Extended Communities token from the string. */
+static const char *ecommunity_gettoken(const char *str, void *eval_ptr,
+ enum ecommunity_token *token, int type)
+{
+ int ret;
+ int dot = 0;
+ int digit = 0;
+ int separator = 0;
+ const char *p = str;
+ char *endptr;
+ struct in_addr ip;
+ struct in6_addr ip6;
+ as_t as = 0;
+ uint32_t val = 0;
+ 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)) {
+ p++;
+ str++;
+ }
+
+ /* Check the end of the line. */
+ if (*p == '\0')
+ return NULL;
+
+ /* "rt", "nt", "soo", and "color" keyword parse. */
+ /* "rt" */
+ if (tolower((unsigned char)*p) == 'r') {
+ p++;
+ if (tolower((unsigned char)*p) == 't') {
+ p++;
+ if (*p != '\0' && tolower((int)*p) == '6')
+ *token = ecommunity_token_rt6;
+ else
+ *token = ecommunity_token_rt;
+ return p;
+ }
+ if (isspace((unsigned char)*p) || *p == '\0') {
+ *token = ecommunity_token_rt;
+ return p;
+ }
+ goto error;
+ }
+
+ /* "nt" */
+ 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" */
+ if (tolower((unsigned char)*p) == 's') {
+ p++;
+ if (tolower((unsigned char)*p) == 'o') {
+ p++;
+ if (tolower((unsigned char)*p) == 'o') {
+ p++;
+ *token = ecommunity_token_soo;
+ return p;
+ }
+ if (isspace((unsigned char)*p) || *p == '\0') {
+ *token = ecommunity_token_soo;
+ return p;
+ }
+ goto error;
+ }
+ if (isspace((unsigned char)*p) || *p == '\0') {
+ *token = ecommunity_token_soo;
+ return p;
+ }
+ goto error;
+ }
+
+ /* "color" */
+ if (tolower((unsigned char)*p) == 'c') {
+ ptr_color = &str_color[0];
+ for (unsigned int i = 0; i < 5; i++) {
+ if (tolower((unsigned char)*p) != *ptr_color)
+ break;
+
+ p++;
+ ptr_color++;
+ }
+ if (isspace((unsigned char)*p) || *p == '\0') {
+ *token = ecommunity_token_color;
+ return p;
+ }
+ goto error;
+ }
+ /* What a mess, there are several possibilities:
+ *
+ * a) A.B.C.D:MN
+ * b) EF:OPQR
+ * c) GHJK:MN
+ * d) <IPV6>:MN (only with rt6)
+ *
+ * A.B.C.D: Four Byte IP
+ * EF: Two byte ASN
+ * GHJK: Four-byte ASN
+ * MN: Two byte value
+ * OPQR: Four byte value
+ *
+ */
+ /* IPv6 case : look for last ':' */
+ if (*token == ecommunity_token_rt6 ||
+ *token == ecommunity_token_val6) {
+ char *limit;
+
+ limit = endptr = strrchr(p, ':');
+ if (!endptr)
+ goto error;
+
+ endptr++;
+ errno = 0;
+ tmp_as = strtoul(endptr, &endptr, 10);
+ /* 'unsigned long' is a uint64 on 64-bit
+ * systems, and uint32 on 32-bit systems. So for
+ * 64-bit we can just directly check the value
+ * against BGP_AS4_MAX/UINT32_MAX, and for
+ * 32-bit we can check for errno (set to ERANGE
+ * upon overflow).
+ */
+ if (*endptr != '\0' || tmp_as == BGP_AS4_MAX || errno)
+ goto error;
+ as = (as_t)tmp_as;
+
+ memcpy(buf, p, (limit - p));
+ buf[limit - p] = '\0';
+ ret = inet_pton(AF_INET6, buf, &ip6);
+ if (ret == 0)
+ goto error;
+
+ ecomm_type = ECOMMUNITY_ENCODE_TRANS_EXP;
+ if (ecommunity_encode_internal(ecomm_type,
+ ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6,
+ 1, 0, NULL, &ip6, as, eval_ptr))
+ goto error;
+
+ *token = ecommunity_token_val6;
+ while (isdigit((int)*p) || *p == ':' || *p == '.') {
+ p++;
+ }
+ return p;
+ }
+ while (isdigit((unsigned char)*p) || *p == ':' || *p == '.') {
+ if (*p == ':') {
+ if (separator)
+ goto error;
+
+ separator = 1;
+ digit = 0;
+
+ if ((p - str) > INET_ADDRSTRLEN)
+ goto error;
+ memset(buf, 0, INET_ADDRSTRLEN + 1);
+ memcpy(buf, str, p - str);
+
+ if (dot) {
+ /* Parsing A.B.C.D in:
+ * A.B.C.D:MN
+ */
+ ret = inet_aton(buf, &ip);
+ if (ret == 0)
+ goto error;
+ } else {
+ /* ASN */
+ errno = 0;
+ tmp_as = strtoul(buf, &endptr, 10);
+ /* 'unsigned long' is a uint64 on 64-bit
+ * systems, and uint32 on 32-bit systems. So for
+ * 64-bit we can just directly check the value
+ * against BGP_AS4_MAX/UINT32_MAX, and for
+ * 32-bit we can check for errno (set to ERANGE
+ * upon overflow).
+ */
+ if (*endptr != '\0' || tmp_as > BGP_AS4_MAX ||
+ errno)
+ goto error;
+ as = (as_t)tmp_as;
+ }
+ } else if (*p == '.') {
+ if (separator)
+ goto error;
+ dot++;
+ if (dot > 4)
+ goto error;
+ } else {
+ digit = 1;
+
+ /* 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 || !val_color_set))
+ goto error;
+
+ /* Encode result into extended community. */
+ if (dot)
+ ecomm_type = ECOMMUNITY_ENCODE_IP;
+ else if (as > BGP_AS_MAX)
+ ecomm_type = ECOMMUNITY_ENCODE_AS4;
+ else if (as > 0)
+ ecomm_type = ECOMMUNITY_ENCODE_AS;
+ 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;
+
+error:
+ *token = ecommunity_token_unknown;
+ return p;
+}
+
+static struct ecommunity *ecommunity_str2com_internal(const char *str, int type,
+ int keyword_included,
+ bool is_ipv6_extcomm)
+{
+ struct ecommunity *ecom = NULL;
+ enum ecommunity_token token = ecommunity_token_unknown;
+ struct ecommunity_val_ipv6 eval;
+ int keyword = 0;
+
+ if (is_ipv6_extcomm)
+ token = ecommunity_token_rt6;
+ 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:
+ case ecommunity_token_color:
+ if (!keyword_included || keyword) {
+ if (ecom)
+ ecommunity_free(&ecom);
+ return NULL;
+ }
+ keyword = 1;
+
+ if (token == ecommunity_token_rt ||
+ token == ecommunity_token_rt6)
+ type = ECOMMUNITY_ROUTE_TARGET;
+ if (token == ecommunity_token_soo)
+ type = ECOMMUNITY_SITE_ORIGIN;
+ 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) {
+ if (!keyword) {
+ ecommunity_free(&ecom);
+ return NULL;
+ }
+ keyword = 0;
+ }
+ if (ecom == NULL)
+ ecom = ecommunity_new();
+ eval.val[1] = type;
+ ecommunity_add_val_internal(ecom, (void *)&eval,
+ false, false,
+ ecom->unit_size);
+ break;
+ case ecommunity_token_val6:
+ if (keyword_included) {
+ if (!keyword) {
+ ecommunity_free(&ecom);
+ return NULL;
+ }
+ keyword = 0;
+ }
+ if (ecom == NULL)
+ ecom = ecommunity_new();
+ ecom->unit_size = IPV6_ECOMMUNITY_SIZE;
+ eval.val[1] = type;
+ ecommunity_add_val_internal(ecom, (void *)&eval, false, false,
+ ecom->unit_size);
+ break;
+ case ecommunity_token_unknown:
+ if (ecom)
+ ecommunity_free(&ecom);
+ return NULL;
+ }
+ }
+ return ecom;
+}
+
+/* Convert string to extended community attribute.
+ *
+ * When type is already known, please specify both str and type. str
+ * should not include keyword such as "rt" and "soo". Type is
+ * ECOMMUNITY_ROUTE_TARGET or ECOMMUNITY_SITE_ORIGIN.
+ * keyword_included should be zero.
+ *
+ * For example route-map's "set extcommunity" command case:
+ *
+ * "rt 100:1 100:2 100:3" -> str = "100:1 100:2 100:3"
+ * type = ECOMMUNITY_ROUTE_TARGET
+ * keyword_included = 0
+ *
+ * "soo 100:1" -> str = "100:1"
+ * type = ECOMMUNITY_SITE_ORIGIN
+ * keyword_included = 0
+ *
+ * When string includes keyword for each extended community value.
+ * Please specify keyword_included as non-zero value.
+ *
+ * For example standard extcommunity-list case:
+ *
+ * "rt 100:1 rt 100:2 soo 100:1" -> str = "rt 100:1 rt 100:2 soo 100:1"
+ * type = 0
+ * keyword_include = 1
+ */
+struct ecommunity *ecommunity_str2com(const char *str, int type,
+ int keyword_included)
+{
+ return ecommunity_str2com_internal(str, type,
+ keyword_included, false);
+}
+
+struct ecommunity *ecommunity_str2com_ipv6(const char *str, int type,
+ int keyword_included)
+{
+ return ecommunity_str2com_internal(str, type,
+ keyword_included, true);
+}
+
+static int ecommunity_rt_soo_str_internal(char *buf, size_t bufsz,
+ const uint8_t *pnt, int type,
+ int sub_type, int format,
+ unsigned short ecom_size)
+{
+ int len = 0;
+ const char *prefix;
+ char buf_local[INET6_ADDRSTRLEN];
+
+ /* For parse Extended Community attribute tupple. */
+ struct ecommunity_as eas;
+ struct ecommunity_ip eip;
+ struct ecommunity_ip6 eip6;
+
+ /* Determine prefix for string, if any. */
+ switch (format) {
+ case ECOMMUNITY_FORMAT_COMMUNITY_LIST:
+ prefix = (sub_type == ECOMMUNITY_ROUTE_TARGET ? "rt " : "soo ");
+ break;
+ case ECOMMUNITY_FORMAT_DISPLAY:
+ prefix = (sub_type == ECOMMUNITY_ROUTE_TARGET ? "RT:" : "SoO:");
+ break;
+ case ECOMMUNITY_FORMAT_ROUTE_MAP:
+ prefix = "";
+ break;
+ default:
+ prefix = "";
+ break;
+ }
+
+ /* Put string into buffer. */
+ if (type == ECOMMUNITY_ENCODE_AS4) {
+ pnt = ptr_get_be32(pnt, &eas.as);
+ eas.val = (*pnt++ << 8);
+ eas.val |= (*pnt++);
+
+ len = snprintf(buf, bufsz, "%s%u:%u", prefix, eas.as, eas.val);
+ } else if (type == ECOMMUNITY_ENCODE_AS) {
+ if (ecom_size == ECOMMUNITY_SIZE) {
+ eas.as = (*pnt++ << 8);
+ eas.as |= (*pnt++);
+ pnt = ptr_get_be32(pnt, &eas.val);
+
+ len = snprintf(buf, bufsz, "%s%u:%u", prefix, eas.as,
+ eas.val);
+ } else {
+ /* this is an IPv6 ext community
+ * first 16 bytes stands for IPv6 addres
+ */
+ memcpy(&eip6.ip, pnt, 16);
+ pnt += 16;
+ eip6.val = (*pnt++ << 8);
+ eip6.val |= (*pnt++);
+
+ inet_ntop(AF_INET6, &eip6.ip, buf_local,
+ sizeof(buf_local));
+ len = snprintf(buf, bufsz, "%s%s:%u", prefix,
+ buf_local, eip6.val);
+ }
+ } else if (type == ECOMMUNITY_ENCODE_IP) {
+ memcpy(&eip.ip, pnt, 4);
+ pnt += 4;
+ eip.val = (*pnt++ << 8);
+ eip.val |= (*pnt++);
+
+ len = snprintfrr(buf, bufsz, "%s%pI4:%u", prefix, &eip.ip,
+ eip.val);
+ }
+
+ /* consume value */
+ (void)pnt;
+
+ return len;
+}
+
+static int ecommunity_rt_soo_str(char *buf, size_t bufsz, const uint8_t *pnt,
+ int type, int sub_type, int format)
+{
+ return ecommunity_rt_soo_str_internal(buf, bufsz, pnt, type,
+ sub_type, format,
+ ECOMMUNITY_SIZE);
+}
+
+/* Helper function to convert IEEE-754 Floating Point to uint32 */
+static uint32_t ieee_float_uint32_to_uint32(uint32_t u)
+{
+ union {
+ float r;
+ uint32_t d;
+ } f = {.d = u};
+
+ return (uint32_t)f.r;
+}
+
+static int ecommunity_lb_str(char *buf, size_t bufsz, const uint8_t *pnt,
+ bool disable_ieee_floating)
+{
+ int len = 0;
+ as_t as;
+ uint32_t bw_tmp, bw;
+ char bps_buf[20] = {0};
+
+#define ONE_GBPS_BYTES (1000 * 1000 * 1000 / 8)
+#define ONE_MBPS_BYTES (1000 * 1000 / 8)
+#define ONE_KBPS_BYTES (1000 / 8)
+
+ as = (*pnt++ << 8);
+ as |= (*pnt++);
+ (void)ptr_get_be32(pnt, &bw_tmp);
+
+ bw = disable_ieee_floating ? bw_tmp
+ : ieee_float_uint32_to_uint32(bw_tmp);
+
+ if (bw >= ONE_GBPS_BYTES)
+ snprintf(bps_buf, sizeof(bps_buf), "%.3f Gbps",
+ (float)(bw / ONE_GBPS_BYTES));
+ else if (bw >= ONE_MBPS_BYTES)
+ snprintf(bps_buf, sizeof(bps_buf), "%.3f Mbps",
+ (float)(bw / ONE_MBPS_BYTES));
+ else if (bw >= ONE_KBPS_BYTES)
+ snprintf(bps_buf, sizeof(bps_buf), "%.3f Kbps",
+ (float)(bw / ONE_KBPS_BYTES));
+ else
+ snprintf(bps_buf, sizeof(bps_buf), "%u bps", bw * 8);
+
+ len = snprintf(buf, bufsz, "LB:%u:%u (%s)", as, bw, bps_buf);
+ return len;
+}
+
+/* Convert extended community attribute to string.
+ * Due to historical reason of industry standard implementation, there
+ * are three types of format:
+ *
+ * route-map set extcommunity format:
+ * "rt 100:1 100:2soo 100:3"
+ *
+ * extcommunity-list:
+ * "rt 100:1 rt 100:2 soo 100:3"
+ *
+ * show bgp:
+ * "RT:100:1 RT:100:2 SoO:100:3"
+ *
+ * For each format please use below definition for format:
+ * ECOMMUNITY_FORMAT_ROUTE_MAP
+ * ECOMMUNITY_FORMAT_COMMUNITY_LIST
+ * ECOMMUNITY_FORMAT_DISPLAY
+ *
+ * Filter is added to display only ECOMMUNITY_ROUTE_TARGET in some cases.
+ * 0 value displays all.
+ */
+char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter)
+{
+ uint32_t i;
+ uint8_t *pnt;
+ uint8_t type = 0;
+ uint8_t sub_type = 0;
+ int str_size;
+ char *str_buf;
+
+ if (!ecom || ecom->size == 0)
+ return XCALLOC(MTYPE_ECOMMUNITY_STR, 1);
+
+ /* ecom strlen + space + null term */
+ str_size = (ecom->size * (ECOMMUNITY_STRLEN + 1)) + 1;
+ str_buf = XCALLOC(MTYPE_ECOMMUNITY_STR, str_size);
+
+ char encbuf[128];
+
+ for (i = 0; i < ecom->size; i++) {
+ int unk_ecom = 0;
+ memset(encbuf, 0x00, sizeof(encbuf));
+
+ /* Space between each value. */
+ if (i > 0)
+ strlcat(str_buf, " ", str_size);
+
+ /* Retrieve value field */
+ pnt = ecom->val + (i * ecom->unit_size);
+
+ /* High-order octet is the type */
+ type = *pnt++;
+
+ if (type == ECOMMUNITY_ENCODE_AS || type == ECOMMUNITY_ENCODE_IP
+ || type == ECOMMUNITY_ENCODE_AS4) {
+ /* Low-order octet of type. */
+ sub_type = *pnt++;
+ if (sub_type != ECOMMUNITY_ROUTE_TARGET
+ && sub_type != ECOMMUNITY_SITE_ORIGIN) {
+ if (sub_type ==
+ ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4 &&
+ type == ECOMMUNITY_ENCODE_IP) {
+ struct in_addr *ipv4 =
+ (struct in_addr *)pnt;
+ snprintfrr(encbuf, sizeof(encbuf),
+ "NH:%pI4:%d", ipv4, pnt[5]);
+ } else if (sub_type ==
+ ECOMMUNITY_LINK_BANDWIDTH &&
+ type == ECOMMUNITY_ENCODE_AS) {
+ 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,
+ format);
+ } else
+ unk_ecom = 1;
+ } else {
+ ecommunity_rt_soo_str(encbuf, sizeof(encbuf),
+ pnt, type, sub_type,
+ format);
+ }
+ } else if (type == ECOMMUNITY_ENCODE_OPAQUE) {
+ if (filter == ECOMMUNITY_ROUTE_TARGET)
+ continue;
+ if (*pnt == ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP) {
+ uint16_t tunneltype;
+ memcpy(&tunneltype, pnt + 5, 2);
+ tunneltype = ntohs(tunneltype);
+
+ snprintf(encbuf, sizeof(encbuf), "ET:%d",
+ tunneltype);
+ } 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;
+ }
+ } else if (type == ECOMMUNITY_ENCODE_EVPN) {
+ if (filter == ECOMMUNITY_ROUTE_TARGET)
+ continue;
+ if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC) {
+ struct ethaddr rmac;
+ pnt++;
+ memcpy(&rmac, pnt, ETH_ALEN);
+
+ snprintf(encbuf, sizeof(encbuf),
+ "Rmac:%02x:%02x:%02x:%02x:%02x:%02x",
+ (uint8_t)rmac.octet[0],
+ (uint8_t)rmac.octet[1],
+ (uint8_t)rmac.octet[2],
+ (uint8_t)rmac.octet[3],
+ (uint8_t)rmac.octet[4],
+ (uint8_t)rmac.octet[5]);
+ } else if (*pnt
+ == ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY) {
+ uint32_t seqnum;
+ uint8_t flags = *++pnt;
+
+ memcpy(&seqnum, pnt + 2, 4);
+ seqnum = ntohl(seqnum);
+
+ snprintf(encbuf, sizeof(encbuf), "MM:%u",
+ seqnum);
+
+ if (CHECK_FLAG(
+ flags,
+ ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY_FLAG_STICKY))
+ strlcat(encbuf, ", sticky MAC",
+ sizeof(encbuf));
+ } else if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_ND) {
+ uint8_t flags = *++pnt;
+
+ if (CHECK_FLAG(
+ flags,
+ ECOMMUNITY_EVPN_SUBTYPE_ND_ROUTER_FLAG))
+ strlcpy(encbuf, "ND:Router Flag",
+ sizeof(encbuf));
+ if (CHECK_FLAG(
+ flags,
+ ECOMMUNITY_EVPN_SUBTYPE_PROXY_FLAG))
+ strlcpy(encbuf, "ND:Proxy",
+ sizeof(encbuf));
+ } else if (*pnt
+ == ECOMMUNITY_EVPN_SUBTYPE_ES_IMPORT_RT) {
+ struct ethaddr mac;
+
+ pnt++;
+ memcpy(&mac, pnt, ETH_ALEN);
+ snprintf(encbuf,
+ sizeof(encbuf),
+ "ES-Import-Rt:%02x:%02x:%02x:%02x:%02x:%02x",
+ (uint8_t)mac.octet[0],
+ (uint8_t)mac.octet[1],
+ (uint8_t)mac.octet[2],
+ (uint8_t)mac.octet[3],
+ (uint8_t)mac.octet[4],
+ (uint8_t)mac.octet[5]);
+ } else if (*pnt
+ == ECOMMUNITY_EVPN_SUBTYPE_ESI_LABEL) {
+ uint8_t flags = *++pnt;
+
+ snprintf(encbuf,
+ sizeof(encbuf), "ESI-label-Rt:%s",
+ (flags &
+ ECOMMUNITY_EVPN_SUBTYPE_ESI_SA_FLAG) ?
+ "SA":"AA");
+ } else if (*pnt
+ == ECOMMUNITY_EVPN_SUBTYPE_DF_ELECTION) {
+ uint8_t alg;
+ uint16_t pref;
+ uint16_t bmap;
+
+ alg = *(pnt + 1);
+ memcpy(&bmap, pnt + 2, 2);
+ bmap = ntohs(bmap);
+ memcpy(&pref, pnt + 5, 2);
+ pref = ntohs(pref);
+
+ if (bmap)
+ snprintf(
+ encbuf, sizeof(encbuf),
+ "DF: (alg: %u, bmap: 0x%x pref: %u)",
+ alg, bmap, pref);
+ else
+ snprintf(encbuf, sizeof(encbuf),
+ "DF: (alg: %u, pref: %u)", alg,
+ pref);
+ } else
+ unk_ecom = 1;
+ } else if (type == ECOMMUNITY_ENCODE_REDIRECT_IP_NH) {
+ sub_type = *pnt++;
+ if (sub_type == ECOMMUNITY_REDIRECT_IP_NH) {
+ snprintf(encbuf, sizeof(encbuf),
+ "FS:redirect IP 0x%x", *(pnt + 5));
+ } else
+ unk_ecom = 1;
+ } else if (type == ECOMMUNITY_ENCODE_TRANS_EXP ||
+ type == ECOMMUNITY_EXTENDED_COMMUNITY_PART_2 ||
+ type == ECOMMUNITY_EXTENDED_COMMUNITY_PART_3) {
+ sub_type = *pnt++;
+
+ if (sub_type == ECOMMUNITY_ROUTE_TARGET) {
+ char buf[ECOMMUNITY_STRLEN];
+
+ memset(buf, 0, sizeof(buf));
+ ecommunity_rt_soo_str_internal(buf, sizeof(buf),
+ (const uint8_t *)pnt,
+ type &
+ ~ECOMMUNITY_ENCODE_TRANS_EXP,
+ ECOMMUNITY_ROUTE_TARGET,
+ format,
+ ecom->unit_size);
+ snprintf(encbuf, sizeof(encbuf), "%s", buf);
+ } else if (sub_type ==
+ ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6) {
+ char buf[64];
+
+ memset(buf, 0, sizeof(buf));
+ ecommunity_rt_soo_str_internal(buf, sizeof(buf),
+ (const uint8_t *)pnt,
+ type &
+ ~ECOMMUNITY_ENCODE_TRANS_EXP,
+ ECOMMUNITY_ROUTE_TARGET,
+ ECOMMUNITY_FORMAT_DISPLAY,
+ ecom->unit_size);
+ snprintf(encbuf, sizeof(encbuf),
+ "FS:redirect VRF %s", buf);
+ } else if (sub_type == ECOMMUNITY_REDIRECT_VRF) {
+ char buf[16];
+
+ memset(buf, 0, sizeof(buf));
+ ecommunity_rt_soo_str(buf, sizeof(buf),
+ (const uint8_t *)pnt,
+ type &
+ ~ECOMMUNITY_ENCODE_TRANS_EXP,
+ ECOMMUNITY_ROUTE_TARGET,
+ ECOMMUNITY_FORMAT_DISPLAY);
+ snprintf(encbuf, sizeof(encbuf),
+ "FS:redirect VRF %s", buf);
+ snprintf(encbuf, sizeof(encbuf),
+ "FS:redirect VRF %s", buf);
+ } else if (type != ECOMMUNITY_ENCODE_TRANS_EXP)
+ unk_ecom = 1;
+ else if (sub_type == ECOMMUNITY_TRAFFIC_ACTION) {
+ char action[64];
+
+ if (*(pnt+3) ==
+ 1 << FLOWSPEC_TRAFFIC_ACTION_TERMINAL)
+ strlcpy(action, "terminate (apply)",
+ sizeof(action));
+ else
+ strlcpy(action, "eval stops",
+ sizeof(action));
+
+ if (*(pnt+3) ==
+ 1 << FLOWSPEC_TRAFFIC_ACTION_SAMPLE)
+ strlcat(action, ", sample",
+ sizeof(action));
+
+
+ snprintf(encbuf, sizeof(encbuf), "FS:action %s",
+ action);
+ } else if (sub_type == ECOMMUNITY_TRAFFIC_RATE) {
+ union traffic_rate data;
+
+ data.rate_byte[3] = *(pnt+2);
+ data.rate_byte[2] = *(pnt+3);
+ data.rate_byte[1] = *(pnt+4);
+ data.rate_byte[0] = *(pnt+5);
+ snprintf(encbuf, sizeof(encbuf), "FS:rate %f",
+ data.rate_float);
+ } else if (sub_type == ECOMMUNITY_TRAFFIC_MARKING) {
+ snprintf(encbuf, sizeof(encbuf),
+ "FS:marking %u", *(pnt + 5));
+ } else
+ unk_ecom = 1;
+ } else if (type == ECOMMUNITY_ENCODE_AS_NON_TRANS) {
+ sub_type = *pnt++;
+ if (sub_type == ECOMMUNITY_LINK_BANDWIDTH)
+ ecommunity_lb_str(encbuf, sizeof(encbuf), pnt,
+ 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, format);
+ else
+ unk_ecom = 1;
+ } else if (type == ECOMMUNITY_ENCODE_OPAQUE_NON_TRANS) {
+ sub_type = *pnt++;
+ if (sub_type == ECOMMUNITY_ORIGIN_VALIDATION_STATE)
+ ecommunity_origin_validation_state_str(
+ encbuf, sizeof(encbuf), pnt);
+ else
+ unk_ecom = 1;
+ } else {
+ sub_type = *pnt++;
+ unk_ecom = 1;
+ }
+
+ if (unk_ecom)
+ snprintf(encbuf, sizeof(encbuf), "UNK:%d, %d", type,
+ sub_type);
+
+ int r = strlcat(str_buf, encbuf, str_size);
+ assert(r < str_size);
+ }
+
+ return str_buf;
+}
+
+bool ecommunity_include(struct ecommunity *e1, struct ecommunity *e2)
+{
+ uint32_t i, j;
+
+ if (!e1 || !e2)
+ return false;
+ for (i = 0; i < e1->size; ++i) {
+ for (j = 0; j < e2->size; ++j) {
+ if (!memcmp(e1->val + (i * e1->unit_size),
+ e2->val + (j * e2->unit_size),
+ e1->unit_size))
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ecommunity_match(const struct ecommunity *ecom1,
+ const struct ecommunity *ecom2)
+{
+ uint32_t i = 0;
+ uint32_t j = 0;
+
+ if (ecom1 == NULL && ecom2 == NULL)
+ return true;
+
+ if (ecom1 == NULL || ecom2 == NULL)
+ return false;
+
+ if (ecom1->size < ecom2->size)
+ return false;
+
+ /* Every community on com2 needs to be on com1 for this to match */
+ while (i < ecom1->size && j < ecom2->size) {
+ if (memcmp(ecom1->val + i * ecom1->unit_size,
+ ecom2->val + j * ecom2->unit_size,
+ ecom2->unit_size)
+ == 0)
+ j++;
+ i++;
+ }
+
+ if (j == ecom2->size)
+ return true;
+ else
+ return false;
+}
+
+/* return last occurence of color */
+/* it will be the greatest color value */
+extern uint32_t ecommunity_select_color(const struct ecommunity *ecom)
+{
+
+ uint32_t aux_color = 0;
+ uint8_t *p;
+ uint32_t c = 0;
+
+ /* If the value already exists in the structure return 0. */
+
+ for (p = ecom->val; c < ecom->size; p += ecom->unit_size, c++) {
+ if (p == NULL)
+ break;
+
+ if (p[0] == ECOMMUNITY_ENCODE_OPAQUE &&
+ p[1] == ECOMMUNITY_COLOR)
+ ptr_get_be32((const uint8_t *)&p[4], &aux_color);
+ }
+ return aux_color;
+}
+
+
+/* return first occurence of type */
+extern struct ecommunity_val *ecommunity_lookup(const struct ecommunity *ecom,
+ uint8_t type, uint8_t subtype)
+{
+ uint8_t *p;
+ uint32_t c;
+
+ /* If the value already exists in the structure return 0. */
+ c = 0;
+ for (p = ecom->val; c < ecom->size; p += ecom->unit_size, c++) {
+ if (p == NULL) {
+ continue;
+ }
+ if (p[0] == type && p[1] == subtype)
+ return (struct ecommunity_val *)p;
+ }
+ return NULL;
+}
+
+/* remove ext. community matching type and subtype
+ * return 1 on success ( removed ), 0 otherwise (not present)
+ */
+bool ecommunity_strip(struct ecommunity *ecom, uint8_t type,
+ uint8_t subtype)
+{
+ uint8_t *p, *q, *new;
+ uint32_t c, found = 0;
+ /* When this is fist value, just add it. */
+ if (ecom == NULL || ecom->val == NULL)
+ return false;
+
+ /* Check if any existing ext community matches. */
+ /* Certain extended communities like the Route Target can be present
+ * multiple times, handle that.
+ */
+ c = 0;
+ for (p = ecom->val; c < ecom->size; p += ecom->unit_size, c++) {
+ if (p[0] == type && p[1] == subtype)
+ found++;
+ }
+ /* If no matching ext community exists, return. */
+ if (found == 0)
+ return false;
+
+ /* Handle the case where everything needs to be stripped. */
+ if (found == ecom->size) {
+ XFREE(MTYPE_ECOMMUNITY_VAL, ecom->val);
+ ecom->size = 0;
+ return true;
+ }
+
+ /* Strip matching ext community(ies). */
+ new = XMALLOC(MTYPE_ECOMMUNITY_VAL,
+ (ecom->size - found) * ecom->unit_size);
+ q = new;
+ for (c = 0, p = ecom->val; c < ecom->size; c++, p += ecom->unit_size) {
+ if (!(p[0] == type && p[1] == subtype)) {
+ memcpy(q, p, ecom->unit_size);
+ q += ecom->unit_size;
+ }
+ }
+ XFREE(MTYPE_ECOMMUNITY_VAL, ecom->val);
+ ecom->val = new;
+ ecom->size -= found;
+ return true;
+}
+
+/*
+ * Remove specified extended community value from extended community.
+ * Returns 1 if value was present (and hence, removed), 0 otherwise.
+ */
+bool ecommunity_del_val(struct ecommunity *ecom, struct ecommunity_val *eval)
+{
+ uint8_t *p;
+ uint32_t c, found = 0;
+
+ /* Make sure specified value exists. */
+ if (ecom == NULL || ecom->val == NULL)
+ return false;
+ c = 0;
+ for (p = ecom->val; c < ecom->size; p += ecom->unit_size, c++) {
+ if (!memcmp(p, eval->val, ecom->unit_size)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found == 0)
+ return false;
+
+ /* Delete the selected value */
+ ecom->size--;
+ if (ecom->size) {
+ p = XMALLOC(MTYPE_ECOMMUNITY_VAL, ecom->size * ecom->unit_size);
+ if (c != 0)
+ memcpy(p, ecom->val, c * ecom->unit_size);
+ if ((ecom->size - c) != 0)
+ memcpy(p + (c)*ecom->unit_size,
+ ecom->val + (c + 1) * ecom->unit_size,
+ (ecom->size - c) * ecom->unit_size);
+ XFREE(MTYPE_ECOMMUNITY_VAL, ecom->val);
+ ecom->val = p;
+ } else
+ XFREE(MTYPE_ECOMMUNITY_VAL, ecom->val);
+
+ return true;
+}
+
+int ecommunity_fill_pbr_action(struct ecommunity_val *ecom_eval,
+ struct bgp_pbr_entry_action *api,
+ afi_t afi)
+{
+ if (ecom_eval->val[1] == ECOMMUNITY_TRAFFIC_RATE) {
+ api->action = ACTION_TRAFFICRATE;
+ api->u.r.rate_info[3] = ecom_eval->val[4];
+ api->u.r.rate_info[2] = ecom_eval->val[5];
+ api->u.r.rate_info[1] = ecom_eval->val[6];
+ api->u.r.rate_info[0] = ecom_eval->val[7];
+ } else if (ecom_eval->val[1] == ECOMMUNITY_TRAFFIC_ACTION) {
+ api->action = ACTION_TRAFFIC_ACTION;
+ /* else distribute code is set by default */
+ if (ecom_eval->val[5] & (1 << FLOWSPEC_TRAFFIC_ACTION_TERMINAL))
+ api->u.za.filter |= TRAFFIC_ACTION_TERMINATE;
+ else
+ api->u.za.filter |= TRAFFIC_ACTION_DISTRIBUTE;
+ if (ecom_eval->val[5] == 1 << FLOWSPEC_TRAFFIC_ACTION_SAMPLE)
+ api->u.za.filter |= TRAFFIC_ACTION_SAMPLE;
+
+ } else if (ecom_eval->val[1] == ECOMMUNITY_TRAFFIC_MARKING) {
+ api->action = ACTION_MARKING;
+ api->u.marking_dscp = ecom_eval->val[7];
+ } else if (ecom_eval->val[1] == ECOMMUNITY_REDIRECT_VRF) {
+ /* must use external function */
+ return 0;
+ } else if (ecom_eval->val[1] == ECOMMUNITY_REDIRECT_IP_NH &&
+ afi == AFI_IP) {
+ /* see draft-ietf-idr-flowspec-redirect-ip-02
+ * Q1: how come a ext. community can host ipv6 address
+ * Q2 : from cisco documentation:
+ * Announces the reachability of one or more flowspec NLRI.
+ * When a BGP speaker receives an UPDATE message with the
+ * redirect-to-IP extended community, it is expected to
+ * create a traffic filtering rule for every flow-spec
+ * NLRI in the message that has this path as its best
+ * path. The filter entry matches the IP packets
+ * described in the NLRI field and redirects them or
+ * copies them towards the IPv4 or IPv6 address specified
+ * in the 'Network Address of Next- Hop'
+ * field of the associated MP_REACH_NLRI.
+ */
+ struct ecommunity_ip *ip_ecom = (struct ecommunity_ip *)
+ ecom_eval + 2;
+
+ api->u.zr.redirect_ip_v4 = ip_ecom->ip;
+ } else
+ return -1;
+ return 0;
+}
+
+static struct ecommunity *bgp_aggr_ecommunity_lookup(
+ struct bgp_aggregate *aggregate,
+ struct ecommunity *ecommunity)
+{
+ return hash_lookup(aggregate->ecommunity_hash, ecommunity);
+}
+
+static void *bgp_aggr_ecommunty_hash_alloc(void *p)
+{
+ struct ecommunity *ref = (struct ecommunity *)p;
+ struct ecommunity *ecommunity = NULL;
+
+ ecommunity = ecommunity_dup(ref);
+ return ecommunity;
+}
+
+static void bgp_aggr_ecommunity_prepare(struct hash_bucket *hb, void *arg)
+{
+ struct ecommunity *hb_ecommunity = hb->data;
+ struct ecommunity **aggr_ecommunity = arg;
+
+ if (*aggr_ecommunity)
+ *aggr_ecommunity = ecommunity_merge(*aggr_ecommunity,
+ hb_ecommunity);
+ else
+ *aggr_ecommunity = ecommunity_dup(hb_ecommunity);
+}
+
+void bgp_aggr_ecommunity_remove(void *arg)
+{
+ struct ecommunity *ecommunity = arg;
+
+ ecommunity_free(&ecommunity);
+}
+
+void bgp_compute_aggregate_ecommunity(struct bgp_aggregate *aggregate,
+ struct ecommunity *ecommunity)
+{
+ bgp_compute_aggregate_ecommunity_hash(aggregate, ecommunity);
+ bgp_compute_aggregate_ecommunity_val(aggregate);
+}
+
+
+void bgp_compute_aggregate_ecommunity_hash(struct bgp_aggregate *aggregate,
+ struct ecommunity *ecommunity)
+{
+ struct ecommunity *aggr_ecommunity = NULL;
+
+ if ((aggregate == NULL) || (ecommunity == NULL))
+ return;
+
+ /* Create hash if not already created.
+ */
+ if (aggregate->ecommunity_hash == NULL)
+ aggregate->ecommunity_hash = hash_create(
+ ecommunity_hash_make, ecommunity_cmp,
+ "BGP Aggregator ecommunity hash");
+
+ aggr_ecommunity = bgp_aggr_ecommunity_lookup(aggregate, ecommunity);
+ if (aggr_ecommunity == NULL) {
+ /* Insert ecommunity into hash.
+ */
+ aggr_ecommunity = hash_get(aggregate->ecommunity_hash,
+ ecommunity,
+ bgp_aggr_ecommunty_hash_alloc);
+ }
+
+ /* Increment reference counter.
+ */
+ aggr_ecommunity->refcnt++;
+}
+
+void bgp_compute_aggregate_ecommunity_val(struct bgp_aggregate *aggregate)
+{
+ struct ecommunity *ecommerge = NULL;
+
+ if (aggregate == NULL)
+ return;
+
+ /* Re-compute aggregate's ecommunity.
+ */
+ if (aggregate->ecommunity)
+ ecommunity_free(&aggregate->ecommunity);
+ if (aggregate->ecommunity_hash
+ && aggregate->ecommunity_hash->count) {
+ hash_iterate(aggregate->ecommunity_hash,
+ bgp_aggr_ecommunity_prepare,
+ &aggregate->ecommunity);
+ ecommerge = aggregate->ecommunity;
+ aggregate->ecommunity = ecommunity_uniq_sort(ecommerge);
+ if (ecommerge)
+ ecommunity_free(&ecommerge);
+ }
+}
+
+void bgp_remove_ecommunity_from_aggregate(struct bgp_aggregate *aggregate,
+ struct ecommunity *ecommunity)
+{
+ struct ecommunity *aggr_ecommunity = NULL;
+ struct ecommunity *ret_ecomm = NULL;
+
+ if ((!aggregate)
+ || (!aggregate->ecommunity_hash)
+ || (!ecommunity))
+ return;
+
+ /* Look-up the ecommunity in the hash.
+ */
+ aggr_ecommunity = bgp_aggr_ecommunity_lookup(aggregate, ecommunity);
+ if (aggr_ecommunity) {
+ aggr_ecommunity->refcnt--;
+
+ if (aggr_ecommunity->refcnt == 0) {
+ ret_ecomm = hash_release(aggregate->ecommunity_hash,
+ aggr_ecommunity);
+ ecommunity_free(&ret_ecomm);
+ bgp_compute_aggregate_ecommunity_val(aggregate);
+ }
+ }
+}
+
+void bgp_remove_ecomm_from_aggregate_hash(struct bgp_aggregate *aggregate,
+ struct ecommunity *ecommunity)
+{
+
+ struct ecommunity *aggr_ecommunity = NULL;
+ struct ecommunity *ret_ecomm = NULL;
+
+ if ((!aggregate)
+ || (!aggregate->ecommunity_hash)
+ || (!ecommunity))
+ return;
+
+ /* Look-up the ecommunity in the hash.
+ */
+ aggr_ecommunity = bgp_aggr_ecommunity_lookup(aggregate, ecommunity);
+ if (aggr_ecommunity) {
+ aggr_ecommunity->refcnt--;
+
+ if (aggr_ecommunity->refcnt == 0) {
+ ret_ecomm = hash_release(aggregate->ecommunity_hash,
+ aggr_ecommunity);
+ ecommunity_free(&ret_ecomm);
+ }
+ }
+}
+
+struct ecommunity *
+ecommunity_add_origin_validation_state(enum rpki_states rpki_state,
+ struct ecommunity *old)
+{
+ struct ecommunity *new = NULL;
+ struct ecommunity ovs_ecomm = {0};
+ struct ecommunity_val ovs_eval;
+
+ encode_origin_validation_state(rpki_state, &ovs_eval);
+
+ if (old) {
+ new = ecommunity_dup(old);
+ ecommunity_add_val(new, &ovs_eval, true, true);
+ if (!old->refcnt)
+ ecommunity_free(&old);
+ } else {
+ ovs_ecomm.size = 1;
+ ovs_ecomm.unit_size = ECOMMUNITY_SIZE;
+ ovs_ecomm.val = (uint8_t *)&ovs_eval.val;
+ new = ecommunity_dup(&ovs_ecomm);
+ }
+
+ return new;
+}
+
+/*
+ * return the BGP link bandwidth extended community, if present;
+ * the actual bandwidth is returned via param
+ */
+const uint8_t *ecommunity_linkbw_present(struct ecommunity *ecom, uint32_t *bw)
+{
+ const uint8_t *eval;
+ uint32_t i;
+
+ if (bw)
+ *bw = 0;
+
+ if (!ecom || !ecom->size)
+ return NULL;
+
+ for (i = 0; i < ecom->size; i++) {
+ const uint8_t *pnt;
+ uint8_t type, sub_type;
+ uint32_t bwval;
+
+ eval = pnt = (ecom->val + (i * ECOMMUNITY_SIZE));
+ type = *pnt++;
+ sub_type = *pnt++;
+
+ if ((type == ECOMMUNITY_ENCODE_AS ||
+ type == ECOMMUNITY_ENCODE_AS_NON_TRANS) &&
+ sub_type == ECOMMUNITY_LINK_BANDWIDTH) {
+ pnt += 2; /* bandwidth is encoded as AS:val */
+ pnt = ptr_get_be32(pnt, &bwval);
+ (void)pnt; /* consume value */
+ if (bw)
+ *bw = ecom->disable_ieee_floating
+ ? bwval
+ : ieee_float_uint32_to_uint32(
+ bwval);
+ return eval;
+ }
+ }
+
+ return NULL;
+}
+
+
+struct ecommunity *ecommunity_replace_linkbw(as_t as, struct ecommunity *ecom,
+ uint64_t cum_bw,
+ bool disable_ieee_floating)
+{
+ struct ecommunity *new;
+ struct ecommunity_val lb_eval;
+ const uint8_t *eval;
+ uint8_t type;
+ uint32_t cur_bw;
+
+ /* Nothing to replace if link-bandwidth doesn't exist or
+ * is non-transitive - just return existing extcommunity.
+ */
+ new = ecom;
+ if (!ecom || !ecom->size)
+ return new;
+
+ eval = ecommunity_linkbw_present(ecom, &cur_bw);
+ if (!eval)
+ return new;
+
+ type = *eval;
+ if (type & ECOMMUNITY_FLAG_NON_TRANSITIVE)
+ return new;
+
+ /* Transitive link-bandwidth exists, replace with the passed
+ * (cumulative) bandwidth value. We need to create a new
+ * extcommunity for this - refer to AS-Path replace function
+ * for reference.
+ */
+ if (cum_bw > 0xFFFFFFFF)
+ cum_bw = 0xFFFFFFFF;
+ encode_lb_extcomm(as > BGP_AS_MAX ? BGP_AS_TRANS : as, cum_bw, false,
+ &lb_eval, disable_ieee_floating);
+ new = ecommunity_dup(ecom);
+ ecommunity_add_val(new, &lb_eval, true, true);
+
+ return new;
+}
+
+bool soo_in_ecom(struct ecommunity *ecom, struct ecommunity *soo)
+{
+ if (ecom && soo) {
+ if ((ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_AS,
+ ECOMMUNITY_SITE_ORIGIN) ||
+ ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_AS4,
+ ECOMMUNITY_SITE_ORIGIN) ||
+ ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_IP,
+ ECOMMUNITY_SITE_ORIGIN)) &&
+ ecommunity_include(ecom, soo))
+ return true;
+ }
+ return false;
+}