xref: /openbsd-src/usr.sbin/bgpd/rde_community.c (revision 07ef39656b1ad87dc7dc7993bbd6ca5d77575ba0)
1*07ef3965Sclaudio /*	$OpenBSD: rde_community.c,v 1.16 2024/09/10 08:53:20 claudio Exp $ */
2e7adcfeaSclaudio 
3e7adcfeaSclaudio /*
4e7adcfeaSclaudio  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
5e7adcfeaSclaudio  *
6e7adcfeaSclaudio  * Permission to use, copy, modify, and distribute this software for any
7e7adcfeaSclaudio  * purpose with or without fee is hereby granted, provided that the above
8e7adcfeaSclaudio  * copyright notice and this permission notice appear in all copies.
9e7adcfeaSclaudio  *
10e7adcfeaSclaudio  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11e7adcfeaSclaudio  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12e7adcfeaSclaudio  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13e7adcfeaSclaudio  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14e7adcfeaSclaudio  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15e7adcfeaSclaudio  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16e7adcfeaSclaudio  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17e7adcfeaSclaudio  */
18e7adcfeaSclaudio #include <sys/queue.h>
19e7adcfeaSclaudio 
20e7adcfeaSclaudio #include <endian.h>
21e7adcfeaSclaudio #include <limits.h>
22e7adcfeaSclaudio #include <stdlib.h>
23e7adcfeaSclaudio #include <string.h>
24e7adcfeaSclaudio 
25e7adcfeaSclaudio #include "bgpd.h"
26e7adcfeaSclaudio #include "rde.h"
27e7adcfeaSclaudio #include "log.h"
28e7adcfeaSclaudio 
29e7adcfeaSclaudio static int
3039386878Sclaudio apply_flag(uint32_t in, uint8_t flag, struct rde_peer *peer, uint32_t *out,
3139386878Sclaudio     uint32_t *mask)
32e7adcfeaSclaudio {
33e7adcfeaSclaudio 	switch (flag) {
34e7adcfeaSclaudio 	case COMMUNITY_ANY:
35e7adcfeaSclaudio 		if (mask == NULL)
36e7adcfeaSclaudio 			return -1;
37e7adcfeaSclaudio 		*out = 0;
38e7adcfeaSclaudio 		*mask = 0;
39e7adcfeaSclaudio 		return 0;
40e7adcfeaSclaudio 	case COMMUNITY_NEIGHBOR_AS:
41e7adcfeaSclaudio 		if (peer == NULL)
42e7adcfeaSclaudio 			return -1;
43e7adcfeaSclaudio 		*out = peer->conf.remote_as;
44e7adcfeaSclaudio 		break;
45e7adcfeaSclaudio 	case COMMUNITY_LOCAL_AS:
46e7adcfeaSclaudio 		if (peer == NULL)
47e7adcfeaSclaudio 			return -1;
48e7adcfeaSclaudio 		*out = peer->conf.local_as;
49e7adcfeaSclaudio 		break;
50e7adcfeaSclaudio 	default:
51e7adcfeaSclaudio 		*out = in;
52e7adcfeaSclaudio 		break;
53e7adcfeaSclaudio 	}
54e7adcfeaSclaudio 	if (mask)
55e7adcfeaSclaudio 		*mask = UINT32_MAX;
56e7adcfeaSclaudio 	return 0;
57e7adcfeaSclaudio }
58e7adcfeaSclaudio 
59e7adcfeaSclaudio static int
60e7adcfeaSclaudio fc2c(struct community *fc, struct rde_peer *peer, struct community *c,
61e7adcfeaSclaudio     struct community *m)
62e7adcfeaSclaudio {
63f8162053Sclaudio 	int type;
6439386878Sclaudio 	uint8_t subtype;
65e7adcfeaSclaudio 
66e7adcfeaSclaudio 	memset(c, 0, sizeof(*c));
67e7adcfeaSclaudio 	if (m)
68e7adcfeaSclaudio 		memset(m, 0xff, sizeof(*m));
69e7adcfeaSclaudio 
7039386878Sclaudio 	c->flags = (uint8_t)fc->flags;
71e7adcfeaSclaudio 
7239386878Sclaudio 	switch ((uint8_t)c->flags) {
73e7adcfeaSclaudio 	case COMMUNITY_TYPE_BASIC:
74e7adcfeaSclaudio 		if (apply_flag(fc->data1, fc->flags >> 8, peer,
75e7adcfeaSclaudio 		    &c->data1, m ? &m->data1 : NULL))
76e7adcfeaSclaudio 			return -1;
77e7adcfeaSclaudio 		if (apply_flag(fc->data2, fc->flags >> 16, peer,
78e7adcfeaSclaudio 		    &c->data2, m ? &m->data2 : NULL))
79e7adcfeaSclaudio 			return -1;
80e7adcfeaSclaudio 
81e7adcfeaSclaudio 		/* check that values fit */
82e7adcfeaSclaudio 		if (c->data1 > USHRT_MAX || c->data2 > USHRT_MAX)
83e7adcfeaSclaudio 			return -1;
84e7adcfeaSclaudio 		return 0;
85e7adcfeaSclaudio 	case COMMUNITY_TYPE_LARGE:
86e7adcfeaSclaudio 		if (apply_flag(fc->data1, fc->flags >> 8, peer,
87e7adcfeaSclaudio 		    &c->data1, m ? &m->data1 : NULL))
88e7adcfeaSclaudio 			return -1;
89e7adcfeaSclaudio 		if (apply_flag(fc->data2, fc->flags >> 16, peer,
90e7adcfeaSclaudio 		    &c->data2, m ? &m->data2 : NULL))
91e7adcfeaSclaudio 			return -1;
92e7adcfeaSclaudio 		if (apply_flag(fc->data3, fc->flags >> 24, peer,
93e7adcfeaSclaudio 		    &c->data3, m ? &m->data3 : NULL))
94e7adcfeaSclaudio 			return -1;
95e7adcfeaSclaudio 		return 0;
96e7adcfeaSclaudio 	case COMMUNITY_TYPE_EXT:
97e7adcfeaSclaudio 		type = (int32_t)fc->data3 >> 8;
98e7adcfeaSclaudio 		subtype = fc->data3 & 0xff;
99e7adcfeaSclaudio 
10041c70dd7Sclaudio 		if ((fc->flags >> 24 & 0xff) == COMMUNITY_ANY) {
10141c70dd7Sclaudio 			/* special case for 'ext-community * *' */
10241c70dd7Sclaudio 			if (m == NULL)
10341c70dd7Sclaudio 				return -1;
10441c70dd7Sclaudio 			m->data1 = 0;
10541c70dd7Sclaudio 			m->data2 = 0;
10641c70dd7Sclaudio 			m->data3 = 0;
10741c70dd7Sclaudio 			return 0;
10841c70dd7Sclaudio 		}
10941c70dd7Sclaudio 
110f8162053Sclaudio 		if (type == -1) {
111e7adcfeaSclaudio 			/* special case for 'ext-community rt *' */
112e7adcfeaSclaudio 			if ((fc->flags >> 8 & 0xff) != COMMUNITY_ANY ||
113e7adcfeaSclaudio 			    m == NULL)
114e7adcfeaSclaudio 				return -1;
115e7adcfeaSclaudio 			c->data3 = subtype;
116e7adcfeaSclaudio 			m->data1 = 0;
117e7adcfeaSclaudio 			m->data2 = 0;
118e7adcfeaSclaudio 			m->data3 = 0xff;
119e7adcfeaSclaudio 			return 0;
120f8162053Sclaudio 		}
121f8162053Sclaudio 
122f8162053Sclaudio 		c->data3 = type << 8 | subtype;
123f8162053Sclaudio 		switch (type & EXT_COMMUNITY_VALUE) {
124e7adcfeaSclaudio 		case EXT_COMMUNITY_TRANS_TWO_AS:
125b2402ebbSclaudio 		case EXT_COMMUNITY_TRANS_FOUR_AS:
126e7adcfeaSclaudio 			if ((fc->flags >> 8 & 0xff) == COMMUNITY_ANY)
127e7adcfeaSclaudio 				break;
128e7adcfeaSclaudio 
129e7adcfeaSclaudio 			if (apply_flag(fc->data1, fc->flags >> 8, peer,
130e7adcfeaSclaudio 			    &c->data1, m ? &m->data1 : NULL))
131e7adcfeaSclaudio 				return -1;
132e7adcfeaSclaudio 			if (apply_flag(fc->data2, fc->flags >> 16, peer,
133e7adcfeaSclaudio 			    &c->data2, m ? &m->data2 : NULL))
134e7adcfeaSclaudio 				return -1;
135b2402ebbSclaudio 			if (m)
136b2402ebbSclaudio 				m->data3 &= ~(EXT_COMMUNITY_TRANS_FOUR_AS << 8);
137e7adcfeaSclaudio 			return 0;
138e7adcfeaSclaudio 		case EXT_COMMUNITY_TRANS_IPV4:
139e7adcfeaSclaudio 			if ((fc->flags >> 8 & 0xff) == COMMUNITY_ANY)
140e7adcfeaSclaudio 				break;
141e7adcfeaSclaudio 
142e7adcfeaSclaudio 			if (apply_flag(fc->data1, fc->flags >> 8, peer,
143e7adcfeaSclaudio 			    &c->data1, m ? &m->data1 : NULL))
144e7adcfeaSclaudio 				return -1;
145e7adcfeaSclaudio 			if (apply_flag(fc->data2, fc->flags >> 16, peer,
146e7adcfeaSclaudio 			    &c->data2, m ? &m->data2 : NULL))
147e7adcfeaSclaudio 				return -1;
148e7adcfeaSclaudio 			/* check that values fit */
149e7adcfeaSclaudio 			if (c->data2 > USHRT_MAX)
150e7adcfeaSclaudio 				return -1;
151e7adcfeaSclaudio 			return 0;
152e7adcfeaSclaudio 		case EXT_COMMUNITY_TRANS_OPAQUE:
153e7adcfeaSclaudio 		case EXT_COMMUNITY_TRANS_EVPN:
154e7adcfeaSclaudio 			if ((fc->flags >> 8 & 0xff) == COMMUNITY_ANY)
155e7adcfeaSclaudio 				break;
156e7adcfeaSclaudio 
157e7adcfeaSclaudio 			c->data1 = fc->data1;
158e7adcfeaSclaudio 			c->data2 = fc->data2;
159ab8e3451Sderaadt 			return 0;
160e7adcfeaSclaudio 		}
161e7adcfeaSclaudio 
16241c70dd7Sclaudio 		/* this is for 'ext-community subtype *' */
16341c70dd7Sclaudio 		if (m == NULL)
16441c70dd7Sclaudio 			return -1;
165e7adcfeaSclaudio 		m->data1 = 0;
166e7adcfeaSclaudio 		m->data2 = 0;
167e7adcfeaSclaudio 		return 0;
168e7adcfeaSclaudio 	default:
16939386878Sclaudio 		fatalx("%s: unknown type %d", __func__, (uint8_t)c->flags);
170e7adcfeaSclaudio 	}
171e7adcfeaSclaudio }
172e7adcfeaSclaudio 
173e7adcfeaSclaudio static int
174e7adcfeaSclaudio fast_match(const void *va, const void *vb)
175e7adcfeaSclaudio {
176e7adcfeaSclaudio 	const struct community *a = va;
177e7adcfeaSclaudio 	const struct community *b = vb;
178e7adcfeaSclaudio 
17939386878Sclaudio 	if ((uint8_t)a->flags != (uint8_t)b->flags)
18039386878Sclaudio 		return (uint8_t)a->flags > (uint8_t)b->flags ? 1 : -1;
181e7adcfeaSclaudio 
182e7adcfeaSclaudio 	if (a->data1 != b->data1)
183e7adcfeaSclaudio 		return a->data1 > b->data1 ? 1 : -1;
184e7adcfeaSclaudio 	if (a->data2 != b->data2)
185e7adcfeaSclaudio 		return a->data2 > b->data2 ? 1 : -1;
186e7adcfeaSclaudio 	if (a->data3 != b->data3)
187e7adcfeaSclaudio 		return a->data3 > b->data3 ? 1 : -1;
188e7adcfeaSclaudio 	return 0;
189e7adcfeaSclaudio }
190e7adcfeaSclaudio 
191e7adcfeaSclaudio static int
192e7adcfeaSclaudio mask_match(struct community *a, struct community *b, struct community *m)
193e7adcfeaSclaudio {
19439386878Sclaudio 	if ((uint8_t)a->flags != (uint8_t)b->flags)
19539386878Sclaudio 		return (uint8_t)a->flags > (uint8_t)b->flags ? 1 : -1;
196e7adcfeaSclaudio 
197e7adcfeaSclaudio 	if ((a->data1 & m->data1) != (b->data1 & m->data1)) {
198e7adcfeaSclaudio 		if ((a->data1 & m->data1) > (b->data1 & m->data1))
199e7adcfeaSclaudio 			return 1;
200e7adcfeaSclaudio 		return -1;
201e7adcfeaSclaudio 	}
202e7adcfeaSclaudio 	if ((a->data2 & m->data2) != (b->data2 & m->data2)) {
203e7adcfeaSclaudio 		if ((a->data2 & m->data2) > (b->data2 & m->data2))
204e7adcfeaSclaudio 			return 1;
205e7adcfeaSclaudio 		return -1;
206e7adcfeaSclaudio 	}
207e7adcfeaSclaudio 	if ((a->data3 & m->data3) != (b->data3 & m->data3)) {
208e7adcfeaSclaudio 		if ((a->data3 & m->data3) > (b->data3 & m->data3))
209e7adcfeaSclaudio 			return 1;
210e7adcfeaSclaudio 		return -1;
211e7adcfeaSclaudio 	}
212e7adcfeaSclaudio 	return 0;
213e7adcfeaSclaudio }
214e7adcfeaSclaudio 
215e7adcfeaSclaudio /*
216e7adcfeaSclaudio  * Insert a community keeping the list sorted. Don't add if already present.
217e7adcfeaSclaudio  */
218e7adcfeaSclaudio static void
219e7adcfeaSclaudio insert_community(struct rde_community *comm, struct community *c)
220e7adcfeaSclaudio {
22123f5897dSclaudio 	int l;
222e7adcfeaSclaudio 	int r;
223e7adcfeaSclaudio 
224e7adcfeaSclaudio 	if (comm->nentries + 1 > comm->size) {
225e7adcfeaSclaudio 		struct community *new;
22623f5897dSclaudio 		int newsize = comm->size + 8;
227e7adcfeaSclaudio 
22866b1afa0Sclaudio 		if ((new = recallocarray(comm->communities, comm->size,
22966b1afa0Sclaudio 		    newsize, sizeof(struct community))) == NULL)
230e7adcfeaSclaudio 			fatal(__func__);
231e7adcfeaSclaudio 		comm->communities = new;
232e7adcfeaSclaudio 		comm->size = newsize;
233e7adcfeaSclaudio 	}
234e7adcfeaSclaudio 
235e7adcfeaSclaudio 	/* XXX can be made faster by binary search */
236e7adcfeaSclaudio 	for (l = 0; l < comm->nentries; l++) {
237e7adcfeaSclaudio 		r = fast_match(comm->communities + l, c);
238e7adcfeaSclaudio 		if (r == 0) {
239e7adcfeaSclaudio 			/* already present, nothing to do */
240e7adcfeaSclaudio 			return;
241e7adcfeaSclaudio 		} else if (r > 0) {
242e7adcfeaSclaudio 			/* shift reminder by one slot */
243e7adcfeaSclaudio 			memmove(comm->communities + l + 1,
244e7adcfeaSclaudio 			    comm->communities + l,
245e7adcfeaSclaudio 			    (comm->nentries - l) * sizeof(*c));
246e7adcfeaSclaudio 			break;
247e7adcfeaSclaudio 		}
248e7adcfeaSclaudio 	}
249e7adcfeaSclaudio 
250e7adcfeaSclaudio 	/* insert community at slot l */
251e7adcfeaSclaudio 	comm->communities[l] = *c;
252e7adcfeaSclaudio 	comm->nentries++;
253e7adcfeaSclaudio }
254e7adcfeaSclaudio 
255e7adcfeaSclaudio static int
256f8162053Sclaudio non_transitive_ext_community(struct community *c)
257e7adcfeaSclaudio {
25866b1afa0Sclaudio 	if ((uint8_t)c->flags != COMMUNITY_TYPE_EXT)
25966b1afa0Sclaudio 		return 0;
260f8162053Sclaudio 	if ((c->data3 >> 8) & EXT_COMMUNITY_NON_TRANSITIVE)
261e7adcfeaSclaudio 		return 1;
262e7adcfeaSclaudio 	return 0;
263e7adcfeaSclaudio }
264e7adcfeaSclaudio 
265e7adcfeaSclaudio /*
266e7adcfeaSclaudio  * Check if a community is present. This function will expand local-as and
267e7adcfeaSclaudio  * neighbor-as and also mask of bits to support partial matches.
268e7adcfeaSclaudio  */
269e7adcfeaSclaudio int
270e7adcfeaSclaudio community_match(struct rde_community *comm, struct community *fc,
271e7adcfeaSclaudio struct rde_peer *peer)
272e7adcfeaSclaudio {
273e7adcfeaSclaudio 	struct community test, mask;
27423f5897dSclaudio 	int l;
275e7adcfeaSclaudio 
276e7adcfeaSclaudio 	if (fc->flags >> 8 == 0) {
277e7adcfeaSclaudio 		/* fast path */
278e7adcfeaSclaudio 		return (bsearch(fc, comm->communities, comm->nentries,
279e7adcfeaSclaudio 		    sizeof(*fc), fast_match) != NULL);
280e7adcfeaSclaudio 	} else {
281e7adcfeaSclaudio 		/* slow path */
282e7adcfeaSclaudio 		if (fc2c(fc, peer, &test, &mask) == -1)
283e7adcfeaSclaudio 			return 0;
284e7adcfeaSclaudio 
285e7adcfeaSclaudio 		for (l = 0; l < comm->nentries; l++) {
286e7adcfeaSclaudio 			if (mask_match(&comm->communities[l], &test,
287e7adcfeaSclaudio 			    &mask) == 0)
288e7adcfeaSclaudio 				return 1;
289e7adcfeaSclaudio 		}
290e7adcfeaSclaudio 		return 0;
291e7adcfeaSclaudio 	}
292e7adcfeaSclaudio }
293e7adcfeaSclaudio 
294e7adcfeaSclaudio /*
295bd0e176eSclaudio  * Count the number of communities of type type.
296bd0e176eSclaudio  */
297bd0e176eSclaudio int
298bd0e176eSclaudio community_count(struct rde_community *comm, uint8_t type)
299bd0e176eSclaudio {
30023f5897dSclaudio 	int l;
301bd0e176eSclaudio 	int count = 0;
302bd0e176eSclaudio 
303bd0e176eSclaudio 	/* use the fact that the array is ordered by type */
304bd0e176eSclaudio 	switch (type) {
305bd0e176eSclaudio 	case COMMUNITY_TYPE_BASIC:
306bd0e176eSclaudio 		for (l = 0; l < comm->nentries; l++) {
307bd0e176eSclaudio 			if ((uint8_t)comm->communities[l].flags == type)
308bd0e176eSclaudio 				count++;
309bd0e176eSclaudio 			else
310bd0e176eSclaudio 				break;
311bd0e176eSclaudio 		}
312bd0e176eSclaudio 		break;
313bd0e176eSclaudio 	case COMMUNITY_TYPE_EXT:
314bd0e176eSclaudio 		for (l = 0; l < comm->nentries; l++) {
315bd0e176eSclaudio 			if ((uint8_t)comm->communities[l].flags == type)
316bd0e176eSclaudio 				count++;
317bd0e176eSclaudio 			else if ((uint8_t)comm->communities[l].flags > type)
318bd0e176eSclaudio 				break;
319bd0e176eSclaudio 		}
320bd0e176eSclaudio 		break;
321bd0e176eSclaudio 	case COMMUNITY_TYPE_LARGE:
322bd0e176eSclaudio 		for (l = comm->nentries; l > 0; l--) {
323bd0e176eSclaudio 			if ((uint8_t)comm->communities[l - 1].flags == type)
324bd0e176eSclaudio 				count++;
325bd0e176eSclaudio 			else
326bd0e176eSclaudio 				break;
327bd0e176eSclaudio 		}
328bd0e176eSclaudio 		break;
329bd0e176eSclaudio 	}
330bd0e176eSclaudio 	return count;
331bd0e176eSclaudio }
332bd0e176eSclaudio 
333bd0e176eSclaudio /*
334e7adcfeaSclaudio  * Insert a community, expanding local-as and neighbor-as if needed.
335e7adcfeaSclaudio  */
336e7adcfeaSclaudio int
337e7adcfeaSclaudio community_set(struct rde_community *comm, struct community *fc,
338e7adcfeaSclaudio struct rde_peer *peer)
339e7adcfeaSclaudio {
340e7adcfeaSclaudio 	struct community set;
341e7adcfeaSclaudio 
342e7adcfeaSclaudio 	if (fc->flags >> 8 == 0) {
343e7adcfeaSclaudio 		/* fast path */
344e7adcfeaSclaudio 		insert_community(comm, fc);
345e7adcfeaSclaudio 	} else {
346e7adcfeaSclaudio 		if (fc2c(fc, peer, &set, NULL) == -1)
347e7adcfeaSclaudio 			return 0;
348b2402ebbSclaudio 		if ((uint8_t)set.flags == COMMUNITY_TYPE_EXT) {
349b2402ebbSclaudio 			int type = (int)set.data3 >> 8;
350b2402ebbSclaudio 			switch (type & EXT_COMMUNITY_VALUE) {
351b2402ebbSclaudio 			case EXT_COMMUNITY_TRANS_TWO_AS:
352b2402ebbSclaudio 			case EXT_COMMUNITY_TRANS_FOUR_AS:
353b2402ebbSclaudio 				/* check that values fit */
354b2402ebbSclaudio 				if (set.data1 > USHRT_MAX &&
355b2402ebbSclaudio 				    set.data2 > USHRT_MAX)
356b2402ebbSclaudio 					return 0;
357b2402ebbSclaudio 				if (set.data1 > USHRT_MAX)
358b2402ebbSclaudio 					set.data3 = (set.data3 & 0xff) |
359b2402ebbSclaudio 					    EXT_COMMUNITY_TRANS_FOUR_AS << 8;
360b2402ebbSclaudio 				else
361b2402ebbSclaudio 					set.data3 = (set.data3 & 0xff) |
362b2402ebbSclaudio 					    EXT_COMMUNITY_TRANS_TWO_AS << 8;
363b2402ebbSclaudio 				break;
364b2402ebbSclaudio 			}
365b2402ebbSclaudio 		}
366e7adcfeaSclaudio 		insert_community(comm, &set);
367e7adcfeaSclaudio 	}
368e7adcfeaSclaudio 	return 1;
369e7adcfeaSclaudio }
370e7adcfeaSclaudio 
371e7adcfeaSclaudio /*
372e7adcfeaSclaudio  * Remove a community if present, This function will expand local-as and
373e7adcfeaSclaudio  * neighbor-as and also mask of bits to support partial matches.
374e7adcfeaSclaudio  */
375e7adcfeaSclaudio void
376e7adcfeaSclaudio community_delete(struct rde_community *comm, struct community *fc,
377e7adcfeaSclaudio struct rde_peer *peer)
378e7adcfeaSclaudio {
379e7adcfeaSclaudio 	struct community test, mask;
380e7adcfeaSclaudio 	struct community *match;
38123f5897dSclaudio 	int l = 0;
382e7adcfeaSclaudio 
383e7adcfeaSclaudio 	if (fc->flags >> 8 == 0) {
384e7adcfeaSclaudio 		/* fast path */
385e7adcfeaSclaudio 		match = bsearch(fc, comm->communities, comm->nentries,
386e7adcfeaSclaudio 		    sizeof(*fc), fast_match);
387e7adcfeaSclaudio 		if (match == NULL)
388e7adcfeaSclaudio 			return;
389e7adcfeaSclaudio 		/* move everything after match down by 1 */
390e7adcfeaSclaudio 		memmove(match, match + 1,
391e7adcfeaSclaudio 		    (char *)(comm->communities + comm->nentries) -
392e7adcfeaSclaudio 		    (char *)(match + 1));
393e7adcfeaSclaudio 		comm->nentries--;
394e7adcfeaSclaudio 		return;
395e7adcfeaSclaudio 	} else {
396e7adcfeaSclaudio 		if (fc2c(fc, peer, &test, &mask) == -1)
397e7adcfeaSclaudio 			return;
398e7adcfeaSclaudio 
399e7adcfeaSclaudio 		while (l < comm->nentries) {
400e7adcfeaSclaudio 			if (mask_match(&comm->communities[l], &test,
401e7adcfeaSclaudio 			    &mask) == 0) {
402e7adcfeaSclaudio 				memmove(comm->communities + l,
403e7adcfeaSclaudio 				    comm->communities + l + 1,
404e7adcfeaSclaudio 				    (comm->nentries - l - 1) * sizeof(test));
405e7adcfeaSclaudio 				comm->nentries--;
406e7adcfeaSclaudio 				continue;
407e7adcfeaSclaudio 			}
408e7adcfeaSclaudio 			l++;
409e7adcfeaSclaudio 		}
410e7adcfeaSclaudio 	}
411e7adcfeaSclaudio }
412e7adcfeaSclaudio 
413e7adcfeaSclaudio /*
414e7adcfeaSclaudio  * Internalize communities from the wireformat.
415e7adcfeaSclaudio  * Store the partial flag in struct rde_community so it is not lost.
416e7adcfeaSclaudio  * - community_add for ATTR_COMMUNITUES
417e7adcfeaSclaudio  * - community_large_add for ATTR_LARGE_COMMUNITIES
418e7adcfeaSclaudio  * - community_ext_add for ATTR_EXT_COMMUNITIES
419e7adcfeaSclaudio  */
420e7adcfeaSclaudio int
421b3b1d939Sclaudio community_add(struct rde_community *comm, int flags, struct ibuf *buf)
422e7adcfeaSclaudio {
423e7adcfeaSclaudio 	struct community set = { .flags = COMMUNITY_TYPE_BASIC };
424b3b1d939Sclaudio 	uint16_t data1, data2;
425e7adcfeaSclaudio 
426b3b1d939Sclaudio 	if (ibuf_size(buf) == 0 || ibuf_size(buf) % 4 != 0)
427e7adcfeaSclaudio 		return -1;
428e7adcfeaSclaudio 
429e7adcfeaSclaudio 	if (flags & ATTR_PARTIAL)
430e7adcfeaSclaudio 		comm->flags |= PARTIAL_COMMUNITIES;
431e7adcfeaSclaudio 
432b3b1d939Sclaudio 	while (ibuf_size(buf) > 0) {
433b3b1d939Sclaudio 		if (ibuf_get_n16(buf, &data1) == -1 ||
434b3b1d939Sclaudio 		    ibuf_get_n16(buf, &data2) == -1)
435b3b1d939Sclaudio 			return -1;
436b3b1d939Sclaudio 		set.data1 = data1;
437b3b1d939Sclaudio 		set.data2 = data2;
438e7adcfeaSclaudio 		insert_community(comm, &set);
439e7adcfeaSclaudio 	}
440e7adcfeaSclaudio 
441e7adcfeaSclaudio 	return 0;
442e7adcfeaSclaudio }
443e7adcfeaSclaudio 
444e7adcfeaSclaudio int
445b3b1d939Sclaudio community_large_add(struct rde_community *comm, int flags, struct ibuf *buf)
446e7adcfeaSclaudio {
447e7adcfeaSclaudio 	struct community set = { .flags = COMMUNITY_TYPE_LARGE };
448e7adcfeaSclaudio 
449b3b1d939Sclaudio 	if (ibuf_size(buf) == 0 || ibuf_size(buf) % 12 != 0)
450e7adcfeaSclaudio 		return -1;
451e7adcfeaSclaudio 
452e7adcfeaSclaudio 	if (flags & ATTR_PARTIAL)
453e7adcfeaSclaudio 		comm->flags |= PARTIAL_LARGE_COMMUNITIES;
454e7adcfeaSclaudio 
455b3b1d939Sclaudio 	while (ibuf_size(buf) > 0) {
456b3b1d939Sclaudio 		if (ibuf_get_n32(buf, &set.data1) == -1 ||
457b3b1d939Sclaudio 		    ibuf_get_n32(buf, &set.data2) == -1 ||
458b3b1d939Sclaudio 		    ibuf_get_n32(buf, &set.data3) == -1)
459b3b1d939Sclaudio 			return -1;
460e7adcfeaSclaudio 		insert_community(comm, &set);
461e7adcfeaSclaudio 	}
462e7adcfeaSclaudio 
463e7adcfeaSclaudio 	return 0;
464e7adcfeaSclaudio }
465e7adcfeaSclaudio 
466e7adcfeaSclaudio int
467f8162053Sclaudio community_ext_add(struct rde_community *comm, int flags, int ebgp,
468b3b1d939Sclaudio     struct ibuf *buf)
469e7adcfeaSclaudio {
470e7adcfeaSclaudio 	struct community set = { .flags = COMMUNITY_TYPE_EXT };
47139386878Sclaudio 	uint64_t c;
472b3b1d939Sclaudio 	uint8_t type;
473e7adcfeaSclaudio 
474b3b1d939Sclaudio 	if (ibuf_size(buf) == 0 || ibuf_size(buf) % 8 != 0)
475e7adcfeaSclaudio 		return -1;
476e7adcfeaSclaudio 
477e7adcfeaSclaudio 	if (flags & ATTR_PARTIAL)
478e7adcfeaSclaudio 		comm->flags |= PARTIAL_EXT_COMMUNITIES;
479e7adcfeaSclaudio 
480b3b1d939Sclaudio 	while (ibuf_size(buf) > 0) {
481b3b1d939Sclaudio 		if (ibuf_get_n64(buf, &c) == -1)
482b3b1d939Sclaudio 			return (-1);
483e7adcfeaSclaudio 
484e7adcfeaSclaudio 		type = c >> 56;
485f8162053Sclaudio 		/* filter out non-transitive ext communuties from ebgp peers */
486f8162053Sclaudio 		if (ebgp && (type & EXT_COMMUNITY_NON_TRANSITIVE))
487f8162053Sclaudio 			continue;
488f8162053Sclaudio 		switch (type & EXT_COMMUNITY_VALUE) {
489e7adcfeaSclaudio 		case EXT_COMMUNITY_TRANS_TWO_AS:
490e7adcfeaSclaudio 		case EXT_COMMUNITY_TRANS_OPAQUE:
491e7adcfeaSclaudio 		case EXT_COMMUNITY_TRANS_EVPN:
492e7adcfeaSclaudio 			set.data1 = c >> 32 & 0xffff;
493e7adcfeaSclaudio 			set.data2 = c;
494e7adcfeaSclaudio 			break;
495e7adcfeaSclaudio 		case EXT_COMMUNITY_TRANS_FOUR_AS:
496e7adcfeaSclaudio 		case EXT_COMMUNITY_TRANS_IPV4:
497e7adcfeaSclaudio 			set.data1 = c >> 16;
498e7adcfeaSclaudio 			set.data2 = c & 0xffff;
499e7adcfeaSclaudio 			break;
500e7adcfeaSclaudio 		}
501e7adcfeaSclaudio 		set.data3 = c >> 48;
502e7adcfeaSclaudio 
503e7adcfeaSclaudio 		insert_community(comm, &set);
504e7adcfeaSclaudio 	}
505e7adcfeaSclaudio 
506e7adcfeaSclaudio 	return 0;
507e7adcfeaSclaudio }
508e7adcfeaSclaudio 
509e7adcfeaSclaudio /*
510e7adcfeaSclaudio  * Convert communities back to the wireformat.
511e7adcfeaSclaudio  * When writing ATTR_EXT_COMMUNITIES non-transitive communities need to
51266b1afa0Sclaudio  * be skipped if they are sent to an ebgp peer.
513e7adcfeaSclaudio  */
514e7adcfeaSclaudio int
51566b1afa0Sclaudio community_writebuf(struct rde_community *comm, uint8_t type, int ebgp,
51666b1afa0Sclaudio     struct ibuf *buf)
517e7adcfeaSclaudio {
518e7adcfeaSclaudio 	struct community *cp;
51939386878Sclaudio 	uint64_t ext;
52066b1afa0Sclaudio 	int l, size, start, end, num;
52166b1afa0Sclaudio 	int flags = ATTR_OPTIONAL | ATTR_TRANSITIVE;
52266b1afa0Sclaudio 	uint8_t t;
523e7adcfeaSclaudio 
52466b1afa0Sclaudio 	switch (type) {
52566b1afa0Sclaudio 	case ATTR_COMMUNITIES:
52666b1afa0Sclaudio 		if (comm->flags & PARTIAL_COMMUNITIES)
52766b1afa0Sclaudio 			flags |= ATTR_PARTIAL;
52866b1afa0Sclaudio 		size = 4;
52966b1afa0Sclaudio 		t = COMMUNITY_TYPE_BASIC;
53066b1afa0Sclaudio 		break;
53166b1afa0Sclaudio 	case ATTR_EXT_COMMUNITIES:
532e7adcfeaSclaudio 		if (comm->flags & PARTIAL_EXT_COMMUNITIES)
533e7adcfeaSclaudio 			flags |= ATTR_PARTIAL;
53466b1afa0Sclaudio 		size = 8;
53566b1afa0Sclaudio 		t = COMMUNITY_TYPE_EXT;
53666b1afa0Sclaudio 		break;
53766b1afa0Sclaudio 	case ATTR_LARGE_COMMUNITIES:
53866b1afa0Sclaudio 		if (comm->flags & PARTIAL_LARGE_COMMUNITIES)
53966b1afa0Sclaudio 			flags |= ATTR_PARTIAL;
54066b1afa0Sclaudio 		size = 12;
54166b1afa0Sclaudio 		t = COMMUNITY_TYPE_LARGE;
54266b1afa0Sclaudio 		break;
54366b1afa0Sclaudio 	default:
54466b1afa0Sclaudio 		return -1;
54566b1afa0Sclaudio 	}
546e7adcfeaSclaudio 
547e7adcfeaSclaudio 	/* first count how many communities will be written */
54866b1afa0Sclaudio 	num = 0;
54966b1afa0Sclaudio 	start = -1;
55066b1afa0Sclaudio 	for (l = 0; l < comm->nentries; l++) {
55166b1afa0Sclaudio 		cp = &comm->communities[l];
552578f8c88Sclaudio 		if ((uint8_t)cp->flags == t) {
55366b1afa0Sclaudio 			if (ebgp && non_transitive_ext_community(cp))
55466b1afa0Sclaudio 				continue;
55566b1afa0Sclaudio 			num++;
55666b1afa0Sclaudio 			if (start == -1)
55766b1afa0Sclaudio 				start = l;
55866b1afa0Sclaudio 		}
55966b1afa0Sclaudio 		if ((uint8_t)cp->flags > t)
56066b1afa0Sclaudio 			break;
56166b1afa0Sclaudio 	}
56266b1afa0Sclaudio 	end = l;
56366b1afa0Sclaudio 
56466b1afa0Sclaudio 	/* no communities for this type present */
56566b1afa0Sclaudio 	if (num == 0)
566e7adcfeaSclaudio 		return 0;
567e7adcfeaSclaudio 
56866b1afa0Sclaudio 	if (num > INT16_MAX / size)
569e7adcfeaSclaudio 		return -1;
57066b1afa0Sclaudio 
57166b1afa0Sclaudio 	/* write attribute header */
57266b1afa0Sclaudio 	if (attr_writebuf(buf, flags, type, NULL, num * size) == -1)
57366b1afa0Sclaudio 		return -1;
574e7adcfeaSclaudio 
575e7adcfeaSclaudio 	/* write out the communities */
57666b1afa0Sclaudio 	for (l = start; l < end; l++) {
57766b1afa0Sclaudio 		cp = &comm->communities[l];
57866b1afa0Sclaudio 
57966b1afa0Sclaudio 		switch (type) {
58066b1afa0Sclaudio 		case ATTR_COMMUNITIES:
58166b1afa0Sclaudio 			if (ibuf_add_n16(buf, cp->data1) == -1)
58266b1afa0Sclaudio 				return -1;
58366b1afa0Sclaudio 			if (ibuf_add_n16(buf, cp->data2) == -1)
58466b1afa0Sclaudio 				return -1;
585e7adcfeaSclaudio 			break;
58666b1afa0Sclaudio 		case ATTR_EXT_COMMUNITIES:
58766b1afa0Sclaudio 			if (ebgp && non_transitive_ext_community(cp))
58853372d18Sclaudio 				continue;
58953372d18Sclaudio 
59039386878Sclaudio 			ext = (uint64_t)cp->data3 << 48;
591f8162053Sclaudio 			switch ((cp->data3 >> 8) & EXT_COMMUNITY_VALUE) {
59253372d18Sclaudio 			case EXT_COMMUNITY_TRANS_TWO_AS:
59353372d18Sclaudio 			case EXT_COMMUNITY_TRANS_OPAQUE:
59453372d18Sclaudio 			case EXT_COMMUNITY_TRANS_EVPN:
59539386878Sclaudio 				ext |= ((uint64_t)cp->data1 & 0xffff) << 32;
59639386878Sclaudio 				ext |= (uint64_t)cp->data2;
59753372d18Sclaudio 				break;
59853372d18Sclaudio 			case EXT_COMMUNITY_TRANS_FOUR_AS:
59953372d18Sclaudio 			case EXT_COMMUNITY_TRANS_IPV4:
60039386878Sclaudio 				ext |= (uint64_t)cp->data1 << 16;
60139386878Sclaudio 				ext |= (uint64_t)cp->data2 & 0xffff;
60253372d18Sclaudio 				break;
60353372d18Sclaudio 			}
60466b1afa0Sclaudio 			if (ibuf_add_n64(buf, ext) == -1)
60553372d18Sclaudio 				return -1;
60666b1afa0Sclaudio 			break;
60766b1afa0Sclaudio 		case ATTR_LARGE_COMMUNITIES:
60866b1afa0Sclaudio 			if (ibuf_add_n32(buf, cp->data1) == -1)
60966b1afa0Sclaudio 				return -1;
61066b1afa0Sclaudio 			if (ibuf_add_n32(buf, cp->data2) == -1)
61166b1afa0Sclaudio 				return -1;
61266b1afa0Sclaudio 			if (ibuf_add_n32(buf, cp->data3) == -1)
61366b1afa0Sclaudio 				return -1;
61466b1afa0Sclaudio 			break;
61553372d18Sclaudio 		}
61653372d18Sclaudio 	}
61753372d18Sclaudio 	return 0;
61853372d18Sclaudio }
61953372d18Sclaudio 
62053372d18Sclaudio /*
621e7adcfeaSclaudio  * Global RIB cache for communities
622e7adcfeaSclaudio  */
62323f5897dSclaudio static inline int
62423f5897dSclaudio communities_compare(struct rde_community *a, struct rde_community *b)
625e7adcfeaSclaudio {
62623f5897dSclaudio 	if (a->nentries != b->nentries)
62723f5897dSclaudio 		return a->nentries > b->nentries ? 1 : -1;
62823f5897dSclaudio 	if (a->flags != b->flags)
62923f5897dSclaudio 		return a->flags > b->flags ? 1 : -1;
630e7adcfeaSclaudio 
63123f5897dSclaudio 	return memcmp(a->communities, b->communities,
63223f5897dSclaudio 	    a->nentries * sizeof(struct community));
633e7adcfeaSclaudio }
634e7adcfeaSclaudio 
63523f5897dSclaudio RB_HEAD(comm_tree, rde_community)	commtable = RB_INITIALIZER(&commtable);
63623f5897dSclaudio RB_GENERATE_STATIC(comm_tree, rde_community, entry, communities_compare);
637e7adcfeaSclaudio 
638e7adcfeaSclaudio void
639e7adcfeaSclaudio communities_shutdown(void)
640e7adcfeaSclaudio {
64123f5897dSclaudio 	if (!RB_EMPTY(&commtable))
642e7adcfeaSclaudio 		log_warnx("%s: free non-free table", __func__);
643e7adcfeaSclaudio }
644e7adcfeaSclaudio 
645e7adcfeaSclaudio struct rde_community *
646e7adcfeaSclaudio communities_lookup(struct rde_community *comm)
647e7adcfeaSclaudio {
64823f5897dSclaudio 	return RB_FIND(comm_tree, &commtable, comm);
649e7adcfeaSclaudio }
650e7adcfeaSclaudio 
651e7adcfeaSclaudio struct rde_community *
652e7adcfeaSclaudio communities_link(struct rde_community *comm)
653e7adcfeaSclaudio {
65423f5897dSclaudio 	struct rde_community *n, *f;
655e7adcfeaSclaudio 
656e7adcfeaSclaudio 	if ((n = malloc(sizeof(*n))) == NULL)
657e7adcfeaSclaudio 		fatal(__func__);
658e7adcfeaSclaudio 	communities_copy(n, comm);
659e7adcfeaSclaudio 
66023f5897dSclaudio 	if ((f = RB_INSERT(comm_tree, &commtable, n)) != NULL) {
66123f5897dSclaudio 		log_warnx("duplicate communities collection inserted");
66223f5897dSclaudio 		free(n->communities);
66323f5897dSclaudio 		free(n);
66423f5897dSclaudio 		return f;
66523f5897dSclaudio 	}
666e7adcfeaSclaudio 	n->refcnt = 1;	/* initial reference by the cache */
667e7adcfeaSclaudio 
668e7adcfeaSclaudio 	rdemem.comm_size += n->size;
669e7adcfeaSclaudio 	rdemem.comm_nmemb += n->nentries;
670e7adcfeaSclaudio 	rdemem.comm_cnt++;
671e7adcfeaSclaudio 
672e7adcfeaSclaudio 	return n;
673e7adcfeaSclaudio }
674e7adcfeaSclaudio 
675e7adcfeaSclaudio void
676e7adcfeaSclaudio communities_unlink(struct rde_community *comm)
677e7adcfeaSclaudio {
678e7adcfeaSclaudio 	if (comm->refcnt != 1)
679e7adcfeaSclaudio 		fatalx("%s: unlinking still referenced communities", __func__);
680e7adcfeaSclaudio 
68123f5897dSclaudio 	RB_REMOVE(comm_tree, &commtable, comm);
682e7adcfeaSclaudio 
683e7adcfeaSclaudio 	rdemem.comm_size -= comm->size;
684e7adcfeaSclaudio 	rdemem.comm_nmemb -= comm->nentries;
685e7adcfeaSclaudio 	rdemem.comm_cnt--;
686e7adcfeaSclaudio 
687e7adcfeaSclaudio 	free(comm->communities);
688e7adcfeaSclaudio 	free(comm);
689e7adcfeaSclaudio }
690e7adcfeaSclaudio 
691e7adcfeaSclaudio /*
692e7adcfeaSclaudio  * Return true/1 if the two communities collections are identical,
693e7adcfeaSclaudio  * otherwise returns zero.
694e7adcfeaSclaudio  */
695e7adcfeaSclaudio int
696e7adcfeaSclaudio communities_equal(struct rde_community *a, struct rde_community *b)
697e7adcfeaSclaudio {
698e7adcfeaSclaudio 	if (a->nentries != b->nentries)
699e7adcfeaSclaudio 		return 0;
700e7adcfeaSclaudio 	if (a->flags != b->flags)
701e7adcfeaSclaudio 		return 0;
702e7adcfeaSclaudio 
703e7adcfeaSclaudio 	return (memcmp(a->communities, b->communities,
704e7adcfeaSclaudio 	    a->nentries * sizeof(struct community)) == 0);
705e7adcfeaSclaudio }
706e7adcfeaSclaudio 
707e7adcfeaSclaudio /*
708e7adcfeaSclaudio  * Copy communities to a new unreferenced struct. Needs to call
709e7adcfeaSclaudio  * communities_clean() when done. to can be statically allocated,
710e7adcfeaSclaudio  * it will be cleaned first.
711e7adcfeaSclaudio  */
712e7adcfeaSclaudio void
713e7adcfeaSclaudio communities_copy(struct rde_community *to, struct rde_community *from)
714e7adcfeaSclaudio {
715e7adcfeaSclaudio 	memset(to, 0, sizeof(*to));
716e7adcfeaSclaudio 
7173a50f0a9Sjmc 	/* ignore from->size and allocate the perfect amount */
718*07ef3965Sclaudio 	to->size = from->nentries;
719e7adcfeaSclaudio 	to->nentries = from->nentries;
720e7adcfeaSclaudio 	to->flags = from->flags;
721e7adcfeaSclaudio 
722*07ef3965Sclaudio 	if (to->nentries == 0)
723*07ef3965Sclaudio 		return;
724*07ef3965Sclaudio 
725e7adcfeaSclaudio 	if ((to->communities = reallocarray(NULL, to->size,
726e7adcfeaSclaudio 	    sizeof(struct community))) == NULL)
727e7adcfeaSclaudio 		fatal(__func__);
728e7adcfeaSclaudio 
729e7adcfeaSclaudio 	memcpy(to->communities, from->communities,
730e7adcfeaSclaudio 	    to->nentries * sizeof(struct community));
731e7adcfeaSclaudio }
732e7adcfeaSclaudio 
733e7adcfeaSclaudio /*
734e7adcfeaSclaudio  * Clean up the communities by freeing any dynamically allocated memory.
735e7adcfeaSclaudio  */
736e7adcfeaSclaudio void
737e7adcfeaSclaudio communities_clean(struct rde_community *comm)
738e7adcfeaSclaudio {
739e7adcfeaSclaudio 	if (comm->refcnt != 0)
740e7adcfeaSclaudio 		fatalx("%s: cleaning still referenced communities", __func__);
741e7adcfeaSclaudio 
742e7adcfeaSclaudio 	free(comm->communities);
743e7adcfeaSclaudio 	memset(comm, 0, sizeof(*comm));
744e7adcfeaSclaudio }
745e7adcfeaSclaudio 
746e7adcfeaSclaudio int
74739386878Sclaudio community_to_rd(struct community *fc, uint64_t *community)
748e7adcfeaSclaudio {
749e7adcfeaSclaudio 	struct community c;
75039386878Sclaudio 	uint64_t rd;
751e7adcfeaSclaudio 
752e7adcfeaSclaudio 	if (fc2c(fc, NULL, &c, NULL) == -1)
753e7adcfeaSclaudio 		return -1;
754e7adcfeaSclaudio 
755f8162053Sclaudio 	switch ((c.data3 >> 8) & EXT_COMMUNITY_VALUE) {
756e7adcfeaSclaudio 	case EXT_COMMUNITY_TRANS_TWO_AS:
757e7adcfeaSclaudio 		rd = (0ULL << 48);
75839386878Sclaudio 		rd |= ((uint64_t)c.data1 & 0xffff) << 32;
75939386878Sclaudio 		rd |= (uint64_t)c.data2;
760e7adcfeaSclaudio 		break;
761e7adcfeaSclaudio 	case EXT_COMMUNITY_TRANS_IPV4:
762e7adcfeaSclaudio 		rd = (1ULL << 48);
76339386878Sclaudio 		rd |= (uint64_t)c.data1 << 16;
76439386878Sclaudio 		rd |= (uint64_t)c.data2 & 0xffff;
765e7adcfeaSclaudio 		break;
766e7adcfeaSclaudio 	case EXT_COMMUNITY_TRANS_FOUR_AS:
767e7adcfeaSclaudio 		rd = (2ULL << 48);
76839386878Sclaudio 		rd |= (uint64_t)c.data1 << 16;
76939386878Sclaudio 		rd |= (uint64_t)c.data2 & 0xffff;
770e7adcfeaSclaudio 		break;
771e7adcfeaSclaudio 	default:
772e7adcfeaSclaudio 		return -1;
773e7adcfeaSclaudio 	}
774e7adcfeaSclaudio 
775e7adcfeaSclaudio 	*community = htobe64(rd);
776e7adcfeaSclaudio 	return 0;
777e7adcfeaSclaudio }
778