1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #20834 from derMihai/mir/nib/drop_for_unreachable_rebase

gnrc/ipv6/nib: don't queue packets on 6lo neighbors and drop/flush if…
This commit is contained in:
benpicco 2024-10-16 09:16:40 +00:00 committed by GitHub
commit 3706589959
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 211 additions and 39 deletions

View File

@ -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
*

View File

@ -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)
{

View File

@ -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
*

View File

@ -279,18 +279,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);

View File

@ -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

View File

@ -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)
@ -1284,19 +1297,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,
@ -1334,13 +1405,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);
@ -1354,11 +1418,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;
}
@ -1390,6 +1450,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)));
@ -1421,10 +1491,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;
}
@ -1750,4 +1817,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 */
/** @} */