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