]> git.ipfire.org Git - thirdparty/bird.git/blobdiff - sysdep/unix/krt.c
Filter: Allow silent filter execution
[thirdparty/bird.git] / sysdep / unix / krt.c
index bf098774cb79cab063f1dddd731fe8b7a016147b..526c0cab64a70a96f0f7a5a30f0722260b8351ff 100644 (file)
@@ -36,7 +36,7 @@
  * only once for all the instances.
  *
  * The code uses OS-dependent parts for kernel updates and scans. These parts are
- * in more specific sysdep directories (e.g. sysdep/linux) in functions krt_sys_* 
+ * in more specific sysdep directories (e.g. sysdep/linux) in functions krt_sys_*
  * and kif_sys_* (and some others like krt_replace_rte()) and krt-sys.h header file.
  * This is also used for platform specific protocol options and route attributes.
  *
 
 pool *krt_pool;
 static linpool *krt_filter_lp;
+static list krt_proto_list;
 
 void
 krt_io_init(void)
 {
   krt_pool = rp_new(&root_pool, "Kernel Syncer");
   krt_filter_lp = lp_new(krt_pool, 4080);
+  init_list(&krt_proto_list);
+  krt_sys_io_init();
 }
 
 /*
  *     Interfaces
  */
 
+struct kif_proto *kif_proto;
 static struct kif_config *kif_cf;
-static struct kif_proto *kif_proto;
 static timer *kif_scan_timer;
 static bird_clock_t kif_last_shot;
 
@@ -113,13 +116,19 @@ kif_request_scan(void)
     tm_start(kif_scan_timer, 1);
 }
 
-static inline int
-prefer_scope(struct ifa *a, struct ifa *b)
-{ return (a->scope > SCOPE_LINK) && (b->scope <= SCOPE_LINK); }
-
 static inline int
 prefer_addr(struct ifa *a, struct ifa *b)
-{ return ipa_compare(a->ip, b->ip) < 0; }
+{
+  int sa = a->scope > SCOPE_LINK;
+  int sb = b->scope > SCOPE_LINK;
+
+  if (sa < sb)
+    return 0;
+  else if (sa > sb)
+    return 1;
+  else
+    return ipa_compare(a->ip, b->ip) < 0;
+}
 
 static inline struct ifa *
 find_preferred_ifa(struct iface *i, ip_addr prefix, ip_addr mask)
@@ -130,7 +139,7 @@ find_preferred_ifa(struct iface *i, ip_addr prefix, ip_addr mask)
     {
       if (!(a->flags & IA_SECONDARY) &&
          ipa_equal(ipa_and(a->ip, mask), prefix) &&
-         (!b || prefer_scope(a, b) || prefer_addr(a, b)))
+         (!b || prefer_addr(a, b)))
        b = a;
     }
 
@@ -151,6 +160,9 @@ kif_choose_primary(struct iface *i)
          return a;
     }
 
+  if (a = kif_get_primary_ip(i))
+    return a;
+
   return find_preferred_ifa(i, IPA_NONE, IPA_NONE);
 }
 
@@ -240,7 +252,7 @@ kif_init_config(int class)
   if (kif_cf)
     cf_error("Kernel device protocol already defined");
 
-  kif_cf = (struct kif_config *) proto_config_new(&proto_unix_iface, sizeof(struct kif_config), class);
+  kif_cf = (struct kif_config *) proto_config_new(&proto_unix_iface, class);
   kif_cf->scan_time = 60;
   init_list(&kif_cf->primary);
 
@@ -266,15 +278,16 @@ kif_copy_config(struct proto_config *dest, struct proto_config *src)
 
 
 struct protocol proto_unix_iface = {
-  name:                "Device",
-  template:    "device%d",
-  preference:  DEF_PREF_DIRECT,
-  preconfig:   kif_preconfig,
-  init:                kif_init,
-  start:       kif_start,
-  shutdown:    kif_shutdown,
-  reconfigure: kif_reconfigure,
-  copy_config: kif_copy_config
+  .name =              "Device",
+  .template =          "device%d",
+  .preference =                DEF_PREF_DIRECT,
+  .config_size =       sizeof(struct kif_config),
+  .preconfig =         kif_preconfig,
+  .init =              kif_init,
+  .start =             kif_start,
+  .shutdown =          kif_shutdown,
+  .reconfigure =       kif_reconfigure,
+  .copy_config =       kif_copy_config
 };
 
 /*
@@ -289,10 +302,10 @@ krt_trace_in(struct krt_proto *p, rte *e, char *msg)
 }
 
 static inline void
-krt_trace_in_rl(struct rate_limit *rl, struct krt_proto *p, rte *e, char *msg)
+krt_trace_in_rl(struct tbf *f, struct krt_proto *p, rte *e, char *msg)
 {
   if (p->p.debug & D_PACKETS)
-    log_rl(rl, L_TRACE "%s: %I/%d: %s", p->p.name, e->net->n.prefix, e->net->n.pxlen, msg);
+    log_rl(f, L_TRACE "%s: %I/%d: %s", p->p.name, e->net->n.prefix, e->net->n.pxlen, msg);
 }
 
 /*
@@ -301,7 +314,7 @@ krt_trace_in_rl(struct rate_limit *rl, struct krt_proto *p, rte *e, char *msg)
 
 #ifdef KRT_ALLOW_LEARN
 
-static struct rate_limit rl_alien_seen, rl_alien_updated, rl_alien_created, rl_alien_ignored;
+static struct tbf rl_alien = TBF_DEFAULT_LOG_LIMITS;
 
 /*
  * krt_same_key() specifies what (aside from the net) is the key in
@@ -340,15 +353,14 @@ krt_learn_announce_update(struct krt_proto *p, rte *e)
   ee->pflags = 0;
   ee->pref = p->p.preference;
   ee->u.krt = e->u.krt;
-  rte_update(p->p.table, nn, &p->p, &p->p, ee);
+  rte_update(&p->p, nn, ee);
 }
 
 static void
 krt_learn_announce_delete(struct krt_proto *p, net *n)
 {
   n = net_find(p->p.table, n->n.prefix, n->n.pxlen);
-  if (n)
-    rte_update(p->p.table, n, &p->p, &p->p, NULL);
+  rte_update(&p->p, n, NULL);
 }
 
 /* Called when alien route is discovered during scan */
@@ -368,20 +380,20 @@ krt_learn_scan(struct krt_proto *p, rte *e)
     {
       if (krt_uptodate(m, e))
        {
-         krt_trace_in_rl(&rl_alien_seen, p, e, "[alien] seen");
+         krt_trace_in_rl(&rl_alien, p, e, "[alien] seen");
          rte_free(e);
          m->u.krt.seen = 1;
        }
       else
        {
-         krt_trace_in_rl(&rl_alien_updated, p, e, "[alien] updated");
+         krt_trace_in(p, e, "[alien] updated");
          *mm = m->next;
          rte_free(m);
          m = NULL;
        }
     }
   else
-    krt_trace_in_rl(&rl_alien_created, p, e, "[alien] created");
+    krt_trace_in(p, e, "[alien] created");
   if (!m)
     {
       e->next = n->routes;
@@ -405,51 +417,65 @@ again:
       net *n = (net *) f;
       rte *e, **ee, *best, **pbest, *old_best;
 
-      old_best = n->routes;
+      /*
+       * Note that old_best may be NULL even if there was an old best route in
+       * the previous step, because it might be replaced in krt_learn_scan().
+       * But in that case there is a new valid best route.
+       */
+
+      old_best = NULL;
       best = NULL;
       pbest = NULL;
       ee = &n->routes;
       while (e = *ee)
        {
+         if (e->u.krt.best)
+           old_best = e;
+
          if (!e->u.krt.seen)
            {
              *ee = e->next;
              rte_free(e);
              continue;
            }
+
          if (!best || best->u.krt.metric > e->u.krt.metric)
            {
              best = e;
              pbest = ee;
            }
+
          e->u.krt.seen = 0;
+         e->u.krt.best = 0;
          ee = &e->next;
        }
       if (!n->routes)
        {
          DBG("%I/%d: deleting\n", n->n.prefix, n->n.pxlen);
          if (old_best)
-           {
-             krt_learn_announce_delete(p, n);
-             n->n.flags &= ~KRF_INSTALLED;
-           }
+           krt_learn_announce_delete(p, n);
+
          FIB_ITERATE_PUT(&fit, f);
          fib_delete(fib, f);
          goto again;
        }
+
+      best->u.krt.best = 1;
       *pbest = best->next;
       best->next = n->routes;
       n->routes = best;
-      if (best != old_best || !(n->n.flags & KRF_INSTALLED))
+
+      if ((best != old_best) || p->reload)
        {
          DBG("%I/%d: announcing (metric=%d)\n", n->n.prefix, n->n.pxlen, best->u.krt.metric);
          krt_learn_announce_update(p, best);
-         n->n.flags |= KRF_INSTALLED;
        }
       else
        DBG("%I/%d: uptodate (metric=%d)\n", n->n.prefix, n->n.pxlen, best->u.krt.metric);
     }
   FIB_ITERATE_END(f);
+
+  p->reload = 0;
 }
 
 static void
@@ -501,31 +527,31 @@ krt_learn_async(struct krt_proto *p, rte *e, int new)
   best = n->routes;
   bestp = &n->routes;
   for(gg=&n->routes; g=*gg; gg=&g->next)
+  {
     if (best->u.krt.metric > g->u.krt.metric)
       {
        best = g;
        bestp = gg;
       }
+
+    g->u.krt.best = 0;
+  }
+
   if (best)
     {
+      best->u.krt.best = 1;
       *bestp = best->next;
       best->next = n->routes;
       n->routes = best;
     }
+
   if (best != old_best)
     {
       DBG("krt_learn_async: distributing change\n");
       if (best)
-       {
-         krt_learn_announce_update(p, best);
-         n->n.flags |= KRF_INSTALLED;
-       }
+       krt_learn_announce_update(p, best);
       else
-       {
-         n->routes = NULL;
-         krt_learn_announce_delete(p, n);
-         n->n.flags &= ~KRF_INSTALLED;
-       }
+       krt_learn_announce_delete(p, n);
     }
 }
 
@@ -550,7 +576,7 @@ krt_dump(struct proto *P)
 static void
 krt_dump_attrs(rte *e)
 {
-  debug(" [m=%d,p=%d,t=%d]", e->u.krt.metric, e->u.krt.proto, e->u.krt.type);
+  debug(" [m=%d,p=%d]", e->u.krt.metric, e->u.krt.proto);
 }
 
 #endif
@@ -559,12 +585,6 @@ krt_dump_attrs(rte *e)
  *     Routes
  */
 
-#ifdef CONFIG_ALL_TABLES_AT_ONCE
-static timer *krt_scan_timer;
-static int krt_instance_count;
-static list krt_instance_list;
-#endif
-
 static void
 krt_flush_routes(struct krt_proto *p)
 {
@@ -575,21 +595,58 @@ krt_flush_routes(struct krt_proto *p)
     {
       net *n = (net *) f;
       rte *e = n->routes;
-      if (e)
+      if (rte_is_valid(e) && (n->n.flags & KRF_INSTALLED))
        {
-         rta *a = e->attrs;
-         if ((n->n.flags & KRF_INSTALLED) &&
-             a->source != RTS_DEVICE && a->source != RTS_INHERIT)
-           {
-             /* FIXME: this does not work if gw is changed in export filter */
-             krt_replace_rte(p, e->net, NULL, e, NULL);
-             n->n.flags &= ~KRF_INSTALLED;
-           }
+         /* FIXME: this does not work if gw is changed in export filter */
+         krt_replace_rte(p, e->net, NULL, e, NULL);
+         n->n.flags &= ~KRF_INSTALLED;
        }
     }
   FIB_WALK_END;
 }
 
+static struct rte *
+krt_export_net(struct krt_proto *p, net *net, rte **rt_free, ea_list **tmpa)
+{
+  struct announce_hook *ah = p->p.main_ahook;
+  struct filter *filter = ah->out_filter;
+  rte *rt;
+
+  if (p->p.accept_ra_types == RA_MERGED)
+    return rt_export_merged(ah, net, rt_free, tmpa, krt_filter_lp, 1);
+
+  rt = net->routes;
+  *rt_free = NULL;
+
+  if (!rte_is_valid(rt))
+    return NULL;
+
+  if (filter == FILTER_REJECT)
+    return NULL;
+
+  struct proto *src = rt->attrs->src->proto;
+  *tmpa = src->make_tmp_attrs ? src->make_tmp_attrs(rt, krt_filter_lp) : NULL;
+
+  /* We could run krt_import_control() here, but it is already handled by KRF_INSTALLED */
+
+  if (filter == FILTER_ACCEPT)
+    goto accept;
+
+  if (f_run(filter, &rt, tmpa, krt_filter_lp, FF_FORCE_TMPATTR | FF_SILENT) > F_ACCEPT)
+    goto reject;
+
+
+accept:
+  if (rt != net->routes)
+    *rt_free = rt;
+  return rt;
+
+reject:
+  if (rt != net->routes)
+    rte_free(rt);
+  return NULL;
+}
+
 static int
 krt_same_dest(rte *k, rte *e)
 {
@@ -618,7 +675,6 @@ krt_same_dest(rte *k, rte *e)
 void
 krt_got_route(struct krt_proto *p, rte *e)
 {
-  rte *old;
   net *net = e->net;
   int verdict;
 
@@ -638,7 +694,7 @@ krt_got_route(struct krt_proto *p, rte *e)
        krt_learn_scan(p, e);
       else
        {
-         krt_trace_in_rl(&rl_alien_ignored, p, e, "[alien] ignored");
+         krt_trace_in_rl(&rl_alien, p, e, "[alien] ignored");
          rte_free(e);
        }
       return;
@@ -654,15 +710,33 @@ krt_got_route(struct krt_proto *p, rte *e)
       return;
     }
 
-  old = net->routes;
-  if ((net->n.flags & KRF_INSTALLED) && old)
+  if (!p->ready)
     {
-      /* There may be changes in route attributes, we ignore that.
-         Also, this does not work well if gw is changed in export filter */
-      if ((net->n.flags & KRF_SYNC_ERROR) || ! krt_same_dest(e, old))
+      /* We wait for the initial feed to have correct KRF_INSTALLED flag */
+      verdict = KRF_IGNORE;
+      goto sentenced;
+    }
+
+  if (net->n.flags & KRF_INSTALLED)
+    {
+      rte *new, *rt_free;
+      ea_list *tmpa;
+
+      new = krt_export_net(p, net, &rt_free, &tmpa);
+
+      /* TODO: There also may be changes in route eattrs, we ignore that for now. */
+
+      if (!new)
+       verdict = KRF_DELETE;
+      else if ((net->n.flags & KRF_SYNC_ERROR) || !krt_same_dest(e, new))
        verdict = KRF_UPDATE;
       else
        verdict = KRF_SEEN;
+
+      if (rt_free)
+       rte_free(rt_free);
+
+      lp_flush(krt_filter_lp);
     }
   else
     verdict = KRF_DELETE;
@@ -683,25 +757,6 @@ krt_got_route(struct krt_proto *p, rte *e)
     rte_free(e);
 }
 
-static inline int
-krt_export_rte(struct krt_proto *p, rte **new, ea_list **tmpa)
-{
-  struct filter *filter = p->p.main_ahook->out_filter;
-
-  if (! *new)
-    return 0;
-
-  if (filter == FILTER_REJECT)
-    return 0;
-
-  if (filter == FILTER_ACCEPT)
-    return 1;
-
-  struct proto *src = (*new)->attrs->proto;
-  *tmpa = src->make_tmp_attrs ? src->make_tmp_attrs(*new, krt_filter_lp) : NULL;
-  return f_run(filter, new, tmpa, krt_filter_lp, FF_FORCE_TMPATTR) <= F_ACCEPT;
-}
-
 static void
 krt_prune(struct krt_proto *p)
 {
@@ -712,7 +767,7 @@ krt_prune(struct krt_proto *p)
     {
       net *n = (net *) f;
       int verdict = f->flags & KRF_VERDICT_MASK;
-      rte *new, *new0, *old;
+      rte *new, *old, *rt_free = NULL;
       ea_list *tmpa = NULL;
 
       if (verdict == KRF_UPDATE || verdict == KRF_DELETE)
@@ -724,16 +779,18 @@ krt_prune(struct krt_proto *p)
       else
        old = NULL;
 
-      new = new0 = n->routes;
       if (verdict == KRF_CREATE || verdict == KRF_UPDATE)
        {
          /* We have to run export filter to get proper 'new' route */
-         if (! krt_export_rte(p, &new, &tmpa))
-           {
-             /* Route rejected, should not happen (KRF_INSTALLED) but to be sure .. */
-             verdict = (verdict == KRF_CREATE) ? KRF_IGNORE : KRF_DELETE; 
-           }
+         new = krt_export_net(p, n, &rt_free, &tmpa);
+
+         if (!new)
+           verdict = (verdict == KRF_CREATE) ? KRF_IGNORE : KRF_DELETE;
+         else
+           tmpa = ea_append(tmpa, new->attrs->eattrs);
        }
+      else
+       new = NULL;
 
       switch (verdict)
        {
@@ -762,8 +819,8 @@ krt_prune(struct krt_proto *p)
 
       if (old)
        rte_free(old);
-      if (new != new0)
-       rte_free(new);
+      if (rt_free)
+       rte_free(rt_free);
       lp_flush(krt_filter_lp);
       f->flags &= ~KRF_VERDICT_MASK;
     }
@@ -773,7 +830,9 @@ krt_prune(struct krt_proto *p)
   if (KRT_CF->learn)
     krt_learn_prune(p);
 #endif
-  p->initialized = 1;
+
+  if (p->ready)
+    p->initialized = 1;
 }
 
 void
@@ -811,34 +870,99 @@ krt_got_route_async(struct krt_proto *p, rte *e, int new)
  *     Periodic scanning
  */
 
+
+#ifdef CONFIG_ALL_TABLES_AT_ONCE
+
+static timer *krt_scan_timer;
+static int krt_scan_count;
+
 static void
 krt_scan(timer *t UNUSED)
 {
   struct krt_proto *p;
 
   kif_force_scan();
-#ifdef CONFIG_ALL_TABLES_AT_ONCE
+
+  /* We need some node to decide whether to print the debug messages or not */
+  p = SKIP_BACK(struct krt_proto, krt_node, HEAD(krt_proto_list));
+  KRT_TRACE(p, D_EVENTS, "Scanning routing table");
+
+  krt_do_scan(NULL);
+
+  void *q;
+  WALK_LIST(q, krt_proto_list)
   {
-    void *q;
-    /* We need some node to decide whether to print the debug messages or not */
-    p = SKIP_BACK(struct krt_proto, instance_node, HEAD(krt_instance_list));
-    if (p->instance_node.next)
-      KRT_TRACE(p, D_EVENTS, "Scanning routing table");
-    krt_do_scan(NULL);
-    WALK_LIST(q, krt_instance_list)
-      {
-       p = SKIP_BACK(struct krt_proto, instance_node, q);
-       krt_prune(p);
-      }
+    p = SKIP_BACK(struct krt_proto, krt_node, q);
+    krt_prune(p);
   }
+}
+
+static void
+krt_scan_timer_start(struct krt_proto *p)
+{
+  if (!krt_scan_count)
+    krt_scan_timer = tm_new_set(krt_pool, krt_scan, NULL, 0, KRT_CF->scan_time);
+
+  krt_scan_count++;
+
+  tm_start(krt_scan_timer, 1);
+}
+
+static void
+krt_scan_timer_stop(struct krt_proto *p UNUSED)
+{
+  krt_scan_count--;
+
+  if (!krt_scan_count)
+  {
+    rfree(krt_scan_timer);
+    krt_scan_timer = NULL;
+  }
+}
+
+static void
+krt_scan_timer_kick(struct krt_proto *p UNUSED)
+{
+  tm_start(krt_scan_timer, 0);
+}
+
 #else
-  p = t->data;
+
+static void
+krt_scan(timer *t)
+{
+  struct krt_proto *p = t->data;
+
+  kif_force_scan();
+
   KRT_TRACE(p, D_EVENTS, "Scanning routing table");
   krt_do_scan(p);
   krt_prune(p);
-#endif
 }
 
+static void
+krt_scan_timer_start(struct krt_proto *p)
+{
+  p->scan_timer = tm_new_set(p->p.pool, krt_scan, p, 0, KRT_CF->scan_time);
+  tm_start(p->scan_timer, 1);
+}
+
+static void
+krt_scan_timer_stop(struct krt_proto *p)
+{
+  tm_stop(p->scan_timer);
+}
+
+static void
+krt_scan_timer_kick(struct krt_proto *p)
+{
+  tm_start(p->scan_timer, 0);
+}
+
+#endif
+
+
+
 
 /*
  *     Updates
@@ -874,16 +998,31 @@ krt_store_tmp_attrs(rte *rt, struct ea_list *attrs)
 }
 
 static int
-krt_import_control(struct proto *P, rte **new, ea_list **attrs, struct linpool *pool)
+krt_import_control(struct proto *P, rte **new, ea_list **attrs UNUSED, struct linpool *pool UNUSED)
 {
   struct krt_proto *p = (struct krt_proto *) P;
   rte *e = *new;
 
-  if (e->attrs->proto == P)
+  if (e->attrs->src->proto == P)
+  {
+#ifdef CONFIG_SINGLE_ROUTE
+    /*
+     * Implicit withdraw - when the imported kernel route becomes the best one,
+     * we know that the previous one exported to the kernel was already removed,
+     * but if we processed the update as usual, we would send withdraw to the
+     * kernel, which would remove the new imported route instead.
+     *
+     * We will remove KRT_INSTALLED flag, which stops such withdraw to be
+     * processed in krt_rt_notify() and krt_replace_rte().
+     */
+    if (e == e->net->routes)
+      e->net->n.flags &= ~KRF_INSTALLED;
+#endif
     return -1;
+  }
 
-  if (!KRT_CF->devroutes && 
-      (e->attrs->dest == RTD_DEVICE) && 
+  if (!KRT_CF->devroutes &&
+      (e->attrs->dest == RTD_DEVICE) &&
       (e->attrs->source != RTS_STATIC_DEVICE))
     return -1;
 
@@ -894,12 +1033,12 @@ krt_import_control(struct proto *P, rte **new, ea_list **attrs, struct linpool *
 }
 
 static void
-krt_notify(struct proto *P, struct rtable *table UNUSED, net *net,
-          rte *new, rte *old, struct ea_list *eattrs)
+krt_rt_notify(struct proto *P, struct rtable *table UNUSED, net *net,
+             rte *new, rte *old, struct ea_list *eattrs)
 {
   struct krt_proto *p = (struct krt_proto *) P;
 
-  if (shutting_down)
+  if (config->shutdown)
     return;
   if (!(net->n.flags & KRF_INSTALLED))
     old = NULL;
@@ -911,6 +1050,49 @@ krt_notify(struct proto *P, struct rtable *table UNUSED, net *net,
     krt_replace_rte(p, net, new, old, eattrs);
 }
 
+static void
+krt_if_notify(struct proto *P, uint flags, struct iface *iface UNUSED)
+{
+  struct krt_proto *p = (struct krt_proto *) P;
+
+  /*
+   * When interface went down, we should remove routes to it. In the ideal world,
+   * OS kernel would send us route removal notifications in such cases, but we
+   * cannot rely on it as it is often not true. E.g. Linux kernel removes related
+   * routes when an interface went down, but it does not notify userspace about
+   * that. To be sure, we just schedule a scan to ensure synchronization.
+   */
+
+  if ((flags & IF_CHANGE_DOWN) && KRT_CF->learn)
+    krt_scan_timer_kick(p);
+}
+
+static int
+krt_reload_routes(struct proto *P)
+{
+  struct krt_proto *p = (struct krt_proto *) P;
+
+  /* Although we keep learned routes in krt_table, we rather schedule a scan */
+
+  if (KRT_CF->learn)
+  {
+    p->reload = 1;
+    krt_scan_timer_kick(p);
+  }
+
+  return 1;
+}
+
+static void
+krt_feed_end(struct proto *P)
+{
+  struct krt_proto *p = (struct krt_proto *) P;
+
+  p->ready = 1;
+  krt_scan_timer_kick(p);
+}
+
+
 static int
 krt_rte_same(rte *a, rte *b)
 {
@@ -926,67 +1108,47 @@ krt_rte_same(rte *a, rte *b)
 struct krt_config *krt_cf;
 
 static struct proto *
-krt_init(struct proto_config *c)
+krt_init(struct proto_config *C)
 {
-  struct krt_proto *p = proto_new(c, sizeof(struct krt_proto));
+  struct krt_proto *p = proto_new(C, sizeof(struct krt_proto));
+  struct krt_config *c = (struct krt_config *) C;
 
-  p->p.accept_ra_types = RA_OPTIMAL;
+  p->p.accept_ra_types = c->merge_paths ? RA_MERGED : RA_OPTIMAL;
+  p->p.merge_limit = c->merge_paths;
+  p->p.import_control = krt_import_control;
+  p->p.rt_notify = krt_rt_notify;
+  p->p.if_notify = krt_if_notify;
+  p->p.reload_routes = krt_reload_routes;
+  p->p.feed_end = krt_feed_end;
   p->p.make_tmp_attrs = krt_make_tmp_attrs;
   p->p.store_tmp_attrs = krt_store_tmp_attrs;
-  p->p.import_control = krt_import_control;
-  p->p.rt_notify = krt_notify;
   p->p.rte_same = krt_rte_same;
 
   krt_sys_init(p);
   return &p->p;
 }
 
-static timer *
-krt_start_timer(struct krt_proto *p)
-{
-  timer *t;
-
-  t = tm_new(p->krt_pool);
-  t->hook = krt_scan;
-  t->data = p;
-  t->recurrent = KRT_CF->scan_time;
-  tm_start(t, 0);
-  return t;
-}
-
 static int
 krt_start(struct proto *P)
 {
   struct krt_proto *p = (struct krt_proto *) P;
-  int first = 1;
 
-#ifdef CONFIG_ALL_TABLES_AT_ONCE
-  if (!krt_instance_count++)
-    init_list(&krt_instance_list);
-  else
-    first = 0;
-  p->krt_pool = krt_pool;
-  add_tail(&krt_instance_list, &p->instance_node);
-#else
-  p->krt_pool = P->pool;
-#endif
+  add_tail(&krt_proto_list, &p->krt_node);
 
 #ifdef KRT_ALLOW_LEARN
   krt_learn_init(p);
 #endif
 
-  krt_sys_start(p, first);
+  if (!krt_sys_start(p))
+  {
+    rem_node(&p->krt_node);
+    return PS_START;
+  }
 
-  /* Start periodic routing table scanning */
-#ifdef CONFIG_ALL_TABLES_AT_ONCE
-  if (first)
-    krt_scan_timer = krt_start_timer(p);
-  else
-    tm_start(krt_scan_timer, 0);
-  p->scan_timer = krt_scan_timer;
-#else
-  p->scan_timer = krt_start_timer(p);
-#endif
+  krt_scan_timer_start(p);
+
+  if (P->gr_recovery && KRT_CF->graceful_restart)
+    P->gr_wait = 1;
 
   return PS_UP;
 }
@@ -995,26 +1157,21 @@ static int
 krt_shutdown(struct proto *P)
 {
   struct krt_proto *p = (struct krt_proto *) P;
-  int last = 1;
 
-#ifdef CONFIG_ALL_TABLES_AT_ONCE
-  rem_node(&p->instance_node);
-  if (--krt_instance_count)
-    last = 0;
-  else
-#endif
-    tm_stop(p->scan_timer);
+  krt_scan_timer_stop(p);
 
   /* FIXME we should flush routes even when persist during reconfiguration */
   if (p->initialized && !KRT_CF->persist)
     krt_flush_routes(p);
 
-  krt_sys_shutdown(p, last);
+  p->ready = 0;
+  p->initialized = 0;
 
-#ifdef CONFIG_ALL_TABLES_AT_ONCE
-  if (last)
-    rfree(krt_scan_timer);
-#endif
+  if (p->p.proto_state == PS_START)
+    return PS_DOWN;
+
+  krt_sys_shutdown(p);
+  rem_node(&p->krt_node);
 
   return PS_DOWN;
 }
@@ -1028,8 +1185,9 @@ krt_reconfigure(struct proto *p, struct proto_config *new)
   if (!krt_sys_reconfigure((struct krt_proto *) p, n, o))
     return 0;
 
-  /* persist needn't be the same */
-  return o->scan_time == n->scan_time && o->learn == n->learn && o->devroutes == n->devroutes;
+  /* persist, graceful restart need not be the same */
+  return o->scan_time == n->scan_time && o->learn == n->learn &&
+    o->devroutes == n->devroutes && o->merge_paths == n->merge_paths;
 }
 
 static void
@@ -1063,7 +1221,7 @@ krt_init_config(int class)
     cf_error("Kernel protocol already defined");
 #endif
 
-  krt_cf = (struct krt_config *) proto_config_new(&proto_unix_kernel, sizeof(struct krt_config), class);
+  krt_cf = (struct krt_config *) proto_config_new(&proto_unix_kernel, class);
   krt_cf->scan_time = 60;
 
   krt_sys_init_config(krt_cf);
@@ -1084,7 +1242,7 @@ krt_copy_config(struct proto_config *dest, struct proto_config *src)
 }
 
 static int
-krt_get_attr(eattr * a, byte * buf, int buflen UNUSED)
+krt_get_attr(eattr *a, byte *buf, int buflen)
 {
   switch (a->id)
   {
@@ -1096,35 +1254,28 @@ krt_get_attr(eattr * a, byte * buf, int buflen UNUSED)
     bsprintf(buf, "metric");
     return GA_NAME;
 
-  case EA_KRT_PREFSRC:
-    bsprintf(buf, "prefsrc");
-    return GA_NAME;
-
-  case EA_KRT_REALM:
-    bsprintf(buf, "realm");
-    return GA_NAME;
-
   default:
-    return GA_UNKNOWN;
+    return krt_sys_get_attr(a, buf, buflen);
   }
 }
 
 
 struct protocol proto_unix_kernel = {
-  name:                "Kernel",
-  template:    "kernel%d",
-  attr_class:  EAP_KRT,
-  preference:  DEF_PREF_INHERITED,
-  preconfig:   krt_preconfig,
-  postconfig:  krt_postconfig,
-  init:                krt_init,
-  start:       krt_start,
-  shutdown:    krt_shutdown,
-  reconfigure: krt_reconfigure,
-  copy_config: krt_copy_config,
-  get_attr:    krt_get_attr,
+  .name =              "Kernel",
+  .template =          "kernel%d",
+  .attr_class =                EAP_KRT,
+  .preference =                DEF_PREF_INHERITED,
+  .config_size =       sizeof(struct krt_config),
+  .preconfig =         krt_preconfig,
+  .postconfig =                krt_postconfig,
+  .init =              krt_init,
+  .start =             krt_start,
+  .shutdown =          krt_shutdown,
+  .reconfigure =       krt_reconfigure,
+  .copy_config =       krt_copy_config,
+  .get_attr =          krt_get_attr,
 #ifdef KRT_ALLOW_LEARN
-  dump:                krt_dump,
-  dump_attrs:  krt_dump_attrs,
+  .dump =              krt_dump,
+  .dump_attrs =                krt_dump_attrs,
 #endif
 };