Notify network observers of route changes.

Bug: 9180552
Change-Id: If8432bc74335676320b815784b21f404d3479c35
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index dd9c39f..b7af374 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -17,6 +17,7 @@
 package android.net;
 
 import android.net.LinkAddress;
+import android.net.RouteInfo;
 
 /**
  * Callback class for receiving events from an INetworkManagementService
@@ -98,4 +99,14 @@
      * @param servers The IP addresses of the DNS servers.
      */
     void interfaceDnsServerInfo(String iface, long lifetime, in String[] servers);
+
+    /**
+     * A route has been added or updated.
+     */
+    void routeUpdated(in RouteInfo route);
+
+    /**
+     * A route has been removed.
+     */
+    void routeRemoved(in RouteInfo route);
 }
diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java
index 430dd63..3d9fb5c 100644
--- a/core/java/com/android/server/net/BaseNetworkObserver.java
+++ b/core/java/com/android/server/net/BaseNetworkObserver.java
@@ -18,6 +18,7 @@
 
 import android.net.INetworkManagementEventObserver;
 import android.net.LinkAddress;
+import android.net.RouteInfo;
 
 /**
  * Base {@link INetworkManagementEventObserver} that provides no-op
@@ -70,4 +71,14 @@
     public void interfaceDnsServerInfo(String iface, long lifetime, String[] servers) {
         // default no-op
     }
+
+    @Override
+    public void routeUpdated(RouteInfo route) {
+        // default no-op
+    }
+
+    @Override
+    public void routeRemoved(RouteInfo route) {
+        // default no-op
+    }
 }
diff --git a/core/java/com/android/server/net/NetlinkTracker.java b/core/java/com/android/server/net/NetlinkTracker.java
index 82df2e5..7dd8dd8 100644
--- a/core/java/com/android/server/net/NetlinkTracker.java
+++ b/core/java/com/android/server/net/NetlinkTracker.java
@@ -18,6 +18,7 @@
 
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.RouteInfo;
 import android.util.Log;
 
 /**
@@ -84,6 +85,12 @@
         }
     }
 
+    private void maybeLog(String operation, Object o) {
+        if (DBG) {
+            Log.d(TAG, operation + ": " + o.toString());
+        }
+    }
+
     @Override
     public void addressUpdated(String iface, LinkAddress address) {
         if (mInterfaceName.equals(iface)) {
@@ -112,6 +119,34 @@
         }
     }
 
+    @Override
+    public void routeUpdated(RouteInfo route) {
+        if (mInterfaceName.equals(route.getInterface())) {
+            maybeLog("routeUpdated", route);
+            boolean changed;
+            synchronized (this) {
+                changed = mLinkProperties.addRoute(route);
+            }
+            if (changed) {
+                mCallback.update();
+            }
+        }
+    }
+
+    @Override
+    public void routeRemoved(RouteInfo route) {
+        if (mInterfaceName.equals(route.getInterface())) {
+            maybeLog("routeRemoved", route);
+            boolean changed;
+            synchronized (this) {
+                changed = mLinkProperties.removeRoute(route);
+            }
+            if (changed) {
+                mCallback.update();
+            }
+        }
+    }
+
     /**
      * Returns a copy of this object's LinkProperties.
      */
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index d26f3fc..7022294 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -41,6 +41,7 @@
 import android.net.ConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.InterfaceConfiguration;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.NetworkStats;
 import android.net.NetworkUtils;
@@ -145,6 +146,7 @@
         public static final int InterfaceClassActivity    = 613;
         public static final int InterfaceAddressChange    = 614;
         public static final int InterfaceDnsServerInfo    = 615;
+        public static final int RouteChange               = 616;
     }
 
     static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1;
@@ -580,6 +582,28 @@
         }
     }
 
+    /**
+     * Notify our observers of a route change.
+     */
+    private void notifyRouteChange(String action, RouteInfo route) {
+        final int length = mObservers.beginBroadcast();
+        try {
+            for (int i = 0; i < length; i++) {
+                try {
+                    if (action.equals("updated")) {
+                        mObservers.getBroadcastItem(i).routeUpdated(route);
+                    } else {
+                        mObservers.getBroadcastItem(i).routeRemoved(route);
+                    }
+                } catch (RemoteException e) {
+                } catch (RuntimeException e) {
+                }
+            }
+        } finally {
+            mObservers.finishBroadcast();
+        }
+    }
+
     //
     // Netd Callback handling
     //
@@ -722,6 +746,47 @@
                     }
                     return true;
                     // break;
+            case NetdResponseCode.RouteChange:
+                    /*
+                     * A route has been updated or removed.
+                     * Format: "NNN Route <updated|removed> <dst> [via <gateway] [dev <iface>]"
+                     */
+                    if (!cooked[1].equals("Route") || cooked.length < 6) {
+                        throw new IllegalStateException(errorMessage);
+                    }
+
+                    String via = null;
+                    String dev = null;
+                    boolean valid = true;
+                    for (int i = 4; (i + 1) < cooked.length && valid; i += 2) {
+                        if (cooked[i].equals("dev")) {
+                            if (dev == null) {
+                                dev = cooked[i+1];
+                            } else {
+                                valid = false;  // Duplicate interface.
+                            }
+                        } else if (cooked[i].equals("via")) {
+                            if (via == null) {
+                                via = cooked[i+1];
+                            } else {
+                                valid = false;  // Duplicate gateway.
+                            }
+                        } else {
+                            valid = false;      // Unknown syntax.
+                        }
+                    }
+                    if (valid) {
+                        try {
+                            // InetAddress.parseNumericAddress(null) inexplicably returns ::1.
+                            InetAddress gateway = null;
+                            if (via != null) gateway = InetAddress.parseNumericAddress(via);
+                            RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev);
+                            notifyRouteChange(cooked[2], route);
+                            return true;
+                        } catch (IllegalArgumentException e) {}
+                    }
+                    throw new IllegalStateException(errorMessage);
+                    // break;
             default: break;
             }
             return false;