From bdbfb43e80f8ebbd31f1e43b6d1fa7677ed143b2 Mon Sep 17 00:00:00 2001
From: Grant Erickson <marathon96@gmail.com>
Date: Fri, 31 May 2013 16:40:55 -0700
Subject: [PATCH] Make the IPv6 Neighbor Discovery Protocol (NDP)
 implementation truly independent of the value of NETIF_MAX_HWADDR_LEN.

---
 src/core/ipv6/nd6.c         | 30 +++++++++++++++---------------
 src/include/ipv6/lwip/nd6.h | 19 ++++++++++++++++---
 2 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/src/core/ipv6/nd6.c b/src/core/ipv6/nd6.c
index 1a43a37..e8fec33 100644
--- a/src/core/ipv6/nd6.c
+++ b/src/core/ipv6/nd6.c
@@ -148,7 +148,7 @@ nd6_input(struct pbuf *p, struct netif *inp)
        * part of DAD mechanism? */
 
       /* Check that link-layer address option also fits in packet. */
-      if (p->len < (sizeof(struct na_header) + sizeof(struct lladdr_option))) {
+      if (p->len < (sizeof(struct na_header) + ND6_LLADDR_OPTION_SIZE(inp->hwaddr_len))) {
         /* TODO debug message */
         pbuf_free(p);
         ND6_STATS_INC(nd6.lenerr);
@@ -225,7 +225,7 @@ nd6_input(struct pbuf *p, struct netif *inp)
       if ((na_hdr->flags & ND6_FLAG_OVERRIDE) ||
           (neighbor_cache[i].state == ND6_INCOMPLETE)) {
         /* Check that link-layer address option also fits in packet. */
-        if (p->len < (sizeof(struct na_header) + sizeof(struct lladdr_option))) {
+        if (p->len < (sizeof(struct na_header) + ND6_LLADDR_OPTION_SIZE(inp->hwaddr_len))) {
           /* TODO debug message */
           pbuf_free(p);
           ND6_STATS_INC(nd6.lenerr);
@@ -266,7 +266,7 @@ nd6_input(struct pbuf *p, struct netif *inp)
 
     /* Check if there is a link-layer address provided. Only point to it if in this buffer. */
     lladdr_opt = NULL;
-    if (p->len >= (sizeof(struct ns_header) + sizeof(struct lladdr_option))) {
+    if (p->len >= (sizeof(struct ns_header) + ND6_LLADDR_OPTION_SIZE(inp->hwaddr_len))) {
       lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct ns_header));
     }
 
@@ -425,7 +425,7 @@ nd6_input(struct pbuf *p, struct netif *inp)
         pbuf_copy_partial(p, buffer, sizeof(struct prefix_option), offset);
       }
       switch (buffer[0]) {
-      case ND6_OPTION_TYPE_SOURCE_LLADDR:
+      case ND6_LLADDR_OPTION_TYPE_SOURCE:
       {
         struct lladdr_option * lladdr_opt;
         lladdr_opt = (struct lladdr_option *)buffer;
@@ -515,7 +515,7 @@ nd6_input(struct pbuf *p, struct netif *inp)
     redir_hdr = (struct redirect_header *)p->payload;
 
     lladdr_opt = NULL;
-    if (p->len >= (sizeof(struct redirect_header) + sizeof(struct lladdr_option))) {
+    if (p->len >= (sizeof(struct redirect_header) + ND6_LLADDR_OPTION_SIZE(inp->hwaddr_len))) {
       lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct redirect_header));
     }
 
@@ -535,7 +535,7 @@ nd6_input(struct pbuf *p, struct netif *inp)
 
     /* If Link-layer address of other router is given, try to add to neighbor cache. */
     if (lladdr_opt != NULL) {
-      if (lladdr_opt->type == ND6_OPTION_TYPE_TARGET_LLADDR) {
+      if (lladdr_opt->type == ND6_LLADDR_OPTION_TYPE_TARGET) {
         /* Copy target address to current source address, to have an aligned copy. */
         ip6_addr_set(ip6_current_src_addr(), &(redir_hdr->target_address));
 
@@ -808,8 +808,8 @@ nd6_send_ns(struct netif * netif, ip6_addr_t * target_addr, u8_t flags)
   }
 
   /* Allocate a packet. */
-  p = pbuf_alloc(PBUF_IP, sizeof(struct ns_header) + sizeof(struct lladdr_option), PBUF_RAM);
-  if ((p == NULL) || (p->len < (sizeof(struct ns_header) + sizeof(struct lladdr_option)))) {
+  p = pbuf_alloc(PBUF_IP, sizeof(struct ns_header) + ND6_LLADDR_OPTION_SIZE(netif->hwaddr_len), PBUF_RAM);
+  if ((p == NULL) || (p->len < (sizeof(struct ns_header) + ND6_LLADDR_OPTION_SIZE(netif->hwaddr_len)))) {
     /* We couldn't allocate a suitable pbuf for the ns. drop it. */
     if (p != NULL) {
       pbuf_free(p);
@@ -828,8 +828,8 @@ nd6_send_ns(struct netif * netif, ip6_addr_t * target_addr, u8_t flags)
   ns_hdr->reserved = 0;
   ip6_addr_set(&(ns_hdr->target_address), target_addr);
 
-  lladdr_opt->type = ND6_OPTION_TYPE_SOURCE_LLADDR;
-  lladdr_opt->length = ((netif->hwaddr_len + 2) >> 3) + (((netif->hwaddr_len + 2) & 0x07) ? 1 : 0);
+  lladdr_opt->type = ND6_LLADDR_OPTION_TYPE_SOURCE;
+  lladdr_opt->length = ND6_LLADDR_OPTION_LENGTH_ENCODE(netif->hwaddr_len);
   SMEMCPY(lladdr_opt->addr, netif->hwaddr, netif->hwaddr_len);
 
   /* Generate the solicited node address for the target address. */
@@ -870,8 +870,8 @@ nd6_send_na(struct netif * netif, ip6_addr_t * target_addr, u8_t flags)
   src_addr = target_addr;
 
   /* Allocate a packet. */
-  p = pbuf_alloc(PBUF_IP, sizeof(struct na_header) + sizeof(struct lladdr_option), PBUF_RAM);
-  if ((p == NULL) || (p->len < (sizeof(struct na_header) + sizeof(struct lladdr_option)))) {
+  p = pbuf_alloc(PBUF_IP, sizeof(struct na_header) + ND6_LLADDR_OPTION_SIZE(netif->hwaddr_len), PBUF_RAM);
+  if ((p == NULL) || (p->len < (sizeof(struct na_header) + ND6_LLADDR_OPTION_SIZE(netif->hwaddr_len)))) {
     /* We couldn't allocate a suitable pbuf for the ns. drop it. */
     if (p != NULL) {
       pbuf_free(p);
@@ -893,7 +893,7 @@ nd6_send_na(struct netif * netif, ip6_addr_t * target_addr, u8_t flags)
   na_hdr->reserved[2] = 0;
   ip6_addr_set(&(na_hdr->target_address), target_addr);
 
-  lladdr_opt->type = ND6_OPTION_TYPE_TARGET_LLADDR;
+  lladdr_opt->type = ND6_LLADDR_OPTION_TYPE_TARGET;
   lladdr_opt->length = ((netif->hwaddr_len + 2) >> 3) + (((netif->hwaddr_len + 2) & 0x07) ? 1 : 0);
   SMEMCPY(lladdr_opt->addr, netif->hwaddr, netif->hwaddr_len);
 
@@ -949,7 +949,7 @@ nd6_send_rs(struct netif * netif)
   /* Allocate a packet. */
   packet_len = sizeof(struct rs_header);
   if (src_addr != IP6_ADDR_ANY) {
-    packet_len += sizeof(struct lladdr_option);
+    packet_len += ND6_LLADDR_OPTION_SIZE(netif->hwaddr_len);
   }
   p = pbuf_alloc(PBUF_IP, packet_len, PBUF_RAM);
   if ((p == NULL) || (p->len < packet_len)) {
@@ -972,7 +972,7 @@ nd6_send_rs(struct netif * netif)
   if (src_addr != IP6_ADDR_ANY) {
     /* Include our hw address. */
     lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct rs_header));
-    lladdr_opt->type = ND6_OPTION_TYPE_SOURCE_LLADDR;
+    lladdr_opt->type = ND6_LLADDR_OPTION_TYPE_SOURCE;
     lladdr_opt->length = ((netif->hwaddr_len + 2) >> 3) + (((netif->hwaddr_len + 2) & 0x07) ? 1 : 0);
     SMEMCPY(lladdr_opt->addr, netif->hwaddr, netif->hwaddr_len);
   }
diff --git a/src/include/ipv6/lwip/nd6.h b/src/include/ipv6/lwip/nd6.h
index 28636e8..7064014 100644
--- a/src/include/ipv6/lwip/nd6.h
+++ b/src/include/ipv6/lwip/nd6.h
@@ -232,8 +232,8 @@ PACK_STRUCT_END
 #endif
 
 /** Link-layer address option. */
-#define ND6_OPTION_TYPE_SOURCE_LLADDR (0x01)
-#define ND6_OPTION_TYPE_TARGET_LLADDR (0x02)
+#define ND6_LLADDR_OPTION_TYPE_SOURCE (0x01)
+#define ND6_LLADDR_OPTION_TYPE_TARGET (0x02)
 #ifdef PACK_STRUCT_USE_INCLUDES
 #  include "arch/bpstruct.h"
 #endif
@@ -241,13 +241,26 @@ PACK_STRUCT_BEGIN
 struct lladdr_option {
   PACK_STRUCT_FIELD(u8_t type);
   PACK_STRUCT_FIELD(u8_t length);
-  PACK_STRUCT_FIELD(u8_t addr[NETIF_MAX_HWADDR_LEN]);
+  PACK_STRUCT_FIELD(u8_t addr[1]);
 } PACK_STRUCT_STRUCT;
 PACK_STRUCT_END
 #ifdef PACK_STRUCT_USE_INCLUDES
 #  include "arch/epstruct.h"
 #endif
 
+/** The address field is variable and not fixed in length, use the
+  * appropriate macros when accessing the length field or calculating
+  * the option size.
+  */
+#define __OFFSETOF(t, m)                       ((size_t)(&(((t *)0)->m)))
+#define __ROUND_UP(n, m)                       ((((n) + ((m) - 1)) / (m)) * (m))
+#define ND6_LLADDR_OPTION_LENGTH_SHIFT         3
+#define ND6_LLADDR_OPTION_LENGTH_SIZE          (1 << ND6_LLADDR_OPTION_LENGTH_SHIFT)
+#define ND6_LLADDR_OPTION_LENGTH_MASK          (ND6_LLADDR_OPTION_LENGTH_SIZE - 1)
+#define ND6_LLADDR_OPTION_LENGTH_ENCODE(hwlen) (__ROUND_UP(hwlen, ND6_LLADDR_OPTION_LENGTH_SIZE) >> ND6_LLADDR_OPTION_LENGTH_SHIFT)
+#define ND6_LLADDR_OPTION_LENGTH_DECODE(value) ((value) << ND6_LLADDR_OPTION_LENGTH_SHIFT)
+#define ND6_LLADDR_OPTION_SIZE(hwlen)          (__OFFSETOF(struct lladdr_option, addr) + (hwlen))
+
 /** Prefix information option. */
 #define ND6_OPTION_TYPE_PREFIX_INFO (0x03)
 #define ND6_PREFIX_FLAG_ON_LINK (0x80)
-- 
1.8.1.1

