Nest: Fix neighbor handling for colliding ranges

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.
This commit is contained in:
Ondrej Zajicek (work) 2020-05-11 04:29:36 +02:00
parent f7c34aa227
commit b8bbbbaf56
3 changed files with 107 additions and 20 deletions

View 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

View 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 *);
/*

View 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);
/* 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,7 +491,25 @@ 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, *ii;
neighbor *n;
node *x, *y;
/* 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;
@ -428,10 +517,7 @@ neigh_ifa_update(struct ifa *a)
/* Update all neighbors whose scope has changed */
WALK_LIST2_DELSAFE(n, x, y, i->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)
if (n->ifa == a)
neigh_update(n, i);
}