| /* BGP Large Communities Attribute |
| |
| Copyright (C) 2016 Keyur Patel <keyur@arrcus.com> |
| |
| This file is part of GNU Zebra. |
| |
| GNU Zebra is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by the |
| Free Software Foundation; either version 2, or (at your option) any |
| later version. |
| |
| GNU Zebra is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Zebra; see the file COPYING. If not, write to the Free |
| Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA |
| 02111-1307, USA. */ |
| |
| #include <zebra.h> |
| |
| #include "hash.h" |
| #include "memory.h" |
| #include "prefix.h" |
| #include "command.h" |
| #include "filter.h" |
| |
| #include "bgpd/bgpd.h" |
| #include "bgpd/bgp_lcommunity.h" |
| #include "bgpd/bgp_aspath.h" |
| |
| /* Hash of community attribute. */ |
| static struct hash *lcomhash; |
| |
| /* Allocate a new lcommunities. */ |
| static struct lcommunity * |
| lcommunity_new (void) |
| { |
| return (struct lcommunity *) XCALLOC (MTYPE_LCOMMUNITY, |
| sizeof (struct lcommunity)); |
| } |
| |
| /* Allocate lcommunities. */ |
| void |
| lcommunity_free (struct lcommunity **lcom) |
| { |
| if ((*lcom)->val) |
| XFREE (MTYPE_LCOMMUNITY_VAL, (*lcom)->val); |
| if ((*lcom)->str) |
| XFREE (MTYPE_LCOMMUNITY_STR, (*lcom)->str); |
| XFREE (MTYPE_LCOMMUNITY, *lcom); |
| lcom = NULL; |
| } |
| |
| /* Add a new Large Communities value to Large 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. */ |
| static int |
| lcommunity_add_val (struct lcommunity *lcom, struct lcommunity_val *lval) |
| { |
| u_int8_t *p; |
| int ret; |
| int c; |
| |
| /* When this is fist value, just add it. */ |
| if (lcom->val == NULL) |
| { |
| lcom->size++; |
| lcom->val = XMALLOC (MTYPE_LCOMMUNITY_VAL, lcom_length (lcom)); |
| memcpy (lcom->val, lval->val, LCOMMUNITY_SIZE); |
| return 1; |
| } |
| |
| /* If the value already exists in the structure return 0. */ |
| c = 0; |
| for (p = lcom->val; c < lcom->size; p += LCOMMUNITY_SIZE, c++) |
| { |
| ret = memcmp (p, lval->val, LCOMMUNITY_SIZE); |
| if (ret == 0) |
| return 0; |
| if (ret > 0) |
| break; |
| } |
| |
| /* Add the value to the structure with numerical sorting. */ |
| lcom->size++; |
| lcom->val = XREALLOC (MTYPE_LCOMMUNITY_VAL, lcom->val, lcom_length (lcom)); |
| |
| memmove (lcom->val + (c + 1) * LCOMMUNITY_SIZE, |
| lcom->val + c * LCOMMUNITY_SIZE, |
| (lcom->size - 1 - c) * LCOMMUNITY_SIZE); |
| memcpy (lcom->val + c * LCOMMUNITY_SIZE, lval->val, LCOMMUNITY_SIZE); |
| |
| return 1; |
| } |
| |
| /* This function takes pointer to Large Communites strucutre then |
| create a new Large Communities structure by uniq and sort each |
| Large Communities value. */ |
| struct lcommunity * |
| lcommunity_uniq_sort (struct lcommunity *lcom) |
| { |
| int i; |
| struct lcommunity *new; |
| struct lcommunity_val *lval; |
| |
| if (! lcom) |
| return NULL; |
| |
| new = lcommunity_new (); |
| |
| for (i = 0; i < lcom->size; i++) |
| { |
| lval = (struct lcommunity_val *) (lcom->val + (i * LCOMMUNITY_SIZE)); |
| lcommunity_add_val (new, lval); |
| } |
| return new; |
| } |
| |
| /* Parse Large Communites Attribute in BGP packet. */ |
| struct lcommunity * |
| lcommunity_parse (u_int8_t *pnt, u_short length) |
| { |
| struct lcommunity tmp; |
| struct lcommunity *new; |
| |
| /* Length check. */ |
| if (length % LCOMMUNITY_SIZE) |
| return NULL; |
| |
| /* Prepare tmporary structure for making a new Large Communities |
| Attribute. */ |
| tmp.size = length / LCOMMUNITY_SIZE; |
| tmp.val = pnt; |
| |
| /* Create a new Large Communities Attribute by uniq and sort each |
| Large Communities value */ |
| new = lcommunity_uniq_sort (&tmp); |
| |
| return lcommunity_intern (new); |
| } |
| |
| /* Duplicate the Large Communities Attribute structure. */ |
| struct lcommunity * |
| lcommunity_dup (struct lcommunity *lcom) |
| { |
| struct lcommunity *new; |
| |
| new = XCALLOC (MTYPE_LCOMMUNITY, sizeof (struct lcommunity)); |
| new->size = lcom->size; |
| if (new->size) |
| { |
| new->val = XMALLOC (MTYPE_LCOMMUNITY_VAL, lcom->size * LCOMMUNITY_SIZE); |
| memcpy (new->val, lcom->val, lcom->size * LCOMMUNITY_SIZE); |
| } |
| else |
| new->val = NULL; |
| return new; |
| } |
| |
| /* Retrun string representation of communities attribute. */ |
| char * |
| lcommunity_str (struct lcommunity *lcom) |
| { |
| if (! lcom->str) |
| lcom->str = lcommunity_lcom2str (lcom, LCOMMUNITY_FORMAT_DISPLAY); |
| return lcom->str; |
| } |
| |
| /* Merge two Large Communities Attribute structure. */ |
| struct lcommunity * |
| lcommunity_merge (struct lcommunity *lcom1, struct lcommunity *lcom2) |
| { |
| if (lcom1->val) |
| lcom1->val = XREALLOC (MTYPE_LCOMMUNITY_VAL, lcom1->val, |
| (lcom1->size + lcom2->size) * LCOMMUNITY_SIZE); |
| else |
| lcom1->val = XMALLOC (MTYPE_LCOMMUNITY_VAL, |
| (lcom1->size + lcom2->size) * LCOMMUNITY_SIZE); |
| |
| memcpy (lcom1->val + (lcom1->size * LCOMMUNITY_SIZE), |
| lcom2->val, lcom2->size * LCOMMUNITY_SIZE); |
| lcom1->size += lcom2->size; |
| |
| return lcom1; |
| } |
| |
| /* Intern Large Communities Attribute. */ |
| struct lcommunity * |
| lcommunity_intern (struct lcommunity *lcom) |
| { |
| struct lcommunity *find; |
| |
| assert (lcom->refcnt == 0); |
| |
| find = (struct lcommunity *) hash_get (lcomhash, lcom, hash_alloc_intern); |
| |
| if (find != lcom) |
| lcommunity_free (&lcom); |
| |
| find->refcnt++; |
| |
| if (! find->str) |
| find->str = lcommunity_lcom2str (find, LCOMMUNITY_FORMAT_DISPLAY); |
| |
| return find; |
| } |
| |
| /* Unintern Large Communities Attribute. */ |
| void |
| lcommunity_unintern (struct lcommunity **lcom) |
| { |
| struct lcommunity *ret; |
| |
| if ((*lcom)->refcnt) |
| (*lcom)->refcnt--; |
| |
| /* Pull off from hash. */ |
| if ((*lcom)->refcnt == 0) |
| { |
| /* Large community must be in the hash. */ |
| ret = (struct lcommunity *) hash_release (lcomhash, *lcom); |
| assert (ret != NULL); |
| |
| lcommunity_free (lcom); |
| } |
| } |
| |
| /* Utility function to make hash key. */ |
| unsigned int |
| lcommunity_hash_make (void *arg) |
| { |
| const struct lcommunity *lcom = arg; |
| int size = lcom->size * LCOMMUNITY_SIZE; |
| u_int8_t *pnt = lcom->val; |
| unsigned int key = 0; |
| int c; |
| |
| for (c = 0; c < size; c += LCOMMUNITY_SIZE) |
| { |
| key += pnt[c]; |
| key += pnt[c + 1]; |
| key += pnt[c + 2]; |
| key += pnt[c + 3]; |
| key += pnt[c + 4]; |
| key += pnt[c + 5]; |
| key += pnt[c + 6]; |
| key += pnt[c + 7]; |
| key += pnt[c + 8]; |
| key += pnt[c + 9]; |
| key += pnt[c + 10]; |
| key += pnt[c + 11]; |
| } |
| |
| return key; |
| } |
| |
| /* Compare two Large Communities Attribute structure. */ |
| int |
| lcommunity_cmp (const void *arg1, const void *arg2) |
| { |
| const struct lcommunity *lcom1 = arg1; |
| const struct lcommunity *lcom2 = arg2; |
| |
| return (lcom1->size == lcom2->size |
| && memcmp (lcom1->val, lcom2->val, lcom1->size * LCOMMUNITY_SIZE) == 0); |
| } |
| |
| /* Return communities hash. */ |
| struct hash * |
| lcommunity_hash (void) |
| { |
| return lcomhash; |
| } |
| |
| /* Initialize Large Comminities related hash. */ |
| void |
| lcommunity_init (void) |
| { |
| lcomhash = hash_create (lcommunity_hash_make, lcommunity_cmp); |
| } |
| |
| void |
| lcommunity_finish (void) |
| { |
| hash_free (lcomhash); |
| lcomhash = NULL; |
| } |
| |
| /* Large Communities token enum. */ |
| enum lcommunity_token |
| { |
| lcommunity_token_unknown = 0, |
| lcommunity_token_val, |
| }; |
| |
| /* Get next Large Communities token from the string. */ |
| static const char * |
| lcommunity_gettoken (const char *str, struct lcommunity_val *lval, |
| enum lcommunity_token *token) |
| { |
| const char *p = str; |
| |
| /* Skip white space. */ |
| while (isspace ((int) *p)) |
| { |
| p++; |
| str++; |
| } |
| |
| /* Check the end of the line. */ |
| if (*p == '\0') |
| return NULL; |
| |
| /* Community value. */ |
| if (isdigit ((int) *p)) |
| { |
| int separator = 0; |
| int digit = 0; |
| u_int32_t globaladmin = 0; |
| u_int32_t localdata1 = 0; |
| u_int32_t localdata2 = 0; |
| |
| while (isdigit ((int) *p) || *p == ':') |
| { |
| if (*p == ':') |
| { |
| if (separator == 2) |
| { |
| *token = lcommunity_token_unknown; |
| return NULL; |
| } |
| else |
| { |
| separator++; |
| digit = 0; |
| if (separator == 1) { |
| globaladmin = localdata2; |
| } else { |
| localdata1 = localdata2; |
| } |
| localdata2 = 0; |
| } |
| } |
| else |
| { |
| digit = 1; |
| localdata2 *= 10; |
| localdata2 += (*p - '0'); |
| } |
| p++; |
| } |
| if (! digit) |
| { |
| *token = lcommunity_token_unknown; |
| return NULL; |
| } |
| |
| /* |
| * Copy the large comm. |
| */ |
| lval->val[0] = (globaladmin >> 24) & 0xff; |
| lval->val[1] = (globaladmin >> 16) & 0xff; |
| lval->val[2] = (globaladmin >> 8) & 0xff; |
| lval->val[3] = globaladmin & 0xff; |
| lval->val[4] = (localdata1 >> 24) & 0xff; |
| lval->val[5] = (localdata1 >> 16) & 0xff; |
| lval->val[6] = (localdata1 >> 8) & 0xff; |
| lval->val[7] = localdata1 & 0xff; |
| lval->val[8] = (localdata2 >> 24) & 0xff; |
| lval->val[9] = (localdata2 >> 16) & 0xff; |
| lval->val[10] = (localdata2 >> 8) & 0xff; |
| lval->val[11] = localdata2 & 0xff; |
| |
| *token = lcommunity_token_val; |
| return p; |
| } |
| *token = lcommunity_token_unknown; |
| return p; |
| } |
| |
| /* |
| Convert string to large community attribute. |
| When type is already known, please specify both str and type. |
| |
| When string includes keyword for each large community value. |
| Please specify keyword_included as non-zero value. |
| */ |
| struct lcommunity * |
| lcommunity_str2com (const char *str) |
| { |
| struct lcommunity *lcom = NULL; |
| enum lcommunity_token token = lcommunity_token_unknown; |
| struct lcommunity_val lval; |
| |
| while ((str = lcommunity_gettoken (str, &lval, &token))) |
| { |
| switch (token) |
| { |
| case lcommunity_token_val: |
| if (lcom == NULL) |
| lcom = lcommunity_new (); |
| lcommunity_add_val (lcom, &lval); |
| break; |
| case lcommunity_token_unknown: |
| default: |
| if (lcom) |
| lcommunity_free (&lcom); |
| return NULL; |
| } |
| } |
| return lcom; |
| } |
| |
| int |
| lcommunity_include (struct lcommunity *lcom, u_char *ptr) |
| { |
| int i; |
| u_char *lcom_ptr; |
| |
| for (i = 0; i < lcom->size; i++) { |
| lcom_ptr = lcom->val + (i * LCOMMUNITY_SIZE); |
| if (memcmp (ptr, lcom_ptr, LCOMMUNITY_SIZE) == 0) |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Convert large community attribute to string. |
| The large coms will be in 65535:65531:0 format. |
| */ |
| char * |
| lcommunity_lcom2str (struct lcommunity *lcom, int format) |
| { |
| int i; |
| u_int8_t *pnt; |
| #define LCOMMUNITY_STR_DEFAULT_LEN 40 |
| int str_size; |
| int str_pnt; |
| char *str_buf; |
| int len = 0; |
| int first = 1; |
| u_int32_t globaladmin, localdata1, localdata2; |
| |
| if (lcom->size == 0) |
| { |
| str_buf = XMALLOC (MTYPE_LCOMMUNITY_STR, 1); |
| str_buf[0] = '\0'; |
| return str_buf; |
| } |
| |
| /* Prepare buffer. */ |
| str_buf = XMALLOC (MTYPE_LCOMMUNITY_STR, LCOMMUNITY_STR_DEFAULT_LEN + 1); |
| str_size = LCOMMUNITY_STR_DEFAULT_LEN + 1; |
| str_pnt = 0; |
| |
| for (i = 0; i < lcom->size; i++) |
| { |
| /* Make it sure size is enough. */ |
| while (str_pnt + LCOMMUNITY_STR_DEFAULT_LEN >= str_size) |
| { |
| str_size *= 2; |
| str_buf = XREALLOC (MTYPE_LCOMMUNITY_STR, str_buf, str_size); |
| } |
| |
| /* Space between each value. */ |
| if (! first) |
| str_buf[str_pnt++] = ' '; |
| |
| pnt = lcom->val + (i * 12); |
| |
| globaladmin = (*pnt++ << 24); |
| globaladmin |= (*pnt++ << 16); |
| globaladmin |= (*pnt++ << 8); |
| globaladmin |= (*pnt++); |
| |
| localdata1 = (*pnt++ << 24); |
| localdata1 |= (*pnt++ << 16); |
| localdata1 |= (*pnt++ << 8); |
| localdata1 |= (*pnt++); |
| |
| localdata2 = (*pnt++ << 24); |
| localdata2 |= (*pnt++ << 16); |
| localdata2 |= (*pnt++ << 8); |
| localdata2 |= (*pnt++); |
| |
| len = sprintf( str_buf + str_pnt, "%u:%u:%u", globaladmin, |
| localdata1, localdata2); |
| str_pnt += len; |
| first = 0; |
| } |
| return str_buf; |
| } |
| |
| int |
| lcommunity_match (const struct lcommunity *lcom1, |
| const struct lcommunity *lcom2) |
| { |
| int i = 0; |
| int j = 0; |
| |
| if (lcom1 == NULL && lcom2 == NULL) |
| return 1; |
| |
| if (lcom1 == NULL || lcom2 == NULL) |
| return 0; |
| |
| if (lcom1->size < lcom2->size) |
| return 0; |
| |
| /* Every community on com2 needs to be on com1 for this to match */ |
| while (i < lcom1->size && j < lcom2->size) |
| { |
| if (memcmp (lcom1->val + (i*12), lcom2->val + (j*12), LCOMMUNITY_SIZE) == 0) |
| j++; |
| i++; |
| } |
| |
| if (j == lcom2->size) |
| return 1; |
| else |
| return 0; |
| } |
| |
| /* Delete one lcommunity. */ |
| void |
| lcommunity_del_val (struct lcommunity *lcom, u_char *ptr) |
| { |
| int i = 0; |
| int c = 0; |
| |
| if (! lcom->val) |
| return; |
| |
| while (i < lcom->size) |
| { |
| if (memcmp (lcom->val + i*LCOMMUNITY_SIZE, ptr, LCOMMUNITY_SIZE) == 0) |
| { |
| c = lcom->size -i -1; |
| |
| if (c > 0) |
| memmove (lcom->val + i*LCOMMUNITY_SIZE, lcom->val + (i + 1)*LCOMMUNITY_SIZE, c * LCOMMUNITY_SIZE); |
| |
| lcom->size--; |
| |
| if (lcom->size > 0) |
| lcom->val = XREALLOC (MTYPE_COMMUNITY_VAL, lcom->val, |
| lcom_length (lcom)); |
| else |
| { |
| XFREE (MTYPE_COMMUNITY_VAL, lcom->val); |
| lcom->val = NULL; |
| } |
| return; |
| } |
| i++; |
| } |
| } |