/* 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 #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) memcpy (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; }