1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* BGP Large Communities Attribute
4 * Copyright (C) 2016 Keyur Patel <keyur@arrcus.com>
17 #include "bgpd/bgpd.h"
18 #include "bgpd/bgp_lcommunity.h"
19 #include "bgpd/bgp_community_alias.h"
20 #include "bgpd/bgp_aspath.h"
22 /* Hash of community attribute. */
23 static struct hash
*lcomhash
;
25 /* Allocate a new lcommunities. */
26 static struct lcommunity
*lcommunity_new(void)
28 return XCALLOC(MTYPE_LCOMMUNITY
, sizeof(struct lcommunity
));
31 /* Allocate lcommunities. */
32 void lcommunity_free(struct lcommunity
**lcom
)
37 XFREE(MTYPE_LCOMMUNITY_VAL
, (*lcom
)->val
);
38 XFREE(MTYPE_LCOMMUNITY_STR
, (*lcom
)->str
);
40 json_object_free((*lcom
)->json
);
41 XFREE(MTYPE_LCOMMUNITY
, *lcom
);
44 static void lcommunity_hash_free(struct lcommunity
*lcom
)
46 lcommunity_free(&lcom
);
49 /* Add a new Large Communities value to Large Communities
50 Attribute structure. When the value is already exists in the
51 structure, we don't add the value. Newly added value is sorted by
52 numerical order. When the value is added to the structure return 1
54 static bool lcommunity_add_val(struct lcommunity
*lcom
,
55 struct lcommunity_val
*lval
)
61 /* When this is fist value, just add it. */
62 if (lcom
->val
== NULL
) {
64 lcom
->val
= XMALLOC(MTYPE_LCOMMUNITY_VAL
, lcom_length(lcom
));
65 memcpy(lcom
->val
, lval
->val
, LCOMMUNITY_SIZE
);
69 /* If the value already exists in the structure return 0. */
71 for (p
= lcom
->val
; c
< lcom
->size
; p
+= LCOMMUNITY_SIZE
, c
++) {
72 ret
= memcmp(p
, lval
->val
, LCOMMUNITY_SIZE
);
79 /* Add the value to the structure with numerical sorting. */
82 XREALLOC(MTYPE_LCOMMUNITY_VAL
, lcom
->val
, lcom_length(lcom
));
84 memmove(lcom
->val
+ (c
+ 1) * LCOMMUNITY_SIZE
,
85 lcom
->val
+ c
* LCOMMUNITY_SIZE
,
86 (lcom
->size
- 1 - c
) * LCOMMUNITY_SIZE
);
87 memcpy(lcom
->val
+ c
* LCOMMUNITY_SIZE
, lval
->val
, LCOMMUNITY_SIZE
);
92 /* This function takes pointer to Large Communites structure then
93 create a new Large Communities structure by uniq and sort each
94 Large Communities value. */
95 struct lcommunity
*lcommunity_uniq_sort(struct lcommunity
*lcom
)
98 struct lcommunity
*new;
99 struct lcommunity_val
*lval
;
104 new = lcommunity_new();
106 for (i
= 0; i
< lcom
->size
; i
++) {
107 lval
= (struct lcommunity_val
*)(lcom
->val
108 + (i
* LCOMMUNITY_SIZE
));
109 lcommunity_add_val(new, lval
);
114 /* Parse Large Communites Attribute in BGP packet. */
115 struct lcommunity
*lcommunity_parse(uint8_t *pnt
, unsigned short length
)
117 struct lcommunity tmp
;
118 struct lcommunity
*new;
121 if (length
% LCOMMUNITY_SIZE
)
124 /* Prepare tmporary structure for making a new Large Communities
126 tmp
.size
= length
/ LCOMMUNITY_SIZE
;
129 /* Create a new Large Communities Attribute by uniq and sort each
130 Large Communities value */
131 new = lcommunity_uniq_sort(&tmp
);
133 return lcommunity_intern(new);
136 /* Duplicate the Large Communities Attribute structure. */
137 struct lcommunity
*lcommunity_dup(struct lcommunity
*lcom
)
139 struct lcommunity
*new;
141 new = lcommunity_new();
142 new->size
= lcom
->size
;
144 new->val
= XMALLOC(MTYPE_LCOMMUNITY_VAL
, lcom_length(lcom
));
145 memcpy(new->val
, lcom
->val
, lcom_length(lcom
));
151 /* Merge two Large Communities Attribute structure. */
152 struct lcommunity
*lcommunity_merge(struct lcommunity
*lcom1
,
153 struct lcommunity
*lcom2
)
155 lcom1
->val
= XREALLOC(MTYPE_LCOMMUNITY_VAL
, lcom1
->val
,
156 lcom_length(lcom1
) + lcom_length(lcom2
));
158 memcpy(lcom1
->val
+ lcom_length(lcom1
), lcom2
->val
, lcom_length(lcom2
));
159 lcom1
->size
+= lcom2
->size
;
164 static void set_lcommunity_string(struct lcommunity
*lcom
, bool make_json
,
165 bool translate_alias
)
171 uint32_t global
, local1
, local2
;
172 json_object
*json_lcommunity_list
= NULL
;
173 json_object
*json_string
= NULL
;
175 /* 3 32-bit integers, 2 colons, and a space */
176 #define LCOMMUNITY_STRLEN (10 * 3 + 2 + 1)
182 lcom
->json
= json_object_new_object();
183 json_lcommunity_list
= json_object_new_array();
186 if (lcom
->size
== 0) {
187 str_buf
= XCALLOC(MTYPE_LCOMMUNITY_STR
, 1);
190 json_object_string_add(lcom
->json
, "string", "");
191 json_object_object_add(lcom
->json
, "list",
192 json_lcommunity_list
);
199 /* 1 space + lcom->size lcom strings + null terminator */
200 size_t str_buf_sz
= (LCOMMUNITY_STRLEN
* lcom
->size
) + 2;
201 str_buf
= XCALLOC(MTYPE_LCOMMUNITY_STR
, str_buf_sz
);
204 for (i
= 0; i
< lcom
->size
; i
++) {
206 len
= strlcat(str_buf
, " ", str_buf_sz
);
208 pnt
= lcom
->val
+ (i
* LCOMMUNITY_SIZE
);
209 pnt
= ptr_get_be32(pnt
, &global
);
210 pnt
= ptr_get_be32(pnt
, &local1
);
211 pnt
= ptr_get_be32(pnt
, &local2
);
214 char lcsb
[LCOMMUNITY_STRLEN
+ 1];
216 snprintf(lcsb
, sizeof(lcsb
), "%u:%u:%u", global
, local1
,
220 * Aliases can cause havoc, if the alias length is greater
221 * than the LCOMMUNITY_STRLEN for a particular item
222 * then we need to realloc the memory associated
223 * with the string so that it can fit
225 const char *com2alias
=
226 translate_alias
? bgp_community2alias(lcsb
) : lcsb
;
227 size_t individual_len
= strlen(com2alias
);
228 if (individual_len
+ len
> str_buf_sz
) {
229 str_buf_sz
= individual_len
+ len
+ 1;
230 str_buf
= XREALLOC(MTYPE_LCOMMUNITY_STR
, str_buf
,
234 len
= strlcat(str_buf
, com2alias
, str_buf_sz
);
237 json_string
= json_object_new_string(com2alias
);
238 json_object_array_add(json_lcommunity_list
,
244 json_object_string_add(lcom
->json
, "string", str_buf
);
245 json_object_object_add(lcom
->json
, "list",
246 json_lcommunity_list
);
252 /* Intern Large Communities Attribute. */
253 struct lcommunity
*lcommunity_intern(struct lcommunity
*lcom
)
255 struct lcommunity
*find
;
257 assert(lcom
->refcnt
== 0);
259 find
= (struct lcommunity
*)hash_get(lcomhash
, lcom
, hash_alloc_intern
);
262 lcommunity_free(&lcom
);
267 set_lcommunity_string(find
, false, true);
272 /* Unintern Large Communities Attribute. */
273 void lcommunity_unintern(struct lcommunity
**lcom
)
275 struct lcommunity
*ret
;
283 /* Pull off from hash. */
284 if ((*lcom
)->refcnt
== 0) {
285 /* Large community must be in the hash. */
286 ret
= (struct lcommunity
*)hash_release(lcomhash
, *lcom
);
289 lcommunity_free(lcom
);
293 /* Return string representation of lcommunities attribute. */
294 char *lcommunity_str(struct lcommunity
*lcom
, bool make_json
,
295 bool translate_alias
)
300 if (make_json
&& !lcom
->json
&& lcom
->str
)
301 XFREE(MTYPE_LCOMMUNITY_STR
, lcom
->str
);
304 set_lcommunity_string(lcom
, make_json
, translate_alias
);
309 /* Utility function to make hash key. */
310 unsigned int lcommunity_hash_make(const void *arg
)
312 const struct lcommunity
*lcom
= arg
;
313 int size
= lcom_length(lcom
);
315 return jhash(lcom
->val
, size
, 0xab125423);
318 /* Compare two Large Communities Attribute structure. */
319 bool lcommunity_cmp(const void *arg1
, const void *arg2
)
321 const struct lcommunity
*lcom1
= arg1
;
322 const struct lcommunity
*lcom2
= arg2
;
324 if (lcom1
== NULL
&& lcom2
== NULL
)
327 if (lcom1
== NULL
|| lcom2
== NULL
)
330 return (lcom1
->size
== lcom2
->size
331 && memcmp(lcom1
->val
, lcom2
->val
, lcom_length(lcom1
)) == 0);
334 /* Return communities hash. */
335 struct hash
*lcommunity_hash(void)
340 /* Initialize Large Comminities related hash. */
341 void lcommunity_init(void)
343 lcomhash
= hash_create(lcommunity_hash_make
, lcommunity_cmp
,
344 "BGP lcommunity hash");
347 void lcommunity_finish(void)
349 hash_clean_and_free(&lcomhash
, (void (*)(void *))lcommunity_hash_free
);
352 /* Get next Large Communities token from the string.
353 * Assumes str is space-delimeted and describes 0 or more
354 * valid large communities
356 static const char *lcommunity_gettoken(const char *str
,
357 struct lcommunity_val
*lval
)
361 /* Skip white space. */
362 while (isspace((unsigned char)*p
)) {
367 /* Check the end of the line. */
371 /* Community value. */
374 uint32_t globaladmin
= 0;
375 uint32_t localdata1
= 0;
376 uint32_t localdata2
= 0;
378 while (*p
&& *p
!= ' ') {
379 /* large community valid chars */
380 assert(isdigit((unsigned char)*p
) || *p
== ':');
385 if (separator
== 1) {
386 globaladmin
= localdata2
;
388 localdata1
= localdata2
;
393 /* left shift the accumulated value and add current
397 localdata2
+= (*p
- '0');
402 /* Assert str was a valid large community */
403 assert(separator
== 2 && digit
== 1);
406 * Copy the large comm.
408 lval
->val
[0] = (globaladmin
>> 24) & 0xff;
409 lval
->val
[1] = (globaladmin
>> 16) & 0xff;
410 lval
->val
[2] = (globaladmin
>> 8) & 0xff;
411 lval
->val
[3] = globaladmin
& 0xff;
412 lval
->val
[4] = (localdata1
>> 24) & 0xff;
413 lval
->val
[5] = (localdata1
>> 16) & 0xff;
414 lval
->val
[6] = (localdata1
>> 8) & 0xff;
415 lval
->val
[7] = localdata1
& 0xff;
416 lval
->val
[8] = (localdata2
>> 24) & 0xff;
417 lval
->val
[9] = (localdata2
>> 16) & 0xff;
418 lval
->val
[10] = (localdata2
>> 8) & 0xff;
419 lval
->val
[11] = localdata2
& 0xff;
425 Convert string to large community attribute.
426 When type is already known, please specify both str and type.
428 When string includes keyword for each large community value.
429 Please specify keyword_included as non-zero value.
431 struct lcommunity
*lcommunity_str2com(const char *str
)
433 struct lcommunity
*lcom
= NULL
;
434 struct lcommunity_val lval
;
436 if (!lcommunity_list_valid(str
, LARGE_COMMUNITY_LIST_STANDARD
))
440 str
= lcommunity_gettoken(str
, &lval
);
442 lcom
= lcommunity_new();
443 lcommunity_add_val(lcom
, &lval
);
449 bool lcommunity_include(struct lcommunity
*lcom
, uint8_t *ptr
)
454 for (i
= 0; i
< lcom
->size
; i
++) {
455 lcom_ptr
= lcom
->val
+ (i
* LCOMMUNITY_SIZE
);
456 if (memcmp(ptr
, lcom_ptr
, LCOMMUNITY_SIZE
) == 0)
462 bool lcommunity_match(const struct lcommunity
*lcom1
,
463 const struct lcommunity
*lcom2
)
468 if (lcom1
== NULL
&& lcom2
== NULL
)
471 if (lcom1
== NULL
|| lcom2
== NULL
)
474 if (lcom1
->size
< lcom2
->size
)
477 /* Every community on com2 needs to be on com1 for this to match */
478 while (i
< lcom1
->size
&& j
< lcom2
->size
) {
479 if (memcmp(lcom1
->val
+ (i
* LCOMMUNITY_SIZE
),
480 lcom2
->val
+ (j
* LCOMMUNITY_SIZE
), LCOMMUNITY_SIZE
)
486 if (j
== lcom2
->size
)
492 /* Delete one lcommunity. */
493 void lcommunity_del_val(struct lcommunity
*lcom
, uint8_t *ptr
)
501 while (i
< lcom
->size
) {
502 if (memcmp(lcom
->val
+ i
* LCOMMUNITY_SIZE
, ptr
,
505 c
= lcom
->size
- i
- 1;
508 memmove(lcom
->val
+ i
* LCOMMUNITY_SIZE
,
509 lcom
->val
+ (i
+ 1) * LCOMMUNITY_SIZE
,
510 c
* LCOMMUNITY_SIZE
);
516 XREALLOC(MTYPE_LCOMMUNITY_VAL
,
517 lcom
->val
, lcom_length(lcom
));
519 XFREE(MTYPE_LCOMMUNITY_VAL
, lcom
->val
);
527 static struct lcommunity
*bgp_aggr_lcommunity_lookup(
528 struct bgp_aggregate
*aggregate
,
529 struct lcommunity
*lcommunity
)
531 return hash_lookup(aggregate
->lcommunity_hash
, lcommunity
);
534 static void *bgp_aggr_lcommunty_hash_alloc(void *p
)
536 struct lcommunity
*ref
= (struct lcommunity
*)p
;
537 struct lcommunity
*lcommunity
= NULL
;
539 lcommunity
= lcommunity_dup(ref
);
543 static void bgp_aggr_lcommunity_prepare(struct hash_bucket
*hb
, void *arg
)
545 struct lcommunity
*hb_lcommunity
= hb
->data
;
546 struct lcommunity
**aggr_lcommunity
= arg
;
548 if (*aggr_lcommunity
)
549 *aggr_lcommunity
= lcommunity_merge(*aggr_lcommunity
,
552 *aggr_lcommunity
= lcommunity_dup(hb_lcommunity
);
555 void bgp_aggr_lcommunity_remove(void *arg
)
557 struct lcommunity
*lcommunity
= arg
;
559 lcommunity_free(&lcommunity
);
562 void bgp_compute_aggregate_lcommunity(struct bgp_aggregate
*aggregate
,
563 struct lcommunity
*lcommunity
)
566 bgp_compute_aggregate_lcommunity_hash(aggregate
, lcommunity
);
567 bgp_compute_aggregate_lcommunity_val(aggregate
);
570 void bgp_compute_aggregate_lcommunity_hash(struct bgp_aggregate
*aggregate
,
571 struct lcommunity
*lcommunity
)
574 struct lcommunity
*aggr_lcommunity
= NULL
;
576 if ((aggregate
== NULL
) || (lcommunity
== NULL
))
579 /* Create hash if not already created.
581 if (aggregate
->lcommunity_hash
== NULL
)
582 aggregate
->lcommunity_hash
= hash_create(
583 lcommunity_hash_make
, lcommunity_cmp
,
584 "BGP Aggregator lcommunity hash");
586 aggr_lcommunity
= bgp_aggr_lcommunity_lookup(aggregate
, lcommunity
);
587 if (aggr_lcommunity
== NULL
) {
588 /* Insert lcommunity into hash.
590 aggr_lcommunity
= hash_get(aggregate
->lcommunity_hash
,
592 bgp_aggr_lcommunty_hash_alloc
);
595 /* Increment reference counter.
597 aggr_lcommunity
->refcnt
++;
600 void bgp_compute_aggregate_lcommunity_val(struct bgp_aggregate
*aggregate
)
602 struct lcommunity
*lcommerge
= NULL
;
604 if (aggregate
== NULL
)
607 /* Re-compute aggregate's lcommunity.
609 if (aggregate
->lcommunity
)
610 lcommunity_free(&aggregate
->lcommunity
);
611 if (aggregate
->lcommunity_hash
&&
612 aggregate
->lcommunity_hash
->count
) {
613 hash_iterate(aggregate
->lcommunity_hash
,
614 bgp_aggr_lcommunity_prepare
,
615 &aggregate
->lcommunity
);
616 lcommerge
= aggregate
->lcommunity
;
617 aggregate
->lcommunity
= lcommunity_uniq_sort(lcommerge
);
619 lcommunity_free(&lcommerge
);
623 void bgp_remove_lcommunity_from_aggregate(struct bgp_aggregate
*aggregate
,
624 struct lcommunity
*lcommunity
)
626 struct lcommunity
*aggr_lcommunity
= NULL
;
627 struct lcommunity
*ret_lcomm
= NULL
;
630 || (!aggregate
->lcommunity_hash
)
634 /* Look-up the lcommunity in the hash.
636 aggr_lcommunity
= bgp_aggr_lcommunity_lookup(aggregate
, lcommunity
);
637 if (aggr_lcommunity
) {
638 aggr_lcommunity
->refcnt
--;
640 if (aggr_lcommunity
->refcnt
== 0) {
641 ret_lcomm
= hash_release(aggregate
->lcommunity_hash
,
643 lcommunity_free(&ret_lcomm
);
645 bgp_compute_aggregate_lcommunity_val(aggregate
);
651 void bgp_remove_lcomm_from_aggregate_hash(struct bgp_aggregate
*aggregate
,
652 struct lcommunity
*lcommunity
)
654 struct lcommunity
*aggr_lcommunity
= NULL
;
655 struct lcommunity
*ret_lcomm
= NULL
;
658 || (!aggregate
->lcommunity_hash
)
662 /* Look-up the lcommunity in the hash.
664 aggr_lcommunity
= bgp_aggr_lcommunity_lookup(aggregate
, lcommunity
);
665 if (aggr_lcommunity
) {
666 aggr_lcommunity
->refcnt
--;
668 if (aggr_lcommunity
->refcnt
== 0) {
669 ret_lcomm
= hash_release(aggregate
->lcommunity_hash
,
671 lcommunity_free(&ret_lcomm
);