ncsid: Find IPv6 gateway stably

Some environments have multiple gateways for the node to use. We want to
ensure that we aren't flip flopping between multiple gateways and lock
on to a specific gateway until it goes away.

Tested: Ran in an environment with multiple gateways and verified that
it did not switch after it acquired the first one.

Change-Id: I12bac9582d35bdca49320fab8f5063ebfb2f3986
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/subprojects/ncsid/src/update_ra_gw.sh b/subprojects/ncsid/src/update_ra_gw.sh
index e228909..8af94e2 100644
--- a/subprojects/ncsid/src/update_ra_gw.sh
+++ b/subprojects/ncsid/src/update_ra_gw.sh
@@ -17,23 +17,18 @@
 
 NCSI_IF="$1"
 
-old_rtr=
+# We would prefer empty string but it's easier for associative array handling
+# to use invalid
+old_rtr=invalid
 old_mac=
 
 function set_rtr() {
-    [ -n "$rtr" -a -n "$lifetime" ] || return
-
-    # Reconfigure gateway in case of anything goes wrong
     if ! ip -6 route show | grep -q '^default'; then
         echo 'default route missing, reconfiguring...' >&2
-        old_rtr=
+        old_rtr=invalid
         old_mac=
     fi
-
     [ "$rtr" != "$old_rtr" -a "$mac" != "$old_mac" ] || return
-    # Only valid default routers can be considered, 0 lifetime implies
-    # a non-default router
-    (( lifetime > 0 )) || return
 
     echo "Setting default router: $rtr at $mac" >&2
 
@@ -65,10 +60,13 @@
 }
 
 retries=1
-w=60
+min_w=10
+declare -A rtrs
+rtrs=()
 while true; do
-    start=$SECONDS
-    args=(-m "$NCSI_IF" -w $(( w * 1000 )))
+    data=(${rtrs["${old_rtr}"]-})
+    curr_dl="${data[1]-$min_w}"
+    args=(-m "$NCSI_IF" -w $(( (curr_dl - SECONDS) * 1000 )))
     if (( retries > 0 )); then
         args+=(-r "$retries")
     else
@@ -78,7 +76,7 @@
         # `script` terminates all lines with a CRLF, remove it
         line="${line:0:-1}"
         if [ -z "$line" ]; then
-            lifetime=
+            lifetime=-1
             mac=
         elif [[ "$line" =~ ^Router' 'lifetime' '*:' '*([0-9]*) ]]; then
             lifetime="${BASH_REMATCH[1]}"
@@ -86,14 +84,42 @@
             mac="${BASH_REMATCH[1]}"
         elif [[ "$line" =~ ^from' '(.*)$ ]]; then
             rtr="${BASH_REMATCH[1]}"
-            set_rtr || true
-            lifetime=
+            # Only valid default routers can be considered, 0 lifetime implies
+            # a non-default router
+            if (( lifetime > 0 )); then
+                dl=$((lifetime + SECONDS))
+                rtrs["$rtr"]="$mac $dl"
+                # If we don't have a router, we want to take the first one
+                # to speed up acquisition time on boot
+                if [ -z "$old_rtr" ]; then
+                    set_rtr || true
+                fi
+            fi
+            lifetime=-1
             mac=
-            rtr=
         fi
     done < <(exec script -q -c "rdisc6 ${args[*]}" /dev/null 2>/dev/null)
-    # If rdisc6 exits early we still want to wait the full `w` time before
-    # starting again.
-    (( timeout = start + w - SECONDS ))
+    # Purge any expired routers
+    for rtr in "${!rtrs[@]}"; do
+        data=(${rtrs["$rtr"]})
+        dl=${data[1]}
+        if (( dl <= SECONDS )); then
+            unset rtrs["$rtr"]
+        fi
+    done
+    # Consider changing the gateway if the old one doesn't send RAs for the entire period
+    # This ensures we don't flip flop between multiple defaults if they exist.
+    if [ -z "${rtrs["$old_rtr"]-}" ]; then
+        echo "Old router $old_rtr disappeared" >&2
+        for rtr in "${!rtrs[@]}"; do
+            data=(${rtrs["$rtr"]})
+            mac=${data[0]}
+            dl=${data[1]}
+            set_rtr && break
+        done
+    fi
+
+    # If rdisc6 exits early we still want to wait for the deadline before retrying
+    (( timeout = curr_dl - SECONDS ))
     sleep $(( timeout < 0 ? 0 : timeout ))
 done