From b8bbbbaf569799ab8faff0ee185528b6a2129154 Mon Sep 17 00:00:00 2001 From: "Ondrej Zajicek (work)" Date: Mon, 11 May 2020 04:29:36 +0200 Subject: 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. --- nest/iface.c | 4 +- nest/iface.h | 5 ++- nest/neighbor.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 106 insertions(+), 19 deletions(-) (limited to 'nest') diff --git a/nest/iface.c b/nest/iface.c index 46a49f8f..83a633a3 100644 --- a/nest/iface.c +++ b/nest/iface.c @@ -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 diff --git a/nest/iface.h b/nest/iface.h index b9796283..1189cdd4 100644 --- a/nest/iface.h +++ b/nest/iface.h @@ -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 *); /* diff --git a/nest/neighbor.c b/nest/neighbor.c index 00a8e8a5..d046e981 100644 --- a/nest/neighbor.c +++ b/nest/neighbor.c @@ -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) { -- cgit v1.2.3