#include "nest/bird.h"
#include "nest/route.h"
#include "nest/protocol.h"
-#include "nest/cli.h"
#include "nest/iface.h"
#include "lib/resource.h"
#include "lib/event.h"
#include "lib/string.h"
#include "conf/conf.h"
#include "filter/filter.h"
+#include "lib/hash.h"
#include "lib/string.h"
#include "lib/alloca.h"
static slab *rte_slab;
static linpool *rte_update_pool;
-static list routing_tables;
+list routing_tables;
static void rt_free_hostcache(rtable *tab);
static void rt_notify_hostcache(rtable *tab, net *net);
static inline void rt_prune_table(rtable *tab);
-static inline struct ea_list *
-make_tmp_attrs(struct rte *rt, struct linpool *pool)
-{
- struct ea_list *(*mta)(struct rte *rt, struct linpool *pool);
- mta = rt->attrs->src->proto->make_tmp_attrs;
- return mta ? mta(rt, pool) : NULL;
-}
-
-
/* Like fib_route(), but skips empty net entries */
static inline void *
net_route_ip4(rtable *t, net_addr_ip4 *n)
return r;
}
+static inline void *
+net_route_ip6_sadr(rtable *t, net_addr_ip6_sadr *n)
+{
+ struct fib_node *fn;
+
+ while (1)
+ {
+ net *best = NULL;
+ int best_pxlen = 0;
+
+ /* We need to do dst first matching. Since sadr addresses are hashed on dst
+ prefix only, find the hash table chain and go through it to find the
+ match with the smallest matching src prefix. */
+ for (fn = fib_get_chain(&t->fib, (net_addr *) n); fn; fn = fn->next)
+ {
+ net_addr_ip6_sadr *a = (void *) fn->addr;
+
+ if (net_equal_dst_ip6_sadr(n, a) &&
+ net_in_net_src_ip6_sadr(n, a) &&
+ (a->src_pxlen >= best_pxlen))
+ {
+ best = fib_node_to_user(&t->fib, fn);
+ best_pxlen = a->src_pxlen;
+ }
+ }
+
+ if (best)
+ return best;
+
+ if (!n->dst_pxlen)
+ break;
+
+ n->dst_pxlen--;
+ ip6_clrbit(&n->dst_prefix, n->dst_pxlen);
+ }
+
+ return NULL;
+}
+
void *
net_route(rtable *tab, const net_addr *n)
{
case NET_ROA6:
return net_route_ip6(tab, (net_addr_ip6 *) n0);
+ case NET_IP6_SADR:
+ return net_route_ip6_sadr(tab, (net_addr_ip6_sadr *) n0);
+
default:
return NULL;
}
if (!rta_is_cached(r->attrs))
return r;
- rte *e = rte_cow(r);
+ r = rte_cow(r);
rta *a = rta_do_cow(r->attrs, lp);
- rta_free(e->attrs);
- e->attrs = a;
- return e;
+ rta_free(r->attrs);
+ r->attrs = a;
+ return r;
}
+
+/* Note that rte_make_tmp_attr() requires free eattr in ea_list */
+void
+rte_make_tmp_attr(rte *r, ea_list *e, uint id, uint type, u32 val)
+{
+ if (r->pflags & EA_ID_FLAG(id))
+ {
+ eattr *a = &e->attrs[e->count++];
+ a->id = id;
+ a->type = type | EAF_TEMP;
+ a->flags = 0;
+ a->u.data = val;
+ }
+}
+
+/* Note that rte has to be writable */
+uint
+rte_store_tmp_attr(rte *r, uint id)
+{
+ eattr *a;
+ if (a = ea_find(r->attrs->eattrs, id))
+ {
+ r->pflags |= EA_ID_FLAG(id);
+ return a->u.data;
+ }
+ else
+ {
+ r->pflags &= ~EA_ID_FLAG(id);
+ return 0;
+ }
+}
+
+
static int /* Actually better or at least as good as */
rte_better(rte *new, rte *old)
{
}
static rte *
-export_filter_(struct channel *c, rte *rt0, rte **rt_free, ea_list **tmpa, linpool *pool, int silent)
+export_filter_(struct channel *c, rte *rt0, rte **rt_free, linpool *pool, int silent)
{
struct proto *p = c->proto;
struct filter *filter = c->out_filter;
struct proto_stats *stats = &c->stats;
- ea_list *tmpb = NULL;
rte *rt;
int v;
rt = rt0;
*rt_free = NULL;
- if (!tmpa)
- tmpa = &tmpb;
-
- *tmpa = make_tmp_attrs(rt, pool);
-
- v = p->import_control ? p->import_control(p, &rt, tmpa, pool) : 0;
+ v = p->preexport ? p->preexport(p, &rt, pool) : 0;
if (v < 0)
{
if (silent)
goto accept;
}
+ rte_make_tmp_attrs(&rt, pool);
+
v = filter && ((filter == FILTER_REJECT) ||
- (f_run(filter, &rt, tmpa, pool, FF_FORCE_TMPATTR) > F_ACCEPT));
+ (f_run(filter, &rt, pool,
+ (silent ? FF_SILENT : 0)) > F_ACCEPT));
if (v)
{
if (silent)
}
static inline rte *
-export_filter(struct channel *c, rte *rt0, rte **rt_free, ea_list **tmpa, int silent)
+export_filter(struct channel *c, rte *rt0, rte **rt_free, int silent)
{
- return export_filter_(c, rt0, rt_free, tmpa, rte_update_pool, silent);
+ return export_filter_(c, rt0, rt_free, rte_update_pool, silent);
}
static void
-do_rt_notify(struct channel *c, net *net, rte *new, rte *old, ea_list *tmpa, int refeed)
+do_rt_notify(struct channel *c, net *net, rte *new, rte *old, int refeed)
{
struct proto *p = c->proto;
struct proto_stats *stats = &c->stats;
else if (old)
rte_trace_out(D_ROUTES, p, old, "removed");
}
- if (!new)
- p->rt_notify(p, c, net, NULL, old, NULL);
- else if (tmpa)
- {
- ea_list *t = tmpa;
- while (t->next)
- t = t->next;
- t->next = new->attrs->eattrs;
- p->rt_notify(p, c, net, new, old, tmpa);
- t->next = NULL;
- }
- else
- p->rt_notify(p, c, net, new, old, new->attrs->eattrs);
+ p->rt_notify(p, c, net, new, old);
}
static void
rte *old = old0;
rte *new_free = NULL;
rte *old_free = NULL;
- ea_list *tmpa = NULL;
if (new)
c->stats.exp_updates_received++;
c->stats.exp_withdraws_received++;
/*
- * This is a tricky part - we don't know whether route 'old' was
- * exported to protocol 'p' or was filtered by the export filter.
- * We try to run the export filter to know this to have a correct
- * value in 'old' argument of rte_update (and proper filter value)
+ * This is a tricky part - we don't know whether route 'old' was exported to
+ * protocol 'p' or was filtered by the export filter. We try to run the export
+ * filter to know this to have a correct value in 'old' argument of rte_update
+ * (and proper filter value).
*
- * FIXME - this is broken because 'configure soft' may change
- * filters but keep routes. Refeed is expected to be called after
- * change of the filters and with old == new, therefore we do not
- * even try to run the filter on an old route, This may lead to
- * 'spurious withdraws' but ensure that there are no 'missing
+ * This is broken because 'configure soft' may change filters but keep routes.
+ * Refeed cycle is expected to be called after change of the filters and with
+ * old == new, therefore we do not even try to run the filter on an old route.
+ * This may lead to 'spurious withdraws' but ensure that there are no 'missing
* withdraws'.
*
- * This is not completely safe as there is a window between
- * reconfiguration and the end of refeed - if a newly filtered
- * route disappears during this period, proper withdraw is not
- * sent (because old would be also filtered) and the route is
- * not refeeded (because it disappeared before that).
+ * This is not completely safe as there is a window between reconfiguration
+ * and the end of refeed - if a newly filtered route disappears during this
+ * period, proper withdraw is not sent (because old would be also filtered)
+ * and the route is not refeeded (because it disappeared before that).
+ * This is handled below as a special case.
*/
if (new)
- new = export_filter(c, new, &new_free, &tmpa, 0);
+ new = export_filter(c, new, &new_free, 0);
if (old && !refeed)
- old = export_filter(c, old, &old_free, NULL, 1);
+ old = export_filter(c, old, &old_free, 1);
if (!new && !old)
{
/*
* As mentioned above, 'old' value may be incorrect in some race conditions.
- * We generally ignore it with the exception of withdraw to pipe protocol.
- * In that case we rather propagate unfiltered withdraws regardless of
- * export filters to ensure that when a protocol is flushed, its routes are
- * removed from all tables. Possible spurious unfiltered withdraws are not
- * problem here as they are ignored if there is no corresponding route at
- * the other end of the pipe. We directly call rt_notify() hook instead of
+ * We generally ignore it with two exceptions:
+ *
+ * First, withdraw to pipe protocol. In that case we rather propagate
+ * unfiltered withdraws regardless of export filters to ensure that when a
+ * protocol is flushed, its routes are removed from all tables. Possible
+ * spurious unfiltered withdraws are not problem here as they are ignored if
+ * there is no corresponding route at the other end of the pipe.
+ *
+ * Second, recent filter change. If old route is older than filter change,
+ * then it was previously evaluated by a different filter and we do not know
+ * whether it was really propagated. In that case we rather send spurious
+ * withdraw than do nothing and possibly cause phantom routes.
+ *
+ * In both cases wqe directly call rt_notify() hook instead of
* do_rt_notify() to avoid logging and stat counters.
*/
+ int pipe_withdraw = 0, filter_change = 0;
#ifdef CONFIG_PIPE
- if ((p->proto == &proto_pipe) && !new0 && (p != old0->sender->proto))
- p->rt_notify(p, c, net, NULL, old0, NULL);
+ pipe_withdraw = (p->proto == &proto_pipe) && !new0;
#endif
+ filter_change = old0 && (old0->lastmod <= c->last_tx_filter_change);
+
+ if ((pipe_withdraw || filter_change) && (p != old0->sender->proto))
+ {
+ c->stats.exp_withdraws_accepted++;
+ p->rt_notify(p, c, net, NULL, old0);
+ }
return;
}
- do_rt_notify(c, net, new, old, tmpa, refeed);
+ do_rt_notify(c, net, new, old, refeed);
/* Discard temporary rte's */
if (new_free)
static void
rt_notify_accepted(struct channel *c, net *net, rte *new_changed, rte *old_changed, rte *before_old, int feed)
{
- // struct proto *p = c->proto;
+ struct proto *p = c->proto;
rte *r;
rte *new_best = NULL;
rte *old_best = NULL;
rte *new_free = NULL;
rte *old_free = NULL;
- ea_list *tmpa = NULL;
/* Used to track whether we met old_changed position. If before_old is NULL
old_changed was the first and we met it implicitly before current best route. */
/* First, find the new_best route - first accepted by filters */
for (r=net->routes; rte_is_valid(r); r=r->next)
{
- if (new_best = export_filter(c, r, &new_free, &tmpa, 0))
+ if (new_best = export_filter(c, r, &new_free, 0))
break;
/* Note if we walked around the position of old_changed route */
old_meet = 1;
}
- /*
+ /*
* Second, handle the feed case. That means we do not care for
- * old_best. It is NULL for feed, and the new_best for refeed.
+ * old_best. It is NULL for feed, and the new_best for refeed.
* For refeed, there is a hack similar to one in rt_notify_basic()
* to ensure withdraws in case of changed filters
*/
*
* - We found new_best the same as new_changed, therefore it cannot
* be old_best and we have to continue search for old_best.
+ *
+ * There is also a hack to ensure consistency in case of changed filters.
+ * It does not find the proper old_best, just selects a non-NULL route.
*/
+ /* Hack for changed filters */
+ if (old_changed &&
+ (p != old_changed->sender->proto) &&
+ (old_changed->lastmod <= c->last_tx_filter_change))
+ {
+ old_best = old_changed;
+ goto found;
+ }
+
/* First case */
if (old_meet)
- if (old_best = export_filter(c, old_changed, &old_free, NULL, 1))
+ if (old_best = export_filter(c, old_changed, &old_free, 1))
goto found;
/* Second case */
/* Fourth case */
for (r=r->next; rte_is_valid(r); r=r->next)
{
- if (old_best = export_filter(c, r, &old_free, NULL, 1))
+ if (old_best = export_filter(c, r, &old_free, 1))
goto found;
if (r == before_old)
- if (old_best = export_filter(c, old_changed, &old_free, NULL, 1))
+ if (old_best = export_filter(c, old_changed, &old_free, 1))
goto found;
}
/* Implicitly, old_best is NULL and new_best is non-NULL */
found:
- do_rt_notify(c, net, new_best, old_best, tmpa, (feed == 2));
+ do_rt_notify(c, net, new_best, old_best, (feed == 2));
/* Discard temporary rte's */
if (new_free)
}
rte *
-rt_export_merged(struct channel *c, net *net, rte **rt_free, ea_list **tmpa, linpool *pool, int silent)
+rt_export_merged(struct channel *c, net *net, rte **rt_free, linpool *pool, int silent)
{
// struct proto *p = c->proto;
struct nexthop *nhs = NULL;
if (!rte_is_valid(best0))
return NULL;
- best = export_filter_(c, best0, rt_free, tmpa, pool, silent);
+ best = export_filter_(c, best0, rt_free, pool, silent);
if (!best || !rte_is_reachable(best))
return best;
if (!rte_mergable(best0, rt0))
continue;
- rt = export_filter_(c, rt0, &tmp, NULL, pool, 1);
+ rt = export_filter_(c, rt0, &tmp, pool, 1);
if (!rt)
continue;
rte *old_best_free = NULL;
rte *new_changed_free = NULL;
rte *old_changed_free = NULL;
- ea_list *tmpa = NULL;
/* We assume that all rte arguments are either NULL or rte_is_valid() */
if ((new_best == old_best) && !refeed)
{
new_changed = rte_mergable(new_best, new_changed) ?
- export_filter(c, new_changed, &new_changed_free, NULL, 1) : NULL;
+ export_filter(c, new_changed, &new_changed_free, 1) : NULL;
old_changed = rte_mergable(old_best, old_changed) ?
- export_filter(c, old_changed, &old_changed_free, NULL, 1) : NULL;
+ export_filter(c, old_changed, &old_changed_free, 1) : NULL;
if (!new_changed && !old_changed)
return;
/* Prepare new merged route */
if (new_best)
- new_best = rt_export_merged(c, net, &new_best_free, &tmpa, rte_update_pool, 0);
+ new_best = rt_export_merged(c, net, &new_best_free, rte_update_pool, 0);
/* Prepare old merged route (without proper merged next hops) */
/* There are some issues with running filter on old route - see rt_notify_basic() */
if (old_best && !refeed)
- old_best = export_filter(c, old_best, &old_best_free, NULL, 1);
+ old_best = export_filter(c, old_best, &old_best_free, 1);
if (new_best || old_best)
- do_rt_notify(c, net, new_best, old_best, tmpa, refeed);
+ do_rt_notify(c, net, new_best, old_best, refeed);
/* Discard temporary rte's */
if (new_best_free)
* @new_best: the new best route for the same network
* @old_best: the previous best route for the same network
* @before_old: The previous route before @old for the same network.
- * If @before_old is NULL @old was the first.
+ * If @before_old is NULL @old was the first.
*
* This function gets a routing table update and announces it
* to all protocols that acccepts given type of route announcement
* routing table @tab) changes In that case @old stores the old route
* from the same protocol.
*
- * For each appropriate protocol, we first call its import_control()
+ * For each appropriate protocol, we first call its preexport()
* hook which performs basic checks on the route (each protocol has a
* right to veto or force accept of the route before any filter is
* asked) and adds default values of attributes specific to the new
if (!old && !new)
return;
- if ((type == RA_OPTIMAL) && tab->hostcache)
- rt_notify_hostcache(tab, net);
+ if (type == RA_OPTIMAL)
+ {
+ if (new)
+ new->sender->stats.pref_routes++;
+ if (old)
+ old->sender->stats.pref_routes--;
+
+ if (tab->hostcache)
+ rt_notify_hostcache(tab, net);
+ }
struct channel *c; node *n;
WALK_LIST2(c, n, tab->channels, table_node)
int c;
net *n = e->net;
- // (n->n.pxlen > BITS_PER_IP_ADDRESS) || !ip_is_prefix(n->n.prefix,n->n.pxlen))
if (!net_validate(n->n.addr))
{
log(L_WARN "Ignoring bogus prefix %N received via %s",
return 0;
}
- c = net_classify(n->n.addr);
+ /* FIXME: better handling different nettypes */
+ c = !net_is_flow(n->n.addr) ?
+ net_classify(n->n.addr): (IADDR_HOST | SCOPE_UNIVERSE);
if ((c < 0) || !(c & IADDR_HOST) || ((c & IADDR_SCOPE_MASK) <= SCOPE_LINK))
{
log(L_WARN "Ignoring bogus route %N received via %s",
return 0;
}
+ if (net_type_match(n->n.addr, NB_DEST) == !e->attrs->dest)
+ {
+ log(L_WARN "Ignoring route %N with invalid dest %d received via %s",
+ n->n.addr, e->attrs->dest, e->sender->proto->name);
+ return 0;
+ }
+
if ((e->attrs->dest == RTD_UNICAST) && !nexthop_is_sorted(&(e->attrs->nh)))
- {
- log(L_WARN "Ignoring unsorted multipath route %N received via %s",
- n->n.addr, e->sender->proto->name);
- return 0;
- }
+ {
+ log(L_WARN "Ignoring unsorted multipath route %N received via %s",
+ n->n.addr, e->sender->proto->name);
+ return 0;
+ }
return 1;
}
static int
rte_same(rte *x, rte *y)
{
+ /* rte.flags are not checked, as they are mostly internal to rtable */
return
x->attrs == y->attrs &&
- x->flags == y->flags &&
x->pflags == y->pflags &&
x->pref == y->pref &&
- (!x->attrs->src->proto->rte_same || x->attrs->src->proto->rte_same(x, y));
+ (!x->attrs->src->proto->rte_same || x->attrs->src->proto->rte_same(x, y)) &&
+ rte_is_filtered(x) == rte_is_filtered(y);
}
static inline int rte_is_ok(rte *e) { return e && !rte_is_filtered(e); }
if (new && rte_same(old, new))
{
- /* No changes, ignore the new route */
+ /* No changes, ignore the new route and refresh the old one */
+
+ old->flags &= ~(REF_STALE | REF_DISCARD | REF_MODIFY);
if (!rte_is_filtered(new))
{
return;
}
*k = old->next;
+ table->rt_count--;
break;
}
k = &old->next;
int old_ok = rte_is_ok(old);
struct channel_limit *l = &c->rx_limit;
- if (l->action && !old && new)
+ if (l->action && !old && new && !c->in_table)
{
u32 all_routes = stats->imp_routes + stats->filt_routes;
new->next = *k;
*k = new;
+ table->rt_count++;
}
}
else
new->next = net->routes;
net->routes = new;
+ table->rt_count++;
}
else if (old == old_best)
{
{
new->next = net->routes;
net->routes = new;
+ table->rt_count++;
}
/* Find a new optimal route (if there is any) */
ASSERT(net->routes != NULL);
new->next = net->routes->next;
net->routes->next = new;
+ table->rt_count++;
}
/* The fourth (empty) case - suboptimal route was removed, nothing to do */
}
if (new)
- new->lastmod = now;
+ new->lastmod = current_time();
/* Log the route change */
if (p->debug & D_ROUTES)
if (!net->routes &&
(table->gc_counter++ >= table->config->gc_max_ops) &&
- (table->gc_time + table->config->gc_min_time <= now))
+ (table->gc_time + table->config->gc_min_time <= current_time()))
rt_schedule_prune(table);
if (old_ok && p->rte_remove)
struct proto *p = c->proto;
struct proto_stats *stats = &c->stats;
struct filter *filter = c->in_filter;
- ea_list *tmpa = NULL;
rte *dummy = NULL;
net *nn;
rte_update_lock();
if (new)
{
- nn = net_get(c->table, n);
+ /* Create a temporary table node */
+ nn = alloca(sizeof(net) + n->length);
+ memset(nn, 0, sizeof(net) + n->length);
+ net_copy(nn->n.addr, n);
new->net = nn;
new->sender = c;
}
else
{
- tmpa = make_tmp_attrs(new, rte_update_pool);
+ rte_make_tmp_attrs(&new, rte_update_pool);
if (filter && (filter != FILTER_REJECT))
{
- ea_list *old_tmpa = tmpa;
- int fr = f_run(filter, &new, &tmpa, rte_update_pool, 0);
+ ea_list *oldea = new->attrs->eattrs;
+ int fr = f_run(filter, &new, rte_update_pool, 0);
if (fr > F_ACCEPT)
{
stats->imp_updates_filtered++;
new->flags |= REF_FILTERED;
}
- if (tmpa != old_tmpa && src->proto->store_tmp_attrs)
- src->proto->store_tmp_attrs(new, tmpa);
+ if (new->attrs->eattrs != oldea && src->proto->store_tmp_attrs)
+ src->proto->store_tmp_attrs(new);
}
}
if (!rta_is_cached(new->attrs)) /* Need to copy attributes */
new->attrs = rta_lookup(new->attrs);
new->flags |= REF_COW;
+
+ /* Use the actual struct network, not the dummy one */
+ nn = net_get(c->table, n);
+ new->net = nn;
}
else
{
}
recalc:
+ /* And recalculate the best route */
rte_hide_dummy_routes(nn, &dummy);
rte_recalculate(c, nn, new, src);
rte_unhide_dummy_routes(nn, &dummy);
+
rte_update_unlock();
return;
drop:
rte_free(new);
new = NULL;
- goto recalc;
+ if (nn = net_find(c->table, n))
+ goto recalc;
+
+ rte_update_unlock();
}
/* Independent call to rte_announce(), used from next hop
recalculation, outside of rte_update(). new must be non-NULL */
-static inline void
+static inline void
rte_announce_i(rtable *tab, unsigned type, net *net, rte *new, rte *old,
rte *new_best, rte *old_best)
{
rte_update_unlock();
}
+/* Modify existing route by protocol hook, used for long-lived graceful restart */
+static inline void
+rte_modify(rte *old)
+{
+ rte_update_lock();
+
+ rte *new = old->sender->proto->rte_modify(old, rte_update_pool);
+ if (new != old)
+ {
+ if (new)
+ {
+ if (!rta_is_cached(new->attrs))
+ new->attrs = rta_lookup(new->attrs);
+ new->flags = (old->flags & ~REF_MODIFY) | REF_COW;
+ }
+
+ rte_recalculate(old->sender, old->net, new, old->attrs->src);
+ }
+
+ rte_update_unlock();
+}
+
/* Check rtable for best route to given net whether it would be exported do p */
int
rt_examine(rtable *t, net_addr *a, struct proto *p, struct filter *filter)
rte_update_lock();
/* Rest is stripped down export_filter() */
- ea_list *tmpa = make_tmp_attrs(rt, rte_update_pool);
- int v = p->import_control ? p->import_control(p, &rt, &tmpa, rte_update_pool) : 0;
+ int v = p->preexport ? p->preexport(p, &rt, rte_update_pool) : 0;
if (v == RIC_PROCESS)
- v = (f_run(filter, &rt, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) <= F_ACCEPT);
+ {
+ rte_make_tmp_attrs(&rt, rte_update_pool);
+ v = (f_run(filter, &rt, rte_update_pool, FF_SILENT) <= F_ACCEPT);
+ }
- /* Discard temporary rte */
+ /* Discard temporary rte */
if (rt != n->routes)
rte_free(rt);
rt_schedule_prune(t);
}
+void
+rt_modify_stale(rtable *t, struct channel *c)
+{
+ int prune = 0;
+
+ FIB_WALK(&t->fib, net, n)
+ {
+ rte *e;
+ for (e = n->routes; e; e = e->next)
+ if ((e->sender == c) && (e->flags & REF_STALE) && !(e->flags & REF_FILTERED))
+ {
+ e->flags |= REF_MODIFY;
+ prune = 1;
+ }
+ }
+ FIB_WALK_END;
+
+ if (prune)
+ rt_schedule_prune(t);
+}
/**
* rte_dump - dump a route
{
net *n = e->net;
debug("%-1N ", n->n.addr);
- debug("KF=%02x PF=%02x pref=%d lm=%d ", n->n.flags, e->pflags, e->pref, now-e->lastmod);
+ debug("KF=%02x PF=%02x pref=%d ", n->n.flags, e->pflags, e->pref);
rta_dump(e->attrs);
if (e->attrs->src->proto->proto->dump_attrs)
e->attrs->src->proto->proto->dump_attrs(e);
}
void
-rt_setup(pool *p, rtable *t, char *name, struct rtable_config *cf)
+rt_setup(pool *p, rtable *t, struct rtable_config *cf)
{
bzero(t, sizeof(*t));
- t->name = name;
+ t->name = cf->name;
t->config = cf;
- t->addr_type = cf ? cf->addr_type : NET_IP4;
+ t->addr_type = cf->addr_type;
fib_init(&t->fib, p, t->addr_type, sizeof(net), OFFSETOF(net, n), 0, NULL);
init_list(&t->channels);
- if (cf)
- {
- t->rt_event = ev_new(p);
- t->rt_event->hook = rt_event;
- t->rt_event->data = t;
- t->gc_time = now;
- }
+ t->rt_event = ev_new_init(p, rt_event, t);
+ t->gc_time = current_time();
}
/**
{
rta_init();
rt_table_pool = rp_new(&root_pool, "Routing tables");
- rte_update_pool = lp_new(rt_table_pool, 4080);
+ rte_update_pool = lp_new_default(rt_table_pool);
rte_slab = sl_new(rt_table_pool, sizeof(rte));
init_list(&routing_tables);
}
rescan:
for (e=n->routes; e; e=e->next)
+ {
if (e->sender->flush_active || (e->flags & REF_DISCARD))
{
if (limit <= 0)
goto rescan;
}
+ if (e->flags & REF_MODIFY)
+ {
+ if (limit <= 0)
+ {
+ FIB_ITERATE_PUT(fit);
+ ev_schedule(tab->rt_event);
+ return;
+ }
+
+ rte_modify(e);
+ limit--;
+
+ goto rescan;
+ }
+ }
+
if (!n->routes) /* Orphaned FIB entry */
{
FIB_ITERATE_PUT(fit);
#endif
tab->gc_counter = 0;
- tab->gc_time = now;
+ tab->gc_time = current_time();
/* state change 2->0, 3->1 */
tab->prune_state &= 1;
}
}
if (ipa_nonzero(nh->gw))
- nhp->gw = nh->gw; /* Router nexthop */
+ {
+ nhp->gw = nh->gw; /* Router nexthop */
+ nhp->flags |= (nh->flags & RNF_ONLINK);
+ }
else if (ipa_nonzero(he->link))
nhp->gw = he->link; /* Device nexthop with link-local address known */
else
}
}
+static struct rtable_config *
+rt_find_table_config(struct config *cf, char *name)
+{
+ struct symbol *sym = cf_find_symbol(cf, name);
+ return (sym && (sym->class == SYM_TABLE)) ? sym->def : NULL;
+}
+
/**
* rt_commit - commit new routing table configuration
* @new: new configuration
rtable *ot = o->table;
if (!ot->deleted)
{
- struct symbol *sym = cf_find_symbol(new, o->name);
- if (sym && sym->class == SYM_TABLE && !new->shutdown)
+ r = rt_find_table_config(new, o->name);
+ if (r && (r->addr_type == o->addr_type) && !new->shutdown)
{
DBG("\t%s: same\n", o->name);
- r = sym->def;
r->table = ot;
ot->name = r->name;
ot->config = r;
{
rtable *t = mb_alloc(rt_table_pool, sizeof(struct rtable));
DBG("\t%s: created\n", r->name);
- rt_setup(rt_table_pool, t, r->name, r);
+ rt_setup(rt_table_pool, t, r);
add_tail(&routing_tables, &t->n);
r->table = t;
}
}
}
-static inline unsigned
-ptr_hash(void *ptr)
+
+int
+rte_update_in(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
+{
+ struct rtable *tab = c->in_table;
+ rte *old, **pos;
+ net *net;
+
+ if (new)
+ {
+ net = net_get(tab, n);
+
+ if (!new->pref)
+ new->pref = c->preference;
+
+ if (!rta_is_cached(new->attrs))
+ new->attrs = rta_lookup(new->attrs);
+ }
+ else
+ {
+ net = net_find(tab, n);
+
+ if (!net)
+ goto drop_withdraw;
+ }
+
+ /* Find the old rte */
+ for (pos = &net->routes; old = *pos; pos = &old->next)
+ if (old->attrs->src == src)
+ {
+ if (new && rte_same(old, new))
+ {
+ /* Refresh the old rte, continue with update to main rtable */
+ if (old->flags & (REF_STALE | REF_DISCARD | REF_MODIFY))
+ {
+ old->flags &= ~(REF_STALE | REF_DISCARD | REF_MODIFY);
+ return 1;
+ }
+
+ goto drop_update;
+ }
+
+ /* Remove the old rte */
+ *pos = old->next;
+ rte_free_quick(old);
+ tab->rt_count--;
+
+ break;
+ }
+
+ if (!new)
+ {
+ if (!old)
+ goto drop_withdraw;
+
+ return 1;
+ }
+
+ struct channel_limit *l = &c->rx_limit;
+ if (l->action && !old)
+ {
+ if (tab->rt_count >= l->limit)
+ channel_notify_limit(c, l, PLD_RX, tab->rt_count);
+
+ if (l->state == PLS_BLOCKED)
+ {
+ rte_trace_in(D_FILTERS, c->proto, new, "ignored [limit]");
+ goto drop_update;
+ }
+ }
+
+ /* Insert the new rte */
+ rte *e = rte_do_cow(new);
+ e->flags |= REF_COW;
+ e->net = net;
+ e->sender = c;
+ e->lastmod = current_time();
+ e->next = *pos;
+ *pos = e;
+ tab->rt_count++;
+ return 1;
+
+drop_update:
+ c->stats.imp_updates_received++;
+ c->stats.imp_updates_ignored++;
+ rte_free(new);
+ return 0;
+
+drop_withdraw:
+ c->stats.imp_withdraws_received++;
+ c->stats.imp_withdraws_ignored++;
+ return 0;
+}
+
+int
+rt_reload_channel(struct channel *c)
+{
+ struct rtable *tab = c->in_table;
+ struct fib_iterator *fit = &c->reload_fit;
+ int max_feed = 64;
+
+ ASSERT(c->channel_state == CS_UP);
+
+ if (!c->reload_active)
+ {
+ FIB_ITERATE_INIT(fit, &tab->fib);
+ c->reload_active = 1;
+ }
+
+ FIB_ITERATE_START(&tab->fib, fit, net, n)
+ {
+ if (max_feed <= 0)
+ {
+ FIB_ITERATE_PUT(fit);
+ return 0;
+ }
+
+ for (rte *e = n->routes; e; e = e->next)
+ {
+ rte_update2(c, n->n.addr, rte_do_cow(e), e->attrs->src);
+ max_feed--;
+ }
+ }
+ FIB_ITERATE_END;
+
+ c->reload_active = 0;
+ return 1;
+}
+
+void
+rt_reload_channel_abort(struct channel *c)
{
- uintptr_t p = (uintptr_t) ptr;
- return p ^ (p << 8) ^ (p >> 16);
+ if (c->reload_active)
+ {
+ /* Unlink the iterator */
+ fit_get(&c->in_table->fib, &c->reload_fit);
+ c->reload_active = 0;
+ }
}
+void
+rt_prune_sync(rtable *t, int all)
+{
+ FIB_WALK(&t->fib, net, n)
+ {
+ rte *e, **ee = &n->routes;
+ while (e = *ee)
+ {
+ if (all || (e->flags & (REF_STALE | REF_DISCARD)))
+ {
+ *ee = e->next;
+ rte_free_quick(e);
+ t->rt_count--;
+ }
+ else
+ ee = &e->next;
+ }
+ }
+ FIB_WALK_END;
+}
+
+
static inline u32
hc_hash(ip_addr a, rtable *dep)
{
hc_alloc_table(hc, HC_DEF_ORDER);
hc->slab = sl_new(rt_table_pool, sizeof(struct hostentry));
- hc->lp = lp_new(rt_table_pool, 1008);
+ hc->lp = lp_new(rt_table_pool, LP_GOOD_SIZE(1024));
hc->trie = f_new_trie(hc->lp, sizeof(struct f_trie_node));
tab->hostcache = hc;
return 0;
}
-static u32
+static u32
rt_get_igp_metric(rte *rt)
{
eattr *ea = ea_find(rt->attrs->eattrs, EA_GEN_IGP_METRIC);
rt_update_hostentry(rtable *tab, struct hostentry *he)
{
rta *old_src = he->src;
+ int direct = 0;
int pxlen = 0;
/* Reset the hostentry */
he->src = NULL;
- he->nexthop_linkable = 0;
he->dest = RTD_UNREACHABLE;
+ he->nexthop_linkable = 0;
he->igp_metric = 0;
net_addr he_addr;
goto done;
}
- he->dest = a->dest;
- he->nexthop_linkable = 1;
- if (he->dest == RTD_UNICAST)
+ if (a->dest == RTD_UNICAST)
{
for (struct nexthop *nh = &(a->nh); nh; nh = nh->next)
if (ipa_zero(nh->gw))
goto done;
}
- he->nexthop_linkable = 0;
- break;
+ direct++;
}
}
he->src = rta_clone(a);
+ he->dest = a->dest;
+ he->nexthop_linkable = !direct;
he->igp_metric = rt_get_igp_metric(e);
}
}
-/*
- * CLI commands
- */
-
-static void
-rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, ea_list *tmpa)
-{
- byte from[IPA_MAX_TEXT_LENGTH+8];
- byte tm[TM_DATETIME_BUFFER_SIZE], info[256];
- rta *a = e->attrs;
- int primary = (e->net->routes == e);
- int sync_error = (e->net->n.flags & KRF_SYNC_ERROR);
- void (*get_route_info)(struct rte *, byte *buf, struct ea_list *attrs);
- struct nexthop *nh;
-
- tm_format_datetime(tm, &config->tf_route, e->lastmod);
- if (ipa_nonzero(a->from) && !ipa_equal(a->from, a->nh.gw))
- bsprintf(from, " from %I", a->from);
- else
- from[0] = 0;
-
- get_route_info = a->src->proto->proto->get_route_info;
- if (get_route_info || d->verbose)
- {
- /* Need to normalize the extended attributes */
- ea_list *t = tmpa;
- t = ea_append(t, a->eattrs);
- tmpa = alloca(ea_scan(t));
- ea_merge(t, tmpa);
- ea_sort(tmpa);
- }
- if (get_route_info)
- get_route_info(e, info, tmpa);
- else
- bsprintf(info, " (%d)", e->pref);
-
- if (!d->show_counter)
- cli_printf(c, -1007, "Table %s:", d->tit->table->name);
-
- cli_printf(c, -1007, "%-18s %s [%s %s%s]%s%s", ia, rta_dest_name(a->dest),
- a->src->proto->name, tm, from, primary ? (sync_error ? " !" : " *") : "", info);
-
- if (a->dest == RTD_UNICAST)
- for (nh = &(a->nh); nh; nh = nh->next)
- {
- char mpls[MPLS_MAX_LABEL_STACK*12 + 5], *lsp = mpls;
-
- if (nh->labels)
- {
- lsp += bsprintf(lsp, " mpls %d", nh->label[0]);
- for (int i=1;i<nh->labels; i++)
- lsp += bsprintf(lsp, "/%d", nh->label[i]);
- }
- *lsp = '\0';
-
- if (a->nh.next)
- cli_printf(c, -1007, "\tvia %I%s on %s weight %d", nh->gw, mpls, nh->iface->name, nh->weight + 1);
- else
- cli_printf(c, -1007, "\tvia %I%s on %s", nh->gw, mpls, nh->iface->name);
- }
-
- if (d->verbose)
- rta_show(c, a, tmpa);
-}
-
-static void
-rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
-{
- rte *e, *ee;
- byte ia[NET_MAX_TEXT_LENGTH+1];
- struct ea_list *tmpa;
- struct channel *ec = d->export_channel;
- int first = 1;
- int pass = 0;
-
- bsnprintf(ia, sizeof(ia), "%N", n->n.addr);
-
- for (e = n->routes; e; e = e->next)
- {
- if (rte_is_filtered(e) != d->filtered)
- continue;
-
- d->rt_counter++;
- d->net_counter += first;
- first = 0;
-
- if (pass)
- continue;
-
- ee = e;
- rte_update_lock(); /* We use the update buffer for filtering */
- tmpa = make_tmp_attrs(e, rte_update_pool);
-
- /* Special case for merged export */
- if ((d->export_mode == RSEM_EXPORT) && (ec->ra_mode == RA_MERGED))
- {
- rte *rt_free;
- e = rt_export_merged(ec, n, &rt_free, &tmpa, rte_update_pool, 1);
- pass = 1;
-
- if (!e)
- { e = ee; goto skip; }
- }
- else if (d->export_mode)
- {
- struct proto *ep = d->export_protocol;
- int ic = ep->import_control ? ep->import_control(ep, &e, &tmpa, rte_update_pool) : 0;
-
- if (ec->ra_mode == RA_OPTIMAL || ec->ra_mode == RA_MERGED)
- pass = 1;
-
- if (ic < 0)
- goto skip;
-
- if (d->export_mode > RSEM_PREEXPORT)
- {
- /*
- * FIXME - This shows what should be exported according to current
- * filters, but not what was really exported. 'configure soft'
- * command may change the export filter and do not update routes.
- */
- int do_export = (ic > 0) ||
- (f_run(ec->out_filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) <= F_ACCEPT);
-
- if (do_export != (d->export_mode == RSEM_EXPORT))
- goto skip;
-
- if ((d->export_mode == RSEM_EXPORT) && (ec->ra_mode == RA_ACCEPTED))
- pass = 1;
- }
- }
-
- if (d->show_protocol && (d->show_protocol != e->attrs->src->proto))
- goto skip;
-
- if (f_run(d->filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)
- goto skip;
-
- if (d->stats < 2)
- rt_show_rte(c, ia, e, d, tmpa);
-
- d->show_counter++;
- ia[0] = 0;
-
- skip:
- if (e != ee)
- {
- rte_free(e);
- e = ee;
- }
- rte_update_unlock();
-
- if (d->primary_only)
- break;
- }
-}
-
-static struct channel *
-rt_show_export_channel(struct rt_show_data *d)
-{
- if (! d->export_protocol->rt_notify)
- return NULL;
-
- return proto_find_channel_by_table(d->export_protocol, d->tit->table);
-}
-
-static void
-rt_show_cleanup(struct cli *c)
-{
- struct rt_show_data *d = c->rover;
-
- /* Unlink the iterator */
- fit_get(&d->tit->table->fib, &d->fit);
- rt_unlock_table(d->tit->table);
- while (NODE_VALID(NODE_NEXT(d->tit)))
- rt_unlock_table((d->tit = NODE_NEXT(d->tit))->table);
-}
-
-static void
-rt_show_cont(struct cli *c)
-{
- struct rt_show_data *d = c->rover;
-#ifdef DEBUGGING
- unsigned max = 4;
-#else
- unsigned max = 64;
-#endif
- struct fib *fib = &d->tit->table->fib;
- struct fib_iterator *it = &d->fit;
-
- if (d->export_mode)
- {
- /* Ensure we have current export channel */
- d->export_channel = rt_show_export_channel(d);
- if (!d->export_channel || (d->export_channel->export_state == ES_DOWN))
- {
- cli_printf(c, 8005, "Channel is down");
- rt_show_cleanup(c);
- goto done;
- }
- }
-
- FIB_ITERATE_START(fib, it, net, n)
- {
- if (!max--)
- {
- FIB_ITERATE_PUT(it);
- return;
- }
- rt_show_net(c, n, d);
- }
- FIB_ITERATE_END;
-
- if (!d->show_counter && (d->tables_defined_by & RSD_TDB_SET))
- cli_printf(c, -1007, "Table %s:", d->tit->table->name);
-
- if (d->stats && d->stats_by_table)
- cli_printf(c, -1007, "%d of %d routes for %d networks in table %s", d->show_counter - d->show_counter_last, d->rt_counter - d->rt_counter_last, d->net_counter - d->net_counter_last, d->tit->table->name);
-
- rt_unlock_table(d->tit->table);
- d->table_counter++;
- if (NODE_VALID(NODE_NEXT(d->tit)))
- {
- d->tit = NODE_NEXT(d->tit);
- FIB_ITERATE_INIT(&d->fit, &d->tit->table->fib);
- d->show_counter_last = d->show_counter;
- d->rt_counter_last = d->rt_counter;
- d->net_counter_last = d->net_counter;
- d->show_counter = 0;
- d->rt_counter = 0;
- d->net_counter = 0;
- return;
- }
-
- if (d->stats)
- cli_printf(c, 14, "Total: %d of %d routes for %d networks in %d tables", d->show_counter, d->rt_counter, d->net_counter, d->table_counter);
- else
- cli_printf(c, 0, "");
-done:
- c->cont = c->cleanup = NULL;
-}
-
-void rt_show_add_table(struct rt_show_data *d, rtable *t)
-{
- struct rt_show_data_rtable *rsdr = cfg_alloc(sizeof(struct rt_show_data_rtable));
- rsdr->table = t;
- add_tail(&(d->table), &(rsdr->n));
-}
-
-static inline void
-rt_show_get_table(struct proto *p, struct rt_show_data *d)
-{
- struct channel *c;
- WALK_LIST(c, p->channels)
- if (c->table)
- rt_show_add_table(d, c->table);
-
-}
-
-static inline void
-rt_show_get_default_table(struct rt_show_data *d)
-{
- if (d->export_protocol || d->show_protocol)
- {
- rt_show_get_table(d->export_protocol ?: d->show_protocol, d);
- return;
- }
-
- for (int i=1; i<NET_MAX; i++)
- if (config->def_tables[i])
- rt_show_add_table(d, config->def_tables[i]->table);
-}
-
-void
-rt_show(struct rt_show_data *d)
-{
- net *n;
-
- /* There may be implicit tables. */
- if (EMPTY_LIST(d->table)) rt_show_get_default_table(d);
-
- /* Filtered routes are neither exported nor have sensible ordering */
- if (d->filtered && (d->export_mode || d->primary_only))
- cli_msg(0, "");
-
- if (!d->addr)
- {
- struct rt_show_data_rtable *rsdr;
- WALK_LIST(rsdr, d->table)
- {
- rt_lock_table(rsdr->table);
- }
- d->tit = HEAD(d->table);
- FIB_ITERATE_INIT(&d->fit, &d->tit->table->fib);
- this_cli->cont = rt_show_cont;
- this_cli->cleanup = rt_show_cleanup;
- this_cli->rover = d;
- }
- else
- {
- if (d->export_mode)
- {
- /* Find channel associated with the export protocol */
- d->export_channel = rt_show_export_channel(d);
- if (!d->export_channel || (d->export_channel->export_state == ES_DOWN))
- {
- cli_msg(8005, "Channel is down");
- return;
- }
- }
-
- struct rt_show_data_rtable *rsdr, *rn;
- WALK_LIST_DELSAFE(rsdr, rn, d->table)
- {
- /* Check table net types matching to query */
- if (rsdr->table->addr_type == d->addr->type)
- continue;
-
- if (d->tables_defined_by & RSD_TDB_NMN)
- {
- cli_msg(8001, "Incompatible type of prefix/ip with table %s", rsdr->table->name);
- return;
- }
-
- rem_node(&(rsdr->n));
- }
-
- WALK_LIST(rsdr, d->table)
- {
- d->tit = rsdr;
-
- if (d->show_for)
- n = net_route(rsdr->table, d->addr);
- else
- n = net_find(rsdr->table, d->addr);
-
- if (n)
- rt_show_net(this_cli, n, d);
- }
-
- if (d->rt_counter)
- cli_msg(0, "");
- else
- cli_msg(8001, "Network not found in any specified table");
- }
-}
-
/*
* Documentation for functions declared inline in route.h
*/