blob: 1bd2dd84e44b292e2257ef512ca444ff3936c8b5 [file] [log] [blame]
/* Community attribute related functions.
Copyright (C) 1998, 2001 Kunihiro Ishiguro
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 "bgpd/bgp_community.h"
/* Hash of community attribute. */
static struct hash *comhash;
/* Allocate a new communities value. */
static struct community *
community_new (void)
{
return (struct community *) XCALLOC (MTYPE_COMMUNITY,
sizeof (struct community));
}
/* Free communities value. */
void
community_free (struct community *com)
{
if (com->val)
XFREE (MTYPE_COMMUNITY_VAL, com->val);
if (com->str)
XFREE (MTYPE_COMMUNITY_STR, com->str);
XFREE (MTYPE_COMMUNITY, com);
}
/* Add one community value to the community. */
static void
community_add_val (struct community *com, u_int32_t val)
{
com->size++;
if (com->val)
com->val = XREALLOC (MTYPE_COMMUNITY_VAL, com->val, com_length (com));
else
com->val = XMALLOC (MTYPE_COMMUNITY_VAL, com_length (com));
val = htonl (val);
memcpy (com_lastval (com), &val, sizeof (u_int32_t));
}
/* Delete one community. */
void
community_del_val (struct community *com, u_int32_t *val)
{
int i = 0;
int c = 0;
if (! com->val)
return;
while (i < com->size)
{
if (memcmp (com->val + i, val, sizeof (u_int32_t)) == 0)
{
c = com->size -i -1;
if (c > 0)
memmove (com->val + i, com->val + (i + 1), c * sizeof (*val));
com->size--;
if (com->size > 0)
com->val = XREALLOC (MTYPE_COMMUNITY_VAL, com->val,
com_length (com));
else
{
XFREE (MTYPE_COMMUNITY_VAL, com->val);
com->val = NULL;
}
return;
}
i++;
}
}
/* Delete all communities listed in com2 from com1 */
struct community *
community_delete (struct community *com1, struct community *com2)
{
int i = 0;
while(i < com2->size)
{
community_del_val (com1, com2->val + i);
i++;
}
return com1;
}
/* Callback function from qsort(). */
static int
community_compare (const void *a1, const void *a2)
{
u_int32_t v1;
u_int32_t v2;
memcpy (&v1, a1, sizeof (u_int32_t));
memcpy (&v2, a2, sizeof (u_int32_t));
v1 = ntohl (v1);
v2 = ntohl (v2);
if (v1 < v2)
return -1;
if (v1 > v2)
return 1;
return 0;
}
int
community_include (struct community *com, u_int32_t val)
{
int i;
val = htonl (val);
for (i = 0; i < com->size; i++)
if (memcmp (&val, com_nthval (com, i), sizeof (u_int32_t)) == 0)
return 1;
return 0;
}
static u_int32_t
community_val_get (struct community *com, int i)
{
u_char *p;
u_int32_t val;
p = (u_char *) com->val;
p += (i * 4);
memcpy (&val, p, sizeof (u_int32_t));
return ntohl (val);
}
/* Sort and uniq given community. */
struct community *
community_uniq_sort (struct community *com)
{
int i;
struct community *new;
u_int32_t val;
if (! com)
return NULL;
new = community_new ();;
for (i = 0; i < com->size; i++)
{
val = community_val_get (com, i);
if (! community_include (new, val))
community_add_val (new, val);
}
qsort (new->val, new->size, sizeof (u_int32_t), community_compare);
return new;
}
/* Convert communities attribute to string.
For Well-known communities value, below keyword is used.
0x0 "internet"
0xFFFFFF01 "no-export"
0xFFFFFF02 "no-advertise"
0xFFFFFF03 "local-AS"
For other values, "AS:VAL" format is used. */
static char *
community_com2str (struct community *com)
{
int i;
char *str;
char *pnt;
int len;
int first;
u_int32_t comval;
u_int16_t as;
u_int16_t val;
if (!com)
return NULL;
/* When communities attribute is empty. */
if (com->size == 0)
{
str = XMALLOC (MTYPE_COMMUNITY_STR, 1);
str[0] = '\0';
return str;
}
/* Memory allocation is time consuming work. So we calculate
required string length first. */
len = 0;
for (i = 0; i < com->size; i++)
{
memcpy (&comval, com_nthval (com, i), sizeof (u_int32_t));
comval = ntohl (comval);
switch (comval)
{
case COMMUNITY_INTERNET:
len += strlen (" internet");
break;
case COMMUNITY_NO_EXPORT:
len += strlen (" no-export");
break;
case COMMUNITY_NO_ADVERTISE:
len += strlen (" no-advertise");
break;
case COMMUNITY_LOCAL_AS:
len += strlen (" local-AS");
break;
default:
len += strlen (" 65536:65535");
break;
}
}
/* Allocate memory. */
str = pnt = XMALLOC (MTYPE_COMMUNITY_STR, len);
first = 1;
/* Fill in string. */
for (i = 0; i < com->size; i++)
{
memcpy (&comval, com_nthval (com, i), sizeof (u_int32_t));
comval = ntohl (comval);
if (first)
first = 0;
else
*pnt++ = ' ';
switch (comval)
{
case COMMUNITY_INTERNET:
strcpy (pnt, "internet");
pnt += strlen ("internet");
break;
case COMMUNITY_NO_EXPORT:
strcpy (pnt, "no-export");
pnt += strlen ("no-export");
break;
case COMMUNITY_NO_ADVERTISE:
strcpy (pnt, "no-advertise");
pnt += strlen ("no-advertise");
break;
case COMMUNITY_LOCAL_AS:
strcpy (pnt, "local-AS");
pnt += strlen ("local-AS");
break;
default:
as = (comval >> 16) & 0xFFFF;
val = comval & 0xFFFF;
sprintf (pnt, "%u:%d", as, val);
pnt += strlen (pnt);
break;
}
}
*pnt = '\0';
return str;
}
/* Intern communities attribute. */
struct community *
community_intern (struct community *com)
{
struct community *find;
/* Assert this community structure is not interned. */
assert (com->refcnt == 0);
/* Lookup community hash. */
find = (struct community *) hash_get (comhash, com, hash_alloc_intern);
/* Arguemnt com is allocated temporary. So when it is not used in
hash, it should be freed. */
if (find != com)
community_free (com);
/* Increment refrence counter. */
find->refcnt++;
/* Make string. */
if (! find->str)
find->str = community_com2str (find);
return find;
}
/* Free community attribute. */
void
community_unintern (struct community **com)
{
struct community *ret;
if ((*com)->refcnt)
(*com)->refcnt--;
/* Pull off from hash. */
if ((*com)->refcnt == 0)
{
/* Community value com must exist in hash. */
ret = (struct community *) hash_release (comhash, *com);
assert (ret != NULL);
community_free (*com);
*com = NULL;
}
}
/* Create new community attribute. */
struct community *
community_parse (u_int32_t *pnt, u_short length)
{
struct community tmp;
struct community *new;
/* If length is malformed return NULL. */
if (length % 4)
return NULL;
/* Make temporary community for hash look up. */
tmp.size = length / 4;
tmp.val = pnt;
new = community_uniq_sort (&tmp);
return community_intern (new);
}
struct community *
community_dup (struct community *com)
{
struct community *new;
new = XCALLOC (MTYPE_COMMUNITY, sizeof (struct community));
new->size = com->size;
if (new->size)
{
new->val = XMALLOC (MTYPE_COMMUNITY_VAL, com->size * 4);
memcpy (new->val, com->val, com->size * 4);
}
else
new->val = NULL;
return new;
}
/* Retrun string representation of communities attribute. */
char *
community_str (struct community *com)
{
if (!com)
return NULL;
if (! com->str)
com->str = community_com2str (com);
return com->str;
}
/* Make hash value of community attribute. This function is used by
hash package.*/
unsigned int
community_hash_make (struct community *com)
{
unsigned char *pnt = (unsigned char *)com->val;
int size = com->size * 4;
unsigned int key = 0;
int c;
for (c = 0; c < size; c += 4)
{
key += pnt[c];
key += pnt[c + 1];
key += pnt[c + 2];
key += pnt[c + 3];
}
return key;
}
int
community_match (const struct community *com1, const struct community *com2)
{
int i = 0;
int j = 0;
if (com1 == NULL && com2 == NULL)
return 1;
if (com1 == NULL || com2 == NULL)
return 0;
if (com1->size < com2->size)
return 0;
/* Every community on com2 needs to be on com1 for this to match */
while (i < com1->size && j < com2->size)
{
if (memcmp (com1->val + i, com2->val + j, sizeof (u_int32_t)) == 0)
j++;
i++;
}
if (j == com2->size)
return 1;
else
return 0;
}
/* If two aspath have same value then return 1 else return 0. This
function is used by hash package. */
int
community_cmp (const struct community *com1, const struct community *com2)
{
if (com1 == NULL && com2 == NULL)
return 1;
if (com1 == NULL || com2 == NULL)
return 0;
if (com1->size == com2->size)
if (memcmp (com1->val, com2->val, com1->size * 4) == 0)
return 1;
return 0;
}
/* Add com2 to the end of com1. */
struct community *
community_merge (struct community *com1, struct community *com2)
{
if (com1->val)
com1->val = XREALLOC (MTYPE_COMMUNITY_VAL, com1->val,
(com1->size + com2->size) * 4);
else
com1->val = XMALLOC (MTYPE_COMMUNITY_VAL, (com1->size + com2->size) * 4);
memcpy (com1->val + com1->size, com2->val, com2->size * 4);
com1->size += com2->size;
return com1;
}
/* Community token enum. */
enum community_token
{
community_token_val,
community_token_no_export,
community_token_no_advertise,
community_token_local_as,
community_token_unknown
};
/* Get next community token from string. */
static const char *
community_gettoken (const char *buf, enum community_token *token,
u_int32_t *val)
{
const char *p = buf;
/* Skip white space. */
while (isspace ((int) *p))
p++;
/* Check the end of the line. */
if (*p == '\0')
return NULL;
/* Well known community string check. */
if (isalpha ((int) *p))
{
if (strncmp (p, "internet", strlen ("internet")) == 0)
{
*val = COMMUNITY_INTERNET;
*token = community_token_no_export;
p += strlen ("internet");
return p;
}
if (strncmp (p, "no-export", strlen ("no-export")) == 0)
{
*val = COMMUNITY_NO_EXPORT;
*token = community_token_no_export;
p += strlen ("no-export");
return p;
}
if (strncmp (p, "no-advertise", strlen ("no-advertise")) == 0)
{
*val = COMMUNITY_NO_ADVERTISE;
*token = community_token_no_advertise;
p += strlen ("no-advertise");
return p;
}
if (strncmp (p, "local-AS", strlen ("local-AS")) == 0)
{
*val = COMMUNITY_LOCAL_AS;
*token = community_token_local_as;
p += strlen ("local-AS");
return p;
}
/* Unknown string. */
*token = community_token_unknown;
return NULL;
}
/* Community value. */
if (isdigit ((int) *p))
{
int separator = 0;
int digit = 0;
u_int32_t community_low = 0;
u_int32_t community_high = 0;
while (isdigit ((int) *p) || *p == ':')
{
if (*p == ':')
{
if (separator)
{
*token = community_token_unknown;
return NULL;
}
else
{
separator = 1;
digit = 0;
community_high = community_low << 16;
community_low = 0;
}
}
else
{
digit = 1;
community_low *= 10;
community_low += (*p - '0');
}
p++;
}
if (! digit)
{
*token = community_token_unknown;
return NULL;
}
*val = community_high + community_low;
*token = community_token_val;
return p;
}
*token = community_token_unknown;
return NULL;
}
/* convert string to community structure */
struct community *
community_str2com (const char *str)
{
struct community *com = NULL;
struct community *com_sort = NULL;
u_int32_t val = 0;
enum community_token token = community_token_unknown;
do
{
str = community_gettoken (str, &token, &val);
switch (token)
{
case community_token_val:
case community_token_no_export:
case community_token_no_advertise:
case community_token_local_as:
if (com == NULL)
com = community_new();
community_add_val (com, val);
break;
case community_token_unknown:
default:
if (com)
community_free (com);
return NULL;
}
} while (str);
if (! com)
return NULL;
com_sort = community_uniq_sort (com);
community_free (com);
return com_sort;
}
/* Return communities hash entry count. */
unsigned long
community_count (void)
{
return comhash->count;
}
/* Return communities hash. */
struct hash *
community_hash (void)
{
return comhash;
}
/* Initialize comminity related hash. */
void
community_init (void)
{
comhash = hash_create ((unsigned int (*) (void *))community_hash_make,
(int (*) (const void *, const void *))community_cmp);
}
void
community_finish (void)
{
hash_free (comhash);
comhash = NULL;
}