Import Upstream version 1.2.2
[quagga-debian.git] / bgpd / bgp_ecommunity.c
1 /* BGP Extended Communities Attribute
2    Copyright (C) 2000 Kunihiro Ishiguro <kunihiro@zebra.org>
3
4 This file is part of GNU Zebra.
5
6 GNU Zebra is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
9 later version.
10
11 GNU Zebra is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Zebra; see the file COPYING.  If not, write to the Free
18 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.  */
20
21 #include <zebra.h>
22
23 #include "hash.h"
24 #include "memory.h"
25 #include "prefix.h"
26 #include "command.h"
27 #include "filter.h"
28
29 #include "bgpd/bgpd.h"
30 #include "bgpd/bgp_ecommunity.h"
31 #include "bgpd/bgp_aspath.h"
32
33 /* Hash of community attribute. */
34 static struct hash *ecomhash;
35
36 /* Allocate a new ecommunities.  */
37 static struct ecommunity *
38 ecommunity_new (void)
39 {
40   return (struct ecommunity *) XCALLOC (MTYPE_ECOMMUNITY,
41                                         sizeof (struct ecommunity));
42 }
43
44 /* Allocate ecommunities.  */
45 void
46 ecommunity_free (struct ecommunity **ecom)
47 {
48   if ((*ecom)->val)
49     XFREE (MTYPE_ECOMMUNITY_VAL, (*ecom)->val);
50   if ((*ecom)->str)
51     XFREE (MTYPE_ECOMMUNITY_STR, (*ecom)->str);
52   XFREE (MTYPE_ECOMMUNITY, *ecom);
53   ecom = NULL;
54 }
55
56 /* Add a new Extended Communities value to Extended Communities
57    Attribute structure.  When the value is already exists in the
58    structure, we don't add the value.  Newly added value is sorted by
59    numerical order.  When the value is added to the structure return 1
60    else return 0.  */
61 static int
62 ecommunity_add_val (struct ecommunity *ecom, struct ecommunity_val *eval)
63 {
64   u_int8_t *p;
65   int ret;
66   int c;
67
68   /* When this is fist value, just add it.  */
69   if (ecom->val == NULL)
70     {
71       ecom->size++;
72       ecom->val = XMALLOC (MTYPE_ECOMMUNITY_VAL, ecom_length (ecom));
73       memcpy (ecom->val, eval->val, ECOMMUNITY_SIZE);
74       return 1;
75     }
76
77   /* If the value already exists in the structure return 0.  */
78   c = 0;
79   for (p = ecom->val; c < ecom->size; p += ECOMMUNITY_SIZE, c++)
80     {
81       ret = memcmp (p, eval->val, ECOMMUNITY_SIZE);
82       if (ret == 0)
83         return 0;
84       if (ret > 0)
85         break;
86     }
87
88   /* Add the value to the structure with numerical sorting.  */
89   ecom->size++;
90   ecom->val = XREALLOC (MTYPE_ECOMMUNITY_VAL, ecom->val, ecom_length (ecom));
91
92   memmove (ecom->val + (c + 1) * ECOMMUNITY_SIZE,
93            ecom->val + c * ECOMMUNITY_SIZE,
94            (ecom->size - 1 - c) * ECOMMUNITY_SIZE);
95   memcpy (ecom->val + c * ECOMMUNITY_SIZE, eval->val, ECOMMUNITY_SIZE);
96
97   return 1;
98 }
99
100 /* This function takes pointer to Extended Communites strucutre then
101    create a new Extended Communities structure by uniq and sort each
102    Extended Communities value.  */
103 struct ecommunity *
104 ecommunity_uniq_sort (struct ecommunity *ecom)
105 {
106   int i;
107   struct ecommunity *new;
108   struct ecommunity_val *eval;
109   
110   if (! ecom)
111     return NULL;
112   
113   new = ecommunity_new ();
114   
115   for (i = 0; i < ecom->size; i++)
116     {
117       eval = (struct ecommunity_val *) (ecom->val + (i * ECOMMUNITY_SIZE));
118       ecommunity_add_val (new, eval);
119     }
120   return new;
121 }
122
123 /* Parse Extended Communites Attribute in BGP packet.  */
124 struct ecommunity *
125 ecommunity_parse (u_int8_t *pnt, u_short length)
126 {
127   struct ecommunity tmp;
128   struct ecommunity *new;
129
130   /* Length check.  */
131   if (length % ECOMMUNITY_SIZE)
132     return NULL;
133
134   /* Prepare tmporary structure for making a new Extended Communities
135      Attribute.  */
136   tmp.size = length / ECOMMUNITY_SIZE;
137   tmp.val = pnt;
138
139   /* Create a new Extended Communities Attribute by uniq and sort each
140      Extended Communities value  */
141   new = ecommunity_uniq_sort (&tmp);
142
143   return ecommunity_intern (new);
144 }
145
146 /* Duplicate the Extended Communities Attribute structure.  */
147 struct ecommunity *
148 ecommunity_dup (struct ecommunity *ecom)
149 {
150   struct ecommunity *new;
151
152   new = XCALLOC (MTYPE_ECOMMUNITY, sizeof (struct ecommunity));
153   new->size = ecom->size;
154   if (new->size)
155     {
156       new->val = XMALLOC (MTYPE_ECOMMUNITY_VAL, ecom->size * ECOMMUNITY_SIZE);
157       memcpy (new->val, ecom->val, ecom->size * ECOMMUNITY_SIZE);
158     }
159   else
160     new->val = NULL;
161   return new;
162 }
163
164 /* Retrun string representation of communities attribute. */
165 char *
166 ecommunity_str (struct ecommunity *ecom)
167 {
168   if (! ecom->str)
169     ecom->str = ecommunity_ecom2str (ecom, ECOMMUNITY_FORMAT_DISPLAY);
170   return ecom->str;
171 }
172
173 /* Merge two Extended Communities Attribute structure.  */
174 struct ecommunity *
175 ecommunity_merge (struct ecommunity *ecom1, struct ecommunity *ecom2)
176 {
177   if (ecom1->val)
178     ecom1->val = XREALLOC (MTYPE_ECOMMUNITY_VAL, ecom1->val, 
179                            (ecom1->size + ecom2->size) * ECOMMUNITY_SIZE);
180   else
181     ecom1->val = XMALLOC (MTYPE_ECOMMUNITY_VAL,
182                           (ecom1->size + ecom2->size) * ECOMMUNITY_SIZE);
183
184   memcpy (ecom1->val + (ecom1->size * ECOMMUNITY_SIZE),
185           ecom2->val, ecom2->size * ECOMMUNITY_SIZE);
186   ecom1->size += ecom2->size;
187
188   return ecom1;
189 }
190
191 /* Intern Extended Communities Attribute.  */
192 struct ecommunity *
193 ecommunity_intern (struct ecommunity *ecom)
194 {
195   struct ecommunity *find;
196
197   assert (ecom->refcnt == 0);
198
199   find = (struct ecommunity *) hash_get (ecomhash, ecom, hash_alloc_intern);
200
201   if (find != ecom)
202     ecommunity_free (&ecom);
203
204   find->refcnt++;
205
206   if (! find->str)
207     find->str = ecommunity_ecom2str (find, ECOMMUNITY_FORMAT_DISPLAY);
208
209   return find;
210 }
211
212 /* Unintern Extended Communities Attribute.  */
213 void
214 ecommunity_unintern (struct ecommunity **ecom)
215 {
216   struct ecommunity *ret;
217
218   if ((*ecom)->refcnt)
219     (*ecom)->refcnt--;
220     
221   /* Pull off from hash.  */
222   if ((*ecom)->refcnt == 0)
223     {
224       /* Extended community must be in the hash.  */
225       ret = (struct ecommunity *) hash_release (ecomhash, *ecom);
226       assert (ret != NULL);
227
228       ecommunity_free (ecom);
229     }
230 }
231
232 /* Utinity function to make hash key.  */
233 unsigned int
234 ecommunity_hash_make (void *arg)
235 {
236   const struct ecommunity *ecom = arg;
237   int size = ecom->size * ECOMMUNITY_SIZE;
238   u_int8_t *pnt = ecom->val;
239   unsigned int key = 0;
240   int c;
241
242   for (c = 0; c < size; c += ECOMMUNITY_SIZE)
243     {
244       key += pnt[c];
245       key += pnt[c + 1];
246       key += pnt[c + 2];
247       key += pnt[c + 3];
248       key += pnt[c + 4];
249       key += pnt[c + 5];
250       key += pnt[c + 6];
251       key += pnt[c + 7];
252     }
253
254   return key;
255 }
256
257 /* Compare two Extended Communities Attribute structure.  */
258 int
259 ecommunity_cmp (const void *arg1, const void *arg2)
260 {
261   const struct ecommunity *ecom1 = arg1;
262   const struct ecommunity *ecom2 = arg2;
263   
264   return (ecom1->size == ecom2->size
265           && memcmp (ecom1->val, ecom2->val, ecom1->size * ECOMMUNITY_SIZE) == 0);
266 }
267
268 /* Initialize Extended Comminities related hash. */
269 void
270 ecommunity_init (void)
271 {
272   ecomhash = hash_create (ecommunity_hash_make, ecommunity_cmp);
273 }
274
275 void
276 ecommunity_finish (void)
277 {
278   hash_free (ecomhash);
279   ecomhash = NULL;
280 }
281
282 /* Extended Communities token enum. */
283 enum ecommunity_token
284 {
285   ecommunity_token_unknown = 0,
286   ecommunity_token_rt,
287   ecommunity_token_soo,
288   ecommunity_token_val,
289 };
290
291 /* Get next Extended Communities token from the string. */
292 static const char *
293 ecommunity_gettoken (const char *str, struct ecommunity_val *eval,
294                      enum ecommunity_token *token)
295 {
296   int ret;
297   int dot = 0;
298   int digit = 0;
299   int separator = 0;
300   const char *p = str;
301   char *endptr;
302   struct in_addr ip;
303   as_t as = 0;
304   u_int32_t val = 0;
305   char buf[INET_ADDRSTRLEN + 1];
306
307   /* Skip white space. */
308   while (isspace ((int) *p))
309     {
310       p++;
311       str++;
312     }
313
314   /* Check the end of the line. */
315   if (*p == '\0')
316     return NULL;
317
318   /* "rt" and "soo" keyword parse. */
319   if (! isdigit ((int) *p)) 
320     {
321       /* "rt" match check.  */
322       if (tolower ((int) *p) == 'r')
323         {
324           p++;
325           if (tolower ((int) *p) == 't')
326             {
327               p++;
328               *token = ecommunity_token_rt;
329               return p;
330             }
331           if (isspace ((int) *p) || *p == '\0')
332             {
333               *token = ecommunity_token_rt;
334               return p;
335             }
336           goto error;
337         }
338       /* "soo" match check.  */
339       else if (tolower ((int) *p) == 's')
340         {
341           p++;
342           if (tolower ((int) *p) == 'o')
343             {
344               p++;
345               if (tolower ((int) *p) == 'o')
346                 {
347                   p++;
348                   *token = ecommunity_token_soo;
349                   return p;
350                 }
351               if (isspace ((int) *p) || *p == '\0')
352                 {
353                   *token = ecommunity_token_soo;
354                   return p;
355                 }
356               goto error;
357             }
358           if (isspace ((int) *p) || *p == '\0')
359             {
360               *token = ecommunity_token_soo;
361               return p;
362             }
363           goto error;
364         }
365       goto error;
366     }
367   
368   /* What a mess, there are several possibilities:
369    *
370    * a) A.B.C.D:MN
371    * b) EF:OPQR
372    * c) GHJK:MN
373    *
374    * A.B.C.D: Four Byte IP
375    * EF:      Two byte ASN
376    * GHJK:    Four-byte ASN
377    * MN:      Two byte value
378    * OPQR:    Four byte value
379    *
380    */
381   while (isdigit ((int) *p) || *p == ':' || *p == '.') 
382     {
383       if (*p == ':')
384         {
385           if (separator)
386             goto error;
387
388           separator = 1;
389           digit = 0;
390           
391           if ((p - str) > INET_ADDRSTRLEN)
392             goto error;
393           memset (buf, 0, INET_ADDRSTRLEN + 1);
394           memcpy (buf, str, p - str);
395           
396           if (dot)
397             {
398               /* Parsing A.B.C.D in:
399                * A.B.C.D:MN
400                */
401               ret = inet_aton (buf, &ip);
402               if (ret == 0)
403                 goto error;
404             }
405           else
406             {
407               /* ASN */
408               as = strtoul (buf, &endptr, 10);
409               if (*endptr != '\0' || as == BGP_AS4_MAX)
410                 goto error;
411             }
412         }
413       else if (*p == '.')
414         {
415           if (separator)
416             goto error;
417           dot++;
418           if (dot > 4)
419             goto error;
420         }
421       else
422         {
423           digit = 1;
424           
425           /* We're past the IP/ASN part */
426           if (separator)
427             {
428               val *= 10;
429               val += (*p - '0');
430             }
431         }
432       p++;
433     }
434
435   /* Low digit part must be there. */
436   if (!digit || !separator)
437     goto error;
438
439   /* Encode result into routing distinguisher.  */
440   if (dot)
441     {
442       if (val > UINT16_MAX)
443         goto error;
444       
445       eval->val[0] = ECOMMUNITY_ENCODE_IP;
446       eval->val[1] = 0;
447       memcpy (&eval->val[2], &ip, sizeof (struct in_addr));
448       eval->val[6] = (val >> 8) & 0xff;
449       eval->val[7] = val & 0xff;
450     }
451   else if (as > BGP_AS_MAX)
452     {
453       if (val > UINT16_MAX)
454         goto error;
455       
456       eval->val[0] = ECOMMUNITY_ENCODE_AS4;
457       eval->val[1] = 0;
458       eval->val[2] = (as >>24) & 0xff;
459       eval->val[3] = (as >>16) & 0xff;
460       eval->val[4] = (as >>8) & 0xff;
461       eval->val[5] =  as & 0xff;
462       eval->val[6] = (val >> 8) & 0xff;
463       eval->val[7] = val & 0xff;
464     }
465   else
466     {
467       eval->val[0] = ECOMMUNITY_ENCODE_AS;
468       eval->val[1] = 0;
469       
470       eval->val[2] = (as >>8) & 0xff;
471       eval->val[3] = as & 0xff;
472       eval->val[4] = (val >>24) & 0xff;
473       eval->val[5] = (val >>16) & 0xff;
474       eval->val[6] = (val >>8) & 0xff;
475       eval->val[7] = val & 0xff;
476     }
477   *token = ecommunity_token_val;
478   return p;
479
480  error:
481   *token = ecommunity_token_unknown;
482   return p;
483 }
484
485 /* Convert string to extended community attribute. 
486
487    When type is already known, please specify both str and type.  str
488    should not include keyword such as "rt" and "soo".  Type is
489    ECOMMUNITY_ROUTE_TARGET or ECOMMUNITY_SITE_ORIGIN.
490    keyword_included should be zero.
491
492    For example route-map's "set extcommunity" command case:
493
494    "rt 100:1 100:2 100:3"        -> str = "100:1 100:2 100:3"
495                                     type = ECOMMUNITY_ROUTE_TARGET
496                                     keyword_included = 0
497
498    "soo 100:1"                   -> str = "100:1"
499                                     type = ECOMMUNITY_SITE_ORIGIN
500                                     keyword_included = 0
501
502    When string includes keyword for each extended community value.
503    Please specify keyword_included as non-zero value.
504
505    For example standard extcommunity-list case:
506
507    "rt 100:1 rt 100:2 soo 100:1" -> str = "rt 100:1 rt 100:2 soo 100:1"
508                                     type = 0
509                                     keyword_include = 1
510 */
511 struct ecommunity *
512 ecommunity_str2com (const char *str, int type, int keyword_included)
513 {
514   struct ecommunity *ecom = NULL;
515   enum ecommunity_token token = ecommunity_token_unknown;
516   struct ecommunity_val eval;
517   int keyword = 0;
518
519   while ((str = ecommunity_gettoken (str, &eval, &token)))
520     {
521       switch (token)
522         {
523         case ecommunity_token_rt:
524         case ecommunity_token_soo:
525           if (! keyword_included || keyword)
526             {
527               if (ecom)
528                 ecommunity_free (&ecom);
529               return NULL;
530             }
531           keyword = 1;
532
533           if (token == ecommunity_token_rt)
534             {
535               type = ECOMMUNITY_ROUTE_TARGET;
536             }
537           if (token == ecommunity_token_soo)
538             {
539               type = ECOMMUNITY_SITE_ORIGIN;
540             }
541           break;
542         case ecommunity_token_val:
543           if (keyword_included)
544             {
545               if (! keyword)
546                 {
547                   if (ecom)
548                     ecommunity_free (&ecom);
549                   return NULL;
550                 }
551               keyword = 0;
552             }
553           if (ecom == NULL)
554             ecom = ecommunity_new ();
555           eval.val[1] = type;
556           ecommunity_add_val (ecom, &eval);
557           break;
558         case ecommunity_token_unknown:
559         default:
560           if (ecom)
561             ecommunity_free (&ecom);
562           return NULL;
563         }
564     }
565   return ecom;
566 }
567
568 /* Convert extended community attribute to string.  
569
570    Due to historical reason of industry standard implementation, there
571    are three types of format.
572
573    route-map set extcommunity format
574         "rt 100:1 100:2"
575         "soo 100:3"
576
577    extcommunity-list
578         "rt 100:1 rt 100:2 soo 100:3"
579
580    "show ip bgp" and extcommunity-list regular expression matching
581         "RT:100:1 RT:100:2 SoO:100:3"
582
583    For each formath please use below definition for format:
584
585    ECOMMUNITY_FORMAT_ROUTE_MAP
586    ECOMMUNITY_FORMAT_COMMUNITY_LIST
587    ECOMMUNITY_FORMAT_DISPLAY
588 */
589 char *
590 ecommunity_ecom2str (struct ecommunity *ecom, int format)
591 {
592   int i;
593   u_int8_t *pnt;
594   int encode = 0;
595   int type = 0;
596 #define ECOMMUNITY_STR_DEFAULT_LEN  27
597   int str_size;
598   int str_pnt;
599   char *str_buf;
600   const char *prefix;
601   int len = 0;
602   int first = 1;
603
604   /* For parse Extended Community attribute tupple. */
605   struct ecommunity_as
606   {
607     as_t as;
608     u_int32_t val;
609   } eas;
610
611   struct ecommunity_ip
612   {
613     struct in_addr ip;
614     u_int16_t val;
615   } eip;
616
617   if (ecom->size == 0)
618     {
619       str_buf = XMALLOC (MTYPE_ECOMMUNITY_STR, 1);
620       str_buf[0] = '\0';
621       return str_buf;
622     }
623
624   /* Prepare buffer.  */
625   str_buf = XMALLOC (MTYPE_ECOMMUNITY_STR, ECOMMUNITY_STR_DEFAULT_LEN + 1);
626   str_size = ECOMMUNITY_STR_DEFAULT_LEN + 1;
627   str_pnt = 0;
628
629   for (i = 0; i < ecom->size; i++)
630     {
631       /* Make it sure size is enough.  */
632       while (str_pnt + ECOMMUNITY_STR_DEFAULT_LEN >= str_size)
633         {
634           str_size *= 2;
635           str_buf = XREALLOC (MTYPE_ECOMMUNITY_STR, str_buf, str_size);
636         }
637
638       /* Space between each value.  */
639       if (! first)
640         str_buf[str_pnt++] = ' ';
641
642       pnt = ecom->val + (i * 8);
643
644       /* High-order octet of type. */
645       encode = *pnt++;
646
647       switch (encode)
648         {
649         case ECOMMUNITY_ENCODE_AS:
650         case ECOMMUNITY_ENCODE_IP:
651         case ECOMMUNITY_ENCODE_AS4:
652           break;
653
654         case ECOMMUNITY_ENCODE_OPAQUE:
655           if (*pnt == ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP)
656             {
657               uint16_t tunneltype;
658               memcpy (&tunneltype, pnt + 5, 2);
659               tunneltype = ntohs(tunneltype);
660               len = sprintf (str_buf + str_pnt, "ET:%d", tunneltype);
661               str_pnt += len;
662               first = 0;
663               continue;
664             }
665             /* fall through */
666
667         default:
668           len = sprintf (str_buf + str_pnt, "?");
669           str_pnt += len;
670           first = 0;
671           continue;
672         }
673
674       /* Low-order octet of type. */
675       type = *pnt++;
676       if (type !=  ECOMMUNITY_ROUTE_TARGET && type != ECOMMUNITY_SITE_ORIGIN)
677         {
678           len = sprintf (str_buf + str_pnt, "?");
679           str_pnt += len;
680           first = 0;
681           continue;
682         }
683
684       switch (format)
685         {
686         case ECOMMUNITY_FORMAT_COMMUNITY_LIST:
687           prefix = (type == ECOMMUNITY_ROUTE_TARGET ? "rt " : "soo ");
688           break;
689         case ECOMMUNITY_FORMAT_DISPLAY:
690           prefix = (type == ECOMMUNITY_ROUTE_TARGET ? "RT:" : "SoO:");
691           break;
692         case ECOMMUNITY_FORMAT_ROUTE_MAP:
693           prefix = "";
694           break;
695         default:
696           prefix = "";
697           break;
698         }
699
700       /* Put string into buffer.  */
701       if (encode == ECOMMUNITY_ENCODE_AS4)
702         {
703           eas.as = (*pnt++ << 24);
704           eas.as |= (*pnt++ << 16);
705           eas.as |= (*pnt++ << 8);
706           eas.as |= (*pnt++);
707
708           eas.val = (*pnt++ << 8);
709           eas.val |= (*pnt++);
710
711           len = sprintf( str_buf + str_pnt, "%s%u:%u", prefix,
712                         eas.as, eas.val );
713           str_pnt += len;
714           first = 0;
715         }
716       if (encode == ECOMMUNITY_ENCODE_AS)
717         {
718           eas.as = (*pnt++ << 8);
719           eas.as |= (*pnt++);
720
721           eas.val = (*pnt++ << 24);
722           eas.val |= (*pnt++ << 16);
723           eas.val |= (*pnt++ << 8);
724           eas.val |= (*pnt++);
725
726           len = sprintf (str_buf + str_pnt, "%s%u:%u", prefix,
727                          eas.as, eas.val);
728           str_pnt += len;
729           first = 0;
730         }
731       else if (encode == ECOMMUNITY_ENCODE_IP)
732         {
733           memcpy (&eip.ip, pnt, 4);
734           pnt += 4;
735           eip.val = (*pnt++ << 8);
736           eip.val |= (*pnt++);
737
738           len = sprintf (str_buf + str_pnt, "%s%s:%u", prefix,
739                          inet_ntoa (eip.ip), eip.val);
740           str_pnt += len;
741           first = 0;
742         }
743     }
744   return str_buf;
745 }
746
747 int
748 ecommunity_match (const struct ecommunity *ecom1, 
749                   const struct ecommunity *ecom2)
750 {
751   int i = 0;
752   int j = 0;
753
754   if (ecom1 == NULL && ecom2 == NULL)
755     return 1;
756
757   if (ecom1 == NULL || ecom2 == NULL)
758     return 0;
759
760   if (ecom1->size < ecom2->size)
761     return 0;
762
763   /* Every community on com2 needs to be on com1 for this to match */
764   while (i < ecom1->size && j < ecom2->size)
765     {
766       if (memcmp (ecom1->val + i, ecom2->val + j, ECOMMUNITY_SIZE) == 0)
767         j++;
768       i++;
769     }
770
771   if (j == ecom2->size)
772     return 1;
773   else
774     return 0;
775 }
776