There is a trivial optimization that bgpd can do when loading the filter ruleset. If the rule is the same as the previous rule than the filterset can be merged. e.g.
match from ebgp set community delete $myAS:* match from ebgp set community $myAS:15 match from ebgp set med 100 Will be optimized into: match from ebgp set { metric 100 community delete $myAS:* community $myAS:15 } The following diff is doing this and saves around 5% of the rules in arouteserver configs and probably similar amount in other peoples config. -- :wq Claudio Index: bgpd.h =================================================================== RCS file: /cvs/src/usr.sbin/bgpd/bgpd.h,v retrieving revision 1.355 diff -u -p -r1.355 bgpd.h --- bgpd.h 28 Nov 2018 08:32:26 -0000 1.355 +++ bgpd.h 28 Nov 2018 10:07:50 -0000 @@ -966,10 +966,10 @@ enum action_types { ACTION_SET_NEXTHOP_BLACKHOLE, ACTION_SET_NEXTHOP_NOMODIFY, ACTION_SET_NEXTHOP_SELF, - ACTION_SET_COMMUNITY, ACTION_DEL_COMMUNITY, - ACTION_SET_EXT_COMMUNITY, + ACTION_SET_COMMUNITY, ACTION_DEL_EXT_COMMUNITY, + ACTION_SET_EXT_COMMUNITY, ACTION_PFTABLE, ACTION_PFTABLE_ID, ACTION_RTLABEL, Index: parse.y =================================================================== RCS file: /cvs/src/usr.sbin/bgpd/parse.y,v retrieving revision 1.364 diff -u -p -r1.364 parse.y --- parse.y 28 Nov 2018 08:32:27 -0000 1.364 +++ parse.y 28 Nov 2018 10:09:34 -0000 @@ -150,7 +150,7 @@ int expand_rule(struct filter_rule *, int str2key(char *, char *, size_t); int neighbor_consistent(struct peer *); int merge_filterset(struct filter_set_head *, struct filter_set *); -void merge_filter_lists(struct filter_head *, struct filter_head *); +void optimize_filters(struct filter_head *); struct filter_rule *get_rule(enum action_types); int parsecommunity(struct filter_community *, int, char *); @@ -3345,13 +3345,15 @@ parse_config(char *filename, struct bgpd free_config(conf); } else { /* - * Move filter list and static group and peer filtersets + * Concatenate filter list and static group and peer filtersets * together. Static group sets come first then peer sets * last normal filter rules. */ - merge_filter_lists(conf->filters, groupfilter_l); - merge_filter_lists(conf->filters, peerfilter_l); - merge_filter_lists(conf->filters, filter_l); + TAILQ_CONCAT(conf->filters, groupfilter_l, entry); + TAILQ_CONCAT(conf->filters, peerfilter_l, entry); + TAILQ_CONCAT(conf->filters, filter_l, entry); + + optimize_filters(conf->filters); errors += mrt_mergeconfig(xconf->mrt, conf->mrt); errors += merge_config(xconf, conf, peer_l); @@ -4180,39 +4182,17 @@ neighbor_consistent(struct peer *p) return (0); } -int -merge_filterset(struct filter_set_head *sh, struct filter_set *s) +static void +filterset_add(struct filter_set_head *sh, struct filter_set *s) { struct filter_set *t; TAILQ_FOREACH(t, sh, entry) { - /* - * need to cycle across the full list because even - * if types are not equal filterset_cmp() may return 0. - */ - if (filterset_cmp(s, t) == 0) { - if (s->type == ACTION_SET_COMMUNITY) - yyerror("community is already set"); - else if (s->type == ACTION_DEL_COMMUNITY) - yyerror("community will already be deleted"); - else if (s->type == ACTION_SET_EXT_COMMUNITY) - yyerror("ext-community is already set"); - else if (s->type == ACTION_DEL_EXT_COMMUNITY) - yyerror( - "ext-community will already be deleted"); - else - yyerror("redefining set parameter %s", - filterset_name(s->type)); - return (-1); - } - } - - TAILQ_FOREACH(t, sh, entry) { if (s->type < t->type) { TAILQ_INSERT_BEFORE(t, s, entry); - return (0); + return; } - if (s->type == t->type) + if (s->type == t->type) { switch (s->type) { case ACTION_SET_COMMUNITY: case ACTION_DEL_COMMUNITY: @@ -4220,42 +4200,145 @@ merge_filterset(struct filter_set_head * &t->action.community, sizeof(s->action.community)) < 0) { TAILQ_INSERT_BEFORE(t, s, entry); - return (0); - } - break; + return; + } else if (memcmp(&s->action.community, + &t->action.community, + sizeof(s->action.community)) == 0) + break; + continue; case ACTION_SET_EXT_COMMUNITY: case ACTION_DEL_EXT_COMMUNITY: if (memcmp(&s->action.ext_community, &t->action.ext_community, sizeof(s->action.ext_community)) < 0) { TAILQ_INSERT_BEFORE(t, s, entry); - return (0); - } - break; + return; + } else if (memcmp(&s->action.ext_community, + &t->action.ext_community, + sizeof(s->action.ext_community)) == 0) + break; + continue; case ACTION_SET_NEXTHOP: + /* only last nexthop per AF matters */ if (s->action.nexthop.aid < t->action.nexthop.aid) { TAILQ_INSERT_BEFORE(t, s, entry); - return (0); + return; + } else if (s->action.nexthop.aid == + t->action.nexthop.aid) { + t->action.nexthop = s->action.nexthop; + break; } + continue; + case ACTION_SET_NEXTHOP_BLACKHOLE: + case ACTION_SET_NEXTHOP_REJECT: + case ACTION_SET_NEXTHOP_NOMODIFY: + case ACTION_SET_NEXTHOP_SELF: + /* set it only once */ + break; + case ACTION_SET_LOCALPREF: + case ACTION_SET_MED: + case ACTION_SET_WEIGHT: + /* only last set matters */ + t->action.metric = s->action.metric; + break; + case ACTION_SET_RELATIVE_LOCALPREF: + case ACTION_SET_RELATIVE_MED: + case ACTION_SET_RELATIVE_WEIGHT: + /* sum all relative numbers */ + t->action.relative += s->action.relative; + break; + case ACTION_SET_ORIGIN: + /* only last set matters */ + t->action.origin = s->action.origin; + break; + case ACTION_PFTABLE: + /* only last set matters */ + strlcpy(t->action.pftable, s->action.pftable, + sizeof(t->action.pftable)); + break; + case ACTION_RTLABEL: + /* only last set matters */ + strlcpy(t->action.rtlabel, s->action.rtlabel, + sizeof(t->action.rtlabel)); break; default: break; } + free(s); + return; + } } TAILQ_INSERT_TAIL(sh, s, entry); +} + +int +merge_filterset(struct filter_set_head *sh, struct filter_set *s) +{ + struct filter_set *t; + + TAILQ_FOREACH(t, sh, entry) { + /* + * need to cycle across the full list because even + * if types are not equal filterset_cmp() may return 0. + */ + if (filterset_cmp(s, t) == 0) { + if (s->type == ACTION_SET_COMMUNITY) + yyerror("community is already set"); + else if (s->type == ACTION_DEL_COMMUNITY) + yyerror("community will already be deleted"); + else if (s->type == ACTION_SET_EXT_COMMUNITY) + yyerror("ext-community is already set"); + else if (s->type == ACTION_DEL_EXT_COMMUNITY) + yyerror( + "ext-community will already be deleted"); + else + yyerror("redefining set parameter %s", + filterset_name(s->type)); + return (-1); + } + } + + filterset_add(sh, s); return (0); } +static int +filter_equal(struct filter_rule *fa, struct filter_rule *fb) +{ + if (fa == NULL || fb == NULL) + return 0; + if (fa->action != fb->action || fa->quick != fb->quick || + fa->dir != fb->dir) + return 0; + if (memcmp(&fa->peer, &fb->peer, sizeof(fa->peer))) + return 0; + if (memcmp(&fa->match, &fb->match, sizeof(fa->match))) + return 0; + + return 1; +} + +/* do a basic optimization by folding equal rules together */ void -merge_filter_lists(struct filter_head *dst, struct filter_head *src) +optimize_filters(struct filter_head *fh) { - struct filter_rule *r; + struct filter_rule *r, *nr; + + TAILQ_FOREACH_SAFE(r, fh, entry, nr) { + while (filter_equal(r, nr)) { + struct filter_set *t; - while ((r = TAILQ_FIRST(src)) != NULL) { - TAILQ_REMOVE(src, r, entry); - TAILQ_INSERT_TAIL(dst, r, entry); + while((t = TAILQ_FIRST(&nr->set)) != NULL) { + TAILQ_REMOVE(&nr->set, t, entry); + filterset_add(&r->set, t); + } + + TAILQ_REMOVE(fh, nr, entry); + free(nr); + nr = TAILQ_NEXT(r, entry); + } } }