From 3a5612ee57f14d74842fcc1d315aeb7cf1463463 Mon Sep 17 00:00:00 2001 From: Mihai Renea Date: Mon, 26 Aug 2024 09:21:52 +0200 Subject: [PATCH] gnrc/ipv6/nib: don't queue packets on 6lo neighbors and drop/flush if UNREACHABLE --- sys/include/net/gnrc/ipv6/nib/conf.h | 12 ++ .../gnrc/network_layer/ipv6/nib/_nib-arsm.c | 18 +- .../gnrc/network_layer/ipv6/nib/_nib-arsm.h | 12 +- .../network_layer/ipv6/nib/_nib-internal.c | 13 +- .../network_layer/ipv6/nib/_nib-internal.h | 38 +++++ sys/net/gnrc/network_layer/ipv6/nib/nib.c | 157 +++++++++++++++--- 6 files changed, 211 insertions(+), 39 deletions(-) diff --git a/sys/include/net/gnrc/ipv6/nib/conf.h b/sys/include/net/gnrc/ipv6/nib/conf.h index fddbc03e8005..90a9bf4b9df3 100644 --- a/sys/include/net/gnrc/ipv6/nib/conf.h +++ b/sys/include/net/gnrc/ipv6/nib/conf.h @@ -281,6 +281,18 @@ extern "C" { #define CONFIG_GNRC_IPV6_NIB_NUMOF (4) #endif +/** + * @brief Per-neighbor packet queue capacity + * + * If @ref CONFIG_GNRC_IPV6_NIB_QUEUE_PKT enabled, this is the maximum number + * of packets, per neighbor, awaiting packet resolution. + * + * @attention This MUST be leq UINT8_MAX + */ +#ifndef CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP +#define CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP (16) +#endif + /** * @brief Number of off-link entries in NIB * diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c index 0709f6b71412..cf949ac64831 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.c @@ -228,9 +228,7 @@ void _handle_snd_ns(_nib_onl_entry_t *nbr) case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_PROBE: if (nbr->ns_sent >= NDP_MAX_UC_SOL_NUMOF) { gnrc_netif_t *netif = gnrc_netif_get_by_pid(_nib_onl_get_if(nbr)); - - _set_nud_state(netif, nbr, - GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE); + _set_unreachable(netif, nbr); } /* intentionally falls through */ case GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE: @@ -387,11 +385,10 @@ void _handle_adv_l2(gnrc_netif_t *netif, _nib_onl_entry_t *nce, nce->info &= ~GNRC_IPV6_NIB_NC_INFO_IS_ROUTER; } } -#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT) && MODULE_GNRC_IPV6 /* send queued packets */ gnrc_pktqueue_t *ptr; DEBUG("nib: Sending queued packets\n"); - while ((ptr = gnrc_pktqueue_remove_head(&nce->pktqueue)) != NULL) { + while ((ptr = _nbr_pop_pkt(nce)) != NULL) { if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_IPV6, GNRC_NETREG_DEMUX_CTX_ALL, ptr->pkt)) { @@ -400,7 +397,7 @@ void _handle_adv_l2(gnrc_netif_t *netif, _nib_onl_entry_t *nce, } ptr->pkt = NULL; } -#endif /* CONFIG_GNRC_IPV6_NIB_QUEUE_PKT */ + if ((icmpv6->type == ICMPV6_NBR_ADV) && !_sflag_set((ndp_nbr_adv_t *)icmpv6) && (_get_nud_state(nce) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_REACHABLE) && @@ -439,6 +436,15 @@ void _set_reachable(gnrc_netif_t *netif, _nib_onl_entry_t *nce) netif->ipv6.reach_time); } +void _set_unreachable(gnrc_netif_t *netif, _nib_onl_entry_t *nce) +{ + DEBUG("nib: set %s to UNREACHABLE\n", + ipv6_addr_to_str(addr_str, &nce->ipv6, sizeof(addr_str))); + + _nbr_flush_pktqueue(nce); + _set_nud_state(netif, nce, GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE); +} + void _set_nud_state(gnrc_netif_t *netif, _nib_onl_entry_t *nce, uint16_t state) { diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.h b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.h index ab69e40d4f6e..a3d37960e3d3 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.h +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-arsm.h @@ -174,14 +174,22 @@ void _handle_adv_l2(gnrc_netif_t *netif, _nib_onl_entry_t *nce, void _recalc_reach_time(gnrc_netif_ipv6_t *netif); /** - * @brief Sets a neighbor cache entry reachable and starts the required + * @brief Sets a neighbor cache entry REACHABLE and starts the required * event timers * * @param[in] netif Interface to the NCE - * @param[in] nce The neighbor cache entry to set reachable + * @param[in] nce The neighbor cache entry to set REACHABLE */ void _set_reachable(gnrc_netif_t *netif, _nib_onl_entry_t *nce); +/** + * @brief Sets a neighbor cache entry UNREACHABLE and flushes its packet queue + * + * @param[in] netif Interface to the NCE + * @param[in] nce The neighbor cache entry to set UNREACHABLE + */ +void _set_unreachable(gnrc_netif_t *netif, _nib_onl_entry_t *nce); + /** * @brief Initializes interface for address registration state machine * diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c index 0d4269533620..9bee421842cb 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.c @@ -276,18 +276,7 @@ void _nib_nc_remove(_nib_onl_entry_t *node) #if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_6LR) evtimer_del((evtimer_t *)&_nib_evtimer, &node->addr_reg_timeout.event); #endif /* CONFIG_GNRC_IPV6_NIB_6LR */ -#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT) - gnrc_pktqueue_t *tmp; - for (gnrc_pktqueue_t *ptr = node->pktqueue; - (ptr != NULL) && (tmp = (ptr->next), 1); - ptr = tmp) { - gnrc_pktqueue_t *entry = gnrc_pktqueue_remove(&node->pktqueue, ptr); - gnrc_icmpv6_error_dst_unr_send(ICMPV6_ERROR_DST_UNR_ADDR, - entry->pkt); - gnrc_pktbuf_release_error(entry->pkt, EHOSTUNREACH); - entry->pkt = NULL; - } -#endif /* CONFIG_GNRC_IPV6_NIB_QUEUE_PKT */ + _nbr_flush_pktqueue(node); /* remove from cache-out procedure */ clist_remove(&_next_removable, (clist_node_t *)node); _nib_onl_clear(node); diff --git a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.h b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.h index 59d48591855c..fa62043b98f4 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.h +++ b/sys/net/gnrc/network_layer/ipv6/nib/_nib-internal.h @@ -180,6 +180,9 @@ typedef struct _nib_onl_entry { */ uint8_t l2addr_len; #endif +#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT) || defined(DOXYGEN) + uint8_t pktqueue_len; /**< Number of queued packets (in pktqueue) */ +#endif } _nib_onl_entry_t; /** @@ -872,6 +875,41 @@ void _nib_ft_get(const _nib_offl_entry_t *dst, gnrc_ipv6_nib_ft_t *fte); int _nib_get_route(const ipv6_addr_t *dst, gnrc_pktsnip_t *ctx, gnrc_ipv6_nib_ft_t *entry); +#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT) || DOXYGEN +/** + * @brief Flush the packet queue of a on-link neighbor. + * + * @param node neighbor entry to be flushed + */ +void _nbr_flush_pktqueue(_nib_onl_entry_t *node); + +/** + * @brief Remove oldest packet from a on-link neighbor's packet queue. + * + * @param node neighbor entry + * + * @retval pointer to the packet entry or NULL if the queue is empty + */ +gnrc_pktqueue_t *_nbr_pop_pkt(_nib_onl_entry_t *node); + +/** + * @brief Push packet to a on-link neighbor's packet queue. + * + * If there are already @ref CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP packets queued, + * the oldest will be dropped silently. + * + * @pre Neighbor is INCOMPLETE. + * + * @param node neighbor entry + * @param pkt packet to be pushed + */ +void _nbr_push_pkt(_nib_onl_entry_t *node, gnrc_pktqueue_t *pkt); +#else +#define _nbr_flush_pktqueue(node) ((void)node) +#define _nbr_pop_pkt(node) ((void)node, NULL) +#define _nbr_push_pkt(node, pkt) ((void)node, (void)pkt) +#endif + #ifdef __cplusplus } #endif diff --git a/sys/net/gnrc/network_layer/ipv6/nib/nib.c b/sys/net/gnrc/network_layer/ipv6/nib/nib.c index ebeca2b23afe..7d8e25652ca4 100644 --- a/sys/net/gnrc/network_layer/ipv6/nib/nib.c +++ b/sys/net/gnrc/network_layer/ipv6/nib/nib.c @@ -54,7 +54,20 @@ static char addr_str[IPV6_ADDR_MAX_STR_LEN]; #if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT) -static gnrc_pktqueue_t _queue_pool[CONFIG_GNRC_IPV6_NIB_NUMOF]; +/* +1 ensures that whenever the pool is empty, there is at least one neighbor + * with 2 or more packets, thus we can always pop a packet from that neighbor + * without leaving it's queue empty, as required by + * + * https://www.rfc-editor.org/rfc/rfc4861#section-7.2.2 + * + * While waiting for address resolution to complete, the sender MUST, + * for each neighbor, retain a small queue of packets waiting for + * address resolution to complete. The queue MUST hold at least one + * packet, and MAY contain more. However, the number of queued packets + * per neighbor SHOULD be limited to some small value. When a queue + * overflows, the new arrival SHOULD replace the oldest entry. Once + * address resolution completes, the node transmits any queued packets. */ +static gnrc_pktqueue_t _queue_pool[CONFIG_GNRC_IPV6_NIB_NUMOF + 1]; #endif /* CONFIG_GNRC_IPV6_NIB_QUEUE_PKT */ #if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_DNS) @@ -1267,19 +1280,77 @@ static void _handle_nbr_adv(gnrc_netif_t *netif, const ipv6_hdr_t *ipv6, } } +#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT) +static _nib_onl_entry_t *_iter_nc_nbr(_nib_onl_entry_t const *last) +{ + while ((last = _nib_onl_iter(last))) { + if (last->mode & _NC) { + break; + } + } + + return (_nib_onl_entry_t *)last; +} +#endif + +/* This function never fails as doing so would force us to drop newer packets + * instead of older, thus leaving stale packets in the neighbor queues. + * + * https://www.rfc-editor.org/rfc/rfc4861#section-7.2.2 + * + * While waiting for address resolution to complete, the sender MUST, + * for each neighbor, retain a small queue of packets waiting for + * address resolution to complete. The queue MUST hold at least one + * packet, and MAY contain more. However, the number of queued packets + * per neighbor SHOULD be limited to some small value. When a queue + * overflows, the new arrival SHOULD replace the oldest entry. Once + * address resolution completes, the node transmits any queued packets. */ static gnrc_pktqueue_t *_alloc_queue_entry(gnrc_pktsnip_t *pkt) { #if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT) - for (int i = 0; i < CONFIG_GNRC_IPV6_NIB_NUMOF; i++) { + for (size_t i = 0; i < ARRAY_SIZE(_queue_pool); i++) { if (_queue_pool[i].pkt == NULL) { _queue_pool[i].pkt = pkt; return &_queue_pool[i]; } } + + /* We run out of free queue entries. Pop from the nbr with the longest queue */ + _nib_onl_entry_t *nbr = _iter_nc_nbr(NULL); + _nib_onl_entry_t *hog = nbr; + /* There MUST be at least a neighbor in the NC */ + assert(hog); + while ((nbr = _iter_nc_nbr(nbr))) { + if (ARRAY_SIZE(_queue_pool) >= CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP && + /* The per-neighbor queue is capped at CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP. + * There cannot be a larger hog than that. */ + hog->pktqueue_len == CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP) { + break; + } + assert(hog->pktqueue_len < CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP); + + if (nbr->pktqueue_len > hog->pktqueue_len) { + hog = nbr; + } + } + + DEBUG("nib: no free pktqueue entries, popping from %s hogging %u\n", + ipv6_addr_to_str(addr_str, &hog->ipv6, sizeof(addr_str)), + hog->pktqueue_len); + + /* We have one more pktqueue entries than neighbors in the NC, therefore + * there must be a neighbor with two or more packets in its queue */ + assert(hog->pktqueue_len >= 2); + + gnrc_pktqueue_t *qentry = _nbr_pop_pkt(hog); + gnrc_pktbuf_release(qentry->pkt); + + qentry->pkt = pkt; + return qentry; #else (void)pkt; -#endif /* CONFIG_GNRC_IPV6_NIB_QUEUE_PKT */ return NULL; +#endif /* CONFIG_GNRC_IPV6_NIB_QUEUE_PKT */ } static bool _resolve_addr_from_nc(_nib_onl_entry_t *entry, gnrc_netif_t *netif, @@ -1317,13 +1388,6 @@ static bool _enqueue_for_resolve(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt, gnrc_pktqueue_t *queue_entry = _alloc_queue_entry(pkt); - if (queue_entry == NULL) { - DEBUG("nib: can't allocate entry for packet queue " - "dropping packet\n"); - gnrc_pktbuf_release(pkt); - return false; - } - if (netif != NULL) { gnrc_pktsnip_t *netif_hdr = gnrc_netif_hdr_build(NULL, 0, NULL, 0); @@ -1337,11 +1401,7 @@ static bool _enqueue_for_resolve(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt, queue_entry->pkt = gnrc_pkt_prepend(queue_entry->pkt, netif_hdr); } -#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT) - gnrc_pktqueue_add(&entry->pktqueue, queue_entry); -#else - (void)entry; -#endif + _nbr_push_pkt(entry, queue_entry); return true; } @@ -1373,6 +1433,16 @@ static bool _resolve_addr(const ipv6_addr_t *dst, gnrc_netif_t *netif, return true; } + /* don't do multicast address resolution on 6lo */ + if (gnrc_netif_is_6ln(netif)) { + /* https://www.rfc-editor.org/rfc/rfc6775.html#section-5.6 + * A LoWPAN node is not required to maintain a minimum of one buffer + * per neighbor as specified in [RFC4861], since packets are never + * queued while waiting for address resolution. */ + gnrc_pktbuf_release(pkt); + return false; + } + bool reset = false; DEBUG("nib: resolve address %s by probing neighbors\n", ipv6_addr_to_str(addr_str, dst, sizeof(addr_str))); @@ -1404,10 +1474,7 @@ static bool _resolve_addr(const ipv6_addr_t *dst, gnrc_netif_t *netif, return false; } - /* don't do multicast address resolution on 6lo */ - if (!gnrc_netif_is_6ln(netif)) { - _probe_nbr(entry, reset); - } + _probe_nbr(entry, reset); return false; } @@ -1733,4 +1800,56 @@ static uint32_t _handle_rio(gnrc_netif_t *netif, const ipv6_hdr_t *ipv6, return route_ltime; } + +#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT) +gnrc_pktqueue_t *_nbr_pop_pkt(_nib_onl_entry_t *node) +{ + if (node->pktqueue_len == 0) { + assert(node->pktqueue == NULL); + return NULL; + } + assert(node->pktqueue != NULL); + + node->pktqueue_len--; + + return gnrc_pktqueue_remove_head(&node->pktqueue); +} + +void _nbr_push_pkt(_nib_onl_entry_t *node, gnrc_pktqueue_t *pkt) +{ + static_assert(CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP <= UINT8_MAX, + "nib: nbr queue cap overflows counter"); + assert(_get_nud_state(node) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE); + /* We're capping the per-neighbor queue length out of following reasons: + * - https://www.rfc-editor.org/rfc/rfc4861#section-7.2.2 recommends a + * small queue size + * - for large CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP, a single neighbor could + * otherwise consume the whole entry cache. By capping we rule out this + * case, thus: 1) a hog will just drop from it's own queue and 2) there's + * less likely to deplete the entry cache. + * + * For small CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP we don't care how the entries + * are distributed: if we run out of entries, finding the hog to drop from + * there is fast anyway. */ + if (ARRAY_SIZE(_queue_pool) > CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP && + node->pktqueue_len == CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP) { + gnrc_pktqueue_t *oldest = _nbr_pop_pkt(node); + gnrc_pktbuf_release(oldest->pkt); + oldest->pkt = NULL; + } + + gnrc_pktqueue_add(&node->pktqueue, pkt); + node->pktqueue_len++; +} + +void _nbr_flush_pktqueue(_nib_onl_entry_t *node) +{ + gnrc_pktqueue_t *entry; + while ((entry = _nbr_pop_pkt(node))) { + gnrc_icmpv6_error_dst_unr_send(ICMPV6_ERROR_DST_UNR_ADDR, entry->pkt); + gnrc_pktbuf_release_error(entry->pkt, EHOSTUNREACH); + entry->pkt = NULL; + } +} +#endif /* CONFIG_GNRC_IPV6_NIB_QUEUE_PKT */ /** @} */