xref: /openbsd/usr.sbin/bgpd/rde_community.c (revision 414d371c)
1 /*	$OpenBSD: rde_community.c,v 1.14 2023/10/10 14:36:28 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
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
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
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
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
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
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
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
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
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
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
421 community_add(struct rde_community *comm, int flags, void *buf, size_t len)
422 {
423 	struct community set = { .flags = COMMUNITY_TYPE_BASIC };
424 	uint8_t *b = buf;
425 	uint16_t c;
426 	size_t l;
427 
428 	if (len == 0 || len % 4 != 0)
429 		return -1;
430 
431 	if (flags & ATTR_PARTIAL)
432 		comm->flags |= PARTIAL_COMMUNITIES;
433 
434 	for (l = 0; l < len; l += 4, b += 4) {
435 		memcpy(&c, b, sizeof(c));
436 		set.data1 = ntohs(c);
437 		memcpy(&c, b + 2, sizeof(c));
438 		set.data2 = ntohs(c);
439 		insert_community(comm, &set);
440 	}
441 
442 	return 0;
443 }
444 
445 int
446 community_large_add(struct rde_community *comm, int flags, void *buf,
447     size_t len)
448 {
449 	struct community set = { .flags = COMMUNITY_TYPE_LARGE };
450 	uint8_t *b = buf;
451 	size_t l;
452 
453 	if (len == 0 || len % 12 != 0)
454 		return -1;
455 
456 	if (flags & ATTR_PARTIAL)
457 		comm->flags |= PARTIAL_LARGE_COMMUNITIES;
458 
459 	for (l = 0; l < len; l += 12, b += 12) {
460 		memcpy(&set.data1, b, sizeof(set.data1));
461 		memcpy(&set.data2, b + 4, sizeof(set.data2));
462 		memcpy(&set.data3, b + 8, sizeof(set.data3));
463 		set.data1 = ntohl(set.data1);
464 		set.data2 = ntohl(set.data2);
465 		set.data3 = ntohl(set.data3);
466 		insert_community(comm, &set);
467 	}
468 
469 	return 0;
470 }
471 
472 int
473 community_ext_add(struct rde_community *comm, int flags, int ebgp,
474     void *buf, size_t len)
475 {
476 	struct community set = { .flags = COMMUNITY_TYPE_EXT };
477 	uint8_t *b = buf, type;
478 	uint64_t c;
479 	size_t l;
480 
481 	if (len == 0 || len % 8 != 0)
482 		return -1;
483 
484 	if (flags & ATTR_PARTIAL)
485 		comm->flags |= PARTIAL_EXT_COMMUNITIES;
486 
487 	for (l = 0; l < len; l += 8, b += 8) {
488 		memcpy(&c, b, 8);
489 
490 		c = be64toh(c);
491 		type = c >> 56;
492 		/* filter out non-transitive ext communuties from ebgp peers */
493 		if (ebgp && (type & EXT_COMMUNITY_NON_TRANSITIVE))
494 			continue;
495 		switch (type & EXT_COMMUNITY_VALUE) {
496 		case EXT_COMMUNITY_TRANS_TWO_AS:
497 		case EXT_COMMUNITY_TRANS_OPAQUE:
498 		case EXT_COMMUNITY_TRANS_EVPN:
499 			set.data1 = c >> 32 & 0xffff;
500 			set.data2 = c;
501 			break;
502 		case EXT_COMMUNITY_TRANS_FOUR_AS:
503 		case EXT_COMMUNITY_TRANS_IPV4:
504 			set.data1 = c >> 16;
505 			set.data2 = c & 0xffff;
506 			break;
507 		}
508 		set.data3 = c >> 48;
509 
510 		insert_community(comm, &set);
511 	}
512 
513 	return 0;
514 }
515 
516 /*
517  * Convert communities back to the wireformat.
518  * When writing ATTR_EXT_COMMUNITIES non-transitive communities need to
519  * be skipped if they are sent to an ebgp peer.
520  */
521 int
522 community_writebuf(struct rde_community *comm, uint8_t type, int ebgp,
523     struct ibuf *buf)
524 {
525 	struct community *cp;
526 	uint64_t ext;
527 	int l, size, start, end, num;
528 	int flags = ATTR_OPTIONAL | ATTR_TRANSITIVE;
529 	uint8_t t;
530 
531 	switch (type) {
532 	case ATTR_COMMUNITIES:
533 		if (comm->flags & PARTIAL_COMMUNITIES)
534 			flags |= ATTR_PARTIAL;
535 		size = 4;
536 		t = COMMUNITY_TYPE_BASIC;
537 		break;
538 	case ATTR_EXT_COMMUNITIES:
539 		if (comm->flags & PARTIAL_EXT_COMMUNITIES)
540 			flags |= ATTR_PARTIAL;
541 		size = 8;
542 		t = COMMUNITY_TYPE_EXT;
543 		break;
544 	case ATTR_LARGE_COMMUNITIES:
545 		if (comm->flags & PARTIAL_LARGE_COMMUNITIES)
546 			flags |= ATTR_PARTIAL;
547 		size = 12;
548 		t = COMMUNITY_TYPE_LARGE;
549 		break;
550 	default:
551 		return -1;
552 	}
553 
554 	/* first count how many communities will be written */
555 	num = 0;
556 	start = -1;
557 	for (l = 0; l < comm->nentries; l++) {
558 		cp = &comm->communities[l];
559 		if ((uint8_t)cp->flags == t) {
560 			if (ebgp && non_transitive_ext_community(cp))
561 				continue;
562 			num++;
563 			if (start == -1)
564 				start = l;
565 		}
566 		if ((uint8_t)cp->flags > t)
567 			break;
568 	}
569 	end = l;
570 
571 	/* no communities for this type present */
572 	if (num == 0)
573 		return 0;
574 
575 	if (num > INT16_MAX / size)
576 		return -1;
577 
578 	/* write attribute header */
579 	if (attr_writebuf(buf, flags, type, NULL, num * size) == -1)
580 		return -1;
581 
582 	/* write out the communities */
583 	for (l = start; l < end; l++) {
584 		cp = &comm->communities[l];
585 
586 		switch (type) {
587 		case ATTR_COMMUNITIES:
588 			if (ibuf_add_n16(buf, cp->data1) == -1)
589 				return -1;
590 			if (ibuf_add_n16(buf, cp->data2) == -1)
591 				return -1;
592 			break;
593 		case ATTR_EXT_COMMUNITIES:
594 			if (ebgp && non_transitive_ext_community(cp))
595 				continue;
596 
597 			ext = (uint64_t)cp->data3 << 48;
598 			switch ((cp->data3 >> 8) & EXT_COMMUNITY_VALUE) {
599 			case EXT_COMMUNITY_TRANS_TWO_AS:
600 			case EXT_COMMUNITY_TRANS_OPAQUE:
601 			case EXT_COMMUNITY_TRANS_EVPN:
602 				ext |= ((uint64_t)cp->data1 & 0xffff) << 32;
603 				ext |= (uint64_t)cp->data2;
604 				break;
605 			case EXT_COMMUNITY_TRANS_FOUR_AS:
606 			case EXT_COMMUNITY_TRANS_IPV4:
607 				ext |= (uint64_t)cp->data1 << 16;
608 				ext |= (uint64_t)cp->data2 & 0xffff;
609 				break;
610 			}
611 			if (ibuf_add_n64(buf, ext) == -1)
612 				return -1;
613 			break;
614 		case ATTR_LARGE_COMMUNITIES:
615 			if (ibuf_add_n32(buf, cp->data1) == -1)
616 				return -1;
617 			if (ibuf_add_n32(buf, cp->data2) == -1)
618 				return -1;
619 			if (ibuf_add_n32(buf, cp->data3) == -1)
620 				return -1;
621 			break;
622 		}
623 	}
624 	return 0;
625 }
626 
627 /*
628  * Global RIB cache for communities
629  */
630 static inline int
631 communities_compare(struct rde_community *a, struct rde_community *b)
632 {
633 	if (a->nentries != b->nentries)
634 		return a->nentries > b->nentries ? 1 : -1;
635 	if (a->flags != b->flags)
636 		return a->flags > b->flags ? 1 : -1;
637 
638 	return memcmp(a->communities, b->communities,
639 	    a->nentries * sizeof(struct community));
640 }
641 
642 RB_HEAD(comm_tree, rde_community)	commtable = RB_INITIALIZER(&commtable);
643 RB_GENERATE_STATIC(comm_tree, rde_community, entry, communities_compare);
644 
645 void
646 communities_shutdown(void)
647 {
648 	if (!RB_EMPTY(&commtable))
649 		log_warnx("%s: free non-free table", __func__);
650 }
651 
652 struct rde_community *
653 communities_lookup(struct rde_community *comm)
654 {
655 	return RB_FIND(comm_tree, &commtable, comm);
656 }
657 
658 struct rde_community *
659 communities_link(struct rde_community *comm)
660 {
661 	struct rde_community *n, *f;
662 
663 	if ((n = malloc(sizeof(*n))) == NULL)
664 		fatal(__func__);
665 	communities_copy(n, comm);
666 
667 	if ((f = RB_INSERT(comm_tree, &commtable, n)) != NULL) {
668 		log_warnx("duplicate communities collection inserted");
669 		free(n->communities);
670 		free(n);
671 		return f;
672 	}
673 	n->refcnt = 1;	/* initial reference by the cache */
674 
675 	rdemem.comm_size += n->size;
676 	rdemem.comm_nmemb += n->nentries;
677 	rdemem.comm_cnt++;
678 
679 	return n;
680 }
681 
682 void
683 communities_unlink(struct rde_community *comm)
684 {
685 	if (comm->refcnt != 1)
686 		fatalx("%s: unlinking still referenced communities", __func__);
687 
688 	RB_REMOVE(comm_tree, &commtable, comm);
689 
690 	rdemem.comm_size -= comm->size;
691 	rdemem.comm_nmemb -= comm->nentries;
692 	rdemem.comm_cnt--;
693 
694 	free(comm->communities);
695 	free(comm);
696 }
697 
698 /*
699  * Return true/1 if the two communities collections are identical,
700  * otherwise returns zero.
701  */
702 int
703 communities_equal(struct rde_community *a, struct rde_community *b)
704 {
705 	if (a->nentries != b->nentries)
706 		return 0;
707 	if (a->flags != b->flags)
708 		return 0;
709 
710 	return (memcmp(a->communities, b->communities,
711 	    a->nentries * sizeof(struct community)) == 0);
712 }
713 
714 /*
715  * Copy communities to a new unreferenced struct. Needs to call
716  * communities_clean() when done. to can be statically allocated,
717  * it will be cleaned first.
718  */
719 void
720 communities_copy(struct rde_community *to, struct rde_community *from)
721 {
722 	memset(to, 0, sizeof(*to));
723 
724 	/* ignore from->size and allocate the perfect amount */
725 	to->size = from->size;
726 	to->nentries = from->nentries;
727 	to->flags = from->flags;
728 
729 	if ((to->communities = reallocarray(NULL, to->size,
730 	    sizeof(struct community))) == NULL)
731 		fatal(__func__);
732 
733 	memcpy(to->communities, from->communities,
734 	    to->nentries * sizeof(struct community));
735 	memset(to->communities + to->nentries, 0, sizeof(struct community) *
736 	    (to->size - to->nentries));
737 }
738 
739 /*
740  * Clean up the communities by freeing any dynamically allocated memory.
741  */
742 void
743 communities_clean(struct rde_community *comm)
744 {
745 	if (comm->refcnt != 0)
746 		fatalx("%s: cleaning still referenced communities", __func__);
747 
748 	free(comm->communities);
749 	memset(comm, 0, sizeof(*comm));
750 }
751 
752 int
753 community_to_rd(struct community *fc, uint64_t *community)
754 {
755 	struct community c;
756 	uint64_t rd;
757 
758 	if (fc2c(fc, NULL, &c, NULL) == -1)
759 		return -1;
760 
761 	switch ((c.data3 >> 8) & EXT_COMMUNITY_VALUE) {
762 	case EXT_COMMUNITY_TRANS_TWO_AS:
763 		rd = (0ULL << 48);
764 		rd |= ((uint64_t)c.data1 & 0xffff) << 32;
765 		rd |= (uint64_t)c.data2;
766 		break;
767 	case EXT_COMMUNITY_TRANS_IPV4:
768 		rd = (1ULL << 48);
769 		rd |= (uint64_t)c.data1 << 16;
770 		rd |= (uint64_t)c.data2 & 0xffff;
771 		break;
772 	case EXT_COMMUNITY_TRANS_FOUR_AS:
773 		rd = (2ULL << 48);
774 		rd |= (uint64_t)c.data1 << 16;
775 		rd |= (uint64_t)c.data2 & 0xffff;
776 		break;
777 	default:
778 		return -1;
779 	}
780 
781 	*community = htobe64(rd);
782 	return 0;
783 }
784