-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gnrc/ipv6/nib: don't queue packets on 6lo neighbors and drop/flush if… #20834
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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) | ||||||||||
|
@@ -549,7 +562,7 @@ | |||||||||
"is not a forwarding interface). Discarding silently\n", | ||||||||||
netif->pid); | ||||||||||
DEBUG(" - IP Hop Limit: %u (should be %u)\n", ipv6->hl, | ||||||||||
NDP_HOP_LIMIT); | ||||||||||
DEBUG(" - ICMP code: %u (should be 0)\n", rtr_sol->code); | ||||||||||
DEBUG(" - ICMP length: %" PRIuSIZE " (should > %" PRIuSIZE ")\n", | ||||||||||
icmpv6_len, sizeof(ndp_rtr_sol_t)); | ||||||||||
|
@@ -1267,19 +1280,77 @@ | |||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
#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 @@ | |||||||||
|
||||||||||
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 @@ | |||||||||
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 @@ | |||||||||
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 @@ | |||||||||
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 @@ | |||||||||
|
||||||||||
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) { | ||||||||||
Comment on lines
+1834
to
+1835
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I thought you wanted to prevent one node from hogging the entire queue? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we're talking past each other here. Let's recap:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah makes perfect sense! |
||||||||||
gnrc_pktqueue_t *oldest = _nbr_pop_pkt(node); | ||||||||||
gnrc_pktbuf_release(oldest->pkt); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think not. You can always send a packet by dropping older ones from a queue, and I think this is arguably normal operation. Also, in this case here we're not necessary out of queue entries, but reaching the per-neighbor limit. |
||||||||||
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 */ | ||||||||||
/** @} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to have the default be so high?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This disables some code when the max neighbor count <
CONFIG_GNRC_IPV6_NIB_NBR_QUEUE_CAP
, which is so per default. With a large neighbor count, we mainly want to prevent a neighbor depleting the queue - for performance reasons - and in that case it's not that big anymore.This was my only rationale when picking 16.