This allows for filtering routes from specific interfaces and
neighbours. With the current internal route selection proto babel exports
only up to one route and an admin cannot do fine-grained filtering.

To fix this we rip out the internal route selection entirely and put them
all into the bird's nest instead.

This appears to not actually be a breaking change as route announcement was
already based on which routes bird would send back to babel_rt_notify
filters shouldn't be able to tell the difference between a single and
multiple routes AFAIK.
---
Changes in v3:
 - Subsume FIB_RESTART v2 patch: instead of restarting FIB iteration
   we keep lists of actions to perform after FIB iteration is finished.

 proto/babel/babel.c | 269 +++++++++++++++++++-------------------------
 proto/babel/babel.h |   8 +-
 2 files changed, 120 insertions(+), 157 deletions(-)

diff --git a/proto/babel/babel.c b/proto/babel/babel.c
index ff8b6b52..087fd95b 100644
--- a/proto/babel/babel.c
+++ b/proto/babel/babel.c
@@ -29,10 +29,11 @@
  * possible routes for the prefix are tracked as &babel_route entries and the
  * feasibility distance is maintained through &babel_source structures.
  *
- * The main route selection is done in babel_select_route(). This is called 
when
- * an entry is updated by receiving updates from the network or when modified 
by
- * internal timers. The function selects from feasible and reachable routes the
- * one with the lowest metric to be announced to the core.
+ * The main route selection is done by the bird nest (heh). For each prefix we
+ * export all feasible routes to the core with a distinct source (rte_src) per
+ * neihbour. Bird will then notify us of the optimal one by calling
+ * babel_rt_notify and we remember which neighbour it selected in
+ * babel_entry.selected_nbr.
  */
 
 #include <stdlib.h>
@@ -50,7 +51,7 @@ static inline int ge_mod64k(uint a, uint b)
 { return (u16)(a - b) < 0x8000; }
 
 static void babel_expire_requests(struct babel_proto *p, struct babel_entry 
*e);
-static void babel_select_route(struct babel_proto *p, struct babel_entry *e, 
struct babel_route *mod);
+static void babel_announce_rte(struct babel_proto *p, struct babel_entry *e, 
struct babel_route *r);
 static inline void babel_announce_retraction(struct babel_proto *p, struct 
babel_entry *e);
 static void babel_send_route_request(struct babel_proto *p, struct babel_entry 
*e, struct babel_neighbor *n);
 static void babel_send_seqno_request(struct babel_proto *p, struct babel_entry 
*e, struct babel_seqno_request *sr, struct babel_neighbor *n);
@@ -164,13 +165,19 @@ babel_get_route(struct babel_proto *p, struct babel_entry 
*e, struct babel_neigh
   return r;
 }
 
+static struct babel_route *
+babel_get_selected_route(struct babel_proto *p, struct babel_entry *e)
+{
+  if (e->selected_nbr)
+    return babel_get_route(p, e, e->selected_nbr);
+  return NULL;
+}
+
 static inline void
 babel_retract_route(struct babel_proto *p, struct babel_route *r)
 {
   r->metric = r->advert_metric = BABEL_INFINITY;
-
-  if (r == r->e->selected)
-    babel_select_route(p, r->e, r);
+  babel_announce_rte(p, r->e, r);
 }
 
 static void
@@ -182,9 +189,6 @@ babel_flush_route(struct babel_proto *p UNUSED, struct 
babel_route *r)
   rem_node(NODE r);
   rem_node(&r->neigh_route);
 
-  if (r->e->selected == r)
-    r->e->selected = NULL;
-
   sl_free(r);
 }
 
@@ -200,6 +204,7 @@ babel_expire_route(struct babel_proto *p, struct 
babel_route *r)
   {
     r->metric = r->advert_metric = BABEL_INFINITY;
     r->expires = current_time() + cf->hold_time;
+    babel_announce_rte(p, r->e, r);
   }
   else
   {
@@ -210,7 +215,7 @@ babel_expire_route(struct babel_proto *p, struct 
babel_route *r)
 static void
 babel_refresh_route(struct babel_proto *p, struct babel_route *r)
 {
-  if (r == r->e->selected)
+  if (r == babel_get_selected_route(p, r->e))
     babel_send_route_request(p, r->e, r->neigh);
 
   r->refresh_time = 0;
@@ -221,16 +226,15 @@ babel_expire_routes_(struct babel_proto *p, struct fib 
*rtable)
 {
   struct babel_config *cf = (void *) p->p.cf;
   struct babel_route *r, *rx;
+  struct babel_entry *retract_head = NULL, *delete_head = NULL;
+  struct babel_route *expire_head = NULL;
   struct fib_iterator fit;
   btime now_ = current_time();
 
-  FIB_ITERATE_INIT(&fit, rtable);
 
-loop:
+  FIB_ITERATE_INIT(&fit, rtable);
   FIB_ITERATE_START(rtable, &fit, struct babel_entry, e)
   {
-    int changed = 0;
-
     WALK_LIST_DELSAFE(r, rx, e->routes)
     {
       if (r->refresh_time && r->refresh_time <= now_)
@@ -238,23 +242,11 @@ loop:
 
       if (r->expires && r->expires <= now_)
       {
-       changed = changed || (r == e->selected);
-       babel_expire_route(p, r);
+       r->expire_next = expire_head;
+       expire_head = r;
       }
     }
 
-    if (changed)
-    {
-      /*
-       * We have to restart the iteration because there may be a cascade of
-       * synchronous events babel_select_route() -> nest table change ->
-       * babel_rt_notify() -> rtable change, invalidating hidden variables.
-       */
-      FIB_ITERATE_PUT(&fit);
-      babel_select_route(p, e, NULL);
-      goto loop;
-    }
-
     /* Clean up stale entries */
     if ((e->valid == BABEL_ENTRY_STALE) && ((e->updated + cf->hold_time) <= 
now_))
       e->valid = BABEL_ENTRY_DUMMY;
@@ -262,9 +254,8 @@ loop:
     /* Clean up unreachable route */
     if (e->unreachable && (!e->valid || (e->router_id == p->router_id)))
     {
-      FIB_ITERATE_PUT(&fit);
-      babel_announce_retraction(p, e);
-      goto loop;
+      e->retract_next = retract_head;
+      retract_head = e;
     }
 
     babel_expire_sources(p, e);
@@ -273,12 +264,25 @@ loop:
     /* Remove empty entries */
     if (!e->valid && EMPTY_LIST(e->routes) && EMPTY_LIST(e->sources) && 
EMPTY_LIST(e->requests))
     {
-      FIB_ITERATE_PUT(&fit);
-      fib_delete(rtable, e);
-      goto loop;
+      e->delete_next = delete_head;
+      delete_head = e;
     }
   }
   FIB_ITERATE_END;
+
+  for (struct babel_route *r_next, *r = expire_head; r ; r = r_next) {
+    r_next = r->expire_next;
+    babel_expire_route(p, r);
+  }
+
+  for (struct babel_entry *e = retract_head; e ; e = e->retract_next) {
+    babel_announce_retraction(p, e);
+  }
+
+  for (struct babel_entry *e_next, *e = delete_head; e ; e = e_next) {
+    e_next = e->delete_next;
+    fib_delete(rtable, e);
+  }
 }
 
 static void
@@ -447,6 +451,7 @@ babel_get_neighbor(struct babel_iface *ifa, ip_addr addr)
 
   nbr = mb_allocz(ifa->pool, sizeof(struct babel_neighbor));
   nbr->ifa = ifa;
+  nbr->src = rt_get_source(&p->p, idm_alloc(&p->src_ids));
   nbr->addr = addr;
   nbr->rxcost = BABEL_INFINITY;
   nbr->txcost = BABEL_INFINITY;
@@ -474,6 +479,8 @@ babel_flush_neighbor(struct babel_proto *p, struct 
babel_neighbor *nbr)
   }
 
   nbr->ifa = NULL;
+
+  idm_free(&p->src_ids, nbr->src->private_id);
   rem_node(NODE nbr);
   mb_free(nbr);
 }
@@ -634,11 +641,40 @@ done:
     WALK_LIST2(r, n, nbr->routes, neigh_route)
     {
       r->metric = babel_compute_metric(nbr, r->advert_metric);
-      babel_select_route(p, r->e, r);
+      babel_announce_rte(p, r->e, r);
     }
   }
 }
 
+static void
+babel_announce_rte_unreachable(struct babel_proto *p, struct babel_entry *e, 
u8 unreachable)
+{
+  struct channel *c = (e->n.addr->type == NET_IP4) ? p->ip4_channel : 
p->ip6_channel;
+  rte *rte = NULL;
+
+  if (unreachable) {
+    /* Unreachable */
+    rta a0 = {
+      .source = RTS_BABEL,
+      .scope = SCOPE_UNIVERSE,
+      .dest = RTD_UNREACHABLE,
+      .pref = 1,
+    };
+
+    rta *a = rta_lookup(&a0);
+    rte = rte_get_temp(a, p->p.main_source);
+  }
+
+  e->unreachable = unreachable;
+
+  /* Unlike the many neighbour routes we only want one unreachable route, for
+   * one it's ugly to have lots of unreachable routes (if we just did this 
right
+   * in babel_announce_rte) but we also loose access to the neibour rte_src id
+   * when the neighbour is deallocated. This happens on a different schedule
+   * than unreachable route removal. */
+  rte_update2(c, e->n.addr, rte, p->p.main_source);
+}
+
 /**
  * babel_announce_rte - announce selected route to the core
  * @p: Babel protocol instance
@@ -649,12 +685,11 @@ done:
  * the entry is valid and ours, the unreachable route is announced instead.
  */
 static void
-babel_announce_rte(struct babel_proto *p, struct babel_entry *e)
+babel_announce_rte(struct babel_proto *p, struct babel_entry *e, struct 
babel_route *r)
 {
-  struct babel_route *r = e->selected;
   struct channel *c = (e->n.addr->type == NET_IP4) ? p->ip4_channel : 
p->ip6_channel;
 
-  if (r)
+  if (r->metric != BABEL_INFINITY && r->feasible)
   {
     rta a0 = {
       .source = RTS_BABEL,
@@ -698,124 +733,27 @@ babel_announce_rte(struct babel_proto *p, struct 
babel_entry *e)
       a0.nh.flags = RNF_ONLINK;
 
     rta *a = rta_lookup(&a0);
-    rte *rte = rte_get_temp(a, p->p.main_source);
+    rte *rte = rte_get_temp(a, r->neigh->src);
 
-    e->unreachable = 0;
-    rte_update2(c, e->n.addr, rte, p->p.main_source);
-  }
-  else if (e->valid && (e->router_id != p->router_id))
-  {
-    /* Unreachable */
-    rta a0 = {
-      .source = RTS_BABEL,
-      .scope = SCOPE_UNIVERSE,
-      .dest = RTD_UNREACHABLE,
-      .pref = 1,
-    };
-
-    rta *a = rta_lookup(&a0);
-    rte *rte = rte_get_temp(a, p->p.main_source);
-
-    e->unreachable = 1;
-    rte_update2(c, e->n.addr, rte, p->p.main_source);
+    rte_update2(c, e->n.addr, rte, r->neigh->src);
+    babel_announce_rte_unreachable(p, e, 0);
   }
   else
-  {
-    /* Retraction */
-    e->unreachable = 0;
-    rte_update2(c, e->n.addr, NULL, p->p.main_source);
-  }
+    rte_update2(c, e->n.addr, NULL, r->neigh->src);
 }
 
 /* Special case of babel_announce_rte() just for retraction */
 static inline void
 babel_announce_retraction(struct babel_proto *p, struct babel_entry *e)
 {
+  TRACE(D_EVENTS, "Retract unreachable route for %N",
+       e->n.addr);
+
   struct channel *c = (e->n.addr->type == NET_IP4) ? p->ip4_channel : 
p->ip6_channel;
   e->unreachable = 0;
   rte_update2(c, e->n.addr, NULL, p->p.main_source);
 }
 
-
-/**
- * babel_select_route - select best route for given route entry
- * @p: Babel protocol instance
- * @e: Babel entry to select the best route for
- * @mod: Babel route that was modified or NULL if unspecified
- *
- * Select the best reachable and feasible route for a given prefix among the
- * routes received from peers, and propagate it to the nest. This just selects
- * the reachable and feasible route with the lowest metric, but keeps selected
- * the old one in case of tie.
- *
- * If no feasible route is available for a prefix that previously had a route
- * selected, a seqno request is sent to try to get a valid route. If the entry
- * is valid and not owned by us, the unreachable route is announced to the nest
- * (to blackhole packets going to it, as per section 2.8). It is later removed
- * by babel_expire_routes(). Otherwise, the route is just removed from the 
nest.
- *
- * Argument @mod is used to optimize best route calculation. When specified, 
the
- * function can assume that only the @mod route was modified to avoid full best
- * route selection and announcement when non-best route was modified in minor
- * way. The caller is advised to not call babel_select_route() when no change 
is
- * done (e.g. periodic route updates) to avoid unnecessary announcements of the
- * same best route. The caller is not required to call the function in case of 
a
- * retraction of a non-best route.
- *
- * Note that the function does not active triggered updates. That is done by
- * babel_rt_notify() when the change is propagated back to Babel.
- */
-static void
-babel_select_route(struct babel_proto *p, struct babel_entry *e, struct 
babel_route *mod)
-{
-  struct babel_route *r, *best = e->selected;
-
-  /* Shortcut if only non-best was modified */
-  if (mod && (mod != best))
-  {
-    /* Either select modified route, or keep old best route */
-    if ((mod->metric < (best ? best->metric : BABEL_INFINITY)) && 
mod->feasible)
-      best = mod;
-    else
-      return;
-  }
-  else
-  {
-    /* Selected route may be modified and no longer admissible */
-    if (!best || (best->metric == BABEL_INFINITY) || !best->feasible)
-      best = NULL;
-
-    /* Find the best feasible route from all routes */
-    WALK_LIST(r, e->routes)
-      if ((r->metric < (best ? best->metric : BABEL_INFINITY)) && r->feasible)
-       best = r;
-  }
-
-  if (best)
-  {
-    if (best != e->selected)
-      TRACE(D_EVENTS, "Picked new route for prefix %N: router-id %lR metric 
%d",
-           e->n.addr, best->router_id, best->metric);
-  }
-  else if (e->selected)
-  {
-    /*
-     * We have lost all feasible routes. We have to broadcast seqno request
-     * (Section 3.8.2.1) and keep unreachable route for a while (section 2.8).
-     * The later is done automatically by babel_announce_rte().
-     */
-
-    TRACE(D_EVENTS, "Lost feasible route for prefix %N", e->n.addr);
-    if (e->valid && (e->selected->router_id == e->router_id))
-      babel_add_seqno_request(p, e, e->selected->router_id, e->selected->seqno 
+ 1, 0, NULL);
-  }
-  else
-    return;
-
-  e->selected = best;
-  babel_announce_rte(p, e);
-}
-
 /*
  *     Functions to send replies
  */
@@ -1300,7 +1238,7 @@ babel_handle_update(union babel_msg *m, struct 
babel_iface *ifa)
   s = babel_find_source(e, msg->router_id); /* for feasibility */
   feasible = babel_is_feasible(s, msg->seqno, msg->metric);
   metric = babel_compute_metric(nbr, msg->metric);
-  best = e->selected;
+  best = babel_get_selected_route(p, e);
 
   /*
    * RFC section 3.8.2.2 - Dealing with unfeasible updates. Generate a one-off
@@ -1338,7 +1276,7 @@ babel_handle_update(union babel_msg *m, struct 
babel_iface *ifa)
     e->updated = current_time();
   }
 
-  babel_select_route(p, e, r);
+  babel_announce_rte(p, e, r);
 }
 
 void
@@ -2003,10 +1941,10 @@ babel_dump_route(struct babel_route *r)
 }
 
 static void
-babel_dump_entry(struct babel_entry *e)
+babel_dump_entry(struct babel_proto *p, struct babel_entry *e)
 {
   struct babel_source *s;
-  struct babel_route *r;
+  struct babel_route *r, *best = babel_get_selected_route(p, e);
 
   debug("Babel: Entry %N:\n", e->n.addr);
 
@@ -2016,7 +1954,7 @@ babel_dump_entry(struct babel_entry *e)
   WALK_LIST(r,e->routes)
   {
     debug(" ");
-    if (r == e->selected) debug("*");
+    if (r == best) debug("*");
     babel_dump_route(r);
   }
 }
@@ -2057,12 +1995,12 @@ babel_dump(struct proto *P)
 
   FIB_WALK(&p->ip4_rtable, struct babel_entry, e)
   {
-    babel_dump_entry(e);
+    babel_dump_entry(p, e);
   }
   FIB_WALK_END;
   FIB_WALK(&p->ip6_rtable, struct babel_entry, e)
   {
-    babel_dump_entry(e);
+    babel_dump_entry(p, e);
   }
   FIB_WALK_END;
 }
@@ -2186,7 +2124,7 @@ babel_show_entries_(struct babel_proto *p, struct fib 
*rtable)
 
   FIB_WALK(rtable, struct babel_entry, e)
   {
-    struct babel_route *r = NULL;
+    struct babel_route *r = NULL, *best = babel_get_selected_route(p, e);
     uint rts = 0, srcs = 0;
     node *n;
 
@@ -2199,7 +2137,7 @@ babel_show_entries_(struct babel_proto *p, struct fib 
*rtable)
     if (e->valid)
       cli_msg(-1025, "%-*N %-23lR %6u %5u %7u %7u", width,
              e->n.addr, e->router_id, e->metric, e->seqno, rts, srcs);
-    else if (r = e->selected)
+    else if (r = best)
       cli_msg(-1025, "%-*N %-23lR %6u %5u %7u %7u", width,
              e->n.addr, r->router_id, r->metric, r->seqno, rts, srcs);
     else
@@ -2236,10 +2174,10 @@ babel_show_routes_(struct babel_proto *p, struct fib 
*rtable)
 
   FIB_WALK(rtable, struct babel_entry, e)
   {
-    struct babel_route *r;
+    struct babel_route *r, *best = babel_get_selected_route(p, e);
     WALK_LIST(r, e->routes)
     {
-      char c = (r == e->selected) ? '*' : (r->feasible ? '+' : ' ');
+      char c = (r == best) ? '*' : (r->feasible ? '+' : ' ');
       btime time = r->expires ? r->expires - current_time() : 0;
       cli_msg(-1025, "%-*N %-25I %-10s %5u %c %5u %7t", width,
              e->n.addr, r->next_hop, r->neigh->ifa->ifname,
@@ -2320,15 +2258,18 @@ babel_rt_notify(struct proto *P, struct channel *c 
UNUSED, struct network *net,
 {
   struct babel_proto *p = (void *) P;
   struct babel_entry *e;
+  struct babel_iface *ifa;
+  struct babel_neighbor *n;
 
   if (new)
   {
     /* Update */
+    uint internal = new->src->proto == P;
     uint rt_seqno;
     uint rt_metric = ea_get_int(new->attrs->eattrs, EA_BABEL_METRIC, 0);
     u64 rt_router_id = 0;
 
-    if (new->src->proto == P)
+    if (internal)
     {
       rt_seqno = ea_find(new->attrs->eattrs, EA_BABEL_SEQNO)->u.data;
       eattr *e = ea_find(new->attrs->eattrs, EA_BABEL_ROUTER_ID);
@@ -2350,6 +2291,18 @@ babel_rt_notify(struct proto *P, struct channel *c 
UNUSED, struct network *net,
 
     e = babel_get_entry(p, net->n.addr);
 
+    while (internal) {
+      ifa = babel_find_iface(p, new->attrs->nh.iface);
+      if (!ifa) break;
+      n = babel_find_neighbor(ifa, new->attrs->from);
+      if (!n) break;
+      e->selected_nbr = n;
+      break;
+    }
+    if (!internal) {
+      e->selected_nbr = NULL;
+    }
+
     /* Activate triggered updates */
     if ((e->valid != BABEL_ENTRY_VALID) ||
        (e->router_id != rt_router_id))
@@ -2373,6 +2326,9 @@ babel_rt_notify(struct proto *P, struct channel *c 
UNUSED, struct network *net,
 
     e->valid = BABEL_ENTRY_STALE;
     e->metric = BABEL_INFINITY;
+    e->selected_nbr = NULL;
+
+    babel_announce_rte_unreachable(p, e, 1);
 
     babel_trigger_update(p);
     e->updated = current_time();
@@ -2464,6 +2420,7 @@ babel_start(struct proto *P)
   p->source_slab = sl_new(P->pool, sizeof(struct babel_source));
   p->msg_slab = sl_new(P->pool, sizeof(struct babel_msg_node));
   p->seqno_slab = sl_new(P->pool, sizeof(struct babel_seqno_request));
+  idm_init(&p->src_ids, P->pool, 8);
 
   p->log_pkt_tbf = (struct tbf){ .rate = 1, .burst = 5 };
 
diff --git a/proto/babel/babel.h b/proto/babel/babel.h
index da8386b3..1d7e19f0 100644
--- a/proto/babel/babel.h
+++ b/proto/babel/babel.h
@@ -25,6 +25,7 @@
 #include "lib/socket.h"
 #include "lib/string.h"
 #include "lib/timer.h"
+#include "lib/idm.h"
 
 #define EA_BABEL_METRIC                EA_CODE(PROTOCOL_BABEL, 0)
 #define EA_BABEL_ROUTER_ID     EA_CODE(PROTOCOL_BABEL, 1)
@@ -174,6 +175,7 @@ struct babel_proto {
   slab *source_slab;
   slab *msg_slab;
   slab *seqno_slab;
+  struct idm src_ids;
 
   struct tbf log_pkt_tbf;              /* TBF for packet messages */
 };
@@ -216,6 +218,7 @@ struct babel_iface {
 struct babel_neighbor {
   node n;
   struct babel_iface *ifa;
+  struct rte_src *src;
 
   ip_addr addr;
   u16 rxcost;                          /* Sent in last IHU */
@@ -256,6 +259,7 @@ struct babel_source {
 struct babel_route {
   node n;
   node neigh_route;
+  struct babel_route *expire_next;
   struct babel_entry    *e;
   struct babel_neighbor *neigh;
 
@@ -281,7 +285,9 @@ struct babel_seqno_request {
 };
 
 struct babel_entry {
-  struct babel_route *selected;
+  struct babel_neighbor *selected_nbr;
+
+  struct babel_entry *retract_next, *delete_next;
 
   list routes;                         /* Routes for this prefix (struct 
babel_route) */
   list sources;                                /* Source entries for this 
prefix (struct babel_source). */
-- 
2.30.2

Reply via email to