From 72aebd69f7cfbcaab33aab1fba48ed05d8bf9c61 Mon Sep 17 00:00:00 2001
From: Heiko Wundram <heiko.wundram@gehrkens.it>
Date: Fri, 11 Sep 2020 21:40:56 +0200
Subject: [PATCH] Update RADV routing protocol to work with device routes to
 assign prefixes to interfaces.

---
 proto/radv/config.Y  |   1 +
 proto/radv/packets.c |   8 +-
 proto/radv/radv.c    | 207 ++++++++++++++++++++++++-------------------
 proto/radv/radv.h    |  11 ++-
 4 files changed, 131 insertions(+), 96 deletions(-)

diff --git a/proto/radv/config.Y b/proto/radv/config.Y
index 84a2de0..3d8aef1 100644
--- a/proto/radv/config.Y
+++ b/proto/radv/config.Y
@@ -61,6 +61,7 @@ radv_proto_item:
      RADV_CFG->trigger_pxlen = $2.len;
      RADV_CFG->trigger_valid = 1;
    }
+ | TRIGGER FILTER bool { RADV_CFG->trigger_keep = !$3; }
  | PROPAGATE ROUTES bool { RADV_CFG->propagate_routes = $3; }
  ;
 
diff --git a/proto/radv/packets.c b/proto/radv/packets.c
index 7d54a82..0b1370f 100644
--- a/proto/radv/packets.c
+++ b/proto/radv/packets.c
@@ -351,14 +351,18 @@ radv_prepare_ra(struct radv_iface *ifa)
   if (radv_prepare_dnssl(ifa, &ic->dnssl_list, &buf, bufend) < 0)
     goto done;
 
-  if (p->fib_up)
+  if (cf->propagate_routes)
   {
     FIB_WALK(&p->routes, n)
     {
       struct radv_route *rt = (void *) n;
 
       /* Skip invalid routes that are past linger timeout but still not pruned */
-      if (!rt->valid && (rt->changed + ic->route_linger_time <= now))
+      if (!cf->trigger_keep && radv_match_trigger(cf, rt->n.prefix, rt->n.pxlen))
+	continue;
+      else if (!rt->valid && (rt->changed + ic->route_linger_time <= now))
+	continue;
+      else if (rt->prefixroute && rt->iface == ifa->iface)
 	continue;
 
       if (radv_prepare_route(ifa, rt, &buf, bufend) < 0)
diff --git a/proto/radv/radv.c b/proto/radv/radv.c
index 22e4b2f..72ffa95 100644
--- a/proto/radv/radv.c
+++ b/proto/radv/radv.c
@@ -28,11 +28,14 @@
  * processes asynchronous events (specified by RA_EV_* codes), and radv_timer(),
  * which triggers sending RAs and computes the next timeout.
  *
- * The RAdv protocol could receive routes (through radv_import_control() and
- * radv_rt_notify()), but only the configured trigger route is tracked (in
- * &active var).  When a radv protocol is reconfigured, the connected routing
- * table is examined (in radv_check_active()) to have proper &active value in
- * case of the specified trigger prefix was changed.
+ * The RAdv protocol could receive routes (through radv_rt_notify()), which it
+ * tracks and assigns device routes as prefixes to the corresponding interface.
+ * A configured trigger route is used to set up the active/inactive state of
+ * of the protocol, which in turn causes updates to interfaces when the settings
+ * change to remove/add prefixes. The routes that are collected by
+ * radv_rt_notify() are also used to export routes to the RA messages, where
+ * the routes that signal device prefixes are filtered on the respective
+ * interface that they are announced on.
  *
  * Supported standards:
  * - RFC 4861 - main RA standard
@@ -90,9 +93,9 @@ static struct radv_prefix_config default_prefix = {
 static struct radv_prefix_config dead_prefix = {
 };
 
-/* Find a corresponding config for the given prefix */
+/* Find a corresponding config for the given prefix from address */
 static struct radv_prefix_config *
-radv_prefix_match(struct radv_iface *ifa, struct ifa *a)
+radv_prefix_match_addr(struct radv_iface *ifa, struct ifa *a)
 {
   struct radv_proto *p = ifa->ra;
   struct radv_config *cf = (struct radv_config *) (p->p.cf);
@@ -112,6 +115,25 @@ radv_prefix_match(struct radv_iface *ifa, struct ifa *a)
   return &default_prefix;
 }
 
+/* Find a corresponding config for the given prefix from route */
+static struct radv_prefix_config *
+radv_prefix_match_route(struct radv_iface *ifa, struct radv_route *rt)
+{
+  struct radv_proto *p = ifa->ra;
+  struct radv_config *cf = (struct radv_config *) (p->p.cf);
+  struct radv_prefix_config *pc;
+
+  WALK_LIST(pc, ifa->cf->pref_list)
+    if ((rt->n.pxlen >= pc->pxlen) && ipa_in_net(rt->n.prefix, pc->prefix, pc->pxlen))
+      return pc;
+
+  WALK_LIST(pc, cf->pref_list)
+    if ((rt->n.pxlen >= pc->pxlen) && ipa_in_net(rt->n.prefix, pc->prefix, pc->pxlen))
+      return pc;
+
+  return &default_prefix;
+}
+
 /*
  * Go through the list of prefixes, compare them with configs and decide if we
  * want them or not.
@@ -120,6 +142,7 @@ static void
 radv_prepare_prefixes(struct radv_iface *ifa)
 {
   struct radv_proto *p = ifa->ra;
+  struct radv_config *cf = (struct radv_config *) (p->p.cf);
   struct radv_prefix *pfx, *next;
 
   /* First mark all the prefixes as unused */
@@ -130,7 +153,7 @@ radv_prepare_prefixes(struct radv_iface *ifa)
   struct ifa *addr;
   WALK_LIST(addr, ifa->iface->addrs)
   {
-    struct radv_prefix_config *pc = radv_prefix_match(ifa, addr);
+    struct radv_prefix_config *pc = radv_prefix_match_addr(ifa, addr);
 
     if (!pc || pc->skip)
       continue;
@@ -146,7 +169,7 @@ radv_prepare_prefixes(struct radv_iface *ifa)
 
     if (!existing)
     {
-      RADV_TRACE(D_EVENTS, "Adding new prefix %I/%d on %s",
+      RADV_TRACE(D_EVENTS, "Adding new prefix %I/%d from address on %s",
 		 addr->prefix, addr->pxlen, ifa->iface->name);
 
       existing = mb_allocz(ifa->pool, sizeof *existing);
@@ -165,6 +188,53 @@ radv_prepare_prefixes(struct radv_iface *ifa)
     existing->cf = pc;
   }
 
+  /* Find all prefixes that exist as device routes. */
+  FIB_WALK(&p->routes, n)
+  {
+    struct radv_route *rt = (void *) n;
+
+    /* Invalid prefixes and explicitly rejected trigger routes don't make it here */
+    if (!cf->trigger_keep && radv_match_trigger(cf, rt->n.prefix, rt->n.pxlen))
+      continue;
+    else if (!rt->valid || !rt->prefixroute || rt->iface != ifa->iface)
+      continue;
+
+    struct radv_prefix_config *pc = radv_prefix_match_route(ifa, rt);
+
+    if (pc->skip)
+      continue;
+
+    /* Do we have it already? */
+    struct radv_prefix *existing = NULL;
+    WALK_LIST(pfx, ifa->prefixes)
+      if ((pfx->len == rt->n.pxlen) && ipa_equal(pfx->prefix, rt->n.prefix))
+      {
+	existing = pfx;
+	break;
+      }
+
+    if (!existing)
+    {
+      RADV_TRACE(D_EVENTS, "Adding new prefix %I/%d from route on %s",
+		 rt->n.prefix, rt->n.pxlen, ifa->iface->name);
+
+      existing = mb_allocz(ifa->pool, sizeof *existing);
+      existing->prefix = rt->n.prefix;
+      existing->len = rt->n.pxlen;
+      add_tail(&ifa->prefixes, NODE existing);
+    }
+
+    /*
+     * Update the information (it may have changed, or even bring a prefix back
+     * to life).
+     */
+    existing->valid = 1;
+    existing->changed = now;
+    existing->mark = 1;
+    existing->cf = pc;
+  }
+  FIB_WALK_END;
+
   WALK_LIST_DELSAFE(pfx, next, ifa->prefixes)
   {
     if (pfx->valid && !pfx->mark)
@@ -384,28 +454,6 @@ radv_ifa_notify(struct proto *P, unsigned flags UNUSED, struct ifa *a)
     radv_iface_notify(ifa, RA_EV_CHANGE);
 }
 
-static inline int radv_net_match_trigger(struct radv_config *cf, net *n)
-{
-  return cf->trigger_valid &&
-    (n->n.pxlen == cf->trigger_pxlen) &&
-    ipa_equal(n->n.prefix, cf->trigger_prefix);
-}
-
-int
-radv_import_control(struct proto *P, rte **new, ea_list **attrs UNUSED, struct linpool *pool UNUSED)
-{
-  // struct radv_proto *p = (struct radv_proto *) P;
-  struct radv_config *cf = (struct radv_config *) (P->cf);
-
-  if (radv_net_match_trigger(cf, (*new)->net))
-    return RIC_PROCESS;
-
-  if (cf->propagate_routes)
-    return RIC_PROCESS;
-  else
-    return RIC_DROP;
-}
-
 static void
 radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs)
 {
@@ -413,27 +461,23 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U
   struct radv_config *cf = (struct radv_config *) (P->cf);
   struct radv_route *rt;
   eattr *ea;
+  struct radv_iface *notify_iface = NULL;
+  u8 notify_all = cf->propagate_routes;
 
-  if (radv_net_match_trigger(cf, n))
+  if (radv_match_trigger(cf, n->n.prefix, n->n.pxlen))
   {
-    u8 old_active = p->active;
-    p->active = !!new;
-
-    if (p->active == old_active)
-      return;
-
-    if (p->active)
-      RADV_TRACE(D_EVENTS, "Triggered");
-    else
-      RADV_TRACE(D_EVENTS, "Suppressed");
+    u8 active = !!new;
+    if (p->active != active) {
+      if (active)
+        RADV_TRACE(D_EVENTS, "Triggered");
+      else
+        RADV_TRACE(D_EVENTS, "Suppressed");
 
-    radv_iface_notify_all(p, RA_EV_CHANGE);
-    return;
+      p->active = active;
+      notify_all = 1;
+    }
   }
 
-  if (!cf->propagate_routes)
-    return;
-
   /*
    * Some other route we want to send (or stop sending). Update the cache,
    * with marking a removed one as dead or creating a new one as needed.
@@ -465,10 +509,14 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U
       lifetime_set = 1;
     }
 
+    u8 prefixroute = new->attrs->iface && new->attrs->dest == RTD_DEVICE && new->attrs->cast == RTC_UNICAST;
+
     rt = fib_get(&p->routes, &n->n.prefix, n->n.pxlen);
 
     /* Ignore update if nothing changed */
     if (rt->valid &&
+	(rt->prefixroute == prefixroute) &&
+	(rt->iface == new->attrs->iface) &&
 	(rt->preference == preference) &&
 	(rt->preference_set == preference_set) &&
 	(rt->lifetime == lifetime) &&
@@ -484,6 +532,18 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U
     rt->preference_set = preference_set;
     rt->lifetime = lifetime;
     rt->lifetime_set = lifetime_set;
+
+    /* If route type changes, signal interfaces */
+    u8 oldprefixroute = rt->prefixroute;
+    struct radv_iface *oldifa = radv_iface_find(p, rt->iface);
+
+    rt->prefixroute = prefixroute;
+    rt->iface = new->attrs->iface;
+    if (rt->prefixroute)
+      notify_iface = radv_iface_find(p, rt->iface);
+
+    if (!notify_all && oldprefixroute && oldifa && notify_iface != oldifa)
+      radv_iface_notify(oldifa, RA_EV_CHANGE);
   }
   else
   {
@@ -496,13 +556,18 @@ radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old U
     /* Invalidate the route */
     rt->valid = 0;
     rt->changed = now;
+    if (rt->prefixroute)
+      notify_iface = radv_iface_find(p, rt->iface);
 
     /* Invalidated route will be pruned eventually */
     bird_clock_t expires = rt->changed + cf->max_linger_time;
     p->prune_time = MIN(p->prune_time, expires);
   }
 
-  radv_iface_notify_all(p, RA_EV_CHANGE);
+  if (notify_all)
+    radv_iface_notify_all(p, RA_EV_CHANGE);
+  else if (notify_iface)
+    radv_iface_notify(notify_iface, RA_EV_CHANGE);
 }
 
 /*
@@ -516,10 +581,6 @@ radv_prune_routes(struct radv_proto *p)
   bird_clock_t next = TIME_INFINITY;
   bird_clock_t expires = 0;
 
-  /* Should not happen */
-  if (!p->fib_up)
-    return;
-
   struct fib_iterator fit;
   FIB_ITERATE_INIT(&fit, &p->routes);
 
@@ -548,25 +609,12 @@ again:
   p->prune_time = next;
 }
 
-static int
-radv_check_active(struct radv_proto *p)
-{
-  struct radv_config *cf = (struct radv_config *) (p->p.cf);
-
-  if (! cf->trigger_valid)
-    return 1;
-
-  return rt_examine(p->p.table, cf->trigger_prefix, cf->trigger_pxlen,
-		    &(p->p), p->p.cf->out_filter);
-}
-
 static struct proto *
 radv_init(struct proto_config *c)
 {
   struct proto *P = proto_new(c, sizeof(struct radv_proto));
 
   P->accept_ra_types = RA_OPTIMAL;
-  P->import_control = radv_import_control;
   P->rt_notify = radv_rt_notify;
   P->if_notify = radv_if_notify;
   P->ifa_notify = radv_ifa_notify;
@@ -574,21 +622,6 @@ radv_init(struct proto_config *c)
   return P;
 }
 
-static void
-radv_set_fib(struct radv_proto *p, int up)
-{
-  if (up == p->fib_up)
-    return;
-
-  if (up)
-    fib_init(&p->routes, p->p.pool, sizeof(struct radv_route), 4, NULL);
-  else
-    fib_free(&p->routes);
-
-  p->fib_up = up;
-  p->prune_time = TIME_INFINITY;
-}
-
 static int
 radv_start(struct proto *P)
 {
@@ -599,8 +632,7 @@ radv_start(struct proto *P)
   p->valid = 1;
   p->active = !cf->trigger_valid;
 
-  p->fib_up = 0;
-  radv_set_fib(p, cf->propagate_routes);
+  fib_init(&p->routes, p->p.pool, sizeof(struct radv_route), 0, NULL);
   p->prune_time = TIME_INFINITY;
 
   return PS_UP;
@@ -637,15 +669,8 @@ radv_reconfigure(struct proto *P, struct proto_config *c)
   struct radv_config *old = (struct radv_config *) (P->cf);
   struct radv_config *new = (struct radv_config *) c;
 
-  P->cf = c; /* radv_check_active() requires proper P->cf */
-  p->active = radv_check_active(p);
-
-  /* Allocate or free FIB */
-  radv_set_fib(p, new->propagate_routes);
-
-  /* We started to accept routes so we need to refeed them */
-  if (!old->propagate_routes && new->propagate_routes)
-    proto_request_feeding(&p->p);
+  P->cf = c;
+  p->active = !new->trigger_valid || fib_find(&p->routes, &new->trigger_prefix, new->trigger_pxlen);
 
   struct iface *iface;
   WALK_LIST(iface, iface_list)
diff --git a/proto/radv/radv.h b/proto/radv/radv.h
index ab08139..d61fbe4 100644
--- a/proto/radv/radv.h
+++ b/proto/radv/radv.h
@@ -53,6 +53,7 @@ struct radv_config
   ip_addr trigger_prefix;	/* Prefix of a trigger route, if defined */
   u8 trigger_pxlen;		/* Pxlen of a trigger route, if defined */
   u8 trigger_valid;		/* Whether a trigger route is defined */
+  u8 trigger_keep;		/* Keep the trigger route as a normal route */
   u8 propagate_routes;		/* Do we propagate more specific routes (RFC 4191)? */
   u32 max_linger_time;		/* Maximum of interface route_linger_time */
 };
@@ -131,11 +132,13 @@ struct radv_dnssl_config
 struct radv_route
 {
   struct fib_node n;
+  struct iface *iface;		/* Interface route is bound to */
   u32 lifetime;			/* Lifetime from an attribute */
   u8 lifetime_set;		/* Whether lifetime is defined */
   u8 preference;		/* Preference of the route, RA_PREF_* */
   u8 preference_set;		/* Whether preference is defined */
-  u8 valid;			/* Whethe route is valid or withdrawn */
+  u8 valid;			/* Whether route is valid or withdrawn */
+  u8 prefixroute;		/* Prefix route indicator */
   bird_clock_t changed;		/* Last time when the route changed */
 };
 
@@ -145,7 +148,6 @@ struct radv_proto
   list iface_list;		/* List of active ifaces */
   u8 valid;			/* Router is valid for forwarding, used for shutdown */
   u8 active;			/* Whether radv is active w.r.t. triggers */
-  u8 fib_up;			/* FIB table (routes) is initialized */
   struct fib routes;		/* FIB table of specific routes (struct radv_route) */
   bird_clock_t prune_time;	/* Next time of route table pruning */
 };
@@ -214,6 +216,9 @@ int radv_process_domain(struct radv_dnssl_config *cf);
 void radv_send_ra(struct radv_iface *ifa);
 int radv_sk_open(struct radv_iface *ifa);
 
-
+static inline int radv_match_trigger(struct radv_config *cf, ip_addr prefix, uint pxlen)
+{
+  return cf->trigger_valid && (pxlen == cf->trigger_pxlen) && ipa_equal(prefix, cf->trigger_prefix);
+}
 
 #endif /* _BIRD_RADV_H_ */
-- 
2.20.1

