xref: /openbsd/usr.sbin/bgpd/rde_filter.c (revision 404b540a)
1 /*	$OpenBSD: rde_filter.c,v 1.57 2009/08/06 08:53:11 claudio Exp $ */
2 
3 /*
4  * Copyright (c) 2004 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/types.h>
19 #include <sys/queue.h>
20 
21 #include <limits.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "bgpd.h"
26 #include "rde.h"
27 
28 int	rde_filter_match(struct filter_rule *, struct rde_aspath *,
29 	    struct bgpd_addr *, u_int8_t, struct rde_peer *);
30 int	filterset_equal(struct filter_set_head *, struct filter_set_head *);
31 
32 enum filter_actions
33 rde_filter(u_int16_t ribid, struct rde_aspath **new, struct filter_head *rules,
34     struct rde_peer *peer, struct rde_aspath *asp, struct bgpd_addr *prefix,
35     u_int8_t prefixlen, struct rde_peer *from, enum directions dir)
36 {
37 	struct filter_rule	*f;
38 	enum filter_actions	 action = ACTION_ALLOW; /* default allow */
39 
40 	if (new != NULL)
41 		*new = NULL;
42 
43 	if (asp->flags & F_ATTR_PARSE_ERR)
44 		/*
45 	 	 * don't try to filter bad updates but let them through
46 		 * so they act as implicit withdraws
47 		 */
48 		return (action);
49 
50 	TAILQ_FOREACH(f, rules, entry) {
51 		if (dir != f->dir)
52 			continue;
53 		if (dir == DIR_IN && f->peer.ribid != ribid)
54 			continue;
55 		if (f->peer.groupid != 0 &&
56 		    f->peer.groupid != peer->conf.groupid)
57 			continue;
58 		if (f->peer.peerid != 0 &&
59 		    f->peer.peerid != peer->conf.id)
60 			continue;
61 		if (rde_filter_match(f, asp, prefix, prefixlen, peer)) {
62 			if (asp != NULL && new != NULL) {
63 				/* asp may get modified so create a copy */
64 				if (*new == NULL) {
65 					*new = path_copy(asp);
66 					/* ... and use the copy from now on */
67 					asp = *new;
68 				}
69 				rde_apply_set(asp, &f->set, prefix->af,
70 				    from, peer);
71 			}
72 			if (f->action != ACTION_NONE)
73 				action = f->action;
74 			if (f->quick)
75 				return (action);
76 		}
77 	}
78 	return (action);
79 }
80 
81 void
82 rde_apply_set(struct rde_aspath *asp, struct filter_set_head *sh,
83     sa_family_t af, struct rde_peer *from, struct rde_peer *peer)
84 {
85 	struct filter_set	*set;
86 	u_char			*np;
87 	int			 as, type;
88 	u_int32_t		 prep_as;
89 	u_int16_t		 nl;
90 	u_int8_t		 prepend;
91 
92 	if (asp == NULL)
93 		return;
94 
95 	TAILQ_FOREACH(set, sh, entry) {
96 		switch (set->type) {
97 		case ACTION_SET_LOCALPREF:
98 			asp->lpref = set->action.metric;
99 			break;
100 		case ACTION_SET_RELATIVE_LOCALPREF:
101 			if (set->action.relative > 0) {
102 				if (set->action.relative + asp->lpref <
103 				    asp->lpref)
104 					asp->lpref = UINT_MAX;
105 				else
106 					asp->lpref += set->action.relative;
107 			} else {
108 				if ((u_int32_t)-set->action.relative >
109 				    asp->lpref)
110 					asp->lpref = 0;
111 				else
112 					asp->lpref += set->action.relative;
113 			}
114 			break;
115 		case ACTION_SET_MED:
116 			asp->flags |= F_ATTR_MED | F_ATTR_MED_ANNOUNCE;
117 			asp->med = set->action.metric;
118 			break;
119 		case ACTION_SET_RELATIVE_MED:
120 			asp->flags |= F_ATTR_MED | F_ATTR_MED_ANNOUNCE;
121 			if (set->action.relative > 0) {
122 				if (set->action.relative + asp->med <
123 				    asp->med)
124 					asp->med = UINT_MAX;
125 				else
126 					asp->med += set->action.relative;
127 			} else {
128 				if ((u_int32_t)-set->action.relative >
129 				    asp->med)
130 					asp->med = 0;
131 				else
132 					asp->med += set->action.relative;
133 			}
134 			break;
135 		case ACTION_SET_WEIGHT:
136 			asp->weight = set->action.metric;
137 			break;
138 		case ACTION_SET_RELATIVE_WEIGHT:
139 			if (set->action.relative > 0) {
140 				if (set->action.relative + asp->weight <
141 				    asp->weight)
142 					asp->weight = UINT_MAX;
143 				else
144 					asp->weight += set->action.relative;
145 			} else {
146 				if ((u_int32_t)-set->action.relative >
147 				    asp->weight)
148 					asp->weight = 0;
149 				else
150 					asp->weight += set->action.relative;
151 			}
152 			break;
153 		case ACTION_SET_PREPEND_SELF:
154 			prep_as = rde_local_as();
155 			prepend = set->action.prepend;
156 			np = aspath_prepend(asp->aspath, prep_as, prepend, &nl);
157 			aspath_put(asp->aspath);
158 			asp->aspath = aspath_get(np, nl);
159 			free(np);
160 			break;
161 		case ACTION_SET_PREPEND_PEER:
162 			if (from == NULL)
163 				break;
164 			prep_as = from->conf.remote_as;
165 			prepend = set->action.prepend;
166 			np = aspath_prepend(asp->aspath, prep_as, prepend, &nl);
167 			aspath_put(asp->aspath);
168 			asp->aspath = aspath_get(np, nl);
169 			free(np);
170 			break;
171 		case ACTION_SET_NEXTHOP:
172 		case ACTION_SET_NEXTHOP_REJECT:
173 		case ACTION_SET_NEXTHOP_BLACKHOLE:
174 		case ACTION_SET_NEXTHOP_NOMODIFY:
175 		case ACTION_SET_NEXTHOP_SELF:
176 			nexthop_modify(asp, &set->action.nexthop, set->type,
177 			    af);
178 			break;
179 		case ACTION_SET_COMMUNITY:
180 			switch (set->action.community.as) {
181 			case COMMUNITY_ERROR:
182 			case COMMUNITY_ANY:
183 				fatalx("rde_apply_set bad community string");
184 			case COMMUNITY_NEIGHBOR_AS:
185 				as = peer->conf.remote_as;
186 				break;
187 			default:
188 				as = set->action.community.as;
189 				break;
190 			}
191 
192 			switch (set->action.community.type) {
193 			case COMMUNITY_ERROR:
194 			case COMMUNITY_ANY:
195 				fatalx("rde_apply_set bad community string");
196 			case COMMUNITY_NEIGHBOR_AS:
197 				type = peer->conf.remote_as;
198 				break;
199 			default:
200 				type = set->action.community.type;
201 				break;
202 			}
203 
204 			community_set(asp, as, type);
205 			break;
206 		case ACTION_DEL_COMMUNITY:
207 			switch (set->action.community.as) {
208 			case COMMUNITY_ERROR:
209 				fatalx("rde_apply_set bad community string");
210 			case COMMUNITY_NEIGHBOR_AS:
211 				as = peer->conf.remote_as;
212 				break;
213 			case COMMUNITY_ANY:
214 			default:
215 				as = set->action.community.as;
216 				break;
217 			}
218 
219 			switch (set->action.community.type) {
220 			case COMMUNITY_ERROR:
221 				fatalx("rde_apply_set bad community string");
222 			case COMMUNITY_NEIGHBOR_AS:
223 				type = peer->conf.remote_as;
224 				break;
225 			case COMMUNITY_ANY:
226 			default:
227 				type = set->action.community.type;
228 				break;
229 			}
230 
231 			community_delete(asp, as, type);
232 			break;
233 		case ACTION_PFTABLE:
234 			/* convert pftable name to an id */
235 			set->action.id = pftable_name2id(set->action.pftable);
236 			set->type = ACTION_PFTABLE_ID;
237 			/* FALLTHROUGH */
238 		case ACTION_PFTABLE_ID:
239 			pftable_unref(asp->pftableid);
240 			asp->pftableid = set->action.id;
241 			pftable_ref(asp->pftableid);
242 			break;
243 		case ACTION_RTLABEL:
244 			/* convert the route label to an id for faster access */
245 			set->action.id = rtlabel_name2id(set->action.rtlabel);
246 			set->type = ACTION_RTLABEL_ID;
247 			/* FALLTHROUGH */
248 		case ACTION_RTLABEL_ID:
249 			rtlabel_unref(asp->rtlabelid);
250 			asp->rtlabelid = set->action.id;
251 			rtlabel_ref(asp->rtlabelid);
252 			break;
253 		}
254 	}
255 }
256 
257 int
258 rde_filter_match(struct filter_rule *f, struct rde_aspath *asp,
259     struct bgpd_addr *prefix, u_int8_t plen, struct rde_peer *peer)
260 {
261 	int	as, type;
262 
263 	if (asp != NULL && f->match.as.type != AS_NONE)
264 		if (aspath_match(asp->aspath, f->match.as.type,
265 		    f->match.as.as) == 0)
266 			return (0);
267 
268 	if (asp != NULL && f->match.community.as != COMMUNITY_UNSET) {
269 		switch (f->match.community.as) {
270 		case COMMUNITY_ERROR:
271 			fatalx("rde_apply_set bad community string");
272 		case COMMUNITY_NEIGHBOR_AS:
273 			as = peer->conf.remote_as;
274 			break;
275 		default:
276 			as = f->match.community.as;
277 			break;
278 		}
279 
280 		switch (f->match.community.type) {
281 		case COMMUNITY_ERROR:
282 			fatalx("rde_apply_set bad community string");
283 		case COMMUNITY_NEIGHBOR_AS:
284 			type = peer->conf.remote_as;
285 			break;
286 		default:
287 			type = f->match.community.type;
288 			break;
289 		}
290 
291 		if (rde_filter_community(asp, as, type) == 0)
292 			return (0);
293 	}
294 
295 	if (f->match.prefix.addr.af != 0) {
296 		if (f->match.prefix.addr.af != prefix->af)
297 			/* don't use IPv4 rules for IPv6 and vice versa */
298 			return (0);
299 
300 		if (prefix_compare(prefix, &f->match.prefix.addr,
301 		    f->match.prefix.len))
302 			return (0);
303 
304 		/* test prefixlen stuff too */
305 		switch (f->match.prefixlen.op) {
306 		case OP_NONE:
307 			/* perfect match */
308 			return (plen == f->match.prefix.len);
309 		case OP_RANGE:
310 			return ((plen >= f->match.prefixlen.len_min) &&
311 			    (plen <= f->match.prefixlen.len_max));
312 		case OP_XRANGE:
313 			return ((plen < f->match.prefixlen.len_min) ||
314 			    (plen > f->match.prefixlen.len_max));
315 		case OP_EQ:
316 			return (plen == f->match.prefixlen.len_min);
317 		case OP_NE:
318 			return (plen != f->match.prefixlen.len_min);
319 		case OP_LE:
320 			return (plen <= f->match.prefixlen.len_min);
321 		case OP_LT:
322 			return (plen < f->match.prefixlen.len_min);
323 		case OP_GE:
324 			return (plen >= f->match.prefixlen.len_min);
325 		case OP_GT:
326 			return (plen > f->match.prefixlen.len_min);
327 		}
328 		/* NOTREACHED */
329 	} else if (f->match.prefixlen.op != OP_NONE) {
330 		/* only prefixlen without a prefix */
331 
332 		if (f->match.prefixlen.af != prefix->af)
333 			/* don't use IPv4 rules for IPv6 and vice versa */
334 			return (0);
335 
336 		switch (f->match.prefixlen.op) {
337 		case OP_NONE:
338 			fatalx("internal filter bug");
339 		case OP_RANGE:
340 			return ((plen >= f->match.prefixlen.len_min) &&
341 			    (plen <= f->match.prefixlen.len_max));
342 		case OP_XRANGE:
343 			return ((plen < f->match.prefixlen.len_min) ||
344 			    (plen > f->match.prefixlen.len_max));
345 		case OP_EQ:
346 			return (plen == f->match.prefixlen.len_min);
347 		case OP_NE:
348 			return (plen != f->match.prefixlen.len_min);
349 		case OP_LE:
350 			return (plen <= f->match.prefixlen.len_min);
351 		case OP_LT:
352 			return (plen < f->match.prefixlen.len_min);
353 		case OP_GE:
354 			return (plen >= f->match.prefixlen.len_min);
355 		case OP_GT:
356 			return (plen > f->match.prefixlen.len_min);
357 		}
358 		/* NOTREACHED */
359 	}
360 
361 	/* matched somewhen or is anymatch rule  */
362 	return (1);
363 }
364 
365 int
366 rde_filter_community(struct rde_aspath *asp, int as, int type)
367 {
368 	struct attr	*a;
369 
370 	a = attr_optget(asp, ATTR_COMMUNITIES);
371 	if (a == NULL)
372 		/* no communities, no match */
373 		return (0);
374 
375 	return (community_match(a->data, a->len, as, type));
376 }
377 
378 int
379 rde_filter_equal(struct filter_head *a, struct filter_head *b,
380     struct rde_peer *peer, enum directions dir)
381 {
382 	struct filter_rule	*fa, *fb;
383 
384 	fa = TAILQ_FIRST(a);
385 	fb = TAILQ_FIRST(b);
386 
387 	while (fa != NULL || fb != NULL) {
388 		/* skip all rules with wrong direction */
389 		if (fa != NULL && dir != fa->dir) {
390 			fa = TAILQ_NEXT(fa, entry);
391 			continue;
392 		}
393 		if (fb != NULL && dir != fb->dir) {
394 			fb = TAILQ_NEXT(fb, entry);
395 			continue;
396 		}
397 
398 		/* skip all rules with wrong peer */
399 		if (fa != NULL && fa->peer.groupid != 0 &&
400 		    fa->peer.groupid != peer->conf.groupid) {
401 			fa = TAILQ_NEXT(fa, entry);
402 			continue;
403 		}
404 		if (fa != NULL && fa->peer.peerid != 0 &&
405 		    fa->peer.peerid != peer->conf.id) {
406 			fa = TAILQ_NEXT(fa, entry);
407 			continue;
408 		}
409 
410 		if (fb != NULL && fb->peer.groupid != 0 &&
411 		    fb->peer.groupid != peer->conf.groupid) {
412 			fb = TAILQ_NEXT(fb, entry);
413 			continue;
414 		}
415 		if (fb != NULL && fb->peer.peerid != 0 &&
416 		    fb->peer.peerid != peer->conf.id) {
417 			fb = TAILQ_NEXT(fb, entry);
418 			continue;
419 		}
420 
421 		/* compare the two rules */
422 		if ((fa == NULL && fb != NULL) || (fa != NULL && fb == NULL))
423 			/* new rule added or removed */
424 			return (0);
425 
426 		if (fa->action != fb->action || fa->quick != fb->quick)
427 			return (0);
428 		if (memcmp(&fa->peer, &fb->peer, sizeof(fa->peer)))
429 			return (0);
430 		if (memcmp(&fa->match, &fb->match, sizeof(fa->match)))
431 			return (0);
432 		if (!filterset_equal(&fa->set, &fb->set))
433 			return (0);
434 
435 		fa = TAILQ_NEXT(fa, entry);
436 		fb = TAILQ_NEXT(fb, entry);
437 	}
438 	return (1);
439 }
440 
441 /* free a filterset and take care of possible name2id references */
442 void
443 filterset_free(struct filter_set_head *sh)
444 {
445 	struct filter_set	*s;
446 	struct nexthop		*nh;
447 
448 	while ((s = TAILQ_FIRST(sh)) != NULL) {
449 		TAILQ_REMOVE(sh, s, entry);
450 		if (s->type == ACTION_RTLABEL_ID)
451 			rtlabel_unref(s->action.id);
452 		else if (s->type == ACTION_PFTABLE_ID)
453 			pftable_unref(s->action.id);
454 		else if (s->type == ACTION_SET_NEXTHOP &&
455 		    bgpd_process == PROC_RDE) {
456 			nh = nexthop_get(&s->action.nexthop);
457 			--nh->refcnt;
458 			(void)nexthop_delete(nh);
459 		}
460 		free(s);
461 	}
462 }
463 
464 /*
465  * this function is a bit more complicated than a memcmp() because there are
466  * types that need to be considered equal e.g. ACTION_SET_MED and
467  * ACTION_SET_RELATIVE_MED. Also ACTION_SET_COMMUNITY and ACTION_SET_NEXTHOP
468  * need some special care. It only checks the types and not the values so
469  * it does not do a real compare.
470  */
471 int
472 filterset_cmp(struct filter_set *a, struct filter_set *b)
473 {
474 	if (strcmp(filterset_name(a->type), filterset_name(b->type)))
475 		return (a->type - b->type);
476 
477 	if (a->type == ACTION_SET_COMMUNITY ||
478 	    a->type == ACTION_DEL_COMMUNITY) {	/* a->type == b->type */
479 		/* compare community */
480 		if (a->action.community.as - b->action.community.as != 0)
481 			return (a->action.community.as -
482 			    b->action.community.as);
483 		return (a->action.community.type - b->action.community.type);
484 	}
485 
486 	if (a->type == ACTION_SET_NEXTHOP && b->type == ACTION_SET_NEXTHOP) {
487 		/*
488 		 * This is the only interesting case, all others are considered
489 		 * equal. It does not make sense to e.g. set a nexthop and
490 		 * reject it at the same time. Allow one IPv4 and one IPv6
491 		 * per filter set or only one of the other nexthop modifiers.
492 		 */
493 		return (a->action.nexthop.af - b->action.nexthop.af);
494 	}
495 
496 	/* equal */
497 	return (0);
498 }
499 
500 int
501 filterset_equal(struct filter_set_head *ah, struct filter_set_head *bh)
502 {
503 	struct filter_set	*a, *b;
504 	const char		*as, *bs;
505 
506 	for (a = TAILQ_FIRST(ah), b = TAILQ_FIRST(bh);
507 	    a != NULL && b != NULL;
508 	    a = TAILQ_NEXT(a, entry), b = TAILQ_NEXT(b, entry)) {
509 		switch (a->type) {
510 		case ACTION_SET_PREPEND_SELF:
511 		case ACTION_SET_PREPEND_PEER:
512 			if (a->type == b->type &&
513 			    a->action.prepend == b->action.prepend)
514 				continue;
515 			break;
516 		case ACTION_SET_LOCALPREF:
517 		case ACTION_SET_MED:
518 		case ACTION_SET_WEIGHT:
519 			if (a->type == b->type &&
520 			    a->action.metric == b->action.metric)
521 				continue;
522 			break;
523 		case ACTION_SET_RELATIVE_LOCALPREF:
524 		case ACTION_SET_RELATIVE_MED:
525 		case ACTION_SET_RELATIVE_WEIGHT:
526 			if (a->type == b->type &&
527 			    a->action.relative == b->action.relative)
528 				continue;
529 			break;
530 		case ACTION_SET_NEXTHOP:
531 			if (a->type == b->type &&
532 			    memcmp(&a->action.nexthop, &b->action.nexthop,
533 			    sizeof(a->action.nexthop)) == 0)
534 				continue;
535 			break;
536 		case ACTION_SET_NEXTHOP_BLACKHOLE:
537 		case ACTION_SET_NEXTHOP_REJECT:
538 		case ACTION_SET_NEXTHOP_NOMODIFY:
539 		case ACTION_SET_NEXTHOP_SELF:
540 			if (a->type == b->type)
541 				continue;
542 			break;
543 		case ACTION_DEL_COMMUNITY:
544 		case ACTION_SET_COMMUNITY:
545 			if (a->type == b->type &&
546 			    memcmp(&a->action.community, &b->action.community,
547 			    sizeof(a->action.community)) == 0)
548 				continue;
549 			break;
550 		case ACTION_PFTABLE:
551 		case ACTION_PFTABLE_ID:
552 			if (b->type == ACTION_PFTABLE)
553 				bs = b->action.pftable;
554 			else if (b->type == ACTION_PFTABLE_ID)
555 				bs = pftable_id2name(b->action.id);
556 			else
557 				break;
558 
559 			if (a->type == ACTION_PFTABLE)
560 				as = a->action.pftable;
561 			else
562 				as = pftable_id2name(a->action.id);
563 
564 			if (strcmp(as, bs) == 0)
565 				continue;
566 			break;
567 		case ACTION_RTLABEL:
568 		case ACTION_RTLABEL_ID:
569 			if (b->type == ACTION_RTLABEL)
570 				bs = b->action.rtlabel;
571 			else if (b->type == ACTION_RTLABEL_ID)
572 				bs = rtlabel_id2name(b->action.id);
573 			else
574 				break;
575 
576 			if (a->type == ACTION_RTLABEL)
577 				as = a->action.rtlabel;
578 			else
579 				as = rtlabel_id2name(a->action.id);
580 
581 			if (strcmp(as, bs) == 0)
582 				continue;
583 			break;
584 		}
585 		/* compare failed */
586 		return (0);
587 	}
588 	if (a != NULL || b != NULL)
589 		return (0);
590 	return (1);
591 }
592 
593 const char *
594 filterset_name(enum action_types type)
595 {
596 	switch (type) {
597 	case ACTION_SET_LOCALPREF:
598 	case ACTION_SET_RELATIVE_LOCALPREF:
599 		return ("localpref");
600 	case ACTION_SET_MED:
601 	case ACTION_SET_RELATIVE_MED:
602 		return ("metric");
603 	case ACTION_SET_WEIGHT:
604 	case ACTION_SET_RELATIVE_WEIGHT:
605 		return ("weight");
606 	case ACTION_SET_PREPEND_SELF:
607 		return ("prepend-self");
608 	case ACTION_SET_PREPEND_PEER:
609 		return ("prepend-peer");
610 	case ACTION_SET_NEXTHOP:
611 	case ACTION_SET_NEXTHOP_REJECT:
612 	case ACTION_SET_NEXTHOP_BLACKHOLE:
613 	case ACTION_SET_NEXTHOP_NOMODIFY:
614 	case ACTION_SET_NEXTHOP_SELF:
615 		return ("nexthop");
616 	case ACTION_SET_COMMUNITY:
617 		return ("community");
618 	case ACTION_DEL_COMMUNITY:
619 		return ("community delete");
620 	case ACTION_PFTABLE:
621 	case ACTION_PFTABLE_ID:
622 		return ("pftable");
623 	case ACTION_RTLABEL:
624 	case ACTION_RTLABEL_ID:
625 		return ("rtlabel");
626 	}
627 
628 	fatalx("filterset_name: got lost");
629 }
630