From 579613a53b09621b58a193cec950894517f23ba6 Mon Sep 17 00:00:00 2001
From: David van Moolenbroek <david@minix3.org>
Date: Fri, 30 Dec 2016 21:41:30 +0000
Subject: [PATCH 7/9] ip6: improve source address selection

The introduction of address lifetimes also means that lwIP correctly
supports transitions between PREFERRED and DEPRECATED address states,
and that means that the source address selection must be changed to
take this into account. Adding this feature to the previous algorithm
would have resulted in a mess, so this patch rewrites the algorithm to
stay close to the rules described in RFC 6724 (formerly 3484) Sec. 5.
This yields the following changes:

- Rule 2 ("prefer appropriate scope") is now fully implemented, most
  importantly allowing larger-scope addresses to be picked if no
  smaller-scope addresses are available (e.g., a global address may
  now be used to connect to a unique-local address);
- Rule 3 ("avoid deprecated addresses") is now also fully implemented;
- unknown-scope addresses are also supported, with lowest priority;
- the link between the prescribed rules and the actual algorithm is
  made much more explicit, hopefully allowing future improvements to
  be made more easily.

For reasons explained in comments, one previous deviation from the RFC
on Rule 2 is retained for now.
---
 src/core/ipv6/ip6.c | 131 ++++++++++++++++++++++++++++------------------------
 1 file changed, 70 insertions(+), 61 deletions(-)

diff --git a/src/core/ipv6/ip6.c b/src/core/ipv6/ip6.c
index 772d891..9dfd558 100644
--- a/src/core/ipv6/ip6.c
+++ b/src/core/ipv6/ip6.c
@@ -197,9 +197,22 @@ ip6_route(const ip6_addr_t *src, const ip6_addr_t *dest)
 
 /**
  * @ingroup ip6
- * Select the best IPv6 source address for a given destination
- * IPv6 address. Loosely follows RFC 3484. "Strong host" behavior
- * is assumed.
+ * Select the best IPv6 source address for a given destination IPv6 address.
+ *
+ * This implementation follows RFC 6724 Sec. 5 to the following extent:
+ * - Rules 1, 2, 3: fully implemented
+ * - Rules 4, 5, 5.5: not applicable
+ * - Rule 6: not implemented
+ * - Rule 7: not applicable
+ * - Rule 8: limited to "prefer /64 subnet match over non-match"
+ *
+ * For Rule 2, we deliberately deviate from RFC 6724 Sec. 3.1 by considering
+ * ULAs to be of smaller scope than global addresses, to avoid that a preferred
+ * ULA is picked over a deprecated global address when given a global address
+ * as destination, as that would likely result in broken two-way communication.
+ *
+ * As long as temporary addresses are not supported (as used in Rule 7), a
+ * proper implementation of Rule 8 would obviate the need to implement Rule 6.
  *
  * @param netif the netif on which to send a packet
  * @param dest the destination we are trying to reach
@@ -209,73 +222,69 @@ ip6_route(const ip6_addr_t *src, const ip6_addr_t *dest)
 const ip_addr_t *
 ip6_select_source_address(struct netif *netif, const ip6_addr_t *dest)
 {
-  const ip_addr_t *src = NULL;
-  u8_t i;
-
-  /* If dest is link-local, choose a link-local source. */
-  if (ip6_addr_islinklocal(dest) || ip6_addr_ismulticast_linklocal(dest) || ip6_addr_ismulticast_iflocal(dest)) {
-    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
-      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
-          ip6_addr_islinklocal(netif_ip6_addr(netif, i))) {
-        return netif_ip_addr6(netif, i);
-      }
-    }
+  const ip_addr_t *best_addr;
+  const ip6_addr_t *cand_addr;
+  s8_t dest_scope, cand_scope, best_scope;
+  u8_t i, cand_pref, best_pref, cand_bits, best_bits;
+
+  /* Start by determining the scope of the given destination address. These
+   * tests are hopefully (roughly) in order of likeliness to match. */
+  if (ip6_addr_isglobal(dest)) {
+    dest_scope = IP6_MULTICAST_SCOPE_GLOBAL;
+  } else if (ip6_addr_islinklocal(dest) || ip6_addr_isloopback(dest)) {
+    dest_scope = IP6_MULTICAST_SCOPE_LINK_LOCAL;
+  } else if (ip6_addr_isuniquelocal(dest)) {
+    dest_scope = IP6_MULTICAST_SCOPE_ORGANIZATION_LOCAL;
+  } else if (ip6_addr_ismulticast(dest)) {
+    dest_scope = ip6_addr_multicast_scope(dest);
+  } else if (ip6_addr_issitelocal(dest)) {
+    dest_scope = IP6_MULTICAST_SCOPE_SITE_LOCAL;
+  } else {
+    /* no match, consider scope global */
+    dest_scope = IP6_MULTICAST_SCOPE_GLOBAL;
   }
 
-  /* Choose a site-local with matching prefix. */
-  if (ip6_addr_issitelocal(dest) || ip6_addr_ismulticast_sitelocal(dest)) {
-    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
-      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
-          ip6_addr_issitelocal(netif_ip6_addr(netif, i)) &&
-          ip6_addr_netcmp(dest, netif_ip6_addr(netif, i))) {
-        return netif_ip_addr6(netif, i);
-      }
-    }
-  }
+  best_addr = NULL;
 
-  /* Choose a unique-local with matching prefix. */
-  if (ip6_addr_isuniquelocal(dest) || ip6_addr_ismulticast_orglocal(dest)) {
-    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
-      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
-          ip6_addr_isuniquelocal(netif_ip6_addr(netif, i)) &&
-          ip6_addr_netcmp(dest, netif_ip6_addr(netif, i))) {
-        return netif_ip_addr6(netif, i);
-      }
+  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+    /* Consider only valid (= preferred and deprecated) addresses. */
+    if (!ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) {
+      continue;
     }
-  }
-
-  /* Choose a global with best matching prefix. */
-  if (ip6_addr_isglobal(dest) || ip6_addr_ismulticast_global(dest)) {
-    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
-      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
-          ip6_addr_isglobal(netif_ip6_addr(netif, i))) {
-        if (src == NULL) {
-          src = netif_ip_addr6(netif, i);
-        }
-        else {
-          /* Replace src only if we find a prefix match. */
-          /* @todo find longest matching prefix. */
-          if ((!(ip6_addr_netcmp(ip_2_ip6(src), dest))) &&
-              ip6_addr_netcmp(netif_ip6_addr(netif, i), dest)) {
-            src = netif_ip_addr6(netif, i);
-          }
-        }
-      }
+    /* Determine the scope of this candidate address. Same ordering idea. */
+    cand_addr = netif_ip6_addr(netif, i);
+    if (ip6_addr_isglobal(cand_addr)) {
+      cand_scope = IP6_MULTICAST_SCOPE_GLOBAL;
+    } else if (ip6_addr_islinklocal(cand_addr)) {
+      cand_scope = IP6_MULTICAST_SCOPE_LINK_LOCAL;
+    } else if (ip6_addr_isuniquelocal(cand_addr)) {
+      cand_scope = IP6_MULTICAST_SCOPE_ORGANIZATION_LOCAL;
+    } else if (ip6_addr_issitelocal(cand_addr)) {
+      cand_scope = IP6_MULTICAST_SCOPE_SITE_LOCAL;
+    } else {
+      /* no match, treat as low-priority global scope */
+      cand_scope = IP6_MULTICAST_SCOPE_RESERVEDF;
     }
-    if (src != NULL) {
-      return src;
+    cand_pref = ip6_addr_ispreferred(netif_ip6_addr_state(netif, i));
+    /* @todo compute the actual common bits, for longest matching prefix. */
+    cand_bits = ip6_addr_netcmp(cand_addr, dest); /* just 1 or 0 for now */
+    if (cand_bits && ip6_addr_nethostcmp(cand_addr, dest)) {
+      return netif_ip_addr6(netif, i); /* Rule 1 */
     }
-  }
-
-  /* Last resort: see if arbitrary prefix matches. */
-  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
-    if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
-        ip6_addr_netcmp(dest, netif_ip6_addr(netif, i))) {
-      return netif_ip_addr6(netif, i);
+    if ((best_addr == NULL) || /* no alternative yet */
+        ((cand_scope < best_scope) && (cand_scope >= dest_scope)) ||
+        ((cand_scope > best_scope) && (best_scope < dest_scope)) || /* Rule 2 */
+        ((cand_scope == best_scope) && ((cand_pref > best_pref) || /* Rule 3 */
+        ((cand_pref == best_pref) && (cand_bits > best_bits))))) { /* Rule 8 */
+      /* We found a new "winning" candidate. */
+      best_addr = netif_ip_addr6(netif, i);
+      best_scope = cand_scope;
+      best_pref = cand_pref;
+      best_bits = cand_bits;
     }
   }
 
-  return NULL;
+  return best_addr; /* may be NULL */
 }
 
 #if LWIP_IPV6_FORWARD
-- 
2.5.2

