/src/frr/bgpd/bgp_community.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* Community attribute related functions. |
3 | | * Copyright (C) 1998, 2001 Kunihiro Ishiguro |
4 | | */ |
5 | | |
6 | | #include <zebra.h> |
7 | | |
8 | | #include "command.h" |
9 | | #include "hash.h" |
10 | | #include "memory.h" |
11 | | #include "jhash.h" |
12 | | #include "frrstr.h" |
13 | | |
14 | | #include "bgpd/bgp_memory.h" |
15 | | #include "bgpd/bgp_community.h" |
16 | | #include "bgpd/bgp_community_alias.h" |
17 | | |
18 | | /* Hash of community attribute. */ |
19 | | static struct hash *comhash; |
20 | | |
21 | | /* Allocate a new communities value. */ |
22 | | static struct community *community_new(void) |
23 | 1.15k | { |
24 | 1.15k | return XCALLOC(MTYPE_COMMUNITY, sizeof(struct community)); |
25 | 1.15k | } |
26 | | |
27 | | /* Free communities value. */ |
28 | | void community_free(struct community **com) |
29 | 1.53k | { |
30 | 1.53k | if (!(*com)) |
31 | 0 | return; |
32 | | |
33 | 1.53k | XFREE(MTYPE_COMMUNITY_VAL, (*com)->val); |
34 | 1.53k | XFREE(MTYPE_COMMUNITY_STR, (*com)->str); |
35 | | |
36 | 1.53k | if ((*com)->json) { |
37 | 0 | json_object_free((*com)->json); |
38 | 0 | (*com)->json = NULL; |
39 | 0 | } |
40 | | |
41 | 1.53k | XFREE(MTYPE_COMMUNITY, (*com)); |
42 | 1.53k | } |
43 | | |
44 | | /* Add one community value to the community. */ |
45 | | void community_add_val(struct community *com, uint32_t val) |
46 | 4.83k | { |
47 | 4.83k | com->size++; |
48 | 4.83k | com->val = XREALLOC(MTYPE_COMMUNITY_VAL, com->val, com_length(com)); |
49 | | |
50 | 4.83k | val = htonl(val); |
51 | 4.83k | memcpy(com_lastval(com), &val, sizeof(uint32_t)); |
52 | 4.83k | } |
53 | | |
54 | | /* Delete one community. */ |
55 | | void community_del_val(struct community *com, uint32_t *val) |
56 | 0 | { |
57 | 0 | int i = 0; |
58 | 0 | int c = 0; |
59 | |
|
60 | 0 | if (!com->val) |
61 | 0 | return; |
62 | | |
63 | 0 | while (i < com->size) { |
64 | 0 | if (memcmp(com->val + i, val, sizeof(uint32_t)) == 0) { |
65 | 0 | c = com->size - i - 1; |
66 | |
|
67 | 0 | if (c > 0) |
68 | 0 | memmove(com->val + i, com->val + (i + 1), |
69 | 0 | c * sizeof(*val)); |
70 | |
|
71 | 0 | com->size--; |
72 | |
|
73 | 0 | if (com->size > 0) |
74 | 0 | com->val = XREALLOC(MTYPE_COMMUNITY_VAL, |
75 | 0 | com->val, com_length(com)); |
76 | 0 | else { |
77 | 0 | XFREE(MTYPE_COMMUNITY_VAL, com->val); |
78 | 0 | } |
79 | 0 | return; |
80 | 0 | } |
81 | 0 | i++; |
82 | 0 | } |
83 | 0 | } |
84 | | |
85 | | /* Delete all communities listed in com2 from com1 */ |
86 | | struct community *community_delete(struct community *com1, |
87 | | struct community *com2) |
88 | 0 | { |
89 | 0 | int i = 0; |
90 | |
|
91 | 0 | while (i < com2->size) { |
92 | 0 | community_del_val(com1, com2->val + i); |
93 | 0 | i++; |
94 | 0 | } |
95 | |
|
96 | 0 | return com1; |
97 | 0 | } |
98 | | |
99 | | /* Callback function from qsort(). */ |
100 | | static int community_compare(const void *a1, const void *a2) |
101 | 8.96k | { |
102 | 8.96k | uint32_t v1; |
103 | 8.96k | uint32_t v2; |
104 | | |
105 | 8.96k | memcpy(&v1, a1, sizeof(uint32_t)); |
106 | 8.96k | memcpy(&v2, a2, sizeof(uint32_t)); |
107 | 8.96k | v1 = ntohl(v1); |
108 | 8.96k | v2 = ntohl(v2); |
109 | | |
110 | 8.96k | if (v1 < v2) |
111 | 6.14k | return -1; |
112 | 2.82k | if (v1 > v2) |
113 | 2.82k | return 1; |
114 | 0 | return 0; |
115 | 2.82k | } |
116 | | |
117 | | bool community_include(struct community *com, uint32_t val) |
118 | 6.34k | { |
119 | 6.34k | int i; |
120 | | |
121 | 6.34k | val = htonl(val); |
122 | | |
123 | 105k | for (i = 0; i < com->size; i++) |
124 | 100k | if (memcmp(&val, com_nthval(com, i), sizeof(uint32_t)) == 0) |
125 | 1.05k | return true; |
126 | 5.28k | return false; |
127 | 6.34k | } |
128 | | |
129 | | uint32_t community_val_get(struct community *com, int i) |
130 | 4.58k | { |
131 | 4.58k | uint8_t *p; |
132 | 4.58k | uint32_t val; |
133 | | |
134 | 4.58k | p = (uint8_t *)com->val; |
135 | 4.58k | p += (i * COMMUNITY_SIZE); |
136 | | |
137 | 4.58k | memcpy(&val, p, sizeof(uint32_t)); |
138 | | |
139 | 4.58k | return ntohl(val); |
140 | 4.58k | } |
141 | | |
142 | | /* Sort and uniq given community. */ |
143 | | struct community *community_uniq_sort(struct community *com) |
144 | 780 | { |
145 | 780 | int i; |
146 | 780 | struct community *new; |
147 | 780 | uint32_t val; |
148 | | |
149 | 780 | if (!com) |
150 | 0 | return NULL; |
151 | | |
152 | 780 | new = community_new(); |
153 | 780 | new->json = NULL; |
154 | | |
155 | 5.36k | for (i = 0; i < com->size; i++) { |
156 | 4.58k | val = community_val_get(com, i); |
157 | | |
158 | 4.58k | if (!community_include(new, val)) |
159 | 4.07k | community_add_val(new, val); |
160 | 4.58k | } |
161 | | |
162 | 780 | qsort(new->val, new->size, sizeof(uint32_t), community_compare); |
163 | | |
164 | 780 | return new; |
165 | 780 | } |
166 | | |
167 | | /* Convert communities attribute to string. |
168 | | |
169 | | For Well-known communities value, below keyword is used. |
170 | | |
171 | | 0xFFFF0000 "graceful-shutdown" |
172 | | 0xFFFF0001 "accept-own" |
173 | | 0xFFFF0002 "route-filter-translated-v4" |
174 | | 0xFFFF0003 "route-filter-v4" |
175 | | 0xFFFF0004 "route-filter-translated-v6" |
176 | | 0xFFFF0005 "route-filter-v6" |
177 | | 0xFFFF0006 "llgr-stale" |
178 | | 0xFFFF0007 "no-llgr" |
179 | | 0xFFFF0008 "accept-own-nexthop" |
180 | | 0xFFFF029A "blackhole" |
181 | | 0xFFFFFF01 "no-export" |
182 | | 0xFFFFFF02 "no-advertise" |
183 | | 0xFFFFFF03 "local-AS" |
184 | | 0xFFFFFF04 "no-peer" |
185 | | |
186 | | For other values, "AS:VAL" format is used. */ |
187 | | static void set_community_string(struct community *com, bool make_json, |
188 | | bool translate_alias) |
189 | 24 | { |
190 | 24 | int i; |
191 | 24 | char *str; |
192 | 24 | int len; |
193 | 24 | int first; |
194 | 24 | uint32_t comval; |
195 | 24 | uint16_t as; |
196 | 24 | uint16_t val; |
197 | 24 | json_object *json_community_list = NULL; |
198 | 24 | json_object *json_string = NULL; |
199 | | |
200 | 24 | if (!com) |
201 | 0 | return; |
202 | | |
203 | 24 | if (make_json) { |
204 | 0 | com->json = json_object_new_object(); |
205 | 0 | json_community_list = json_object_new_array(); |
206 | 0 | } |
207 | | |
208 | | /* When communities attribute is empty. */ |
209 | 24 | if (com->size == 0) { |
210 | 0 | str = XMALLOC(MTYPE_COMMUNITY_STR, 1); |
211 | 0 | str[0] = '\0'; |
212 | |
|
213 | 0 | if (make_json) { |
214 | 0 | json_object_string_add(com->json, "string", ""); |
215 | 0 | json_object_object_add(com->json, "list", |
216 | 0 | json_community_list); |
217 | 0 | } |
218 | 0 | com->str = str; |
219 | 0 | return; |
220 | 0 | } |
221 | | |
222 | | /* Memory allocation is time consuming work. So we calculate |
223 | | required string length first. */ |
224 | 24 | len = 0; |
225 | | |
226 | 1.07k | for (i = 0; i < com->size; i++) { |
227 | 1.05k | memcpy(&comval, com_nthval(com, i), sizeof(uint32_t)); |
228 | 1.05k | comval = ntohl(comval); |
229 | | |
230 | 1.05k | switch (comval) { |
231 | | #if CONFDATE > 20230801 |
232 | | CPP_NOTICE("Deprecate COMMUNITY_INTERNET BGP community") |
233 | | #endif |
234 | 12 | case COMMUNITY_INTERNET: |
235 | 12 | len += strlen(" internet"); |
236 | 12 | zlog_warn("`internet` community is deprecated"); |
237 | 12 | break; |
238 | 3 | case COMMUNITY_GSHUT: |
239 | 3 | len += strlen(" graceful-shutdown"); |
240 | 3 | break; |
241 | 0 | case COMMUNITY_ACCEPT_OWN: |
242 | 0 | len += strlen(" accept-own"); |
243 | 0 | break; |
244 | 0 | case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: |
245 | 0 | len += strlen(" route-filter-translated-v4"); |
246 | 0 | break; |
247 | 0 | case COMMUNITY_ROUTE_FILTER_v4: |
248 | 0 | len += strlen(" route-filter-v4"); |
249 | 0 | break; |
250 | 0 | case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: |
251 | 0 | len += strlen(" route-filter-translated-v6"); |
252 | 0 | break; |
253 | 1 | case COMMUNITY_ROUTE_FILTER_v6: |
254 | 1 | len += strlen(" route-filter-v6"); |
255 | 1 | break; |
256 | 0 | case COMMUNITY_LLGR_STALE: |
257 | 0 | len += strlen(" llgr-stale"); |
258 | 0 | break; |
259 | 0 | case COMMUNITY_NO_LLGR: |
260 | 0 | len += strlen(" no-llgr"); |
261 | 0 | break; |
262 | 0 | case COMMUNITY_ACCEPT_OWN_NEXTHOP: |
263 | 0 | len += strlen(" accept-own-nexthop"); |
264 | 0 | break; |
265 | 6 | case COMMUNITY_BLACKHOLE: |
266 | 6 | len += strlen(" blackhole"); |
267 | 6 | break; |
268 | 1 | case COMMUNITY_NO_EXPORT: |
269 | 1 | len += strlen(" no-export"); |
270 | 1 | break; |
271 | 4 | case COMMUNITY_NO_ADVERTISE: |
272 | 4 | len += strlen(" no-advertise"); |
273 | 4 | break; |
274 | 0 | case COMMUNITY_LOCAL_AS: |
275 | 0 | len += strlen(" local-AS"); |
276 | 0 | break; |
277 | 4 | case COMMUNITY_NO_PEER: |
278 | 4 | len += strlen(" no-peer"); |
279 | 4 | break; |
280 | 1.01k | default: |
281 | 1.01k | len = BUFSIZ; |
282 | 1.01k | break; |
283 | 1.05k | } |
284 | 1.05k | } |
285 | | |
286 | | /* Allocate memory. */ |
287 | 24 | str = XCALLOC(MTYPE_COMMUNITY_STR, len); |
288 | 24 | first = 1; |
289 | | |
290 | | /* Fill in string. */ |
291 | 1.07k | for (i = 0; i < com->size; i++) { |
292 | 1.05k | memcpy(&comval, com_nthval(com, i), sizeof(uint32_t)); |
293 | 1.05k | comval = ntohl(comval); |
294 | | |
295 | 1.05k | if (first) |
296 | 24 | first = 0; |
297 | 1.02k | else |
298 | 1.02k | strlcat(str, " ", len); |
299 | | |
300 | 1.05k | switch (comval) { |
301 | | #if CONFDATE > 20230801 |
302 | | CPP_NOTICE("Deprecate COMMUNITY_INTERNET BGP community") |
303 | | #endif |
304 | 12 | case COMMUNITY_INTERNET: |
305 | 12 | strlcat(str, "internet", len); |
306 | 12 | if (make_json) { |
307 | 0 | json_string = |
308 | 0 | json_object_new_string("internet"); |
309 | 0 | json_object_array_add(json_community_list, |
310 | 0 | json_string); |
311 | 0 | } |
312 | 12 | zlog_warn("`internet` community is deprecated"); |
313 | 12 | break; |
314 | 3 | case COMMUNITY_GSHUT: |
315 | 3 | strlcat(str, "graceful-shutdown", len); |
316 | 3 | if (make_json) { |
317 | 0 | json_string = json_object_new_string( |
318 | 0 | "gracefulShutdown"); |
319 | 0 | json_object_array_add(json_community_list, |
320 | 0 | json_string); |
321 | 0 | } |
322 | 3 | break; |
323 | 0 | case COMMUNITY_ACCEPT_OWN: |
324 | 0 | strlcat(str, "accept-own", len); |
325 | 0 | if (make_json) { |
326 | 0 | json_string = json_object_new_string( |
327 | 0 | "acceptown"); |
328 | 0 | json_object_array_add(json_community_list, |
329 | 0 | json_string); |
330 | 0 | } |
331 | 0 | break; |
332 | 0 | case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: |
333 | 0 | strlcat(str, "route-filter-translated-v4", len); |
334 | 0 | if (make_json) { |
335 | 0 | json_string = json_object_new_string( |
336 | 0 | "routeFilterTranslatedV4"); |
337 | 0 | json_object_array_add(json_community_list, |
338 | 0 | json_string); |
339 | 0 | } |
340 | 0 | break; |
341 | 0 | case COMMUNITY_ROUTE_FILTER_v4: |
342 | 0 | strlcat(str, "route-filter-v4", len); |
343 | 0 | if (make_json) { |
344 | 0 | json_string = json_object_new_string( |
345 | 0 | "routeFilterV4"); |
346 | 0 | json_object_array_add(json_community_list, |
347 | 0 | json_string); |
348 | 0 | } |
349 | 0 | break; |
350 | 0 | case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: |
351 | 0 | strlcat(str, "route-filter-translated-v6", len); |
352 | 0 | if (make_json) { |
353 | 0 | json_string = json_object_new_string( |
354 | 0 | "routeFilterTranslatedV6"); |
355 | 0 | json_object_array_add(json_community_list, |
356 | 0 | json_string); |
357 | 0 | } |
358 | 0 | break; |
359 | 1 | case COMMUNITY_ROUTE_FILTER_v6: |
360 | 1 | strlcat(str, "route-filter-v6", len); |
361 | 1 | if (make_json) { |
362 | 0 | json_string = json_object_new_string( |
363 | 0 | "routeFilterV6"); |
364 | 0 | json_object_array_add(json_community_list, |
365 | 0 | json_string); |
366 | 0 | } |
367 | 1 | break; |
368 | 0 | case COMMUNITY_LLGR_STALE: |
369 | 0 | strlcat(str, "llgr-stale", len); |
370 | 0 | if (make_json) { |
371 | 0 | json_string = json_object_new_string( |
372 | 0 | "llgrStale"); |
373 | 0 | json_object_array_add(json_community_list, |
374 | 0 | json_string); |
375 | 0 | } |
376 | 0 | break; |
377 | 0 | case COMMUNITY_NO_LLGR: |
378 | 0 | strlcat(str, "no-llgr", len); |
379 | 0 | if (make_json) { |
380 | 0 | json_string = json_object_new_string( |
381 | 0 | "noLlgr"); |
382 | 0 | json_object_array_add(json_community_list, |
383 | 0 | json_string); |
384 | 0 | } |
385 | 0 | break; |
386 | 0 | case COMMUNITY_ACCEPT_OWN_NEXTHOP: |
387 | 0 | strlcat(str, "accept-own-nexthop", len); |
388 | 0 | if (make_json) { |
389 | 0 | json_string = json_object_new_string( |
390 | 0 | "acceptownnexthop"); |
391 | 0 | json_object_array_add(json_community_list, |
392 | 0 | json_string); |
393 | 0 | } |
394 | 0 | break; |
395 | 6 | case COMMUNITY_BLACKHOLE: |
396 | 6 | strlcat(str, "blackhole", len); |
397 | 6 | if (make_json) { |
398 | 0 | json_string = json_object_new_string( |
399 | 0 | "blackhole"); |
400 | 0 | json_object_array_add(json_community_list, |
401 | 0 | json_string); |
402 | 0 | } |
403 | 6 | break; |
404 | 1 | case COMMUNITY_NO_EXPORT: |
405 | 1 | strlcat(str, "no-export", len); |
406 | 1 | if (make_json) { |
407 | 0 | json_string = |
408 | 0 | json_object_new_string("noExport"); |
409 | 0 | json_object_array_add(json_community_list, |
410 | 0 | json_string); |
411 | 0 | } |
412 | 1 | break; |
413 | 4 | case COMMUNITY_NO_ADVERTISE: |
414 | 4 | strlcat(str, "no-advertise", len); |
415 | 4 | if (make_json) { |
416 | 0 | json_string = |
417 | 0 | json_object_new_string("noAdvertise"); |
418 | 0 | json_object_array_add(json_community_list, |
419 | 0 | json_string); |
420 | 0 | } |
421 | 4 | break; |
422 | 0 | case COMMUNITY_LOCAL_AS: |
423 | 0 | strlcat(str, "local-AS", len); |
424 | 0 | if (make_json) { |
425 | 0 | json_string = json_object_new_string("localAs"); |
426 | 0 | json_object_array_add(json_community_list, |
427 | 0 | json_string); |
428 | 0 | } |
429 | 0 | break; |
430 | 4 | case COMMUNITY_NO_PEER: |
431 | 4 | strlcat(str, "no-peer", len); |
432 | 4 | if (make_json) { |
433 | 0 | json_string = json_object_new_string("noPeer"); |
434 | 0 | json_object_array_add(json_community_list, |
435 | 0 | json_string); |
436 | 0 | } |
437 | 4 | break; |
438 | 1.01k | default: |
439 | 1.01k | as = (comval >> 16) & 0xFFFF; |
440 | 1.01k | val = comval & 0xFFFF; |
441 | 1.01k | char buf[32]; |
442 | 1.01k | snprintf(buf, sizeof(buf), "%u:%d", as, val); |
443 | 1.01k | const char *com2alias = |
444 | 1.01k | translate_alias ? bgp_community2alias(buf) |
445 | 1.01k | : buf; |
446 | | |
447 | 1.01k | strlcat(str, com2alias, len); |
448 | 1.01k | if (make_json) { |
449 | 0 | json_string = json_object_new_string(com2alias); |
450 | 0 | json_object_array_add(json_community_list, |
451 | 0 | json_string); |
452 | 0 | } |
453 | 1.01k | break; |
454 | 1.05k | } |
455 | 1.05k | } |
456 | | |
457 | 24 | if (make_json) { |
458 | 0 | json_object_string_add(com->json, "string", str); |
459 | 0 | json_object_object_add(com->json, "list", json_community_list); |
460 | 0 | } |
461 | 24 | com->str = str; |
462 | 24 | } |
463 | | |
464 | | /* Intern communities attribute. */ |
465 | | struct community *community_intern(struct community *com) |
466 | 24 | { |
467 | 24 | struct community *find; |
468 | | |
469 | | /* Assert this community structure is not interned. */ |
470 | 24 | assert(com->refcnt == 0); |
471 | | |
472 | | /* Lookup community hash. */ |
473 | 24 | find = (struct community *)hash_get(comhash, com, hash_alloc_intern); |
474 | | |
475 | | /* Arguemnt com is allocated temporary. So when it is not used in |
476 | | hash, it should be freed. */ |
477 | 24 | if (find != com) |
478 | 0 | community_free(&com); |
479 | | |
480 | | /* Increment refrence counter. */ |
481 | 24 | find->refcnt++; |
482 | | |
483 | | /* Make string. */ |
484 | 24 | if (!find->str) |
485 | 24 | set_community_string(find, false, true); |
486 | | |
487 | 24 | return find; |
488 | 24 | } |
489 | | |
490 | | /* Free community attribute. */ |
491 | | void community_unintern(struct community **com) |
492 | 401 | { |
493 | 401 | struct community *ret; |
494 | | |
495 | 401 | if (!*com) |
496 | 377 | return; |
497 | | |
498 | 24 | if ((*com)->refcnt) |
499 | 24 | (*com)->refcnt--; |
500 | | |
501 | | /* Pull off from hash. */ |
502 | 24 | if ((*com)->refcnt == 0) { |
503 | | /* Community value com must exist in hash. */ |
504 | 24 | ret = (struct community *)hash_release(comhash, *com); |
505 | 24 | assert(ret != NULL); |
506 | | |
507 | 24 | community_free(com); |
508 | 24 | } |
509 | 24 | } |
510 | | |
511 | | /* Create new community attribute. */ |
512 | | struct community *community_parse(uint32_t *pnt, unsigned short length) |
513 | 24 | { |
514 | 24 | struct community tmp; |
515 | 24 | struct community *new; |
516 | | |
517 | | /* If length is malformed return NULL. */ |
518 | 24 | if (length % COMMUNITY_SIZE) |
519 | 0 | return NULL; |
520 | | |
521 | | /* Make temporary community for hash look up. */ |
522 | 24 | tmp.size = length / COMMUNITY_SIZE; |
523 | 24 | tmp.val = pnt; |
524 | | |
525 | 24 | new = community_uniq_sort(&tmp); |
526 | | |
527 | 24 | return community_intern(new); |
528 | 24 | } |
529 | | |
530 | | struct community *community_dup(struct community *com) |
531 | 378 | { |
532 | 378 | struct community *new; |
533 | | |
534 | 378 | new = XCALLOC(MTYPE_COMMUNITY, sizeof(struct community)); |
535 | 378 | new->size = com->size; |
536 | 378 | if (new->size) { |
537 | 378 | new->val = XMALLOC(MTYPE_COMMUNITY_VAL, |
538 | 378 | com->size * COMMUNITY_SIZE); |
539 | 378 | memcpy(new->val, com->val, com->size * COMMUNITY_SIZE); |
540 | 378 | } else |
541 | 0 | new->val = NULL; |
542 | 378 | return new; |
543 | 378 | } |
544 | | |
545 | | /* Return string representation of communities attribute. */ |
546 | | char *community_str(struct community *com, bool make_json, bool translate_alias) |
547 | 3 | { |
548 | 3 | if (!com) |
549 | 0 | return NULL; |
550 | | |
551 | 3 | if (make_json && !com->json && com->str) |
552 | 0 | XFREE(MTYPE_COMMUNITY_STR, com->str); |
553 | | |
554 | 3 | if (!com->str) |
555 | 0 | set_community_string(com, make_json, translate_alias); |
556 | 3 | return com->str; |
557 | 3 | } |
558 | | |
559 | | /* Make hash value of community attribute. This function is used by |
560 | | hash package.*/ |
561 | | unsigned int community_hash_make(const struct community *com) |
562 | 48 | { |
563 | 48 | uint32_t *pnt = com->val; |
564 | | |
565 | 48 | return jhash2(pnt, com->size, 0x43ea96c1); |
566 | 48 | } |
567 | | |
568 | | bool community_match(const struct community *com1, const struct community *com2) |
569 | 0 | { |
570 | 0 | int i = 0; |
571 | 0 | int j = 0; |
572 | |
|
573 | 0 | if (com1 == NULL && com2 == NULL) |
574 | 0 | return true; |
575 | | |
576 | 0 | if (com1 == NULL || com2 == NULL) |
577 | 0 | return false; |
578 | | |
579 | 0 | if (com1->size < com2->size) |
580 | 0 | return false; |
581 | | |
582 | | /* Every community on com2 needs to be on com1 for this to match */ |
583 | 0 | while (i < com1->size && j < com2->size) { |
584 | 0 | if (memcmp(com1->val + i, com2->val + j, sizeof(uint32_t)) == 0) |
585 | 0 | j++; |
586 | 0 | i++; |
587 | 0 | } |
588 | |
|
589 | 0 | if (j == com2->size) |
590 | 0 | return true; |
591 | 0 | else |
592 | 0 | return false; |
593 | 0 | } |
594 | | |
595 | | bool community_cmp(const struct community *com1, const struct community *com2) |
596 | 24 | { |
597 | 24 | if (com1 == NULL && com2 == NULL) |
598 | 0 | return true; |
599 | 24 | if (com1 == NULL || com2 == NULL) |
600 | 0 | return false; |
601 | | |
602 | 24 | if (com1->size == com2->size) |
603 | 24 | if (memcmp(com1->val, com2->val, com1->size * COMMUNITY_SIZE) |
604 | 24 | == 0) |
605 | 24 | return true; |
606 | 0 | return false; |
607 | 24 | } |
608 | | |
609 | | /* Add com2 to the end of com1. */ |
610 | | struct community *community_merge(struct community *com1, |
611 | | struct community *com2) |
612 | 378 | { |
613 | 378 | com1->val = XREALLOC(MTYPE_COMMUNITY_VAL, com1->val, |
614 | 378 | (com1->size + com2->size) * COMMUNITY_SIZE); |
615 | | |
616 | 378 | memcpy(com1->val + com1->size, com2->val, com2->size * COMMUNITY_SIZE); |
617 | 378 | com1->size += com2->size; |
618 | | |
619 | 378 | return com1; |
620 | 378 | } |
621 | | |
622 | | /* Community token enum. */ |
623 | | enum community_token { |
624 | | community_token_val, |
625 | | community_token_gshut, |
626 | | community_token_accept_own, |
627 | | community_token_route_filter_translated_v4, |
628 | | community_token_route_filter_v4, |
629 | | community_token_route_filter_translated_v6, |
630 | | community_token_route_filter_v6, |
631 | | community_token_llgr_stale, |
632 | | community_token_no_llgr, |
633 | | community_token_accept_own_nexthop, |
634 | | community_token_blackhole, |
635 | | community_token_no_export, |
636 | | community_token_no_advertise, |
637 | | community_token_local_as, |
638 | | community_token_no_peer, |
639 | | community_token_unknown |
640 | | }; |
641 | | |
642 | | /* Helper to check if a given community is valid */ |
643 | | static bool community_valid(const char *community) |
644 | 0 | { |
645 | 0 | int octets = 0; |
646 | 0 | char **splits; |
647 | 0 | int num; |
648 | 0 | int invalid = 0; |
649 | |
|
650 | 0 | frrstr_split(community, ":", &splits, &num); |
651 | |
|
652 | 0 | for (int i = 0; i < num; i++) { |
653 | 0 | if (strtoul(splits[i], NULL, 10) > UINT16_MAX) |
654 | 0 | invalid++; |
655 | |
|
656 | 0 | if (strlen(splits[i]) == 0) |
657 | 0 | invalid++; |
658 | |
|
659 | 0 | octets++; |
660 | 0 | XFREE(MTYPE_TMP, splits[i]); |
661 | 0 | } |
662 | 0 | XFREE(MTYPE_TMP, splits); |
663 | |
|
664 | 0 | return (octets < 2 || invalid) ? false : true; |
665 | 0 | } |
666 | | |
667 | | /* Get next community token from string. */ |
668 | | static const char * |
669 | | community_gettoken(const char *buf, enum community_token *token, uint32_t *val) |
670 | 756 | { |
671 | 756 | const char *p = buf; |
672 | | |
673 | | /* Skip white space. */ |
674 | 756 | while (isspace((unsigned char)*p)) |
675 | 0 | p++; |
676 | | |
677 | | /* Check the end of the line. */ |
678 | 756 | if (*p == '\0') |
679 | 378 | return NULL; |
680 | | |
681 | | /* Well known community string check. */ |
682 | 378 | if (isalpha((unsigned char)*p)) { |
683 | | #if CONFDATE > 20230801 |
684 | | CPP_NOTICE("Deprecate COMMUNITY_INTERNET BGP community") |
685 | | #endif |
686 | 378 | if (strncmp(p, "internet", strlen("internet")) == 0) { |
687 | 0 | *val = COMMUNITY_INTERNET; |
688 | 0 | *token = community_token_no_export; |
689 | 0 | p += strlen("internet"); |
690 | 0 | zlog_warn("`internet` community is deprecated"); |
691 | 0 | return p; |
692 | 0 | } |
693 | 378 | if (strncmp(p, "graceful-shutdown", strlen("graceful-shutdown")) |
694 | 378 | == 0) { |
695 | 0 | *val = COMMUNITY_GSHUT; |
696 | 0 | *token = community_token_gshut; |
697 | 0 | p += strlen("graceful-shutdown"); |
698 | 0 | return p; |
699 | 0 | } |
700 | 378 | if (strncmp(p, "accept-own-nexthop", |
701 | 378 | strlen("accept-own-nexthop")) |
702 | 378 | == 0) { |
703 | 0 | *val = COMMUNITY_ACCEPT_OWN_NEXTHOP; |
704 | 0 | *token = community_token_accept_own_nexthop; |
705 | 0 | p += strlen("accept-own-nexthop"); |
706 | 0 | return p; |
707 | 0 | } |
708 | 378 | if (strncmp(p, "accept-own", strlen("accept-own")) |
709 | 378 | == 0) { |
710 | 0 | *val = COMMUNITY_ACCEPT_OWN; |
711 | 0 | *token = community_token_accept_own; |
712 | 0 | p += strlen("accept-own"); |
713 | 0 | return p; |
714 | 0 | } |
715 | 378 | if (strncmp(p, "route-filter-translated-v4", |
716 | 378 | strlen("route-filter-translated-v4")) |
717 | 378 | == 0) { |
718 | 0 | *val = COMMUNITY_ROUTE_FILTER_TRANSLATED_v4; |
719 | 0 | *token = community_token_route_filter_translated_v4; |
720 | 0 | p += strlen("route-filter-translated-v4"); |
721 | 0 | return p; |
722 | 0 | } |
723 | 378 | if (strncmp(p, "route-filter-v4", strlen("route-filter-v4")) |
724 | 378 | == 0) { |
725 | 0 | *val = COMMUNITY_ROUTE_FILTER_v4; |
726 | 0 | *token = community_token_route_filter_v4; |
727 | 0 | p += strlen("route-filter-v4"); |
728 | 0 | return p; |
729 | 0 | } |
730 | 378 | if (strncmp(p, "route-filter-translated-v6", |
731 | 378 | strlen("route-filter-translated-v6")) |
732 | 378 | == 0) { |
733 | 0 | *val = COMMUNITY_ROUTE_FILTER_TRANSLATED_v6; |
734 | 0 | *token = community_token_route_filter_translated_v6; |
735 | 0 | p += strlen("route-filter-translated-v6"); |
736 | 0 | return p; |
737 | 0 | } |
738 | 378 | if (strncmp(p, "route-filter-v6", strlen("route-filter-v6")) |
739 | 378 | == 0) { |
740 | 0 | *val = COMMUNITY_ROUTE_FILTER_v6; |
741 | 0 | *token = community_token_route_filter_v6; |
742 | 0 | p += strlen("route-filter-v6"); |
743 | 0 | return p; |
744 | 0 | } |
745 | 378 | if (strncmp(p, "llgr-stale", strlen("llgr-stale")) |
746 | 378 | == 0) { |
747 | 0 | *val = COMMUNITY_LLGR_STALE; |
748 | 0 | *token = community_token_llgr_stale; |
749 | 0 | p += strlen("llgr-stale"); |
750 | 0 | return p; |
751 | 0 | } |
752 | 378 | if (strncmp(p, "no-llgr", strlen("no-llgr")) |
753 | 378 | == 0) { |
754 | 0 | *val = COMMUNITY_NO_LLGR; |
755 | 0 | *token = community_token_no_llgr; |
756 | 0 | p += strlen("no-llgr"); |
757 | 0 | return p; |
758 | 0 | } |
759 | 378 | if (strncmp(p, "blackhole", strlen("blackhole")) |
760 | 378 | == 0) { |
761 | 0 | *val = COMMUNITY_BLACKHOLE; |
762 | 0 | *token = community_token_blackhole; |
763 | 0 | p += strlen("blackhole"); |
764 | 0 | return p; |
765 | 0 | } |
766 | 378 | if (strncmp(p, "no-export", strlen("no-export")) == 0) { |
767 | 378 | *val = COMMUNITY_NO_EXPORT; |
768 | 378 | *token = community_token_no_export; |
769 | 378 | p += strlen("no-export"); |
770 | 378 | return p; |
771 | 378 | } |
772 | 0 | if (strncmp(p, "no-advertise", strlen("no-advertise")) == 0) { |
773 | 0 | *val = COMMUNITY_NO_ADVERTISE; |
774 | 0 | *token = community_token_no_advertise; |
775 | 0 | p += strlen("no-advertise"); |
776 | 0 | return p; |
777 | 0 | } |
778 | 0 | if (strncmp(p, "local-AS", strlen("local-AS")) == 0) { |
779 | 0 | *val = COMMUNITY_LOCAL_AS; |
780 | 0 | *token = community_token_local_as; |
781 | 0 | p += strlen("local-AS"); |
782 | 0 | return p; |
783 | 0 | } |
784 | 0 | if (strncmp(p, "no-peer", strlen("no-peer")) == 0) { |
785 | 0 | *val = COMMUNITY_NO_PEER; |
786 | 0 | *token = community_token_no_peer; |
787 | 0 | p += strlen("no-peer"); |
788 | 0 | return p; |
789 | 0 | } |
790 | | |
791 | | /* Unknown string. */ |
792 | 0 | *token = community_token_unknown; |
793 | 0 | return NULL; |
794 | 0 | } |
795 | | |
796 | | /* Community value. */ |
797 | 0 | if (isdigit((unsigned char)*p)) { |
798 | 0 | int separator = 0; |
799 | 0 | int digit = 0; |
800 | 0 | uint32_t community_low = 0; |
801 | 0 | uint32_t community_high = 0; |
802 | |
|
803 | 0 | if (!community_valid(p)) { |
804 | 0 | *token = community_token_unknown; |
805 | 0 | return NULL; |
806 | 0 | } |
807 | | |
808 | 0 | while (isdigit((unsigned char)*p) || *p == ':') { |
809 | 0 | if (*p == ':') { |
810 | 0 | if (separator) { |
811 | 0 | *token = community_token_unknown; |
812 | 0 | return NULL; |
813 | 0 | } else { |
814 | 0 | separator = 1; |
815 | 0 | digit = 0; |
816 | |
|
817 | 0 | if (community_low > UINT16_MAX) { |
818 | 0 | *token = |
819 | 0 | community_token_unknown; |
820 | 0 | return NULL; |
821 | 0 | } |
822 | | |
823 | 0 | community_high = community_low << 16; |
824 | 0 | community_low = 0; |
825 | 0 | } |
826 | 0 | } else { |
827 | 0 | digit = 1; |
828 | 0 | community_low *= 10; |
829 | 0 | community_low += (*p - '0'); |
830 | 0 | } |
831 | 0 | p++; |
832 | 0 | } |
833 | 0 | if (!digit) { |
834 | 0 | *token = community_token_unknown; |
835 | 0 | return NULL; |
836 | 0 | } |
837 | | |
838 | 0 | *val = community_high + community_low; |
839 | 0 | *token = community_token_val; |
840 | 0 | return p; |
841 | 0 | } |
842 | 0 | *token = community_token_unknown; |
843 | 0 | return NULL; |
844 | 0 | } |
845 | | |
846 | | /* convert string to community structure */ |
847 | | struct community *community_str2com(const char *str) |
848 | 378 | { |
849 | 378 | struct community *com = NULL; |
850 | 378 | struct community *com_sort = NULL; |
851 | 378 | uint32_t val = 0; |
852 | 378 | enum community_token token = community_token_unknown; |
853 | | |
854 | 756 | do { |
855 | 756 | str = community_gettoken(str, &token, &val); |
856 | | |
857 | 756 | switch (token) { |
858 | 0 | case community_token_val: |
859 | 0 | case community_token_gshut: |
860 | 0 | case community_token_accept_own: |
861 | 0 | case community_token_route_filter_translated_v4: |
862 | 0 | case community_token_route_filter_v4: |
863 | 0 | case community_token_route_filter_translated_v6: |
864 | 0 | case community_token_route_filter_v6: |
865 | 0 | case community_token_llgr_stale: |
866 | 0 | case community_token_no_llgr: |
867 | 0 | case community_token_accept_own_nexthop: |
868 | 0 | case community_token_blackhole: |
869 | 756 | case community_token_no_export: |
870 | 756 | case community_token_no_advertise: |
871 | 756 | case community_token_local_as: |
872 | 756 | case community_token_no_peer: |
873 | 756 | if (com == NULL) { |
874 | 378 | com = community_new(); |
875 | 378 | com->json = NULL; |
876 | 378 | } |
877 | 756 | community_add_val(com, val); |
878 | 756 | break; |
879 | 0 | case community_token_unknown: |
880 | 0 | if (com) |
881 | 0 | community_free(&com); |
882 | 0 | return NULL; |
883 | 756 | } |
884 | 756 | } while (str); |
885 | | |
886 | 378 | com_sort = community_uniq_sort(com); |
887 | 378 | community_free(&com); |
888 | | |
889 | 378 | return com_sort; |
890 | 378 | } |
891 | | |
892 | | /* Return communities hash entry count. */ |
893 | | unsigned long community_count(void) |
894 | 0 | { |
895 | 0 | return comhash->count; |
896 | 0 | } |
897 | | |
898 | | /* Return communities hash. */ |
899 | | struct hash *community_hash(void) |
900 | 0 | { |
901 | 0 | return comhash; |
902 | 0 | } |
903 | | |
904 | | /* Initialize comminity related hash. */ |
905 | | void community_init(void) |
906 | 1 | { |
907 | 1 | comhash = |
908 | 1 | hash_create((unsigned int (*)(const void *))community_hash_make, |
909 | 1 | (bool (*)(const void *, const void *))community_cmp, |
910 | 1 | "BGP Community Hash"); |
911 | 1 | } |
912 | | |
913 | | static void community_hash_free(void *data) |
914 | 0 | { |
915 | 0 | struct community *com = data; |
916 | |
|
917 | 0 | community_free(&com); |
918 | 0 | } |
919 | | |
920 | | void community_finish(void) |
921 | 0 | { |
922 | 0 | hash_clean_and_free(&comhash, community_hash_free); |
923 | 0 | } |
924 | | |
925 | | static struct community *bgp_aggr_community_lookup( |
926 | | struct bgp_aggregate *aggregate, |
927 | | struct community *community) |
928 | 0 | { |
929 | 0 | return hash_lookup(aggregate->community_hash, community); |
930 | 0 | } |
931 | | |
932 | | static void *bgp_aggr_community_hash_alloc(void *p) |
933 | 0 | { |
934 | 0 | struct community *ref = (struct community *)p; |
935 | 0 | struct community *community = NULL; |
936 | |
|
937 | 0 | community = community_dup(ref); |
938 | 0 | return community; |
939 | 0 | } |
940 | | |
941 | | static void bgp_aggr_community_prepare(struct hash_bucket *hb, void *arg) |
942 | 0 | { |
943 | 0 | struct community *hb_community = hb->data; |
944 | 0 | struct community **aggr_community = arg; |
945 | |
|
946 | 0 | if (*aggr_community) |
947 | 0 | *aggr_community = community_merge(*aggr_community, |
948 | 0 | hb_community); |
949 | 0 | else |
950 | 0 | *aggr_community = community_dup(hb_community); |
951 | 0 | } |
952 | | |
953 | | void bgp_aggr_community_remove(void *arg) |
954 | 0 | { |
955 | 0 | struct community *community = arg; |
956 | |
|
957 | 0 | community_free(&community); |
958 | 0 | } |
959 | | |
960 | | void bgp_compute_aggregate_community(struct bgp_aggregate *aggregate, |
961 | | struct community *community) |
962 | 0 | { |
963 | 0 | bgp_compute_aggregate_community_hash(aggregate, community); |
964 | 0 | bgp_compute_aggregate_community_val(aggregate); |
965 | 0 | } |
966 | | |
967 | | |
968 | | void bgp_compute_aggregate_community_hash(struct bgp_aggregate *aggregate, |
969 | | struct community *community) |
970 | 0 | { |
971 | 0 | struct community *aggr_community = NULL; |
972 | |
|
973 | 0 | if ((aggregate == NULL) || (community == NULL)) |
974 | 0 | return; |
975 | | |
976 | | /* Create hash if not already created. |
977 | | */ |
978 | 0 | if (aggregate->community_hash == NULL) |
979 | 0 | aggregate->community_hash = hash_create( |
980 | 0 | (unsigned int (*)(const void *))community_hash_make, |
981 | 0 | (bool (*)(const void *, const void *))community_cmp, |
982 | 0 | "BGP Aggregator community hash"); |
983 | |
|
984 | 0 | aggr_community = bgp_aggr_community_lookup(aggregate, community); |
985 | 0 | if (aggr_community == NULL) { |
986 | | /* Insert community into hash. |
987 | | */ |
988 | 0 | aggr_community = hash_get(aggregate->community_hash, community, |
989 | 0 | bgp_aggr_community_hash_alloc); |
990 | 0 | } |
991 | | |
992 | | /* Increment reference counter. |
993 | | */ |
994 | 0 | aggr_community->refcnt++; |
995 | 0 | } |
996 | | |
997 | | void bgp_compute_aggregate_community_val(struct bgp_aggregate *aggregate) |
998 | 0 | { |
999 | 0 | struct community *commerge = NULL; |
1000 | |
|
1001 | 0 | if (aggregate == NULL) |
1002 | 0 | return; |
1003 | | |
1004 | | /* Re-compute aggregate's community. |
1005 | | */ |
1006 | 0 | if (aggregate->community) |
1007 | 0 | community_free(&aggregate->community); |
1008 | 0 | if (aggregate->community_hash && |
1009 | 0 | aggregate->community_hash->count) { |
1010 | 0 | hash_iterate(aggregate->community_hash, |
1011 | 0 | bgp_aggr_community_prepare, |
1012 | 0 | &aggregate->community); |
1013 | 0 | commerge = aggregate->community; |
1014 | 0 | aggregate->community = community_uniq_sort(commerge); |
1015 | 0 | if (commerge) |
1016 | 0 | community_free(&commerge); |
1017 | 0 | } |
1018 | 0 | } |
1019 | | |
1020 | | |
1021 | | |
1022 | | void bgp_remove_community_from_aggregate(struct bgp_aggregate *aggregate, |
1023 | | struct community *community) |
1024 | 0 | { |
1025 | 0 | struct community *aggr_community = NULL; |
1026 | 0 | struct community *ret_comm = NULL; |
1027 | |
|
1028 | 0 | if ((!aggregate) |
1029 | 0 | || (!aggregate->community_hash) |
1030 | 0 | || (!community)) |
1031 | 0 | return; |
1032 | | |
1033 | | /* Look-up the community in the hash. |
1034 | | */ |
1035 | 0 | aggr_community = bgp_aggr_community_lookup(aggregate, community); |
1036 | 0 | if (aggr_community) { |
1037 | 0 | aggr_community->refcnt--; |
1038 | |
|
1039 | 0 | if (aggr_community->refcnt == 0) { |
1040 | 0 | ret_comm = hash_release(aggregate->community_hash, |
1041 | 0 | aggr_community); |
1042 | 0 | community_free(&ret_comm); |
1043 | |
|
1044 | 0 | bgp_compute_aggregate_community_val(aggregate); |
1045 | 0 | } |
1046 | 0 | } |
1047 | 0 | } |
1048 | | |
1049 | | void bgp_remove_comm_from_aggregate_hash(struct bgp_aggregate *aggregate, |
1050 | | struct community *community) |
1051 | 0 | { |
1052 | |
|
1053 | 0 | struct community *aggr_community = NULL; |
1054 | 0 | struct community *ret_comm = NULL; |
1055 | |
|
1056 | 0 | if ((!aggregate) |
1057 | 0 | || (!aggregate->community_hash) |
1058 | 0 | || (!community)) |
1059 | 0 | return; |
1060 | | |
1061 | | /* Look-up the community in the hash. |
1062 | | */ |
1063 | 0 | aggr_community = bgp_aggr_community_lookup(aggregate, community); |
1064 | 0 | if (aggr_community) { |
1065 | 0 | aggr_community->refcnt--; |
1066 | |
|
1067 | 0 | if (aggr_community->refcnt == 0) { |
1068 | 0 | ret_comm = hash_release(aggregate->community_hash, |
1069 | 0 | aggr_community); |
1070 | 0 | community_free(&ret_comm); |
1071 | 0 | } |
1072 | 0 | } |
1073 | 0 | } |