]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Nest: Fix neighbor handling for colliding ranges
authorOndrej Zajicek (work) <santiago@crfreenet.org>
Mon, 11 May 2020 02:29:36 +0000 (04:29 +0200)
committerOndrej Zajicek (work) <santiago@crfreenet.org>
Mon, 11 May 2020 02:29:36 +0000 (04:29 +0200)
Resolve neighbors using longest prefix match. Although interface ranges
should not generally collide, it may happen for unnumbered links.

Thanks to Kenth Eriksson for the bugreport.

nest/iface.c
nest/iface.h
nest/neighbor.c

index 46a49f8fae5f5f0811001d8d8998f7097f410763..83a633a39ad4eb4d00e20d9ee862f23dfa219be6 100644 (file)
@@ -172,12 +172,12 @@ static inline void
 ifa_notify_change(unsigned c, struct ifa *a)
 {
   if (c & IF_CHANGE_DOWN)
-    neigh_ifa_update(a);
+    neigh_ifa_down(a);
 
   ifa_notify_change_(c, a);
 
   if (c & IF_CHANGE_UP)
-    neigh_ifa_update(a);
+    neigh_ifa_up(a);
 }
 
 static inline void
index b9796283d4c066d84d33dc4fc72ba069aa9f8cea..1189cdd4cad49955339f3a665842e6f04f84a65d 100644 (file)
@@ -123,7 +123,7 @@ void if_recalc_all_preferred_addresses(void);
 /* The Neighbor Cache */
 
 typedef struct neighbor {
-  node n;                              /* Node in global neighbor list */
+  node n;                              /* Node in neighbor hash table chain */
   node if_n;                           /* Node in per-interface neighbor list */
   ip_addr addr;                                /* Address of the neighbor */
   struct ifa *ifa;                     /* Ifa on related iface */
@@ -150,7 +150,8 @@ void neigh_prune(void);
 void neigh_if_up(struct iface *);
 void neigh_if_down(struct iface *);
 void neigh_if_link(struct iface *);
-void neigh_ifa_update(struct ifa *);
+void neigh_ifa_up(struct ifa *a);
+void neigh_ifa_down(struct ifa *a);
 void neigh_init(struct pool *);
 
 /*
index 00a8e8a531154f51f053428f6521aa812839e570..d046e9814db4c65fdab466add6a61b17676e55a1 100644 (file)
@@ -66,10 +66,32 @@ neigh_hash(struct proto *p, ip_addr a, struct iface *i)
   return (p->hash_key ^ ipa_hash(a) ^ ptr_hash(i)) >> NEIGH_HASH_OFFSET;
 }
 
+static inline int
+ifa_better(struct ifa *a, struct ifa *b)
+{
+  return a && (!b || (a->prefix.pxlen > b->prefix.pxlen));
+}
+
+static inline int
+scope_better(int sa, int sb)
+{
+  /* Order per preference: -1 unknown, 0 for remote, 1 for local */
+  sa = (sa < 0) ? sa : !sa;
+  sb = (sb < 0) ? sb : !sb;
+
+  return sa > sb;
+}
+
+static inline int
+scope_remote(int sa, int sb)
+{
+  return (sa > SCOPE_HOST) && (sb > SCOPE_HOST);
+}
+
 static int
 if_connected(ip_addr a, struct iface *i, struct ifa **ap, uint flags)
 {
-  struct ifa *b;
+  struct ifa *b, *addr = NULL;
 
   /* Handle iface pseudo-neighbors */
   if (flags & NEF_IFACE)
@@ -89,12 +111,12 @@ if_connected(ip_addr a, struct iface *i, struct ifa **ap, uint flags)
   {
     if (b->flags & IA_PEER)
     {
-      if (ipa_equal(a, b->opposite))
-       return *ap = b, b->scope;
+      if (ipa_equal(a, b->opposite) && ifa_better(b, addr))
+       addr = b;
     }
     else
     {
-      if (ipa_in_netX(a, &b->prefix))
+      if (ipa_in_netX(a, &b->prefix) && ifa_better(b, addr))
       {
        /* Do not allow IPv4 network and broadcast addresses */
        if (ipa_is_ip4(a) &&
@@ -103,11 +125,15 @@ if_connected(ip_addr a, struct iface *i, struct ifa **ap, uint flags)
             ipa_equal(a, b->brd)))                     /* Broadcast */
          return *ap = NULL, -1;
 
-       return *ap = b, b->scope;
+       addr = b;
       }
     }
   }
 
+  /* Return found address */
+  if (addr)
+    return *ap = addr, addr->scope;
+
   /* Handle ONLINK flag */
   if (flags & NEF_ONLINK)
     return *ap = NULL, ipa_classify(a) & IADDR_SCOPE_MASK;
@@ -125,10 +151,10 @@ if_connected_any(ip_addr a, struct iface *vrf, uint vrf_set, struct iface **ifac
   *iface = NULL;
   *addr = NULL;
 
-  /* Get first match, but prefer SCOPE_HOST to other matches */
+  /* Prefer SCOPE_HOST or longer prefix */
   WALK_LIST(i, iface_list)
     if ((!vrf_set || vrf == i->master) && ((s = if_connected(a, i, &b, flags)) >= 0))
-      if ((scope < 0) || ((scope > SCOPE_HOST) && (s == SCOPE_HOST)))
+      if (scope_better(s, scope) || (scope_remote(s, scope) && ifa_better(b, *addr)))
       {
        *iface = i;
        *addr = b;
@@ -138,6 +164,33 @@ if_connected_any(ip_addr a, struct iface *vrf, uint vrf_set, struct iface **ifac
   return scope;
 }
 
+/* Is ifa @a subnet of any ifa on iface @ib ? */
+static inline int
+ifa_intersect(struct ifa *a, struct iface *ib)
+{
+  struct ifa *b;
+
+  WALK_LIST(b, ib->addrs)
+    if (net_in_netX(&a->prefix, &b->prefix))
+      return 1;
+
+  return 0;
+}
+
+/* Is any ifa of iface @ia subnet of any ifa on iface @ib ? */
+static inline int
+if_intersect(struct iface *ia, struct iface *ib)
+{
+  struct ifa *a, *b;
+
+  WALK_LIST(a, ia->addrs)
+    WALK_LIST(b, ib->addrs)
+    if (net_in_netX(&a->prefix, &b->prefix))
+      return 1;
+
+  return 0;
+}
+
 /**
  * neigh_find - find or create a neighbor entry
  * @p: protocol which asks for the entry
@@ -323,9 +376,20 @@ neigh_update(neighbor *n, struct iface *iface)
 
   scope = if_connected(n->addr, iface, &ifa, n->flags);
 
-  /* When neighbor is going down, try to respawn it on other ifaces */
-  if ((scope < 0) && (n->scope >= 0) && !n->ifreq && (n->flags & NEF_STICKY))
-    scope = if_connected_any(n->addr, p->vrf, p->vrf_set, &iface, &ifa, n->flags);
+  /* Update about already assigned iface, or some other iface */
+  if (iface == n->iface)
+  {
+    /* When neighbor is going down, try to respawn it on other ifaces */
+    if ((scope < 0) && (n->scope >= 0) && !n->ifreq && (n->flags & NEF_STICKY))
+      scope = if_connected_any(n->addr, p->vrf, p->vrf_set, &iface, &ifa, n->flags);
+  }
+  else
+  {
+    /* Continue only if the new variant is better than the existing one */
+    if (! (scope_better(scope, n->scope) ||
+          (scope_remote(scope, n->scope) && ifa_better(ifa, n->ifa))))
+      return;
+  }
 
   /* No change or minor change - ignore or notify */
   if ((scope == n->scope) && (iface == n->iface))
@@ -367,9 +431,16 @@ neigh_update(neighbor *n, struct iface *iface)
 void
 neigh_if_up(struct iface *i)
 {
+  struct iface *ii;
   neighbor *n;
   node *x, *y;
 
+  /* Update neighbors that might be better off with the new iface */
+  WALK_LIST(ii, iface_list)
+    if (!EMPTY_LIST(ii->neighbors) && (ii != i) && if_intersect(i, ii))
+      WALK_LIST2_DELSAFE(n, x, y, ii->neighbors, if_n)
+       neigh_update(n, i);
+
   WALK_LIST2_DELSAFE(n, x, y, sticky_neigh_list, if_n)
     neigh_update(n, i);
 }
@@ -420,21 +491,36 @@ neigh_if_link(struct iface *i)
  * and causes all unreachable neighbors to be flushed.
  */
 void
-neigh_ifa_update(struct ifa *a)
+neigh_ifa_up(struct ifa *a)
 {
-  struct iface *i = a->iface;
+  struct iface *i = a->iface, *ii;
   neighbor *n;
   node *x, *y;
 
-  /* Update all neighbors whose scope has changed */
-  WALK_LIST2_DELSAFE(n, x, y, i->neighbors, if_n)
-    neigh_update(n, i);
+  /* Update neighbors that might be better off with the new ifa */
+  WALK_LIST(ii, iface_list)
+    if (!EMPTY_LIST(ii->neighbors) && ifa_intersect(a, ii))
+      WALK_LIST2_DELSAFE(n, x, y, ii->neighbors, if_n)
+       neigh_update(n, i);
 
   /* Wake up all sticky neighbors that are reachable now */
   WALK_LIST2_DELSAFE(n, x, y, sticky_neigh_list, if_n)
     neigh_update(n, i);
 }
 
+void
+neigh_ifa_down(struct ifa *a)
+{
+  struct iface *i = a->iface;
+  neighbor *n;
+  node *x, *y;
+
+  /* Update all neighbors whose scope has changed */
+  WALK_LIST2_DELSAFE(n, x, y, i->neighbors, if_n)
+    if (n->ifa == a)
+      neigh_update(n, i);
+}
+
 static inline void
 neigh_prune_one(neighbor *n)
 {