mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
gnrc/ipv6/nib: don't queue packets on 6lo neighbors and drop/flush if UNREACHABLE
This commit is contained in:
parent
0e7636a463
commit
3a5612ee57
@ -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
|
||||
*
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
/** @} */
|
||||
|
Loading…
Reference in New Issue
Block a user