blob: cc67e124527f646e1257b5237d54da262080b3d2 [file] [log] [blame]
/* 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++;
}
}