bgpd: Add support for BGP Large Communities

As described by Michael Lambert <lambert@psc.edu>  to the list:

  Traditional communities are four-octet entities to support two-octet ASNs
  and are usually represented as <asn>:<data>.  Large communities are an
  enhancement to support four-octet ASNs and are 12 octets long, represented
  as <asn>:<data-1>:<data-2>.

  This issue has been tracked in quagga bugzilla ticket #875, which documents
  some of the usage and indicates that some testing has been done.

TODO: Documentation - update doc/bgpd.texi.

* bgp_attr.{c,h}: Add BGP_ATTR_LARGE_COMMUNITIES codepoint. Add
  (struct lcommunity *) to (struct bgp_attr_extra).
* bgp_clist.{c,h}: Large community codepoints and routines.
* bgp_route.c: Display support.
* bgp_routemap.c: 'match lcommunity', 'set large-community' and
  'set large-comm-list'
* bgp_vty.c: Peer configuration, add 'large' to 'neighbor send-community ..'.
  Add "show ip bgp large-community", ""ip large-community-list ...".

Authors: Keyur Patel <keyur@arrcus.com>
         Job Snijders <job@instituut.net>
diff --git a/bgpd/bgp_clist.c b/bgpd/bgp_clist.c
index 800bd01..13bdf8e 100644
--- a/bgpd/bgp_clist.c
+++ b/bgpd/bgp_clist.c
@@ -28,6 +28,7 @@
 #include "bgpd/bgpd.h"
 #include "bgpd/bgp_community.h"
 #include "bgpd/bgp_ecommunity.h"
+#include "bgpd/bgp_lcommunity.h"
 #include "bgpd/bgp_aspath.h"
 #include "bgpd/bgp_regex.h"
 #include "bgpd/bgp_clist.h"
@@ -44,6 +45,8 @@
 	return &ch->community_list;
       case EXTCOMMUNITY_LIST_MASTER:
 	return &ch->extcommunity_list;
+      case LARGE_COMMUNITY_LIST_MASTER:
+	return &ch->lcommunity_list;
       }
   return NULL;
 }
@@ -65,6 +68,10 @@
       if (entry->u.com)
         community_free (entry->u.com);
       break;
+    case LARGE_COMMUNITY_LIST_STANDARD:
+      if (entry->u.lcom)
+        lcommunity_free (&entry->u.lcom);
+      break;
     case EXTCOMMUNITY_LIST_STANDARD:
       /* In case of standard extcommunity-list, configuration string
          is made by ecommunity_ecom2str().  */
@@ -75,6 +82,7 @@
       break;
     case COMMUNITY_LIST_EXPANDED:
     case EXTCOMMUNITY_LIST_EXPANDED:
+    case LARGE_COMMUNITY_LIST_EXPANDED:
       if (entry->config)
         XFREE (MTYPE_COMMUNITY_LIST_CONFIG, entry->config);
       if (entry->reg)
@@ -315,12 +323,17 @@
           if (community_cmp (entry->u.com, arg))
             return entry;
           break;
+	case LARGE_COMMUNITY_LIST_STANDARD:
+          if (lcommunity_cmp (entry->u.lcom, arg))
+            return entry;
+          break;
         case EXTCOMMUNITY_LIST_STANDARD:
           if (ecommunity_cmp (entry->u.ecom, arg))
             return entry;
           break;
         case COMMUNITY_LIST_EXPANDED:
         case EXTCOMMUNITY_LIST_EXPANDED:
+	case LARGE_COMMUNITY_LIST_EXPANDED:
           if (strcmp (entry->config, arg) == 0)
             return entry;
           break;
@@ -446,6 +459,91 @@
   return 0;
 }
 
+static char *
+lcommunity_str_get (struct lcommunity *lcom, int i)
+{
+  struct lcommunity_val lcomval;
+  u_int32_t globaladmin;
+  u_int32_t localdata1;
+  u_int32_t localdata2;
+  char *str;
+  u_char *ptr;
+  char *pnt;
+
+  ptr = lcom->val;
+  ptr += (i * LCOMMUNITY_SIZE);
+
+  memcpy (&lcomval, ptr, LCOMMUNITY_SIZE);
+
+  /* Allocate memory.  48 bytes taken off bgp_lcommunity.c */
+  str = pnt = XMALLOC (MTYPE_LCOMMUNITY_STR, 48);
+
+  ptr = (u_char *)lcomval.val;
+  globaladmin = (*ptr++ << 24);
+  globaladmin |= (*ptr++ << 16);
+  globaladmin |= (*ptr++ << 8);
+  globaladmin |= (*ptr++);
+
+  localdata1 = (*ptr++ << 24);
+  localdata1 |= (*ptr++ << 16);
+  localdata1 |= (*ptr++ << 8);
+  localdata1 |= (*ptr++);
+
+  localdata2 = (*ptr++ << 24);
+  localdata2 |= (*ptr++ << 16);
+  localdata2 |= (*ptr++ << 8);
+  localdata2 |= (*ptr++);
+
+  sprintf (pnt, "%u:%u:%u", globaladmin, localdata1, localdata2);
+  pnt += strlen (pnt);
+  *pnt = '\0';
+
+  return str;
+}
+
+/* Internal function to perform regular expression match for
+ *  * a single community. */
+static int
+lcommunity_regexp_include (regex_t * reg, struct lcommunity *lcom, int i)
+{
+  const char *str;
+
+  /* When there is no communities attribute it is treated as empty
+ *      string.  */
+  if (lcom == NULL || lcom->size == 0)
+    str = "";
+  else
+    str = lcommunity_str_get (lcom, i);
+
+  /* Regular expression match.  */
+  if (regexec (reg, str, 0, NULL, 0) == 0)
+    return 1;
+
+  /* No match.  */
+  return 0;
+}
+
+static int
+lcommunity_regexp_match (struct lcommunity *com, regex_t * reg)
+{
+  const char *str;
+
+  /* When there is no communities attribute it is treated as empty
+     string.  */
+  if (com == NULL || com->size == 0)
+    str = "";
+  else
+    str = lcommunity_str (com);
+
+  /* Regular expression match.  */
+  if (regexec (reg, str, 0, NULL, 0) == 0)
+    return 1;
+
+  /* No match.  */
+  return 0;
+}
+
+
 static int
 ecommunity_regexp_match (struct ecommunity *ecom, regex_t * reg)
 {
@@ -496,6 +594,30 @@
 }
 
 int
+lcommunity_list_match (struct lcommunity *lcom, struct community_list *list)
+{
+  struct community_entry *entry;
+
+  for (entry = list->head; entry; entry = entry->next)
+    {
+      if (entry->any)
+        return entry->direct == COMMUNITY_PERMIT ? 1 : 0;
+
+      if (entry->style == LARGE_COMMUNITY_LIST_STANDARD)
+        {
+          if (lcommunity_match (lcom, entry->u.lcom))
+            return entry->direct == COMMUNITY_PERMIT ? 1 : 0;
+        }
+      else if (entry->style == LARGE_COMMUNITY_LIST_EXPANDED)
+        {
+          if (lcommunity_regexp_match (lcom, entry->reg))
+            return entry->direct == COMMUNITY_PERMIT ? 1 : 0;
+        }
+    }
+  return 0;
+}
+
+int
 ecommunity_list_match (struct ecommunity *ecom, struct community_list *list)
 {
   struct community_entry *entry;
@@ -647,8 +769,13 @@
           if (ecommunity_cmp (entry->u.ecom, new->u.ecom))
             return 1;
           break;
+        case LARGE_COMMUNITY_LIST_STANDARD:
+          if (lcommunity_cmp (entry->u.lcom, new->u.lcom))
+            return 1;
+          break;
         case COMMUNITY_LIST_EXPANDED:
         case EXTCOMMUNITY_LIST_EXPANDED:
+        case LARGE_COMMUNITY_LIST_EXPANDED:
           if (entry->config && new->config
               && strcmp (entry->config, new->config) == 0)
             return 1;
@@ -770,6 +897,186 @@
   return 0;
 }
 
+/* Delete all permitted large communities in the list from com.  */
+struct lcommunity *
+lcommunity_list_match_delete (struct lcommunity *lcom,
+			      struct community_list *list)
+{
+  struct community_entry *entry;
+  u_int32_t com_index_to_delete[lcom->size];
+  u_char *ptr;
+  int delete_index = 0;
+  int i;
+
+  /* Loop over each lcommunity value and evaluate each against the
+   * community-list.  If we need to delete a community value add its index to
+   * com_index_to_delete.
+   */
+
+  for (i = 0; i < lcom->size; i++)
+    {
+      ptr = lcom->val + (i * LCOMMUNITY_SIZE);
+      for (entry = list->head; entry; entry = entry->next)
+        {
+          if (entry->any)
+            {
+              if (entry->direct == COMMUNITY_PERMIT)
+                {
+                  com_index_to_delete[delete_index] = i;
+                  delete_index++;
+                }
+              break;
+            }
+
+          else if ((entry->style == LARGE_COMMUNITY_LIST_STANDARD)
+                   && lcommunity_include (entry->u.lcom, ptr) )
+            {
+              if (entry->direct == COMMUNITY_PERMIT)
+                {
+                  com_index_to_delete[delete_index] = i;
+                  delete_index++;
+                }
+              break;
+            }
+
+          else if ((entry->style == LARGE_COMMUNITY_LIST_STANDARD)
+                   && entry->reg
+		   && lcommunity_regexp_include (entry->reg, lcom, i))
+            {
+              if (entry->direct == COMMUNITY_PERMIT)
+                {
+                  com_index_to_delete[delete_index] = i;
+                  delete_index++;
+                }
+              break;
+            }
+         }
+     }
+
+  /* Delete all of the communities we flagged for deletion */
+
+  for (i = delete_index-1; i >= 0; i--)
+    {
+      ptr = lcom->val + (com_index_to_delete[i] * LCOMMUNITY_SIZE);
+      lcommunity_del_val (lcom, ptr);
+    }
+
+  return lcom;
+}
+
+/* Set lcommunity-list.  */
+int
+lcommunity_list_set (struct community_list_handler *ch,
+		     const char *name, const char *str, int direct, int style)
+{
+  struct community_entry *entry = NULL;
+  struct community_list *list;
+  struct lcommunity *lcom = NULL;
+  regex_t *regex = NULL;
+
+  /* Get community list. */
+  list = community_list_get (ch, name, LARGE_COMMUNITY_LIST_MASTER);
+
+  /* When community-list already has entry, new entry should have same
+     style.  If you want to have mixed style community-list, you can
+     comment out this check.  */
+  if (!community_list_empty_p (list))
+    {
+      struct community_entry *first;
+
+      first = list->head;
+
+      if (style != first->style)
+	{
+	  return (first->style == COMMUNITY_LIST_STANDARD
+		  ? COMMUNITY_LIST_ERR_STANDARD_CONFLICT
+		  : COMMUNITY_LIST_ERR_EXPANDED_CONFLICT);
+	}
+    }
+
+  if (str)
+    {
+      if (style == LARGE_COMMUNITY_LIST_STANDARD)
+	lcom = lcommunity_str2com (str);
+      else
+	regex = bgp_regcomp (str);
+
+      if (! lcom && ! regex)
+	return COMMUNITY_LIST_ERR_MALFORMED_VAL;
+    }
+
+  entry = community_entry_new ();
+  entry->direct = direct;
+  entry->style = style;
+  entry->any = (str ? 0 : 1);
+  entry->u.lcom = lcom;
+  entry->reg = regex;
+  if (lcom)
+    entry->config = lcommunity_lcom2str (lcom, LCOMMUNITY_FORMAT_COMMUNITY_LIST);
+  else if (regex)
+    entry->config = XSTRDUP (MTYPE_COMMUNITY_LIST_CONFIG, str);
+  else
+    entry->config = NULL;
+
+  /* Do not put duplicated community entry.  */
+  if (community_list_dup_check (list, entry))
+    community_entry_free (entry);
+  else
+    community_list_entry_add (list, entry);
+
+  return 0;
+}
+
+/* Unset community-list.  When str is NULL, delete all of
+   community-list entry belongs to the specified name.  */
+int
+lcommunity_list_unset (struct community_list_handler *ch,
+		       const char *name, const char *str,
+		       int direct, int style)
+{
+  struct community_entry *entry = NULL;
+  struct community_list *list;
+  struct lcommunity *lcom = NULL;
+  regex_t *regex = NULL;
+
+  /* Lookup community list.  */
+  list = community_list_lookup (ch, name, LARGE_COMMUNITY_LIST_MASTER);
+  if (list == NULL)
+    return COMMUNITY_LIST_ERR_CANT_FIND_LIST;
+
+  /* Delete all of entry belongs to this community-list.  */
+  if (!str)
+    {
+      community_list_delete (list);
+      return 0;
+    }
+
+  if (style == LARGE_COMMUNITY_LIST_STANDARD)
+    lcom = lcommunity_str2com (str);
+  else
+    regex = bgp_regcomp (str);
+
+  if (! lcom && ! regex)
+    return COMMUNITY_LIST_ERR_MALFORMED_VAL;
+
+  if (lcom)
+    entry = community_list_entry_lookup (list, lcom, direct);
+  else
+    entry = community_list_entry_lookup (list, str, direct);
+
+  if (lcom)
+    lcommunity_free (&lcom);
+  if (regex)
+    bgp_regex_free (regex);
+
+  if (!entry)
+    return COMMUNITY_LIST_ERR_CANT_FIND_LIST;
+
+  community_list_entry_delete (list, entry, style);
+
+  return 0;
+}
+
 /* Set extcommunity-list.  */
 int
 extcommunity_list_set (struct community_list_handler *ch,
@@ -912,6 +1219,12 @@
   while ((list = cm->str.head) != NULL)
     community_list_delete (list);
 
+  cm = &ch->lcommunity_list;
+  while ((list = cm->num.head) != NULL)
+    community_list_delete (list);
+  while ((list = cm->str.head) != NULL)
+    community_list_delete (list);
+
   cm = &ch->extcommunity_list;
   while ((list = cm->num.head) != NULL)
     community_list_delete (list);