nd6: Parse options instead of only using first

When parsing Neighbor Solicitation, Neighbor Advertisement and Redirect
messages, options will be searched through for the expected link-layer
address type instead of just using the first option.
This commit is contained in:
Erik Ekman
2026-06-02 22:49:12 +02:00
parent 0f8331d47b
commit 0a65e65494

View File

@@ -292,6 +292,37 @@ nd6_process_autoconfig_prefix(struct netif *netif,
} }
#endif /* LWIP_IPV6_AUTOCONFIG */ #endif /* LWIP_IPV6_AUTOCONFIG */
/**
* Return a pointer to the first instance of a specific ICMP option in a pbuf,
* starting from a specific offset.
*
* @param p the nd packet, p->payload pointing to the icmpv6 header
* @param offset the byte offset for the start of icmpv6 options
* @param wanted_type the option type to search for
* @return A pointer into p->payload where the option starts,
* or NULL if option is not found, option was not fully inside p->payload,
* or a zero-length option was found.
*/
static void *
nd6_find_option(struct pbuf *p, int offset, int wanted_type)
{
while ((p->tot_len - offset) >= 2) {
u8_t option_type;
u16_t option_len;
int option_len8 = pbuf_try_get_at(p, offset + 1);
if (option_len8 <= 0) {
break;
}
option_len = ((u8_t)option_len8) << 3;
option_type = pbuf_get_at(p, offset);
if (p->len >= (offset + option_len) && option_type == wanted_type) {
return ((u8_t*)p->payload + offset);
}
offset += option_len;
}
return NULL;
}
/** /**
* Process an incoming neighbor discovery message * Process an incoming neighbor discovery message
* *
@@ -363,19 +394,9 @@ nd6_input(struct pbuf *p, struct netif *inp)
} }
#endif /* LWIP_IPV6_DUP_DETECT_ATTEMPTS */ #endif /* LWIP_IPV6_DUP_DETECT_ATTEMPTS */
/* Check that link-layer address option also fits in packet. */ lladdr_opt = nd6_find_option(p, sizeof(*na_hdr), ND6_OPTION_TYPE_TARGET_LLADDR);
if (p->len < (sizeof(struct na_header) + 2)) { if (!lladdr_opt) {
/* @todo debug message */ /* Missing target lladdr option, or did not fully fit inside p */
pbuf_free(p);
ND6_STATS_INC(nd6.lenerr);
ND6_STATS_INC(nd6.drop);
return;
}
lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct na_header));
if (p->len < (sizeof(struct na_header) + (lladdr_opt->length << 3))) {
/* @todo debug message */
pbuf_free(p); pbuf_free(p);
ND6_STATS_INC(nd6.lenerr); ND6_STATS_INC(nd6.lenerr);
ND6_STATS_INC(nd6.drop); ND6_STATS_INC(nd6.drop);
@@ -405,19 +426,9 @@ nd6_input(struct pbuf *p, struct netif *inp)
/* Update cache entry. */ /* Update cache entry. */
if ((na_hdr->flags & ND6_FLAG_OVERRIDE) || if ((na_hdr->flags & ND6_FLAG_OVERRIDE) ||
(neighbor_cache[i].state == ND6_INCOMPLETE)) { (neighbor_cache[i].state == ND6_INCOMPLETE)) {
/* Check that link-layer address option also fits in packet. */ lladdr_opt = nd6_find_option(p, sizeof(*na_hdr), ND6_OPTION_TYPE_TARGET_LLADDR);
if (p->len < (sizeof(struct na_header) + 2)) { if (!lladdr_opt) {
/* @todo debug message */ /* Missing target lladdr option, or did not fully fit inside p */
pbuf_free(p);
ND6_STATS_INC(nd6.lenerr);
ND6_STATS_INC(nd6.drop);
return;
}
lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct na_header));
if (p->len < (sizeof(struct na_header) + (lladdr_opt->length << 3))) {
/* @todo debug message */
pbuf_free(p); pbuf_free(p);
ND6_STATS_INC(nd6.lenerr); ND6_STATS_INC(nd6.lenerr);
ND6_STATS_INC(nd6.drop); ND6_STATS_INC(nd6.drop);
@@ -475,14 +486,7 @@ nd6_input(struct pbuf *p, struct netif *inp)
/* @todo RFC MUST: if IP source is 'any', there is no source LL address option */ /* @todo RFC MUST: if IP source is 'any', there is no source LL address option */
/* Check if there is a link-layer address provided. Only point to it if in this buffer. */ /* Check if there is a link-layer address provided. Only point to it if in this buffer. */
if (p->len >= (sizeof(struct ns_header) + 2)) { lladdr_opt = nd6_find_option(p, sizeof(*ns_hdr), ND6_OPTION_TYPE_SOURCE_LLADDR);
lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct ns_header));
if (p->len < (sizeof(struct ns_header) + (lladdr_opt->length << 3))) {
lladdr_opt = NULL;
}
} else {
lladdr_opt = NULL;
}
/* Check if the target address is configured on the receiving netif. */ /* Check if the target address is configured on the receiving netif. */
accepted = 0; accepted = 0;
@@ -853,14 +857,8 @@ nd6_input(struct pbuf *p, struct netif *inp)
/* @todo RFC MUST: ICMP target address is either link-local address or same as destination_address */ /* @todo RFC MUST: ICMP target address is either link-local address or same as destination_address */
/* @todo RFC MUST: all included options have a length greater than zero */ /* @todo RFC MUST: all included options have a length greater than zero */
if (p->len >= (sizeof(struct redirect_header) + 2)) { /* Check if there is a link-layer address provided. Only point to it if in this buffer. */
lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct redirect_header)); lladdr_opt = nd6_find_option(p, sizeof(*redir_hdr), ND6_OPTION_TYPE_TARGET_LLADDR);
if (p->len < (sizeof(struct redirect_header) + (lladdr_opt->length << 3))) {
lladdr_opt = NULL;
}
} else {
lladdr_opt = NULL;
}
/* Find dest address in cache */ /* Find dest address in cache */
dest_idx = nd6_find_destination_cache_entry(&destination_address); dest_idx = nd6_find_destination_cache_entry(&destination_address);
@@ -879,29 +877,27 @@ nd6_input(struct pbuf *p, struct netif *inp)
/* If Link-layer address of other router is given, try to add to neighbor cache. */ /* If Link-layer address of other router is given, try to add to neighbor cache. */
if (lladdr_opt != NULL) { if (lladdr_opt != NULL) {
if (lladdr_opt->type == ND6_OPTION_TYPE_TARGET_LLADDR) { i = nd6_find_neighbor_cache_entry(&target_address);
i = nd6_find_neighbor_cache_entry(&target_address); if (i < 0) {
if (i < 0) { i = nd6_new_neighbor_cache_entry();
i = nd6_new_neighbor_cache_entry();
if (i >= 0) {
neighbor_cache[i].netif = inp;
MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
ip6_addr_copy(neighbor_cache[i].next_hop_address, target_address);
/* Receiving a message does not prove reachability: only in one direction.
* Delay probe in case we get confirmation of reachability from upper layer (TCP). */
neighbor_cache[i].state = ND6_DELAY;
neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
}
}
if (i >= 0) { if (i >= 0) {
if (neighbor_cache[i].state == ND6_INCOMPLETE) { neighbor_cache[i].netif = inp;
MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len); MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
/* Receiving a message does not prove reachability: only in one direction. ip6_addr_copy(neighbor_cache[i].next_hop_address, target_address);
* Delay probe in case we get confirmation of reachability from upper layer (TCP). */
neighbor_cache[i].state = ND6_DELAY; /* Receiving a message does not prove reachability: only in one direction.
neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL; * Delay probe in case we get confirmation of reachability from upper layer (TCP). */
} neighbor_cache[i].state = ND6_DELAY;
neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
}
}
if (i >= 0) {
if (neighbor_cache[i].state == ND6_INCOMPLETE) {
MEMCPY(neighbor_cache[i].lladdr, lladdr_opt->addr, inp->hwaddr_len);
/* Receiving a message does not prove reachability: only in one direction.
* Delay probe in case we get confirmation of reachability from upper layer (TCP). */
neighbor_cache[i].state = ND6_DELAY;
neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
} }
} }
} }