After bringing in as-set this diff makes prefix-set fast by using a
bitwise search trie for fast lookups. It does not yet change the bad
syntax of prefix-sets but benno@ and I will tackle that once this is
hopefully in.
The code was inspired by what bird is doing for their prefix-set
lookups but the implementation is slighly different.
--
:wq Claudio
Index: Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/Makefile,v
retrieving revision 1.33
diff -u -p -r1.33 Makefile
--- Makefile 7 Sep 2018 05:43:33 -0000 1.33
+++ Makefile 7 Sep 2018 05:50:17 -0000
@@ -4,7 +4,8 @@ PROG= bgpd
SRCS= bgpd.c session.c log.c logmsg.c parse.y config.c \
rde.c rde_rib.c rde_decide.c rde_prefix.c mrt.c kroute.c \
control.c pfkey.c rde_update.c rde_attr.c printconf.c \
- rde_filter.c rde_sets.c pftable.c name2id.c util.c carp.c timer.c
+ rde_filter.c rde_sets.c rde_trie.c pftable.c name2id.c \
+ util.c carp.c timer.c
CFLAGS+= -Wall -I${.CURDIR}
CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
CFLAGS+= -Wmissing-declarations
Index: bgpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/bgpd.c,v
retrieving revision 1.195
diff -u -p -r1.195 bgpd.c
--- bgpd.c 7 Sep 2018 05:43:33 -0000 1.195
+++ bgpd.c 7 Sep 2018 05:47:59 -0000
@@ -507,7 +507,7 @@ reconfigure(char *conffile, struct bgpd_
while ((ps = SIMPLEQ_FIRST(conf->prefixsets)) != NULL) {
SIMPLEQ_REMOVE_HEAD(conf->prefixsets, entry);
if (imsg_compose(ibuf_rde, IMSG_RECONF_PREFIXSET, 0, 0, -1,
- ps, sizeof(*ps)) == -1)
+ ps->name, sizeof(ps->name)) == -1)
return (-1);
while ((psi = SIMPLEQ_FIRST(&ps->psitems)) != NULL) {
SIMPLEQ_REMOVE_HEAD(&ps->psitems, entry);
Index: bgpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/bgpd.h,v
retrieving revision 1.334
diff -u -p -r1.334 bgpd.h
--- bgpd.h 7 Sep 2018 05:43:33 -0000 1.334
+++ bgpd.h 7 Sep 2018 07:48:20 -0000
@@ -212,6 +212,8 @@ TAILQ_HEAD(network_head, network);
struct prefixset;
SIMPLEQ_HEAD(prefixset_head, prefixset);
+struct rde_prefixset_head;
+struct rde_prefixset;
struct as_set;
SIMPLEQ_HEAD(as_set_head, as_set);
@@ -226,6 +228,7 @@ struct bgpd_config {
struct listen_addrs *listen_addrs;
struct mrt_head *mrt;
struct prefixset_head *prefixsets;
+ struct rde_prefixset_head *rde_prefixsets;
struct as_set_head *as_sets;
char *csock;
char *rcsock;
@@ -680,7 +683,7 @@ struct filter_aslen {
struct filter_prefixset {
int flags;
char name[SET_NAME_LEN];
- struct prefixset *ps;
+ struct rde_prefixset *ps;
};
struct filter_community {
Index: rde.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde.c,v
retrieving revision 1.419
diff -u -p -r1.419 rde.c
--- rde.c 7 Sep 2018 05:43:33 -0000 1.419
+++ rde.c 7 Sep 2018 05:51:40 -0000
@@ -100,8 +100,10 @@ static void rde_softreconfig_unload_pee
void rde_up_dump_upcall(struct rib_entry *, void *);
void rde_update_queue_runner(void);
void rde_update6_queue_runner(u_int8_t);
-void rde_mark_prefixsets_dirty(struct prefixset_head *,
- struct prefixset_head *);
+struct rde_prefixset *rde_find_prefixset(char *, struct rde_prefixset_head *);
+void rde_free_prefixsets(struct rde_prefixset_head *);
+void rde_mark_prefixsets_dirty(struct rde_prefixset_head *,
+ struct rde_prefixset_head *);
void peer_init(u_int32_t);
void peer_shutdown(void);
@@ -128,7 +130,7 @@ struct bgpd_config *conf, *nconf;
time_t reloadtime;
struct rde_peer_head peerlist;
struct rde_peer *peerself;
-struct prefixset_head *prefixsets_tmp, *prefixsets_old;
+struct rde_prefixset_head *prefixsets_tmp, *prefixsets_old;
struct as_set_head *as_sets_tmp, *as_sets_old;
struct filter_head *out_rules, *out_rules_tmp;
struct rdomain_head *rdomains_l, *newdomains;
@@ -686,9 +688,9 @@ badnetdel:
void
rde_dispatch_imsg_parent(struct imsgbuf *ibuf)
{
- static struct rdomain *rd;
- static struct prefixset *last_prefixset;
+ static struct rde_prefixset *last_prefixset;
static struct as_set *last_as_set;
+ static struct rdomain *rd;
struct imsg imsg;
struct mrt xmrt;
struct rde_rib rn;
@@ -697,8 +699,8 @@ rde_dispatch_imsg_parent(struct imsgbuf
struct filter_rule *r;
struct filter_set *s;
struct rib *rib;
- struct prefixset *ps;
- struct prefixset_item *psi;
+ struct rde_prefixset *ps;
+ struct prefixset_item psi;
char *name;
size_t nmemb;
int n, fd;
@@ -769,7 +771,7 @@ rde_dispatch_imsg_parent(struct imsgbuf
fatalx("IMSG_RECONF_CONF bad len");
reloadtime = time(NULL);
prefixsets_tmp = calloc(1,
- sizeof(struct prefixset_head));
+ sizeof(struct rde_prefixset_head));
if (prefixsets_tmp == NULL)
fatal(NULL);
SIMPLEQ_INIT(prefixsets_tmp);
@@ -832,7 +834,7 @@ rde_dispatch_imsg_parent(struct imsgbuf
memcpy(r, imsg.data, sizeof(struct filter_rule));
if (r->match.prefixset.flags != 0) {
r->match.prefixset.ps =
- find_prefixset(r->match.prefixset.name,
+ rde_find_prefixset(r->match.prefixset.name,
prefixsets_tmp);
if (r->match.prefixset.ps == NULL)
log_warnx("%s: no prefixset for %s",
@@ -877,27 +879,27 @@ rde_dispatch_imsg_parent(struct imsgbuf
break;
case IMSG_RECONF_PREFIXSET:
if (imsg.hdr.len - IMSG_HEADER_SIZE !=
- sizeof(struct prefixset))
+ sizeof(ps->name))
fatalx("IMSG_RECONF_PREFIXSET bad len");
- ps = malloc(sizeof(struct prefixset));
+ ps = calloc(1, sizeof(struct rde_prefixset));
if (ps == NULL)
fatal(NULL);
- memcpy(ps, imsg.data, sizeof(struct prefixset));
- SIMPLEQ_INIT(&ps->psitems);
+ memcpy(ps->name, imsg.data, sizeof(ps->name));
SIMPLEQ_INSERT_TAIL(prefixsets_tmp, ps, entry);
last_prefixset = ps;
break;
case IMSG_RECONF_PREFIXSETITEM:
if (imsg.hdr.len - IMSG_HEADER_SIZE !=
- sizeof(struct prefixset_item))
+ sizeof(psi))
fatalx("IMSG_RECONF_PREFIXSETITEM bad len");
- psi = malloc(sizeof(struct prefixset_item));
- if (psi == NULL)
- fatal(NULL);
- memcpy(psi, imsg.data, sizeof(struct prefixset_item));
+ memcpy(&psi, imsg.data, sizeof(psi));
if (last_prefixset == NULL)
fatalx("King Bula has no prefixset");
- SIMPLEQ_INSERT_TAIL(&last_prefixset->psitems, psi,
entry);
+ if (trie_add(&last_prefixset->th, &psi.p.addr,
+ psi.p.len, psi.p.len_min, psi.p.len_max) == -1)
+ log_warnx("trie_add(%s) %s/%u, %u-%u) failed",
+ last_prefixset->name, log_addr(&psi.p.addr),
+ psi.p.len, psi.p.len_min, psi.p.len_max);
break;
case IMSG_RECONF_AS_SET:
if (imsg.hdr.len - IMSG_HEADER_SIZE !=
@@ -2793,13 +2795,14 @@ rde_reload_done(void)
nconf->flags &= ~BGPD_FLAG_NO_EVALUATE;
}
- prefixsets_old = conf->prefixsets;
+ prefixsets_old = conf->rde_prefixsets;
as_sets_old = conf->as_sets;
memcpy(conf, nconf, sizeof(struct bgpd_config));
conf->listen_addrs = NULL;
conf->csock = NULL;
conf->rcsock = NULL;
+ conf->prefixsets = NULL;
free(nconf);
nconf = NULL;
@@ -2827,7 +2830,7 @@ rde_reload_done(void)
as_sets_mark_dirty(as_sets_old, as_sets_tmp);
/* swap the prefixsets */
- conf->prefixsets = prefixsets_tmp;
+ conf->rde_prefixsets = prefixsets_tmp;
prefixsets_tmp = NULL;
/* and the as_sets */
conf->as_sets = as_sets_tmp;
@@ -3018,7 +3021,7 @@ rde_softreconfig_done(void)
ribs[rid].state = RECONF_NONE;
}
- free_prefixsets(prefixsets_old);
+ rde_free_prefixsets(prefixsets_old);
prefixsets_old = NULL;
as_sets_free(as_sets_old);
as_sets_old = NULL;
@@ -3809,25 +3812,47 @@ sa_cmp(struct bgpd_addr *a, struct socka
return (0);
}
+struct rde_prefixset *
+rde_find_prefixset(char *name, struct rde_prefixset_head *p)
+{
+ struct rde_prefixset *ps;
+
+ SIMPLEQ_FOREACH(ps, p, entry) {
+ if (!strcmp(ps->name, name))
+ return (ps);
+ }
+ return (NULL);
+}
+
+void
+rde_free_prefixsets(struct rde_prefixset_head *psh)
+{
+ struct rde_prefixset *ps;
+
+ if (psh == NULL)
+ return;
+
+ while (!SIMPLEQ_EMPTY(psh)) {
+ ps = SIMPLEQ_FIRST(psh);
+ trie_free(&ps->th);
+ SIMPLEQ_REMOVE_HEAD(psh, entry);
+ free(ps);
+ }
+}
+
void
-rde_mark_prefixsets_dirty(struct prefixset_head *psold,
- struct prefixset_head *psnew)
+rde_mark_prefixsets_dirty(struct rde_prefixset_head *psold,
+ struct rde_prefixset_head *psnew)
{
- struct prefixset *new, *fps;
- struct prefixset_item *item;
+ struct rde_prefixset *new, *old;
SIMPLEQ_FOREACH(new, psnew, entry) {
if ((psold == NULL) ||
- (fps = find_prefixset(new->name, psold)) == NULL) {
- new->sflags |= PREFIXSET_FLAG_DIRTY;
+ (old = rde_find_prefixset(new->name, psold)) == NULL) {
+ new->dirty = 1;
} else {
- SIMPLEQ_FOREACH(item, &new->psitems, entry) {
- if (find_prefixsetitem(item, &fps->psitems)
- == NULL) {
- new->sflags |= PREFIXSET_FLAG_DIRTY;
- break;
- }
- }
+ if (trie_equal(&new->th, &old->th) == 0)
+ new->dirty = 1;
}
}
}
Index: rde.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde.h,v
retrieving revision 1.187
diff -u -p -r1.187 rde.h
--- rde.h 7 Sep 2018 05:43:33 -0000 1.187
+++ rde.h 7 Sep 2018 05:52:09 -0000
@@ -323,6 +323,23 @@ struct filterstate {
u_int8_t nhflags;
};
+struct tentry_v4;
+struct tentry_v6;
+struct trie_head {
+ struct tentry_v4 *root_v4;
+ struct tentry_v6 *root_v6;
+ int match_default_v4;
+ int match_default_v6;
+};
+
+struct rde_prefixset {
+ char name[SET_NAME_LEN];
+ struct trie_head th;
+ SIMPLEQ_ENTRY(rde_prefixset) entry;
+ int dirty;
+};
+SIMPLEQ_HEAD(rde_prefixset_head, rde_prefixset);
+
extern struct rde_memstats rdemem;
/* prototypes */
@@ -562,5 +579,13 @@ u_char *up_dump_mp_unreach(u_char *, u_
u_int8_t);
int up_dump_mp_reach(u_char *, u_int16_t *, struct rde_peer *,
u_int8_t);
+
+/* rde_trie.c */
+int trie_add(struct trie_head *, struct bgpd_addr *, u_int8_t,
+ u_int8_t, u_int8_t);
+void trie_free(struct trie_head *);
+int trie_match(struct trie_head *, struct bgpd_addr *, u_int8_t);
+void trie_dump(struct trie_head *);
+int trie_equal(struct trie_head *, struct trie_head *);
#endif /* __RDE_H__ */
Index: rde_filter.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde_filter.c,v
retrieving revision 1.102
diff -u -p -r1.102 rde_filter.c
--- rde_filter.c 7 Sep 2018 05:43:33 -0000 1.102
+++ rde_filter.c 7 Sep 2018 05:51:53 -0000
@@ -342,7 +342,6 @@ rde_filter_match(struct filter_rule *f,
{
int cas, type;
int64_t las, ld1, ld2;
- struct prefixset_item *psi;
struct rde_aspath *asp = NULL;
if (state != NULL)
@@ -483,17 +482,15 @@ rde_filter_match(struct filter_rule *f,
* prefixset and prefix filter rules are mutual exclusive
*/
if (f->match.prefixset.flags != 0) {
- log_debug("%s: processing filter for prefixset %s",
- __func__, f->match.prefixset.name);
- SIMPLEQ_FOREACH(psi, &f->match.prefixset.ps->psitems, entry) {
- if (rde_prefix_match(&psi->p, p)) {
- log_debug("%s: prefixset %s matched %s",
- __func__, f->match.prefixset.ps->name,
- log_addr(&psi->p.addr));
- return (1);
- }
- }
- return (0);
+ struct bgpd_addr addr, *prefix = &addr;
+ u_int8_t plen;
+
+ pt_getaddr(p->re->prefix, prefix);
+ plen = p->re->prefix->prefixlen;
+
+ if (f->match.prefixset.ps == NULL ||
+ !trie_match(&f->match.prefixset.ps->th, prefix, plen))
+ return (0);
} else if (f->match.prefix.addr.aid != 0)
return (rde_prefix_match(&f->match.prefix, p));
@@ -574,7 +571,7 @@ rde_filter_equal(struct filter_head *a,
struct rde_peer *peer)
{
struct filter_rule *fa, *fb;
- struct prefixset *psa, *psb;
+ struct rde_prefixset *psa, *psb;
struct as_set *asa, *asb;
fa = a ? TAILQ_FIRST(a) : NULL;
@@ -615,10 +612,9 @@ rde_filter_equal(struct filter_head *a,
fa->match.as.aset = asa;
fb->match.as.aset = asb;
- if ((fa->match.prefixset.flags != 0) &&
- (fa->match.prefixset.ps != NULL) &&
- ((fa->match.prefixset.ps->sflags
- & PREFIXSET_FLAG_DIRTY) != 0)) {
+ if (fa->match.prefixset.flags != 0 &&
+ fa->match.prefixset.ps != NULL &&
+ fa->match.prefixset.ps->dirty) {
log_debug("%s: prefixset %s has changed",
__func__, fa->match.prefixset.name);
return (0);
Index: rde_trie.c
===================================================================
RCS file: rde_trie.c
diff -N rde_trie.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ rde_trie.c 7 Sep 2018 07:25:40 -0000
@@ -0,0 +1,588 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2018 Claudio Jeker <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <netinet/in.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "bgpd.h"
+#include "rde.h"
+
+/*
+ * Bitwise compressed trie for prefix-sets.
+ * Since the trie is path compressed the traversing function needs to check
+ * that the lookup is still on path before taking a branch.
+ * There are two types of nodes in the trie. Internal branch nodes and real
+ * nodes. Internal nodes are added when needed because off path nodes are being
+ * inserted and are just used for branching.
+ * During lookup every node defines which bit is checked for branching. This
+ * offset is strictly increasing. For IPv4 the maximum is therefor 32 levels.
+ * Every node checks the bit at position plen to decide which branch to take.
+ * The real nodes also include a prefixlen mask which represents the prefixlen
+ * range that was defined. The prefixlen mask for IPv4 only has 32 bits but
+ * the prefixlen range is from 0 - 32 which needs 33bits. Now prefixlen 0 is
+ * special and only one element -- the default route -- can have prefixlen 0.
+ * So this default route is added as a flag to the trie_head and needs to be
+ * checked independent of the trie when doing a lookup.
+ * On insertion a prefix is added with its prefixlen plen and a min & max
+ * for the prefixlen range. The following precondition needs to hold
+ * plen <= min <= max
+ * To only match the prefix itself min and max are set to plen.
+ * If a same prefix (addr/plen) is added to the trie but with different
+ * min & max values then the masks of both nodes are merged with a binary OR.
+ * The match function returns true the moment the first node is found which
+ * covers the looked up prefix and where the prefixlen mask matches for the
+ * looked up prefixlen. The moment the plen of a node is bigger is bigger than
+ * the prefixlen of the looked up prefix the search can be stopped since there
+ * will be no match.
+ */
+struct tentry_v4 {
+ struct tentry_v4 *trie[2];
+ struct in_addr addr;
+ struct in_addr plenmask;
+ u_int8_t plen;
+ u_int8_t node;
+
+ /* roa source-as list pointer */
+};
+
+struct tentry_v6 {
+ struct tentry_v6 *trie[2];
+ struct in6_addr addr;
+ struct in6_addr plenmask;
+ u_int8_t plen;
+ u_int8_t node;
+
+ /* roa source-as list pointer */
+};
+
+/*
+ * Find first different bit between a & b starting from the MSB,
+ * a & b have to be different.
+ */
+static int
+inet4findmsb(struct in_addr *a, struct in_addr *b)
+{
+ int r = 0;
+ u_int32_t v;
+
+ v = ntohl(a->s_addr ^ b->s_addr);
+ if (v > 0xffff) { r += 16; v >>= 16; }
+ if (v > 0x00ff) { r += 8; v >>= 8; }
+ if (v > 0x000f) { r += 4; v >>= 4; }
+ if (v > 0x0003) { r += 2; v >>= 2; }
+ if (v > 0x0001) { r += 1; }
+
+ return 31 - r;
+}
+
+/*
+ * Find first different bit between a & b starting from the MSB,
+ * a & b have to be different.
+ */
+static int
+inet6findmsb(struct in6_addr *a, struct in6_addr *b)
+{
+ int r = 0;
+ u_int8_t i, x;
+
+ for (i = 0; i < sizeof(*a) && a->s6_addr[i] == b->s6_addr[i]; i++)
+ ;
+ /* first different octet */
+ x = a->s6_addr[i] ^ b->s6_addr[i];
+
+ /* first different bit */
+ if (x > 0xf) { r += 4; x >>= 4; }
+ if (x > 0x3) { r += 2; x >>= 2; }
+ if (x > 0x1) { r += 1; }
+
+ return i * 8 + 7 - r;
+}
+
+static int
+inet4isset(struct in_addr *addr, u_int8_t bit)
+{
+ return addr->s_addr & htonl(1 << (31 - bit));
+}
+
+static int
+inet6isset(struct in6_addr *addr, u_int8_t bit)
+{
+ return addr->s6_addr[bit / 8] & (0x80 >> (bit % 8));
+}
+
+static void
+inet4setbit(struct in_addr *addr, u_int8_t bit)
+{
+ /* bit 0 sets the MSB and 31 sets the LSB */
+ addr->s_addr |= (1 << (31 - bit));
+}
+
+static void
+inet6setbit(struct in6_addr *addr, u_int8_t bit)
+{
+ addr->s6_addr[bit / 8] |= (0x80 >> (bit % 8));
+}
+
+static int
+trie_add_v4(struct trie_head *th, struct in_addr *prefix, u_int8_t plen,
+ u_int8_t min, u_int8_t max)
+{
+ struct tentry_v4 *n, *new, *b, **prev;
+ struct in_addr p, plenmask;
+ u_int8_t i;
+
+ /*
+ * check for default route, this is special cased since prefixlen 0
+ * can't be checked in the prefixlen mask plenmask. Also there is only
+ * one default route so using a flag for this works.
+ */
+ if (min == 0) {
+ th->match_default_v4 = 1;
+ if (max == 0) /* just the default route */
+ return 0;
+ min = 1;
+ }
+
+ inet4applymask(&p, prefix, plen);
+
+ /*
+ * The prefixlen mask goes from 1 to 32 but the bitmask
+ * starts at 0 and so all bits are set with an offset of 1.
+ * The default /0 route is handled specially above.
+ */
+ memset(&plenmask, 0, sizeof(plenmask));
+ for (i = min; i <= max; i++)
+ inet4setbit(&plenmask, i - 1);
+
+ /* walk tree finding spot to insert */
+ prev = &th->root_v4;
+ n = *prev;
+ while (n) {
+ struct in_addr mp;
+ u_int8_t minlen;
+
+ minlen = n->plen > plen ? plen : n->plen;
+ inet4applymask(&mp, &p, minlen);
+ if (n->addr.s_addr != mp.s_addr) {
+ /*
+ * out of path, insert intermediary node between
+ * np and n, then insert n and new node there
+ */
+ if ((b = calloc(1, sizeof(*b))) == NULL)
+ return -1;
+ b->plen = inet4findmsb(&n->addr, &mp);
+ inet4applymask(&b->addr, &n->addr, b->plen);
+
+ *prev = b;
+ if (inet4isset(&n->addr, b->plen)) {
+ b->trie[1] = n;
+ prev = &b->trie[0];
+ } else {
+ b->trie[0] = n;
+ prev = &b->trie[1];
+ }
+ n = NULL;
+ break;
+ }
+
+ if (n->plen > plen) {
+ /* n is more specific, just insert new in between */
+ break;
+ }
+
+ if (n->plen == plen) {
+ /* matching node, adjust */
+ n->node = 1;
+ n->plenmask.s_addr |= plenmask.s_addr;
+ return 0;
+ }
+
+ /* no need to check for n->plen == 32 because of above if */
+ if (inet4isset(&p, n->plen))
+ prev = &n->trie[1];
+ else
+ prev = &n->trie[0];
+ n = *prev;
+ }
+
+ /* create new node */
+ if ((new = calloc(1, sizeof(*new))) == NULL)
+ return -1;
+ new->addr = p;
+ new->plen = plen;
+ new->plenmask = plenmask;
+ new->node = 1;
+
+ /* link node */
+ *prev = new;
+ if (n) {
+ if (inet4isset(&n->addr, new->plen))
+ new->trie[1] = n;
+ else
+ new->trie[0] = n;
+ }
+ return 0;
+}
+
+static int
+trie_add_v6(struct trie_head *th, struct in6_addr *prefix, u_int8_t plen,
+ u_int8_t min, u_int8_t max)
+{
+ struct tentry_v6 *n, *new, *b, **prev;
+ struct in6_addr p, plenmask;
+ u_int8_t i;
+
+ /*
+ * check for default route, this is special cased since prefixlen 0
+ * can't be checked in the prefixlen mask plenmask. Also there is only
+ * one default route so using a flag for this works.
+ */
+ if (min == 0) {
+ th->match_default_v6 = 1;
+ if (max == 0) /* just the default route */
+ return 0;
+ min = 1;
+ }
+
+ inet6applymask(&p, prefix, plen);
+
+ /*
+ * The prefixlen mask goes from 1 to 128 but the bitmask
+ * starts at 0 and so all bits are set with an offset of 1.
+ * The default /0 route is handled specially above.
+ */
+ memset(&plenmask, 0, sizeof(plenmask));
+ for (i = min; i <= max; i++)
+ inet6setbit(&plenmask, i - 1);
+
+ /* walk tree finding spot to insert */
+ prev = &th->root_v6;
+ n = *prev;
+ while (n) {
+ struct in6_addr mp;
+ u_int8_t minlen;
+
+ minlen = n->plen > plen ? plen : n->plen;
+ inet6applymask(&mp, &p, minlen);
+ if (memcmp(&n->addr, &mp, sizeof(mp)) != 0) {
+ /*
+ * out of path, insert intermediary node between
+ * np and n, then insert n and new node there
+ */
+ if ((b = calloc(1, sizeof(*b))) == NULL)
+ return -1;
+ b->plen = inet6findmsb(&n->addr, &mp);
+ inet6applymask(&b->addr, &n->addr, b->plen);
+
+ *prev = b;
+ if (inet6isset(&n->addr, b->plen)) {
+ b->trie[1] = n;
+ prev = &b->trie[0];
+ } else {
+ b->trie[0] = n;
+ prev = &b->trie[1];
+ }
+ n = NULL;
+ break;
+ }
+
+ if (n->plen > plen) {
+ /* n is more specific, just insert new in between */
+ break;
+ }
+
+ if (n->plen == plen) {
+ /* matching node, adjust */
+ n->node = 1;
+ for (i = 0; i < sizeof(plenmask); i++)
+ n->plenmask.s6_addr[i] |= plenmask.s6_addr[i];
+ return 0;
+ }
+
+ /* no need to check for n->plen == 32 because of above if */
+ if (inet6isset(&p, n->plen))
+ prev = &n->trie[1];
+ else
+ prev = &n->trie[0];
+ n = *prev;
+ }
+
+ /* create new node */
+ if ((new = calloc(1, sizeof(*new))) == NULL)
+ return -1;
+ new->addr = p;
+ new->plen = plen;
+ new->plenmask = plenmask;
+ new->node = 1;
+
+ /* link node */
+ *prev = new;
+ if (n) {
+ if (inet6isset(&n->addr, new->plen))
+ new->trie[1] = n;
+ else
+ new->trie[0] = n;
+ }
+ return 0;
+}
+
+int
+trie_add(struct trie_head *th, struct bgpd_addr *prefix, u_int8_t plen,
+ u_int8_t min, u_int8_t max)
+{
+ /* precondition plen <= min <= max */
+ if (plen > min || min > max)
+ return -1;
+
+ switch (prefix->aid) {
+ case AID_INET:
+ if (max > 32)
+ return -1;
+ return trie_add_v4(th, &prefix->v4, plen, min, max);
+ case AID_INET6:
+ if (max > 128)
+ return -1;
+ return trie_add_v6(th, &prefix->v6, plen, min, max);
+ default:
+ /* anything else fails */
+ return -1;
+ }
+}
+
+static void
+trie_free_v4(struct tentry_v4 *n)
+{
+ if (n == NULL)
+ return;
+ trie_free_v4(n->trie[0]);
+ trie_free_v4(n->trie[1]);
+ free(n);
+}
+
+static void
+trie_free_v6(struct tentry_v6 *n)
+{
+ if (n == NULL)
+ return;
+ trie_free_v6(n->trie[0]);
+ trie_free_v6(n->trie[1]);
+ free(n);
+}
+
+void
+trie_free(struct trie_head *th)
+{
+ trie_free_v4(th->root_v4);
+ trie_free_v6(th->root_v6);
+}
+
+static int
+trie_match_v4(struct trie_head *th, struct in_addr *prefix, u_int8_t plen)
+{
+ struct tentry_v4 *n;
+
+ if (plen == 0) {
+ /* special handling for default route */
+ return th->match_default_v4;
+ }
+
+ n = th->root_v4;
+ while (n) {
+ struct in_addr mp;
+
+ if (n->plen > plen)
+ break; /* too specific, no match possible */
+
+ inet4applymask(&mp, prefix, n->plen);
+ if (n->addr.s_addr != mp.s_addr)
+ break; /* off path, no match possible */
+
+ /* plen is from 1 - 32 but the bitmask starts with 0 */
+ if (n->node && inet4isset(&n->plenmask, plen - 1))
+ return 1; /* prefixlen allowed, match */
+
+ if (n->plen == 32)
+ break; /* can't go deeper */
+ if (inet4isset(prefix, n->plen))
+ n = n->trie[1];
+ else
+ n = n->trie[0];
+ }
+
+ return 0;
+}
+
+static int
+trie_match_v6(struct trie_head *th, struct in6_addr *prefix, u_int8_t plen)
+{
+ struct tentry_v6 *n;
+
+ if (plen == 0) {
+ /* special handling for default route */
+ return th->match_default_v6;
+ }
+
+ n = th->root_v6;
+ while (n) {
+ struct in6_addr mp;
+
+ if (n->plen > plen)
+ break; /* too specific, no match possible */
+
+ inet6applymask(&mp, prefix, n->plen);
+ if (memcmp(&n->addr, &mp, sizeof(mp)) != 0)
+ break; /* off path, no match possible */
+
+ /* plen is from 1 - 128 but the bitmask starts with 0 */
+ if (n->node && inet6isset(&n->plenmask, plen - 1))
+ return 1; /* prefixlen allowed, match */
+
+ if (n->plen == 128)
+ break; /* can't go deeper */
+ if (inet6isset(prefix, n->plen))
+ n = n->trie[1];
+ else
+ n = n->trie[0];
+ }
+ return 0;
+}
+
+/* find first matching element in the trie for prefix "prefix/plen" */
+int
+trie_match(struct trie_head *th, struct bgpd_addr *prefix, u_int8_t plen)
+{
+ switch (prefix->aid) {
+ case AID_INET:
+ return trie_match_v4(th, &prefix->v4, plen);
+ case AID_INET6:
+ return trie_match_v6(th, &prefix->v6, plen);
+ default:
+ /* anything else is no match */
+ return 0;
+ }
+}
+
+static int
+trie_equal_v4(struct tentry_v4 *a, struct tentry_v4 *b)
+{
+ if (a == NULL && b == NULL)
+ return 1;
+ if (a == NULL || b == NULL)
+ return 0;
+
+ if (a->addr.s_addr != b->addr.s_addr ||
+ a->plen != b->plen ||
+ a->node != b->node ||
+ a->plenmask.s_addr != b->plenmask.s_addr)
+ return 0;
+
+ if (trie_equal_v4(a->trie[0], b->trie[0]) == 0 ||
+ trie_equal_v4(a->trie[1], b->trie[1]) == 0)
+ return 0;
+
+ return 1;
+}
+
+static int
+trie_equal_v6(struct tentry_v6 *a, struct tentry_v6 *b)
+{
+ if (a == NULL && b == NULL)
+ return 1;
+ if (a == NULL || b == NULL)
+ return 0;
+
+ if (memcmp(&a->addr, &b->addr, sizeof(a->addr)) != 0 ||
+ a->plen != b->plen ||
+ a->node != b->node ||
+ memcmp(&a->plenmask, &b->plenmask, sizeof(a->plenmask)) != 0)
+ return 0;
+
+ if (trie_equal_v6(a->trie[0], b->trie[0]) == 0 ||
+ trie_equal_v6(a->trie[1], b->trie[1]) == 0)
+ return 0;
+
+ return 1;
+}
+
+/* Compare two tires and return 1 if they are the same else 0. */
+int
+trie_equal(struct trie_head *a, struct trie_head *b)
+{
+ if (a->match_default_v4 != b->match_default_v4 ||
+ a->match_default_v6 != b->match_default_v6)
+ return 0;
+ if (trie_equal_v4(a->root_v4, b->root_v4) == 0)
+ return 0;
+ if (trie_equal_v6(a->root_v6, b->root_v6) == 0)
+ return 0;
+ return 1;
+}
+
+/* debugging functions for printing the trie */
+static void
+trie_dump_v4(struct tentry_v4 *n)
+{
+ if (n == NULL)
+ return;
+ if (n->node)
+ printf("%s/%u plenmask %08x\n", inet_ntoa(n->addr), n->plen,
+ n->plenmask.s_addr);
+ else
+ printf(" %s/%u\n", inet_ntoa(n->addr), n->plen);
+
+ trie_dump_v4(n->trie[0]);
+ trie_dump_v4(n->trie[1]);
+}
+
+static void
+trie_dump_v6(struct tentry_v6 *n)
+{
+ char buf[48];
+ unsigned int i;
+
+ if (n == NULL)
+ return;
+ if (n->node) {
+ printf("%s/%u plenmask ",
+ inet_ntop(AF_INET6, &n->addr, buf, sizeof(buf)), n->plen);
+ for (i = 0; i < sizeof(n->plenmask); i++)
+ printf("%02x", n->plenmask.s6_addr[i]);
+ printf("\n");
+ } else
+ printf(" %s/%u\n",
+ inet_ntop(AF_INET6, &n->addr, buf, sizeof(buf)), n->plen);
+
+ trie_dump_v6(n->trie[0]);
+ trie_dump_v6(n->trie[1]);
+}
+
+void
+trie_dump(struct trie_head *th)
+{
+ if (th->match_default_v4)
+ printf("0.0.0.0/0 plenmask %08x\n", 0);
+ trie_dump_v4(th->root_v4);
+ if (th->match_default_v6)
+ printf("::/0 plenmask 0\n");
+ trie_dump_v6(th->root_v6);
+}