xref: /openbsd/usr.sbin/bgpd/rde_community.c (revision b3b1d939)
1 /*	$OpenBSD: rde_community.c,v 1.15 2024/01/24 14:51:12 claudio Exp $ */
2 
3 /*
4  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #include <sys/queue.h>
19 
20 #include <endian.h>
21 #include <limits.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "bgpd.h"
26 #include "rde.h"
27 #include "log.h"
28 
29 static int
apply_flag(uint32_t in,uint8_t flag,struct rde_peer * peer,uint32_t * out,uint32_t * mask)30 apply_flag(uint32_t in, uint8_t flag, struct rde_peer *peer, uint32_t *out,
31     uint32_t *mask)
32 {
33 	switch (flag) {
34 	case COMMUNITY_ANY:
35 		if (mask == NULL)
36 			return -1;
37 		*out = 0;
38 		*mask = 0;
39 		return 0;
40 	case COMMUNITY_NEIGHBOR_AS:
41 		if (peer == NULL)
42 			return -1;
43 		*out = peer->conf.remote_as;
44 		break;
45 	case COMMUNITY_LOCAL_AS:
46 		if (peer == NULL)
47 			return -1;
48 		*out = peer->conf.local_as;
49 		break;
50 	default:
51 		*out = in;
52 		break;
53 	}
54 	if (mask)
55 		*mask = UINT32_MAX;
56 	return 0;
57 }
58 
59 static int
fc2c(struct community * fc,struct rde_peer * peer,struct community * c,struct community * m)60 fc2c(struct community *fc, struct rde_peer *peer, struct community *c,
61     struct community *m)
62 {
63 	int type;
64 	uint8_t subtype;
65 
66 	memset(c, 0, sizeof(*c));
67 	if (m)
68 		memset(m, 0xff, sizeof(*m));
69 
70 	c->flags = (uint8_t)fc->flags;
71 
72 	switch ((uint8_t)c->flags) {
73 	case COMMUNITY_TYPE_BASIC:
74 		if (apply_flag(fc->data1, fc->flags >> 8, peer,
75 		    &c->data1, m ? &m->data1 : NULL))
76 			return -1;
77 		if (apply_flag(fc->data2, fc->flags >> 16, peer,
78 		    &c->data2, m ? &m->data2 : NULL))
79 			return -1;
80 
81 		/* check that values fit */
82 		if (c->data1 > USHRT_MAX || c->data2 > USHRT_MAX)
83 			return -1;
84 		return 0;
85 	case COMMUNITY_TYPE_LARGE:
86 		if (apply_flag(fc->data1, fc->flags >> 8, peer,
87 		    &c->data1, m ? &m->data1 : NULL))
88 			return -1;
89 		if (apply_flag(fc->data2, fc->flags >> 16, peer,
90 		    &c->data2, m ? &m->data2 : NULL))
91 			return -1;
92 		if (apply_flag(fc->data3, fc->flags >> 24, peer,
93 		    &c->data3, m ? &m->data3 : NULL))
94 			return -1;
95 		return 0;
96 	case COMMUNITY_TYPE_EXT:
97 		type = (int32_t)fc->data3 >> 8;
98 		subtype = fc->data3 & 0xff;
99 
100 		if ((fc->flags >> 24 & 0xff) == COMMUNITY_ANY) {
101 			/* special case for 'ext-community * *' */
102 			if (m == NULL)
103 				return -1;
104 			m->data1 = 0;
105 			m->data2 = 0;
106 			m->data3 = 0;
107 			return 0;
108 		}
109 
110 		if (type == -1) {
111 			/* special case for 'ext-community rt *' */
112 			if ((fc->flags >> 8 & 0xff) != COMMUNITY_ANY ||
113 			    m == NULL)
114 				return -1;
115 			c->data3 = subtype;
116 			m->data1 = 0;
117 			m->data2 = 0;
118 			m->data3 = 0xff;
119 			return 0;
120 		}
121 
122 		c->data3 = type << 8 | subtype;
123 		switch (type & EXT_COMMUNITY_VALUE) {
124 		case EXT_COMMUNITY_TRANS_TWO_AS:
125 		case EXT_COMMUNITY_TRANS_FOUR_AS:
126 			if ((fc->flags >> 8 & 0xff) == COMMUNITY_ANY)
127 				break;
128 
129 			if (apply_flag(fc->data1, fc->flags >> 8, peer,
130 			    &c->data1, m ? &m->data1 : NULL))
131 				return -1;
132 			if (apply_flag(fc->data2, fc->flags >> 16, peer,
133 			    &c->data2, m ? &m->data2 : NULL))
134 				return -1;
135 			if (m)
136 				m->data3 &= ~(EXT_COMMUNITY_TRANS_FOUR_AS << 8);
137 			return 0;
138 		case EXT_COMMUNITY_TRANS_IPV4:
139 			if ((fc->flags >> 8 & 0xff) == COMMUNITY_ANY)
140 				break;
141 
142 			if (apply_flag(fc->data1, fc->flags >> 8, peer,
143 			    &c->data1, m ? &m->data1 : NULL))
144 				return -1;
145 			if (apply_flag(fc->data2, fc->flags >> 16, peer,
146 			    &c->data2, m ? &m->data2 : NULL))
147 				return -1;
148 			/* check that values fit */
149 			if (c->data2 > USHRT_MAX)
150 				return -1;
151 			return 0;
152 		case EXT_COMMUNITY_TRANS_OPAQUE:
153 		case EXT_COMMUNITY_TRANS_EVPN:
154 			if ((fc->flags >> 8 & 0xff) == COMMUNITY_ANY)
155 				break;
156 
157 			c->data1 = fc->data1;
158 			c->data2 = fc->data2;
159 			return 0;
160 		}
161 
162 		/* this is for 'ext-community subtype *' */
163 		if (m == NULL)
164 			return -1;
165 		m->data1 = 0;
166 		m->data2 = 0;
167 		return 0;
168 	default:
169 		fatalx("%s: unknown type %d", __func__, (uint8_t)c->flags);
170 	}
171 }
172 
173 static int
fast_match(const void * va,const void * vb)174 fast_match(const void *va, const void *vb)
175 {
176 	const struct community *a = va;
177 	const struct community *b = vb;
178 
179 	if ((uint8_t)a->flags != (uint8_t)b->flags)
180 		return (uint8_t)a->flags > (uint8_t)b->flags ? 1 : -1;
181 
182 	if (a->data1 != b->data1)
183 		return a->data1 > b->data1 ? 1 : -1;
184 	if (a->data2 != b->data2)
185 		return a->data2 > b->data2 ? 1 : -1;
186 	if (a->data3 != b->data3)
187 		return a->data3 > b->data3 ? 1 : -1;
188 	return 0;
189 }
190 
191 static int
mask_match(struct community * a,struct community * b,struct community * m)192 mask_match(struct community *a, struct community *b, struct community *m)
193 {
194 	if ((uint8_t)a->flags != (uint8_t)b->flags)
195 		return (uint8_t)a->flags > (uint8_t)b->flags ? 1 : -1;
196 
197 	if ((a->data1 & m->data1) != (b->data1 & m->data1)) {
198 		if ((a->data1 & m->data1) > (b->data1 & m->data1))
199 			return 1;
200 		return -1;
201 	}
202 	if ((a->data2 & m->data2) != (b->data2 & m->data2)) {
203 		if ((a->data2 & m->data2) > (b->data2 & m->data2))
204 			return 1;
205 		return -1;
206 	}
207 	if ((a->data3 & m->data3) != (b->data3 & m->data3)) {
208 		if ((a->data3 & m->data3) > (b->data3 & m->data3))
209 			return 1;
210 		return -1;
211 	}
212 	return 0;
213 }
214 
215 /*
216  * Insert a community keeping the list sorted. Don't add if already present.
217  */
218 static void
insert_community(struct rde_community * comm,struct community * c)219 insert_community(struct rde_community *comm, struct community *c)
220 {
221 	int l;
222 	int r;
223 
224 	if (comm->nentries + 1 > comm->size) {
225 		struct community *new;
226 		int newsize = comm->size + 8;
227 
228 		if ((new = recallocarray(comm->communities, comm->size,
229 		    newsize, sizeof(struct community))) == NULL)
230 			fatal(__func__);
231 		comm->communities = new;
232 		comm->size = newsize;
233 	}
234 
235 	/* XXX can be made faster by binary search */
236 	for (l = 0; l < comm->nentries; l++) {
237 		r = fast_match(comm->communities + l, c);
238 		if (r == 0) {
239 			/* already present, nothing to do */
240 			return;
241 		} else if (r > 0) {
242 			/* shift reminder by one slot */
243 			memmove(comm->communities + l + 1,
244 			    comm->communities + l,
245 			    (comm->nentries - l) * sizeof(*c));
246 			break;
247 		}
248 	}
249 
250 	/* insert community at slot l */
251 	comm->communities[l] = *c;
252 	comm->nentries++;
253 }
254 
255 static int
non_transitive_ext_community(struct community * c)256 non_transitive_ext_community(struct community *c)
257 {
258 	if ((uint8_t)c->flags != COMMUNITY_TYPE_EXT)
259 		return 0;
260 	if ((c->data3 >> 8) & EXT_COMMUNITY_NON_TRANSITIVE)
261 		return 1;
262 	return 0;
263 }
264 
265 /*
266  * Check if a community is present. This function will expand local-as and
267  * neighbor-as and also mask of bits to support partial matches.
268  */
269 int
community_match(struct rde_community * comm,struct community * fc,struct rde_peer * peer)270 community_match(struct rde_community *comm, struct community *fc,
271 struct rde_peer *peer)
272 {
273 	struct community test, mask;
274 	int l;
275 
276 	if (fc->flags >> 8 == 0) {
277 		/* fast path */
278 		return (bsearch(fc, comm->communities, comm->nentries,
279 		    sizeof(*fc), fast_match) != NULL);
280 	} else {
281 		/* slow path */
282 		if (fc2c(fc, peer, &test, &mask) == -1)
283 			return 0;
284 
285 		for (l = 0; l < comm->nentries; l++) {
286 			if (mask_match(&comm->communities[l], &test,
287 			    &mask) == 0)
288 				return 1;
289 		}
290 		return 0;
291 	}
292 }
293 
294 /*
295  * Count the number of communities of type type.
296  */
297 int
community_count(struct rde_community * comm,uint8_t type)298 community_count(struct rde_community *comm, uint8_t type)
299 {
300 	int l;
301 	int count = 0;
302 
303 	/* use the fact that the array is ordered by type */
304 	switch (type) {
305 	case COMMUNITY_TYPE_BASIC:
306 		for (l = 0; l < comm->nentries; l++) {
307 			if ((uint8_t)comm->communities[l].flags == type)
308 				count++;
309 			else
310 				break;
311 		}
312 		break;
313 	case COMMUNITY_TYPE_EXT:
314 		for (l = 0; l < comm->nentries; l++) {
315 			if ((uint8_t)comm->communities[l].flags == type)
316 				count++;
317 			else if ((uint8_t)comm->communities[l].flags > type)
318 				break;
319 		}
320 		break;
321 	case COMMUNITY_TYPE_LARGE:
322 		for (l = comm->nentries; l > 0; l--) {
323 			if ((uint8_t)comm->communities[l - 1].flags == type)
324 				count++;
325 			else
326 				break;
327 		}
328 		break;
329 	}
330 	return count;
331 }
332 
333 /*
334  * Insert a community, expanding local-as and neighbor-as if needed.
335  */
336 int
community_set(struct rde_community * comm,struct community * fc,struct rde_peer * peer)337 community_set(struct rde_community *comm, struct community *fc,
338 struct rde_peer *peer)
339 {
340 	struct community set;
341 
342 	if (fc->flags >> 8 == 0) {
343 		/* fast path */
344 		insert_community(comm, fc);
345 	} else {
346 		if (fc2c(fc, peer, &set, NULL) == -1)
347 			return 0;
348 		if ((uint8_t)set.flags == COMMUNITY_TYPE_EXT) {
349 			int type = (int)set.data3 >> 8;
350 			switch (type & EXT_COMMUNITY_VALUE) {
351 			case EXT_COMMUNITY_TRANS_TWO_AS:
352 			case EXT_COMMUNITY_TRANS_FOUR_AS:
353 				/* check that values fit */
354 				if (set.data1 > USHRT_MAX &&
355 				    set.data2 > USHRT_MAX)
356 					return 0;
357 				if (set.data1 > USHRT_MAX)
358 					set.data3 = (set.data3 & 0xff) |
359 					    EXT_COMMUNITY_TRANS_FOUR_AS << 8;
360 				else
361 					set.data3 = (set.data3 & 0xff) |
362 					    EXT_COMMUNITY_TRANS_TWO_AS << 8;
363 				break;
364 			}
365 		}
366 		insert_community(comm, &set);
367 	}
368 	return 1;
369 }
370 
371 /*
372  * Remove a community if present, This function will expand local-as and
373  * neighbor-as and also mask of bits to support partial matches.
374  */
375 void
community_delete(struct rde_community * comm,struct community * fc,struct rde_peer * peer)376 community_delete(struct rde_community *comm, struct community *fc,
377 struct rde_peer *peer)
378 {
379 	struct community test, mask;
380 	struct community *match;
381 	int l = 0;
382 
383 	if (fc->flags >> 8 == 0) {
384 		/* fast path */
385 		match = bsearch(fc, comm->communities, comm->nentries,
386 		    sizeof(*fc), fast_match);
387 		if (match == NULL)
388 			return;
389 		/* move everything after match down by 1 */
390 		memmove(match, match + 1,
391 		    (char *)(comm->communities + comm->nentries) -
392 		    (char *)(match + 1));
393 		comm->nentries--;
394 		return;
395 	} else {
396 		if (fc2c(fc, peer, &test, &mask) == -1)
397 			return;
398 
399 		while (l < comm->nentries) {
400 			if (mask_match(&comm->communities[l], &test,
401 			    &mask) == 0) {
402 				memmove(comm->communities + l,
403 				    comm->communities + l + 1,
404 				    (comm->nentries - l - 1) * sizeof(test));
405 				comm->nentries--;
406 				continue;
407 			}
408 			l++;
409 		}
410 	}
411 }
412 
413 /*
414  * Internalize communities from the wireformat.
415  * Store the partial flag in struct rde_community so it is not lost.
416  * - community_add for ATTR_COMMUNITUES
417  * - community_large_add for ATTR_LARGE_COMMUNITIES
418  * - community_ext_add for ATTR_EXT_COMMUNITIES
419  */
420 int
community_add(struct rde_community * comm,int flags,struct ibuf * buf)421 community_add(struct rde_community *comm, int flags, struct ibuf *buf)
422 {
423 	struct community set = { .flags = COMMUNITY_TYPE_BASIC };
424 	uint16_t data1, data2;
425 
426 	if (ibuf_size(buf) == 0 || ibuf_size(buf) % 4 != 0)
427 		return -1;
428 
429 	if (flags & ATTR_PARTIAL)
430 		comm->flags |= PARTIAL_COMMUNITIES;
431 
432 	while (ibuf_size(buf) > 0) {
433 		if (ibuf_get_n16(buf, &data1) == -1 ||
434 		    ibuf_get_n16(buf, &data2) == -1)
435 			return -1;
436 		set.data1 = data1;
437 		set.data2 = data2;
438 		insert_community(comm, &set);
439 	}
440 
441 	return 0;
442 }
443 
444 int
community_large_add(struct rde_community * comm,int flags,struct ibuf * buf)445 community_large_add(struct rde_community *comm, int flags, struct ibuf *buf)
446 {
447 	struct community set = { .flags = COMMUNITY_TYPE_LARGE };
448 
449 	if (ibuf_size(buf) == 0 || ibuf_size(buf) % 12 != 0)
450 		return -1;
451 
452 	if (flags & ATTR_PARTIAL)
453 		comm->flags |= PARTIAL_LARGE_COMMUNITIES;
454 
455 	while (ibuf_size(buf) > 0) {
456 		if (ibuf_get_n32(buf, &set.data1) == -1 ||
457 		    ibuf_get_n32(buf, &set.data2) == -1 ||
458 		    ibuf_get_n32(buf, &set.data3) == -1)
459 			return -1;
460 		insert_community(comm, &set);
461 	}
462 
463 	return 0;
464 }
465 
466 int
community_ext_add(struct rde_community * comm,int flags,int ebgp,struct ibuf * buf)467 community_ext_add(struct rde_community *comm, int flags, int ebgp,
468     struct ibuf *buf)
469 {
470 	struct community set = { .flags = COMMUNITY_TYPE_EXT };
471 	uint64_t c;
472 	uint8_t type;
473 
474 	if (ibuf_size(buf) == 0 || ibuf_size(buf) % 8 != 0)
475 		return -1;
476 
477 	if (flags & ATTR_PARTIAL)
478 		comm->flags |= PARTIAL_EXT_COMMUNITIES;
479 
480 	while (ibuf_size(buf) > 0) {
481 		if (ibuf_get_n64(buf, &c) == -1)
482 			return (-1);
483 
484 		type = c >> 56;
485 		/* filter out non-transitive ext communuties from ebgp peers */
486 		if (ebgp && (type & EXT_COMMUNITY_NON_TRANSITIVE))
487 			continue;
488 		switch (type & EXT_COMMUNITY_VALUE) {
489 		case EXT_COMMUNITY_TRANS_TWO_AS:
490 		case EXT_COMMUNITY_TRANS_OPAQUE:
491 		case EXT_COMMUNITY_TRANS_EVPN:
492 			set.data1 = c >> 32 & 0xffff;
493 			set.data2 = c;
494 			break;
495 		case EXT_COMMUNITY_TRANS_FOUR_AS:
496 		case EXT_COMMUNITY_TRANS_IPV4:
497 			set.data1 = c >> 16;
498 			set.data2 = c & 0xffff;
499 			break;
500 		}
501 		set.data3 = c >> 48;
502 
503 		insert_community(comm, &set);
504 	}
505 
506 	return 0;
507 }
508 
509 /*
510  * Convert communities back to the wireformat.
511  * When writing ATTR_EXT_COMMUNITIES non-transitive communities need to
512  * be skipped if they are sent to an ebgp peer.
513  */
514 int
community_writebuf(struct rde_community * comm,uint8_t type,int ebgp,struct ibuf * buf)515 community_writebuf(struct rde_community *comm, uint8_t type, int ebgp,
516     struct ibuf *buf)
517 {
518 	struct community *cp;
519 	uint64_t ext;
520 	int l, size, start, end, num;
521 	int flags = ATTR_OPTIONAL | ATTR_TRANSITIVE;
522 	uint8_t t;
523 
524 	switch (type) {
525 	case ATTR_COMMUNITIES:
526 		if (comm->flags & PARTIAL_COMMUNITIES)
527 			flags |= ATTR_PARTIAL;
528 		size = 4;
529 		t = COMMUNITY_TYPE_BASIC;
530 		break;
531 	case ATTR_EXT_COMMUNITIES:
532 		if (comm->flags & PARTIAL_EXT_COMMUNITIES)
533 			flags |= ATTR_PARTIAL;
534 		size = 8;
535 		t = COMMUNITY_TYPE_EXT;
536 		break;
537 	case ATTR_LARGE_COMMUNITIES:
538 		if (comm->flags & PARTIAL_LARGE_COMMUNITIES)
539 			flags |= ATTR_PARTIAL;
540 		size = 12;
541 		t = COMMUNITY_TYPE_LARGE;
542 		break;
543 	default:
544 		return -1;
545 	}
546 
547 	/* first count how many communities will be written */
548 	num = 0;
549 	start = -1;
550 	for (l = 0; l < comm->nentries; l++) {
551 		cp = &comm->communities[l];
552 		if ((uint8_t)cp->flags == t) {
553 			if (ebgp && non_transitive_ext_community(cp))
554 				continue;
555 			num++;
556 			if (start == -1)
557 				start = l;
558 		}
559 		if ((uint8_t)cp->flags > t)
560 			break;
561 	}
562 	end = l;
563 
564 	/* no communities for this type present */
565 	if (num == 0)
566 		return 0;
567 
568 	if (num > INT16_MAX / size)
569 		return -1;
570 
571 	/* write attribute header */
572 	if (attr_writebuf(buf, flags, type, NULL, num * size) == -1)
573 		return -1;
574 
575 	/* write out the communities */
576 	for (l = start; l < end; l++) {
577 		cp = &comm->communities[l];
578 
579 		switch (type) {
580 		case ATTR_COMMUNITIES:
581 			if (ibuf_add_n16(buf, cp->data1) == -1)
582 				return -1;
583 			if (ibuf_add_n16(buf, cp->data2) == -1)
584 				return -1;
585 			break;
586 		case ATTR_EXT_COMMUNITIES:
587 			if (ebgp && non_transitive_ext_community(cp))
588 				continue;
589 
590 			ext = (uint64_t)cp->data3 << 48;
591 			switch ((cp->data3 >> 8) & EXT_COMMUNITY_VALUE) {
592 			case EXT_COMMUNITY_TRANS_TWO_AS:
593 			case EXT_COMMUNITY_TRANS_OPAQUE:
594 			case EXT_COMMUNITY_TRANS_EVPN:
595 				ext |= ((uint64_t)cp->data1 & 0xffff) << 32;
596 				ext |= (uint64_t)cp->data2;
597 				break;
598 			case EXT_COMMUNITY_TRANS_FOUR_AS:
599 			case EXT_COMMUNITY_TRANS_IPV4:
600 				ext |= (uint64_t)cp->data1 << 16;
601 				ext |= (uint64_t)cp->data2 & 0xffff;
602 				break;
603 			}
604 			if (ibuf_add_n64(buf, ext) == -1)
605 				return -1;
606 			break;
607 		case ATTR_LARGE_COMMUNITIES:
608 			if (ibuf_add_n32(buf, cp->data1) == -1)
609 				return -1;
610 			if (ibuf_add_n32(buf, cp->data2) == -1)
611 				return -1;
612 			if (ibuf_add_n32(buf, cp->data3) == -1)
613 				return -1;
614 			break;
615 		}
616 	}
617 	return 0;
618 }
619 
620 /*
621  * Global RIB cache for communities
622  */
623 static inline int
communities_compare(struct rde_community * a,struct rde_community * b)624 communities_compare(struct rde_community *a, struct rde_community *b)
625 {
626 	if (a->nentries != b->nentries)
627 		return a->nentries > b->nentries ? 1 : -1;
628 	if (a->flags != b->flags)
629 		return a->flags > b->flags ? 1 : -1;
630 
631 	return memcmp(a->communities, b->communities,
632 	    a->nentries * sizeof(struct community));
633 }
634 
635 RB_HEAD(comm_tree, rde_community)	commtable = RB_INITIALIZER(&commtable);
636 RB_GENERATE_STATIC(comm_tree, rde_community, entry, communities_compare);
637 
638 void
communities_shutdown(void)639 communities_shutdown(void)
640 {
641 	if (!RB_EMPTY(&commtable))
642 		log_warnx("%s: free non-free table", __func__);
643 }
644 
645 struct rde_community *
communities_lookup(struct rde_community * comm)646 communities_lookup(struct rde_community *comm)
647 {
648 	return RB_FIND(comm_tree, &commtable, comm);
649 }
650 
651 struct rde_community *
communities_link(struct rde_community * comm)652 communities_link(struct rde_community *comm)
653 {
654 	struct rde_community *n, *f;
655 
656 	if ((n = malloc(sizeof(*n))) == NULL)
657 		fatal(__func__);
658 	communities_copy(n, comm);
659 
660 	if ((f = RB_INSERT(comm_tree, &commtable, n)) != NULL) {
661 		log_warnx("duplicate communities collection inserted");
662 		free(n->communities);
663 		free(n);
664 		return f;
665 	}
666 	n->refcnt = 1;	/* initial reference by the cache */
667 
668 	rdemem.comm_size += n->size;
669 	rdemem.comm_nmemb += n->nentries;
670 	rdemem.comm_cnt++;
671 
672 	return n;
673 }
674 
675 void
communities_unlink(struct rde_community * comm)676 communities_unlink(struct rde_community *comm)
677 {
678 	if (comm->refcnt != 1)
679 		fatalx("%s: unlinking still referenced communities", __func__);
680 
681 	RB_REMOVE(comm_tree, &commtable, comm);
682 
683 	rdemem.comm_size -= comm->size;
684 	rdemem.comm_nmemb -= comm->nentries;
685 	rdemem.comm_cnt--;
686 
687 	free(comm->communities);
688 	free(comm);
689 }
690 
691 /*
692  * Return true/1 if the two communities collections are identical,
693  * otherwise returns zero.
694  */
695 int
communities_equal(struct rde_community * a,struct rde_community * b)696 communities_equal(struct rde_community *a, struct rde_community *b)
697 {
698 	if (a->nentries != b->nentries)
699 		return 0;
700 	if (a->flags != b->flags)
701 		return 0;
702 
703 	return (memcmp(a->communities, b->communities,
704 	    a->nentries * sizeof(struct community)) == 0);
705 }
706 
707 /*
708  * Copy communities to a new unreferenced struct. Needs to call
709  * communities_clean() when done. to can be statically allocated,
710  * it will be cleaned first.
711  */
712 void
communities_copy(struct rde_community * to,struct rde_community * from)713 communities_copy(struct rde_community *to, struct rde_community *from)
714 {
715 	memset(to, 0, sizeof(*to));
716 
717 	/* ignore from->size and allocate the perfect amount */
718 	to->size = from->size;
719 	to->nentries = from->nentries;
720 	to->flags = from->flags;
721 
722 	if ((to->communities = reallocarray(NULL, to->size,
723 	    sizeof(struct community))) == NULL)
724 		fatal(__func__);
725 
726 	memcpy(to->communities, from->communities,
727 	    to->nentries * sizeof(struct community));
728 	memset(to->communities + to->nentries, 0, sizeof(struct community) *
729 	    (to->size - to->nentries));
730 }
731 
732 /*
733  * Clean up the communities by freeing any dynamically allocated memory.
734  */
735 void
communities_clean(struct rde_community * comm)736 communities_clean(struct rde_community *comm)
737 {
738 	if (comm->refcnt != 0)
739 		fatalx("%s: cleaning still referenced communities", __func__);
740 
741 	free(comm->communities);
742 	memset(comm, 0, sizeof(*comm));
743 }
744 
745 int
community_to_rd(struct community * fc,uint64_t * community)746 community_to_rd(struct community *fc, uint64_t *community)
747 {
748 	struct community c;
749 	uint64_t rd;
750 
751 	if (fc2c(fc, NULL, &c, NULL) == -1)
752 		return -1;
753 
754 	switch ((c.data3 >> 8) & EXT_COMMUNITY_VALUE) {
755 	case EXT_COMMUNITY_TRANS_TWO_AS:
756 		rd = (0ULL << 48);
757 		rd |= ((uint64_t)c.data1 & 0xffff) << 32;
758 		rd |= (uint64_t)c.data2;
759 		break;
760 	case EXT_COMMUNITY_TRANS_IPV4:
761 		rd = (1ULL << 48);
762 		rd |= (uint64_t)c.data1 << 16;
763 		rd |= (uint64_t)c.data2 & 0xffff;
764 		break;
765 	case EXT_COMMUNITY_TRANS_FOUR_AS:
766 		rd = (2ULL << 48);
767 		rd |= (uint64_t)c.data1 << 16;
768 		rd |= (uint64_t)c.data2 & 0xffff;
769 		break;
770 	default:
771 		return -1;
772 	}
773 
774 	*community = htobe64(rd);
775 	return 0;
776 }
777