From 83ee56f91f8be447551c65f01ac719e901d7dad7 Mon Sep 17 00:00:00 2001
From: David van Moolenbroek <david@minix3.org>
Date: Wed, 14 Dec 2016 16:19:24 +0000
Subject: [PATCH 3/3] nd6: centralize link-local packet send decision

Previously, ethip6 and lowpan6 each had their own copy of code that
used internal nd6 data structures to decide whether to send a packet
on the local link right away, or queue it while nd6 performed local
address resolution.  This patch moves that code into nd6, thereby
eliminating all remaining cases of external access to internal nd6
data structures, as well as the need to expose two specific nd6
functions.

As a side effect, the patch effectively fixes two bugs in the lowpan6
code that were already fixed in the ethip6 code.
---
 src/core/ipv6/ethip6.c | 40 ++++++++++++++-------------------
 src/core/ipv6/nd6.c    | 60 ++++++++++++++++++++++++++++++++++++++++++++++++--
 src/include/lwip/nd6.h |  3 +--
 src/netif/lowpan6.c    | 39 ++++++++++++--------------------
 4 files changed, 89 insertions(+), 53 deletions(-)

diff --git a/src/core/ipv6/ethip6.c b/src/core/ipv6/ethip6.c
index 35d424e..32d0edf 100644
--- a/src/core/ipv6/ethip6.c
+++ b/src/core/ipv6/ethip6.c
@@ -62,7 +62,9 @@
  * For IPv6 multicast, corresponding Ethernet addresses
  * are selected and the packet is transmitted on the link.
  *
- * For unicast addresses, ...
+ * For unicast addresses, ask the ND6 module what to do. It will either let us
+ * send the the packet right away, or queue the packet for later itself, unless
+ * an error occurs.
  *
  * @todo anycast addresses
  *
@@ -71,14 +73,14 @@
  * @param ip6addr The IP address of the packet destination.
  *
  * @return
- * - ERR_RTE No route to destination (no gateway to external networks),
- * or the return type of either nd6_queue_packet() or ethernet_output().
+ * - ERR_OK or the return value of nd6_packet_send_check.
  */
 err_t
 ethip6_output(struct netif *netif, struct pbuf *q, const ip6_addr_t *ip6addr)
 {
   struct eth_addr dest;
-  s8_t i;
+  const u8_t *hwaddr;
+  err_t result;
 
   /* multicast destination IP address? */
   if (ip6_addr_ismulticast(ip6addr)) {
@@ -96,31 +98,21 @@ ethip6_output(struct netif *netif, struct pbuf *q, const ip6_addr_t *ip6addr)
 
   /* We have a unicast destination IP address */
   /* @todo anycast? */
-  /* Get next hop record. */
-  i = nd6_get_next_hop_entry(ip6addr, netif);
-  if (i < 0) {
-    /* failed to get a next hop neighbor record. */
-    return i;
-  }
 
-  /* Now that we have a destination record, send or queue the packet. */
-  if (neighbor_cache[i].state == ND6_STALE) {
-    /* Switch to delay state. */
-    neighbor_cache[i].state = ND6_DELAY;
-    neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
+  /* Ask ND6 what to do with the packet. */
+  result = nd6_packet_send_check(netif, q, ip6addr, &hwaddr);
+  if (result != ERR_OK) {
+    return result;
   }
-  /* @todo should we send or queue if PROBE? send for now, to let unicast NS pass. */
-  if ((neighbor_cache[i].state == ND6_REACHABLE) ||
-      (neighbor_cache[i].state == ND6_DELAY) ||
-      (neighbor_cache[i].state == ND6_PROBE)) {
 
-    /* Send out. */
-    SMEMCPY(dest.addr, neighbor_cache[i].lladdr, 6);
-    return ethernet_output(netif, q, (struct eth_addr*)(netif->hwaddr), &dest, ETHTYPE_IPV6);
+  /* If no hardware address is returned, nd6 has queued the packet for later. */
+  if (hwaddr == NULL) {
+    return ERR_OK;
   }
 
-  /* We should queue packet on this interface. */
-  return nd6_queue_packet(i, q);
+  /* Send out the packet using the returned hardware address. */
+  SMEMCPY(dest.addr, hwaddr, 6);
+  return ethernet_output(netif, q, (struct eth_addr*)(netif->hwaddr), &dest, ETHTYPE_IPV6);
 }
 
 #endif /* LWIP_IPV6 && LWIP_ETHERNET */
diff --git a/src/core/ipv6/nd6.c b/src/core/ipv6/nd6.c
index 7986d70..c3c5d42 100644
--- a/src/core/ipv6/nd6.c
+++ b/src/core/ipv6/nd6.c
@@ -98,6 +98,8 @@ static s8_t nd6_get_router(const ip6_addr_t *router_addr, struct netif *netif);
 static s8_t nd6_new_router(const ip6_addr_t *router_addr, struct netif *netif);
 static s8_t nd6_get_onlink_prefix(ip6_addr_t *prefix, struct netif *netif);
 static s8_t nd6_new_onlink_prefix(ip6_addr_t *prefix, struct netif *netif);
+static s8_t nd6_get_next_hop_entry(const ip6_addr_t *ip6addr, struct netif *netif);
+static err_t nd6_queue_packet(s8_t neighbor_index, struct pbuf *q);
 
 #define ND6_SEND_FLAG_MULTICAST_DEST 0x01
 #define ND6_SEND_FLAG_ALLNODES_DEST 0x02
@@ -1562,7 +1564,7 @@ nd6_new_onlink_prefix(ip6_addr_t *prefix, struct netif *netif)
  *         suitable next hop was found, ERR_MEM if no cache entry
  *         could be created
  */
-s8_t
+static s8_t
 nd6_get_next_hop_entry(const ip6_addr_t *ip6addr, struct netif *netif)
 {
 #ifdef LWIP_HOOK_ND6_GET_GW
@@ -1684,7 +1686,7 @@ nd6_get_next_hop_entry(const ip6_addr_t *ip6addr, struct netif *netif)
  * @param q packet to be queued
  * @return ERR_OK if succeeded, ERR_MEM if out of memory
  */
-err_t
+static err_t
 nd6_queue_packet(s8_t neighbor_index, struct pbuf *q)
 {
   err_t result = ERR_MEM;
@@ -1861,6 +1863,60 @@ nd6_send_q(s8_t i)
 #endif /* LWIP_ND6_QUEUEING */
 }
 
+/**
+ * A packet is to be transmitted to a specific IPv6 destination on a specific
+ * interface. Check if we can find the hardware address of the next hop to use
+ * for the packet. If so, give the hardware address to the caller, which should
+ * use it to send the packet right away. Otherwise, enqueue the packet for
+ * later transmission while looking up the hardware address, if possible.
+ *
+ * As such, this function returns one of three different possible results:
+ *
+ * - ERR_OK with a non-NULL 'hwaddrp': the caller should send the packet now.
+ * - ERR_OK with a NULL 'hwaddrp': the packet has been enqueued for later.
+ * - not ERR_OK: something went wrong; forward the error upward in the stack.
+ *
+ * @param netif The lwIP network interface on which the IP packet will be sent.
+ * @param q The pbuf(s) containing the IP packet to be sent.
+ * @param ip6addr The destination IPv6 address of the packet.
+ * @param hwaddrp On success, filled with a pointer to a HW address or NULL.
+ * @return
+ * - ERR_OK on success, ERR_RTE if no route was found for the packet,
+ * or ERR_MEM if low memory conditions prohibit sending the packet at all.
+ */
+err_t
+nd6_packet_send_check(struct netif *netif, struct pbuf *q, const ip6_addr_t *ip6addr, const u8_t **hwaddrp)
+{
+  s8_t i;
+
+  /* Get next hop record. */
+  i = nd6_get_next_hop_entry(ip6addr, netif);
+  if (i < 0) {
+    /* failed to get a next hop neighbor record. */
+    return i;
+  }
+
+  /* Now that we have a destination record, send or queue the packet. */
+  if (neighbor_cache[i].state == ND6_STALE) {
+    /* Switch to delay state. */
+    neighbor_cache[i].state = ND6_DELAY;
+    neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
+  }
+  /* @todo should we send or queue if PROBE? send for now, to let unicast NS pass. */
+  if ((neighbor_cache[i].state == ND6_REACHABLE) ||
+      (neighbor_cache[i].state == ND6_DELAY) ||
+      (neighbor_cache[i].state == ND6_PROBE)) {
+
+    /* Tell the caller to send out the packet now. */
+    *hwaddrp = neighbor_cache[i].lladdr;
+    return ERR_OK;
+  }
+
+  /* We should queue packet on this interface. */
+  *hwaddrp = NULL;
+  return nd6_queue_packet(i, q);
+}
+
 
 /**
  * Get the Path MTU for a destination.
diff --git a/src/include/lwip/nd6.h b/src/include/lwip/nd6.h
index 1d4b2f2..97df6b0 100644
--- a/src/include/lwip/nd6.h
+++ b/src/include/lwip/nd6.h
@@ -142,10 +142,9 @@ extern u32_t retrans_timer;
 void nd6_tmr(void);
 void nd6_input(struct pbuf *p, struct netif *inp);
 void nd6_clear_destination_cache(void);
-s8_t nd6_get_next_hop_entry(const ip6_addr_t *ip6addr, struct netif *netif);
 struct netif *nd6_find_route(const ip6_addr_t *ip6addr);
+err_t nd6_packet_send_check(struct netif *netif, struct pbuf *q, const ip6_addr_t *ip6addr, const u8_t **hwaddrp);
 u16_t nd6_get_destination_mtu(const ip6_addr_t *ip6addr, struct netif *netif);
-err_t nd6_queue_packet(s8_t neighbor_index, struct pbuf *p);
 #if LWIP_ND6_TCP_REACHABILITY_HINTS
 void nd6_reachability_hint(const ip6_addr_t *ip6addr);
 #endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */
diff --git a/src/netif/lowpan6.c b/src/netif/lowpan6.c
index eca9a45..36c041a 100644
--- a/src/netif/lowpan6.c
+++ b/src/netif/lowpan6.c
@@ -616,7 +616,8 @@ lowpan4_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr)
 err_t
 lowpan6_output(struct netif *netif, struct pbuf *q, const ip6_addr_t *ip6addr)
 {
-  s8_t i;
+  err_t result;
+  const u8_t *hwaddr;
   struct ieee_802154_addr src, dest;
 #if LWIP_6LOWPAN_INFER_SHORT_ADDRESS
   ip6_addr_t ip6_src;
@@ -663,35 +664,23 @@ lowpan6_output(struct netif *netif, struct pbuf *q, const ip6_addr_t *ip6addr)
   }
 #endif /* LWIP_6LOWPAN_INFER_SHORT_ADDRESS */
 
-
-  /* Get next hop record. */
-  i = nd6_get_next_hop_entry(ip6addr, netif);
-  if (i < 0) {
+  /* Ask ND6 what to do with the packet. */
+  result = nd6_packet_send_check(netif, q, ip6addr, &hwaddr);
+  if (result != ERR_OK) {
     MIB2_STATS_NETIF_INC(netif, ifoutdiscards);
-    /* failed to get a next hop neighbor record. */
-    return ERR_MEM;
+    return result;
   }
 
-  /* Now that we have a destination record, send or queue the packet. */
-  if (neighbor_cache[i].state == ND6_STALE) {
-    /* Switch to delay state. */
-    neighbor_cache[i].state = ND6_DELAY;
-    neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME;
-  }
-  /* @todo should we send or queue if PROBE? send for now, to let unicast NS pass. */
-  if ((neighbor_cache[i].state == ND6_REACHABLE) ||
-      (neighbor_cache[i].state == ND6_DELAY) ||
-      (neighbor_cache[i].state == ND6_PROBE)) {
-
-    /* Send out. */
-    dest.addr_len = netif->hwaddr_len;
-    SMEMCPY(dest.addr, neighbor_cache[i].lladdr, netif->hwaddr_len);
-    MIB2_STATS_NETIF_INC(netif, ifoutucastpkts);
-    return lowpan6_frag(netif, q, &src, &dest);
+  /* If no hardware address is returned, nd6 has queued the packet for later. */
+  if (hwaddr == NULL) {
+    return ERR_OK;
   }
 
-  /* We should queue packet on this interface. */
-  return nd6_queue_packet(i, q);
+  /* Send out the packet using the returned hardware address. */
+  dest.addr_len = netif->hwaddr_len;
+  SMEMCPY(dest.addr, hwaddr, netif->hwaddr_len);
+  MIB2_STATS_NETIF_INC(netif, ifoutucastpkts);
+  return lowpan6_frag(netif, q, &src, &dest);
 }
 
 static struct pbuf *
-- 
2.5.2

