]> git.ipfire.org Git - thirdparty/bird.git/blobdiff - nest/rt-table.c
OSPF: Improved handling of tmpattrs
[thirdparty/bird.git] / nest / rt-table.c
index eb9dc3a503564311bcaa3b49953a257c65af33ba..7990fca3c01799937a3da7f220a7568d20f3918e 100644 (file)
 #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"
 
@@ -48,9 +48,8 @@ pool *rt_table_pool;
 static slab *rte_slab;
 static linpool *rte_update_pool;
 
-static list routing_tables;
+list routing_tables;
 
-static byte *rt_format_via(rte *e);
 static void rt_free_hostcache(rtable *tab);
 static void rt_notify_hostcache(rtable *tab, net *net);
 static void rt_update_hostcache(rtable *tab);
@@ -58,23 +57,13 @@ static void rt_next_hop_update(rtable *tab);
 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, rte_update_pool) : NULL;
-}
-
-
 /* Like fib_route(), but skips empty net entries */
 static inline void *
-net_route_ip4(struct fib *f, net_addr_ip4 *n)
+net_route_ip4(rtable *t, net_addr_ip4 *n)
 {
   net *r;
 
-  while (r = fib_find(f, (net_addr *) n),
-        !(r && rte_is_valid(r->routes)) && (n->pxlen > 0))
+  while (r = net_find_valid(t, (net_addr *) n), (!r) && (n->pxlen > 0))
   {
     n->pxlen--;
     ip4_clrbit(&n->prefix, n->pxlen);
@@ -84,12 +73,11 @@ net_route_ip4(struct fib *f, net_addr_ip4 *n)
 }
 
 static inline void *
-net_route_ip6(struct fib *f, net_addr_ip6 *n)
+net_route_ip6(rtable *t, net_addr_ip6 *n)
 {
   net *r;
 
-  while (r = fib_find(f, (net_addr *) n),
-        !(r && rte_is_valid(r->routes)) && (n->pxlen > 0))
+  while (r = net_find_valid(t, (net_addr *) n), (!r) && (n->pxlen > 0))
   {
     n->pxlen--;
     ip6_clrbit(&n->prefix, n->pxlen);
@@ -98,6 +86,45 @@ net_route_ip6(struct fib *f, net_addr_ip6 *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)
 {
@@ -111,12 +138,15 @@ net_route(rtable *tab, const net_addr *n)
   case NET_IP4:
   case NET_VPN4:
   case NET_ROA4:
-    return net_route_ip4(&tab->fib, (net_addr_ip4 *) n0);
+    return net_route_ip4(tab, (net_addr_ip4 *) n0);
 
   case NET_IP6:
   case NET_VPN6:
   case NET_ROA6:
-    return net_route_ip6(&tab->fib, (net_addr_ip6 *) n0);
+    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;
@@ -288,13 +318,46 @@ rte_cow_rta(rte *r, linpool *lp)
   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)
 {
@@ -346,7 +409,7 @@ rte_mergable(rte *pri, rte *sec)
 static void
 rte_trace(struct proto *p, rte *e, int dir, char *msg)
 {
-  log(L_TRACE "%s %c %s %N %s", p->name, dir, msg, e->net->n.addr, rt_format_via(e));
+  log(L_TRACE "%s %c %s %N %s", p->name, dir, msg, e->net->n.addr, rta_dest_name(e->attrs->dest));
 }
 
 static inline void
@@ -364,24 +427,18 @@ rte_trace_out(uint flag, struct proto *p, rte *e, char *msg)
 }
 
 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)
@@ -399,8 +456,11 @@ export_filter_(struct channel *c, rte *rt0, rte **rt_free, ea_list **tmpa, linpo
       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)
@@ -424,13 +484,13 @@ export_filter_(struct channel *c, rte *rt0, rte **rt_free, ea_list **tmpa, linpo
 }
 
 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;
@@ -503,19 +563,7 @@ do_rt_notify(struct channel *c, net *net, rte *new, rte *old, ea_list *tmpa, int
       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
@@ -527,7 +575,6 @@ rt_notify_basic(struct channel *c, net *net, rte *new0, rte *old0, int refeed)
   rte *old = old0;
   rte *new_free = NULL;
   rte *old_free = NULL;
-  ea_list *tmpa = NULL;
 
   if (new)
     c->stats.exp_updates_received++;
@@ -535,53 +582,67 @@ rt_notify_basic(struct channel *c, net *net, rte *new0, rte *old0, int refeed)
     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)
@@ -593,14 +654,13 @@ rt_notify_basic(struct channel *c, net *net, rte *new0, rte *old0, int refeed)
 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. */
@@ -618,7 +678,7 @@ rt_notify_accepted(struct channel *c, net *net, rte *new_changed, rte *old_chang
   /* 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 */
@@ -626,9 +686,9 @@ rt_notify_accepted(struct channel *c, net *net, rte *new_changed, rte *old_chang
        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
    */
@@ -665,11 +725,23 @@ rt_notify_accepted(struct channel *c, net *net, rte *new_changed, rte *old_chang
    *
    * - 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 */
@@ -687,18 +759,18 @@ rt_notify_accepted(struct channel *c, net *net, rte *new_changed, rte *old_chang
   /* 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)
@@ -708,19 +780,17 @@ rt_notify_accepted(struct channel *c, net *net, rte *new_changed, rte *old_chang
 }
 
 
-static struct mpnh *
-mpnh_merge_rta(struct mpnh *nhs, rta *a, linpool *pool, int max)
+static struct nexthop *
+nexthop_merge_rta(struct nexthop *nhs, rta *a, linpool *pool, int max)
 {
-  struct mpnh nh = { .gw = a->gw, .iface = a->iface };
-  struct mpnh *nh2 = (a->dest == RTD_MULTIPATH) ? a->nexthops : &nh;
-  return mpnh_merge(nhs, nh2, 1, 0, max, rte_update_pool);
+  return nexthop_merge(nhs, &(a->nh), 1, 0, max, pool);
 }
 
 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 mpnh *nhs = NULL;
+  struct nexthop *nhs = NULL;
   rte *best0, *best, *rt0, *rt, *tmp;
 
   best0 = net->routes;
@@ -729,7 +799,7 @@ rt_export_merged(struct channel *c, net *net, rte **rt_free, ea_list **tmpa, lin
   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;
@@ -739,13 +809,13 @@ rt_export_merged(struct channel *c, net *net, rte **rt_free, ea_list **tmpa, lin
     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;
 
     if (rte_is_reachable(rt))
-      nhs = mpnh_merge_rta(nhs, rt->attrs, pool, c->merge_limit);
+      nhs = nexthop_merge_rta(nhs, rt->attrs, pool, c->merge_limit);
 
     if (tmp)
       rte_free(tmp);
@@ -753,13 +823,12 @@ rt_export_merged(struct channel *c, net *net, rte **rt_free, ea_list **tmpa, lin
 
   if (nhs)
   {
-    nhs = mpnh_merge_rta(nhs, best->attrs, pool, c->merge_limit);
+    nhs = nexthop_merge_rta(nhs, best->attrs, pool, c->merge_limit);
 
     if (nhs->next)
     {
       best = rte_cow_rta(best, pool);
-      best->attrs->dest = RTD_MULTIPATH;
-      best->attrs->nexthops = nhs;
+      nexthop_link(best->attrs, nhs);
     }
   }
 
@@ -780,7 +849,6 @@ rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed
   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() */
 
@@ -792,10 +860,10 @@ rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed
   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;
@@ -808,15 +876,15 @@ rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed
 
   /* 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)
@@ -840,7 +908,7 @@ rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed
  * @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
@@ -854,7 +922,7 @@ rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed
  * 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
@@ -881,8 +949,16 @@ rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old,
   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)
@@ -906,7 +982,6 @@ rte_validate(rte *e)
   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",
@@ -914,7 +989,9 @@ rte_validate(rte *e)
     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",
@@ -922,12 +999,19 @@ rte_validate(rte *e)
     return 0;
   }
 
-  if ((e->attrs->dest == RTD_MULTIPATH) && !mpnh_is_sorted(e->attrs->nexthops))
-    {
-      log(L_WARN "Ignoring unsorted multipath route %N received via %s",
-         n->n.addr, e->sender->proto->name);
-      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;
+  }
 
   return 1;
 }
@@ -956,12 +1040,13 @@ rte_free_quick(rte *e)
 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); }
@@ -1005,7 +1090,9 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
 
          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))
                {
@@ -1017,6 +1104,7 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
              return;
            }
          *k = old->next;
+         table->rt_count--;
          break;
        }
       k = &old->next;
@@ -1036,7 +1124,7 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
   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;
 
@@ -1119,6 +1207,7 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
 
          new->next = *k;
          *k = new;
+         table->rt_count++;
        }
     }
   else
@@ -1136,6 +1225,7 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
 
          new->next = net->routes;
          net->routes = new;
+         table->rt_count++;
        }
       else if (old == old_best)
        {
@@ -1151,6 +1241,7 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
            {
              new->next = net->routes;
              net->routes = new;
+             table->rt_count++;
            }
 
          /* Find a new optimal route (if there is any) */
@@ -1178,12 +1269,13 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
          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)
@@ -1211,7 +1303,7 @@ rte_recalculate(struct channel *c, net *net, rte *new, struct rte_src *src)
 
   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)
@@ -1301,12 +1393,11 @@ rte_unhide_dummy_routes(net *net, rte **dummy)
  */
 
 void
-rte_update2(struct channel *c, net_addr *n, rte *new, struct rte_src *src)
+rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
 {
   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;
 
@@ -1315,7 +1406,10 @@ rte_update2(struct channel *c, net_addr *n, rte *new, struct rte_src *src)
   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;
@@ -1344,11 +1438,11 @@ rte_update2(struct channel *c, net_addr *n, rte *new, struct rte_src *src)
        }
       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++;
@@ -1359,13 +1453,17 @@ rte_update2(struct channel *c, net_addr *n, rte *new, struct rte_src *src)
 
                  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
     {
@@ -1380,21 +1478,26 @@ rte_update2(struct channel *c, net_addr *n, rte *new, struct rte_src *src)
     }
 
  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)
 {
@@ -1403,14 +1506,36 @@ rte_announce_i(rtable *tab, unsigned type, net *net, rte *new, rte *old,
   rte_update_unlock();
 }
 
-void
-rte_discard(rtable *t, rte *old)       /* Non-filtered route deletion, used during garbage collection */
+static inline void
+rte_discard(rte *old)  /* Non-filtered route deletion, used during garbage collection */
 {
   rte_update_lock();
   rte_recalculate(old->sender, old->net, NULL, old->attrs->src);
   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)
@@ -1424,12 +1549,14 @@ 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);
 
@@ -1495,6 +1622,26 @@ rt_refresh_end(rtable *t, struct channel *c)
     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
@@ -1507,7 +1654,7 @@ rte_dump(rte *e)
 {
   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);
@@ -1564,11 +1711,14 @@ rt_schedule_hcu(rtable *tab)
 static inline void
 rt_schedule_nhu(rtable *tab)
 {
-  if (tab->nhu_state == 0)
+  if (tab->nhu_state == NHU_CLEAN)
     ev_schedule(tab->rt_event);
 
-  /* state change 0->1, 2->3 */
-  tab->nhu_state |= 1;
+  /* state change:
+   *   NHU_CLEAN   -> NHU_SCHEDULED
+   *   NHU_RUNNING -> NHU_DIRTY
+   */
+  tab->nhu_state |= NHU_SCHEDULED;
 }
 
 void
@@ -1602,22 +1752,17 @@ rt_event(void *ptr)
 }
 
 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();
 }
 
 /**
@@ -1631,7 +1776,7 @@ rt_init(void)
 {
   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);
 }
@@ -1686,6 +1831,7 @@ again:
 
     rescan:
       for (e=n->routes; e; e=e->next)
+      {
        if (e->sender->flush_active || (e->flags & REF_DISCARD))
          {
            if (limit <= 0)
@@ -1695,11 +1841,27 @@ again:
                return;
              }
 
-           rte_discard(tab, e);
+           rte_discard(e);
+           limit--;
+
+           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 */
        {
@@ -1715,7 +1877,7 @@ again:
 #endif
 
   tab->gc_counter = 0;
-  tab->gc_time = now;
+  tab->gc_time = current_time();
 
   /* state change 2->0, 3->1 */
   tab->prune_state &= 1;
@@ -1763,33 +1925,105 @@ rta_next_hop_outdated(rta *a)
   if (!he->src)
     return a->dest != RTD_UNREACHABLE;
 
-  return (a->iface != he->src->iface) || !ipa_equal(a->gw, he->gw) ||
-    (a->dest != he->dest) || (a->igp_metric != he->igp_metric) ||
-    !mpnh_same(a->nexthops, he->src->nexthops);
+  return (a->dest != he->dest) || (a->igp_metric != he->igp_metric) ||
+    (!he->nexthop_linkable) || !nexthop_same(&(a->nh), &(he->src->nh));
 }
 
-static inline void
-rta_apply_hostentry(rta *a, struct hostentry *he)
+void
+rta_apply_hostentry(rta *a, struct hostentry *he, mpls_label_stack *mls)
 {
   a->hostentry = he;
-  a->iface = he->src ? he->src->iface : NULL;
-  a->gw = he->gw;
   a->dest = he->dest;
   a->igp_metric = he->igp_metric;
-  a->nexthops = he->src ? he->src->nexthops : NULL;
+
+  if (a->dest != RTD_UNICAST)
+  {
+    /* No nexthop */
+no_nexthop:
+    a->nh = (struct nexthop) {};
+    if (mls)
+    { /* Store the label stack for later changes */
+      a->nh.labels_orig = a->nh.labels = mls->len;
+      memcpy(a->nh.label, mls->stack, mls->len * sizeof(u32));
+    }
+    return;
+  }
+
+  if (((!mls) || (!mls->len)) && he->nexthop_linkable)
+  { /* Just link the nexthop chain, no label append happens. */
+    memcpy(&(a->nh), &(he->src->nh), nexthop_size(&(he->src->nh)));
+    return;
+  }
+
+  struct nexthop *nhp = NULL, *nhr = NULL;
+  int skip_nexthop = 0;
+
+  for (struct nexthop *nh = &(he->src->nh); nh; nh = nh->next)
+  {
+    if (skip_nexthop)
+      skip_nexthop--;
+    else
+    {
+      nhr = nhp;
+      nhp = (nhp ? (nhp->next = lp_allocz(rte_update_pool, NEXTHOP_MAX_SIZE)) : &(a->nh));
+    }
+
+    nhp->iface = nh->iface;
+    nhp->weight = nh->weight;
+    if (mls)
+    {
+      nhp->labels = nh->labels + mls->len;
+      nhp->labels_orig = mls->len;
+      if (nhp->labels <= MPLS_MAX_LABEL_STACK)
+      {
+       memcpy(nhp->label, nh->label, nh->labels * sizeof(u32)); /* First the hostentry labels */
+       memcpy(&(nhp->label[nh->labels]), mls->stack, mls->len * sizeof(u32)); /* Then the bottom labels */
+      }
+      else
+      {
+       log(L_WARN "Sum of label stack sizes %d + %d = %d exceedes allowed maximum (%d)",
+           nh->labels, mls->len, nhp->labels, MPLS_MAX_LABEL_STACK);
+       skip_nexthop++;
+       continue;
+      }
+    }
+    if (ipa_nonzero(nh->gw))
+    {
+      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
+      nhp->gw = he->addr;              /* Device nexthop with link-local address unknown */
+  }
+
+  if (skip_nexthop)
+    if (nhr)
+      nhr->next = NULL;
+    else
+    {
+      a->dest = RTD_UNREACHABLE;
+      log(L_WARN "No valid nexthop remaining, setting route unreachable");
+      goto no_nexthop;
+    }
 }
 
 static inline rte *
-rt_next_hop_update_rte(rtable *tab, rte *old)
+rt_next_hop_update_rte(rtable *tab UNUSED, rte *old)
 {
-  rta a;
-  memcpy(&a, old->attrs, sizeof(rta));
-  rta_apply_hostentry(&a, old->attrs->hostentry);
-  a.aflags = 0;
+  rta *a = alloca(RTA_MAX_SIZE);
+  memcpy(a, old->attrs, rta_size(old->attrs));
+
+  mpls_label_stack mls = { .len = a->nh.labels_orig };
+  memcpy(mls.stack, &a->nh.label[a->nh.labels - mls.len], mls.len * sizeof(u32));
+
+  rta_apply_hostentry(a, old->attrs->hostentry, &mls);
+  a->aflags = 0;
 
   rte *e = sl_alloc(rte_slab);
   memcpy(e, old, sizeof(rte));
-  e->attrs = rta_lookup(&a);
+  e->attrs = rta_lookup(a);
 
   return e;
 }
@@ -1870,13 +2104,13 @@ rt_next_hop_update(rtable *tab)
   struct fib_iterator *fit = &tab->nhu_fit;
   int max_feed = 32;
 
-  if (tab->nhu_state == 0)
+  if (tab->nhu_state == NHU_CLEAN)
     return;
 
-  if (tab->nhu_state == 1)
+  if (tab->nhu_state == NHU_SCHEDULED)
     {
       FIB_ITERATE_INIT(fit, &tab->fib);
-      tab->nhu_state = 2;
+      tab->nhu_state = NHU_RUNNING;
     }
 
   FIB_ITERATE_START(&tab->fib, fit, net, n)
@@ -1891,10 +2125,13 @@ rt_next_hop_update(rtable *tab)
     }
   FIB_ITERATE_END;
 
-  /* state change 2->0, 3->1 */
+  /* State change:
+   *   NHU_DIRTY   -> NHU_SCHEDULED
+   *   NHU_RUNNING -> NHU_CLEAN
+   */
   tab->nhu_state &= 1;
 
-  if (tab->nhu_state > 0)
+  if (tab->nhu_state != NHU_CLEAN)
     ev_schedule(tab->rt_event);
 }
 
@@ -1965,6 +2202,13 @@ rt_unlock_table(rtable *r)
     }
 }
 
+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
@@ -1990,11 +2234,10 @@ rt_commit(struct config *new, struct config *old)
          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;
@@ -2018,7 +2261,7 @@ rt_commit(struct config *new, struct config *old)
       {
        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;
       }
@@ -2124,13 +2367,168 @@ rt_feed_channel_abort(struct channel *c)
     }
 }
 
-static inline unsigned
-ptr_hash(void *ptr)
+
+int
+rte_update_in(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
 {
-  uintptr_t p = (uintptr_t) ptr;
-  return p ^ (p << 8) ^ (p >> 16);
+  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)
+{
+  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)
 {
@@ -2166,11 +2564,11 @@ hc_remove(struct hostcache *hc, struct hostentry *he)
 static void
 hc_alloc_table(struct hostcache *hc, unsigned order)
 {
-  unsigned hsize = 1 << order;
+  uint hsize = 1 << order;
   hc->hash_order = order;
   hc->hash_shift = 32 - order;
-  hc->hash_max = (order >= HC_HI_ORDER) ? ~0 : (hsize HC_HI_MARK);
-  hc->hash_min = (order <= HC_LO_ORDER) ?  0 : (hsize HC_LO_MARK);
+  hc->hash_max = (order >= HC_HI_ORDER) ? ~0U : (hsize HC_HI_MARK);
+  hc->hash_min = (order <= HC_LO_ORDER) ?  0U : (hsize HC_LO_MARK);
 
   hc->hash_table = mb_allocz(rt_table_pool, hsize * sizeof(struct hostentry *));
 }
@@ -2178,10 +2576,10 @@ hc_alloc_table(struct hostcache *hc, unsigned order)
 static void
 hc_resize(struct hostcache *hc, unsigned new_order)
 {
-  unsigned old_size = 1 << hc->hash_order;
   struct hostentry **old_table = hc->hash_table;
   struct hostentry *he, *hen;
-  int i;
+  uint old_size = 1 << hc->hash_order;
+  uint i;
 
   hc_alloc_table(hc, new_order);
   for (i = 0; i < old_size; i++)
@@ -2198,12 +2596,12 @@ hc_new_hostentry(struct hostcache *hc, ip_addr a, ip_addr ll, rtable *dep, unsig
 {
   struct hostentry *he = sl_alloc(hc->slab);
 
-  he->addr = a;
-  he->link = ll;
-  he->tab = dep;
-  he->hash_key = k;
-  he->uc = 0;
-  he->src = NULL;
+  *he = (struct hostentry) {
+    .addr = a,
+    .link = ll,
+    .tab = dep,
+    .hash_key = k,
+  };
 
   add_tail(&hc->hostentries, &he->ln);
   hc_insert(hc, he);
@@ -2239,7 +2637,7 @@ rt_init_hostcache(rtable *tab)
   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;
@@ -2288,7 +2686,7 @@ if_local_addr(ip_addr a, struct iface *i)
   return 0;
 }
 
-static u32 
+static u32
 rt_get_igp_metric(rte *rt)
 {
   eattr *ea = ea_find(rt->attrs->eattrs, EA_GEN_IGP_METRIC);
@@ -2310,8 +2708,7 @@ rt_get_igp_metric(rte *rt)
     return rt->u.rip.metric;
 #endif
 
-  /* Device routes */
-  if ((a->dest != RTD_ROUTER) && (a->dest != RTD_MULTIPATH))
+  if (a->source == RTS_DEVICE)
     return 0;
 
   return IGP_METRIC_UNKNOWN;
@@ -2321,12 +2718,13 @@ static int
 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->gw = IPA_NONE;
   he->dest = RTD_UNREACHABLE;
+  he->nexthop_linkable = 0;
   he->igp_metric = 0;
 
   net_addr he_addr;
@@ -2346,32 +2744,30 @@ rt_update_hostentry(rtable *tab, struct hostentry *he)
          goto done;
        }
 
-      if (a->dest == RTD_DEVICE)
-       {
-         if (if_local_addr(he->addr, a->iface))
-           {
-             /* The host address is a local address, this is not valid */
-             log(L_WARN "Next hop address %I is a local address of iface %s",
-                 he->addr, a->iface->name);
-             goto done;
-           }
-
-         /* The host is directly reachable, use link as a gateway */
-         he->gw = he->link;
-         he->dest = RTD_ROUTER;
-       }
-      else
+      if (a->dest == RTD_UNICAST)
        {
-         /* The host is reachable through some route entry */
-         he->gw = a->gw;
-         he->dest = a->dest;
+         for (struct nexthop *nh = &(a->nh); nh; nh = nh->next)
+           if (ipa_zero(nh->gw))
+             {
+               if (if_local_addr(he->addr, nh->iface))
+                 {
+                   /* The host address is a local address, this is not valid */
+                   log(L_WARN "Next hop address %I is a local address of iface %s",
+                       he->addr, nh->iface->name);
+                   goto done;
+                 }
+
+               direct++;
+             }
        }
 
       he->src = rta_clone(a);
+      he->dest = a->dest;
+      he->nexthop_linkable = !direct;
       he->igp_metric = rt_get_igp_metric(e);
     }
 
- done:
+done:
   /* Add a prefix range to the trie */
   trie_add_prefix(tab->hostcache->trie, &he_addr, pxlen, he_addr.pxlen);
 
@@ -2406,7 +2802,7 @@ rt_update_hostcache(rtable *tab)
   tab->hcu_scheduled = 0;
 }
 
-static struct hostentry *
+struct hostentry *
 rt_get_hostentry(rtable *tab, ip_addr a, ip_addr ll, rtable *dep)
 {
   struct hostentry *he;
@@ -2420,295 +2816,11 @@ rt_get_hostentry(rtable *tab, ip_addr a, ip_addr ll, rtable *dep)
     if (ipa_equal(he->addr, a) && (he->tab == dep))
       return he;
 
-  he = hc_new_hostentry(hc, a, ll, dep, k);
+  he = hc_new_hostentry(hc, a, ipa_zero(ll) ? a : ll, dep, k);
   rt_update_hostentry(tab, he);
   return he;
 }
 
-void
-rta_set_recursive_next_hop(rtable *dep, rta *a, rtable *tab, ip_addr *gw, ip_addr *ll)
-{
-  rta_apply_hostentry(a, rt_get_hostentry(tab, *gw, *ll, dep));
-}
-
-
-/*
- *  CLI commands
- */
-
-static byte *
-rt_format_via(rte *e)
-{
-  rta *a = e->attrs;
-
-  /* Max text length w/o IP addr and interface name is 16 */
-  static byte via[IPA_MAX_TEXT_LENGTH+sizeof(a->iface->name)+16];
-
-  switch (a->dest)
-    {
-    case RTD_ROUTER:   bsprintf(via, "via %I on %s", a->gw, a->iface->name); break;
-    case RTD_DEVICE:   bsprintf(via, "dev %s", a->iface->name); break;
-    case RTD_BLACKHOLE:        bsprintf(via, "blackhole"); break;
-    case RTD_UNREACHABLE:      bsprintf(via, "unreachable"); break;
-    case RTD_PROHIBIT: bsprintf(via, "prohibited"); break;
-    case RTD_MULTIPATH:        bsprintf(via, "multipath"); break;
-    default:           bsprintf(via, "???");
-    }
-  return via;
-}
-
-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 mpnh *nh;
-
-  tm_format_datetime(tm, &config->tf_route, e->lastmod);
-  if (ipa_nonzero(a->from) && !ipa_equal(a->from, a->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);
-  cli_printf(c, -1007, "%-18s %s [%s %s%s]%s%s", ia, rt_format_via(e), a->src->proto->name,
-            tm, from, primary ? (sync_error ? " !" : " *") : "", info);
-  for (nh = a->nexthops; nh; nh = nh->next)
-    cli_printf(c, -1007, "\tvia %I on %s weight %d", nh->gw, nh->iface->name, nh->weight + 1);
-  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;
-
-  bsprintf(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;
-
-      d->show_counter++;
-      if (d->stats < 2)
-       rt_show_rte(c, ia, e, d, tmpa);
-      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->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->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");
-         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->stats)
-    cli_printf(c, 14, "%d of %d routes for %d networks", d->show_counter, d->rt_counter, d->net_counter);
-  else
-    cli_printf(c, 0, "");
-done:
-  c->cont = c->cleanup = NULL;
-}
-
-static void
-rt_show_cleanup(struct cli *c)
-{
-  struct rt_show_data *d = c->rover;
-
-  /* Unlink the iterator */
-  fit_get(&d->table->fib, &d->fit);
-}
-
-static inline rtable *
-rt_show_get_table(struct proto *p)
-{
-  /* FIXME: Use a better way to handle multi-channel protocols */
-
-  if (p->main_channel)
-    return p->main_channel->table;
-
-  if (!EMPTY_LIST(p->channels))
-    return ((struct channel *) HEAD(p->channels))->table;
-
-  return NULL;
-}
-
-void
-rt_show(struct rt_show_data *d)
-{
-  net *n;
-
-  /* Default is either a master table or a table related to a respective protocol */
-  if (!d->table && d->export_protocol) d->table = rt_show_get_table(d->export_protocol);
-  if (!d->table && d->show_protocol) d->table = rt_show_get_table(d->show_protocol);
-  if (!d->table) d->table = config->def_tables[NET_IP4]->table; /* FIXME: iterate through all tables ? */
-
-  /* Filtered routes are neither exported nor have sensible ordering */
-  if (d->filtered && (d->export_mode || d->primary_only))
-    cli_msg(0, "");
-
-  if (!d->addr)
-    {
-      FIB_ITERATE_INIT(&d->fit, &d->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;
-           }
-       }
-
-      if (d->show_for)
-       n = net_route(d->table, d->addr);
-      else
-       n = net_find(d->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 in table");
-    }
-}
 
 /*
  *  Documentation for functions declared inline in route.h