[PATCH v2 1/2] Babel: Add option to support ecmp routes

Daniel Gröber dxld at darkboxed.org
Mon May 9 14:38:59 CEST 2022


---
 doc/bird.sgml        | 25 ++++++++++++++++
 proto/babel/babel.c  | 70 +++++++++++++++++++++++++++++++++++++-------
 proto/babel/babel.h  |  5 ++++
 proto/babel/config.Y |  3 ++
 4 files changed, 92 insertions(+), 11 deletions(-)

diff --git a/doc/bird.sgml b/doc/bird.sgml
index 1580facd..d1e6376b 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -1865,6 +1865,7 @@ protocol babel [<name>] {
 	ipv4 { <channel config> };
 	ipv6 [sadr] { <channel config> };
         randomize router id <switch>;
+	ecmp <switch> [limit <num>];
 	interface <interface pattern> {
 		type <wired|wireless>;
 		rxcost <number>;
@@ -1909,6 +1910,18 @@ protocol babel [<name>] {
       router ID every time it starts up, which avoids this problem at the cost
       of not having stable router IDs in the network. Default: no.
 
+      <tag><label id="babel-ecmp">ecmp <m>switch</m> [limit <m>number</m>]</tag>
+
+      Determines whether babel will emit ECMP (equal-cost multipath)
+      routes, allowing to load-balancing traffic across multiple paths. If
+      enabled the maximum number of next-hops to allow can be specified,
+      defaulting to 16.
+
+      When neibours are using a dynamic link-quality metric this is
+      unlikely to be useful. For best results <ref id="babel-type"
+      name="type wired"> should be used throughout the network to get what
+      amounts to a hop count metric.
+
       <tag><label id="babel-type">type wired|wireless </tag>
       This option specifies the interface type: Wired or wireless. On wired
       interfaces a neighbor is considered unreachable after a small number of
@@ -1928,6 +1941,18 @@ protocol babel [<name>] {
       selection and not local route selection. Default: 96 for wired interfaces,
       256 for wireless.
 
+      <tag><label id="babel-ecmp">ecmp <m>switch</m> [limit <m>number</m>]</tag>
+
+      Determines whether babel will emit ECMP (equal-cost multipath)
+      routes, allowing to load-balancing traffic across multiple paths. If
+      enabled the maximum number of next-hops to allow can be specified,
+      defaulting to 16.
+
+      When neibours are using a dynamic link-quality metric this is
+      unlikely to be useful. For best results <ref id="babel-type"
+      name="type wired"> should be used throughout the network to get what
+      amounts to a hop count metric.
+
       <tag><label id="babel-limit">limit <m/num/</tag>
       BIRD keeps track of received Hello messages from each neighbor to
       establish neighbor reachability. For wired type interfaces, this option
diff --git a/proto/babel/babel.c b/proto/babel/babel.c
index 4a7d550f..01ea3a44 100644
--- a/proto/babel/babel.c
+++ b/proto/babel/babel.c
@@ -623,6 +623,34 @@ done:
   }
 }
 
+/**
+ * babel_nexthop_insert - add next_hop of route to nexthop list
+ * @p: Babel protocol instance
+ * @r: Babel route
+ * @nhs: nexthop list head to append onto
+ * @nh: freshly allocated buffer to fill
+ */
+static void
+babel_nexthop_insert(
+  struct babel_proto *p,
+  struct babel_route *r,
+  struct nexthop **nhs,
+  struct nexthop *nh)
+{
+  nh->gw = r->next_hop;
+  nh->iface = r->neigh->ifa->iface;
+
+  /*
+   * If we cannot find a reachable neighbour, set the entry to be onlink. This
+   * makes it possible to, e.g., assign /32 addresses on a mesh interface and
+   * have routing work.
+   */
+  if (!neigh_find(&p->p, r->next_hop, r->neigh->ifa->iface, 0))
+    nh->flags = RNF_ONLINK;
+
+  nexthop_insert(nhs, nh);
+}
+
 /**
  * babel_announce_rte - announce selected route to the core
  * @p: Babel protocol instance
@@ -635,6 +663,7 @@ done:
 static void
 babel_announce_rte(struct babel_proto *p, struct babel_entry *e)
 {
+  struct babel_config *cf = (void *) p->p.cf;
   struct babel_route *r = e->selected;
   struct channel *c = (e->n.addr->type == NET_IP4) ? p->ip4_channel : p->ip6_channel;
 
@@ -645,18 +674,24 @@ babel_announce_rte(struct babel_proto *p, struct babel_entry *e)
       .source = RTS_BABEL,
       .scope = SCOPE_UNIVERSE,
       .dest = RTD_UNICAST,
-      .from = r->neigh->addr,
-      .nh.gw = r->next_hop,
-      .nh.iface = r->neigh->ifa->iface,
     };
 
-    /*
-     * If we cannot find a reachable neighbour, set the entry to be onlink. This
-     * makes it possible to, e.g., assign /32 addresses on a mesh interface and
-     * have routing work.
-     */
-    if (!neigh_find(&p->p, r->next_hop, r->neigh->ifa->iface, 0))
-      a0.nh.flags = RNF_ONLINK;
+    struct nexthop *nhs = NULL;
+    babel_nexthop_insert(p, r, &nhs, allocz(sizeof(struct nexthop)));
+    int num_nexthops = 1;
+
+    struct babel_route *cr;
+    WALK_LIST(cr, e->routes) {
+      if (cr == r || !cr->feasible || cr->metric != r->metric)
+	continue;
+
+      if (num_nexthops++ >= cf->max_nexthops)
+	break;
+
+      babel_nexthop_insert(p, cr, &nhs, allocz(sizeof(struct nexthop)));
+    }
+
+    a0.nh = *nhs;
 
     rta *a = rta_lookup(&a0);
     rte *rte = rte_get_temp(a);
@@ -736,6 +771,7 @@ babel_announce_retraction(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)
 {
+  struct babel_config *cf = (void *) p->p.cf;
   struct babel_route *r, *best = e->selected;
 
   /* Shortcut if only non-best was modified */
@@ -744,8 +780,10 @@ babel_select_route(struct babel_proto *p, struct babel_entry *e, struct babel_ro
     /* Either select modified route, or keep old best route */
     if ((mod->metric < (best ? best->metric : BABEL_INFINITY)) && mod->feasible)
       best = mod;
-    else
+    else if (cf->max_nexthops == 1)
       return;
+    /* With ecmp one of the non-selected but equal metric routes might have
+     * changed so contine on with the announcement in that case. */
   }
   else
   {
@@ -1879,6 +1917,16 @@ babel_reconfigure_iface(struct babel_proto *p, struct babel_iface *ifa, struct b
   if (ifa->up)
     babel_iface_kick_timer(ifa);
 
+  /* Update all routes to refresh ecmp settings. */
+  FIB_WALK(&p->ip6_rtable, struct babel_entry, e) {
+    babel_select_route(p, e, NULL);
+  }
+  FIB_WALK_END;
+  FIB_WALK(&p->ip4_rtable, struct babel_entry, e) {
+    babel_select_route(p, e, NULL);
+  }
+  FIB_WALK_END;
+
   return 1;
 }
 
diff --git a/proto/babel/babel.h b/proto/babel/babel.h
index 84feb085..46e268fd 100644
--- a/proto/babel/babel.h
+++ b/proto/babel/babel.h
@@ -62,6 +62,8 @@
 #define BABEL_OVERHEAD		(IP6_HEADER_LENGTH+UDP_HEADER_LENGTH)
 #define BABEL_MIN_MTU		(512 + BABEL_OVERHEAD)
 
+#define BABEL_DEFAULT_ECMP_LIMIT	16
+
 #define BABEL_AUTH_NONE			0
 #define BABEL_AUTH_MAC			1
 
@@ -120,6 +122,9 @@ struct babel_config {
   list iface_list;			/* List of iface configs (struct babel_iface_config) */
   uint hold_time;			/* Time to hold stale entries and unreachable routes */
   u8 randomize_router_id;
+  u8 max_nexthops;                      /* Maximum number of nexthops to
+					   install.  Defaults to 1. It's >1 if
+					   ECMP is enabled. */
 
   struct channel_config *ip4_channel;
   struct channel_config *ip6_channel;
diff --git a/proto/babel/config.Y b/proto/babel/config.Y
index 05210fa4..92e754cd 100644
--- a/proto/babel/config.Y
+++ b/proto/babel/config.Y
@@ -36,6 +36,7 @@ babel_proto_start: proto_start BABEL
   this_proto = proto_config_new(&proto_babel, $1);
   init_list(&BABEL_CFG->iface_list);
   BABEL_CFG->hold_time = 1 S_;
+  BABEL_CFG->max_nexthops = 1;
 };
 
 babel_proto_item:
@@ -43,6 +44,8 @@ babel_proto_item:
  | proto_channel
  | INTERFACE babel_iface
  | RANDOMIZE ROUTER ID bool { BABEL_CFG->randomize_router_id = $4; }
+ | ECMP bool { BABEL_CFG->max_nexthops = $2 ? BABEL_DEFAULT_ECMP_LIMIT : 1; }
+ | ECMP bool LIMIT expr { BABEL_CFG->max_nexthops = $2 ? $4 : 1; }
  ;
 
 babel_proto_opts:
-- 
2.30.2



More information about the Bird-users mailing list