Fix the adding of host routes.

We used to just add

Change-Id: I991e4cc976cc2932887dd3242fd50e013d521b0a
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 823d10f..fbe5379 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -225,4 +225,50 @@
         }
         return addRoute(interfaceName, dstStr, prefixLength, gwStr) == 0;
     }
+
+    /**
+     * Get InetAddress masked with prefixLength.  Will never return null.
+     * @param IP address which will be masked with specified prefixLength
+     * @param prefixLength the prefixLength used to mask the IP
+     */
+    public static InetAddress getNetworkPart(InetAddress address, int prefixLength) {
+        if (address == null) {
+            throw new RuntimeException("getNetworkPart doesn't accept null address");
+        }
+
+        byte[] array = address.getAddress();
+
+        if (prefixLength < 0 || prefixLength > array.length * 8) {
+            throw new RuntimeException("getNetworkPart - bad prefixLength");
+        }
+
+        int offset = prefixLength / 8;
+        int reminder = prefixLength % 8;
+        byte mask = (byte)(0xFF << (8 - reminder));
+
+        if (offset < array.length) array[offset] = (byte)(array[offset] & mask);
+
+        offset++;
+
+        for (; offset < array.length; offset++) {
+            array[offset] = 0;
+        }
+
+        InetAddress netPart = null;
+        try {
+            netPart = InetAddress.getByAddress(array);
+        } catch (UnknownHostException e) {
+            throw new RuntimeException("getNetworkPart error - " + e.toString());
+        }
+        return netPart;
+    }
+
+    /**
+     * Check if IP address type is consistent between two InetAddress.
+     * @return true if both are the same type.  False otherwise.
+     */
+    public static boolean addressTypeMatches(InetAddress left, InetAddress right) {
+        return (((left instanceof Inet4Address) && (right instanceof Inet4Address)) ||
+                ((left instanceof Inet6Address) && (right instanceof Inet6Address)));
+    }
 }
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 5b10531..9c4e48b 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -23,6 +23,9 @@
 import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
+
+import java.util.Collection;
+
 /**
  * A simple container for route information.
  *
@@ -44,39 +47,30 @@
     public RouteInfo(LinkAddress destination, InetAddress gateway) {
         if (destination == null) {
             try {
-                if ((gateway != null) && (gateway instanceof Inet4Address)) {
-                    destination = new LinkAddress(InetAddress.getByName("0.0.0.0"), 32);
+                if ((gateway != null) || (gateway instanceof Inet4Address)) {
+                    destination = new LinkAddress(Inet4Address.ANY, 0);
                 } else {
-                    destination = new LinkAddress(InetAddress.getByName("::0"), 128);
+                    destination = new LinkAddress(Inet6Address.ANY, 0);
                 }
             } catch (Exception e) {}
         }
-        mDestination = destination;
+        mDestination = new LinkAddress(NetworkUtils.getNetworkPart(destination.getAddress(),
+                destination.getNetworkPrefixLength()), destination.getNetworkPrefixLength());
         mGateway = gateway;
         mIsDefault = isDefault();
     }
 
     public RouteInfo(InetAddress gateway) {
-        LinkAddress destination = null;
-        try {
-            if ((gateway != null) && (gateway instanceof Inet4Address)) {
-                destination = new LinkAddress(InetAddress.getByName("0.0.0.0"), 32);
-            } else {
-                destination = new LinkAddress(InetAddress.getByName("::0"), 128);
-            }
-        } catch (Exception e) {}
-        mDestination = destination;
-        mGateway = gateway;
-        mIsDefault = isDefault();
+        this(null, gateway);
     }
 
     private boolean isDefault() {
         boolean val = false;
         if (mGateway != null) {
             if (mGateway instanceof Inet4Address) {
-                val = (mDestination == null || mDestination.getNetworkPrefixLength() == 32);
+                val = (mDestination == null || mDestination.getNetworkPrefixLength() == 0);
             } else {
-                val = (mDestination == null || mDestination.getNetworkPrefixLength() == 128);
+                val = (mDestination == null || mDestination.getNetworkPrefixLength() == 0);
             }
         }
         return val;
@@ -159,4 +153,43 @@
             return new RouteInfo[size];
         }
     };
+
+    private boolean matches(InetAddress destination) {
+        if (destination == null) return false;
+
+        // if the destination is present and the route is default.
+        // return true
+        if (isDefault()) return true;
+
+        // match the route destination and destination with prefix length
+        InetAddress dstNet = NetworkUtils.getNetworkPart(destination,
+                mDestination.getNetworkPrefixLength());
+
+        return mDestination.getAddress().equals(dstNet);
+    }
+
+    /**
+     * Find the route from a Collection of routes that best matches a given address.
+     * May return null if no routes are applicable.
+     * @param routes a Collection of RouteInfos to chose from
+     * @param dest the InetAddress your trying to get to
+     * @return the RouteInfo from the Collection that best fits the given address
+     */
+    public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
+        if ((routes == null) || (dest == null)) return null;
+
+        RouteInfo bestRoute = null;
+        // pick a longest prefix match under same address type
+        for (RouteInfo route : routes) {
+            if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) {
+                if ((bestRoute != null) &&
+                        (bestRoute.mDestination.getNetworkPrefixLength() >=
+                        route.mDestination.getNetworkPrefixLength())) {
+                    continue;
+                }
+                if (route.matches(dest)) bestRoute = route;
+            }
+        }
+        return bestRoute;
+    }
 }
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 428c94f..9bc7a9f 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -81,6 +81,10 @@
     private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
             "android.telephony.apn-restore";
 
+    // used in recursive route setting to add gateways for the host for which
+    // a host route was requested.
+    private static final int MAX_HOSTROUTE_CYCLE_COUNT = 10;
+
     private Tethering mTethering;
     private boolean mTetheringConfigValid = false;
 
@@ -921,7 +925,7 @@
         }
         try {
             InetAddress addr = InetAddress.getByAddress(hostAddress);
-            return addHostRoute(tracker, addr);
+            return addHostRoute(tracker, addr, 0);
         } catch (UnknownHostException e) {}
         return false;
     }
@@ -934,24 +938,45 @@
      * TODO - deprecate
      * @return {@code true} on success, {@code false} on failure
      */
-    private boolean addHostRoute(NetworkStateTracker nt, InetAddress hostAddress) {
+    private boolean addHostRoute(NetworkStateTracker nt, InetAddress hostAddress, int cycleCount) {
         if (nt.getNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI) {
             return false;
         }
 
-        LinkProperties p = nt.getLinkProperties();
-        if (p == null) return false;
-        String interfaceName = p.getInterfaceName();
+        LinkProperties lp = nt.getLinkProperties();
+        if ((lp == null) || (hostAddress == null)) return false;
 
+        String interfaceName = lp.getInterfaceName();
         if (DBG) {
-            log("Requested host route to " + hostAddress + "(" + interfaceName + ")");
+            log("Requested host route to " + hostAddress + "(" + interfaceName + "), cycleCount=" +
+                    cycleCount);
         }
-        if (interfaceName != null) {
-            return NetworkUtils.addHostRoute(interfaceName, hostAddress, null);
-        } else {
+        if (interfaceName == null) {
             if (DBG) loge("addHostRoute failed due to null interface name");
             return false;
         }
+
+        RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), hostAddress);
+        InetAddress gateway = null;
+        if (bestRoute != null) {
+            gateway = bestRoute.getGateway();
+            // if the best route is ourself, don't relf-reference, just add the host route
+            if (hostAddress.equals(gateway)) gateway = null;
+        }
+        if (gateway != null) {
+            if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) {
+                loge("Error adding hostroute - too much recursion");
+                return false;
+            }
+            if (!addHostRoute(nt, gateway, cycleCount+1)) return false;
+        }
+        return NetworkUtils.addHostRoute(interfaceName, hostAddress, gateway);
+    }
+
+    // TODO support the removal of single host routes.  Keep a ref count of them so we
+    // aren't over-zealous
+    private boolean removeHostRoute(NetworkStateTracker nt, InetAddress hostAddress) {
+        return false;
     }
 
     /**
@@ -1389,7 +1414,7 @@
             Collection<InetAddress> dnsList = p.getDnses();
             for (InetAddress dns : dnsList) {
                 if (DBG) log("  adding " + dns);
-                NetworkUtils.addHostRoute(interfaceName, dns, null);
+                addHostRoute(nt, dns, 0);
             }
             nt.privateDnsRouteSet(true);
         }
@@ -1423,7 +1448,7 @@
             //TODO - handle non-default routes
             if (route.isDefaultRoute()) {
                 InetAddress gateway = route.getGateway();
-                if (NetworkUtils.addHostRoute(interfaceName, gateway, null) &&
+                if (addHostRoute(nt, gateway, 0) &&
                         NetworkUtils.addDefaultRoute(interfaceName, gateway)) {
                     if (DBG) {
                         NetworkInfo networkInfo = nt.getNetworkInfo();