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