Merge "Prohibit address families by default unless a VPN explicitly allows them." into lmp-dev
diff --git a/server/CommandListener.cpp b/server/CommandListener.cpp
index ab6b952..cba3731 100644
--- a/server/CommandListener.cpp
+++ b/server/CommandListener.cpp
@@ -1345,9 +1345,9 @@
         return syntaxError(client, "Missing argument");
     }
 
-    //    0      1      2      3      4       5         6            7           8
-    // network route [legacy <uid>]  add   <netId> <interface> <destination> [nexthop]
-    // network route [legacy <uid>] remove <netId> <interface> <destination> [nexthop]
+    //    0      1      2      3      4       5         6            7               8
+    // network route [legacy <uid>]  add   <netId> <interface> <destination> [nexthop|"unreachable"]
+    // network route [legacy <uid>] remove <netId> <interface> <destination> [nexthop|"unreachable"]
     if (!strcmp(argv[1], "route")) {
         if (argc < 6 || argc > 9) {
             return syntaxError(client, "Incorrect number of arguments");
@@ -1370,6 +1370,10 @@
         }
         ++nextArg;
 
+        if (argc < nextArg + 3 || argc > nextArg + 4) {
+            return syntaxError(client, "Incorrect number of arguments");
+        }
+
         unsigned netId = stringToNetId(argv[nextArg++]);
         const char* interface = argv[nextArg++];
         const char* destination = argv[nextArg++];
diff --git a/server/NetworkController.h b/server/NetworkController.h
index 104f98c..fca4125 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -72,6 +72,9 @@
     int addUsersToNetwork(unsigned netId, const UidRanges& uidRanges) WARN_UNUSED_RESULT;
     int removeUsersFromNetwork(unsigned netId, const UidRanges& uidRanges) WARN_UNUSED_RESULT;
 
+    // |nexthop| can be NULL (to indicate a directly-connected route), "unreachable" (to indicate a
+    // route that's blocked) or a regular IP address.
+    //
     // Routes are added to tables determined by the interface, so only |interface| is actually used.
     // |netId| is given only to sanity check that the interface has the correct netId.
     int addRoute(unsigned netId, const char* interface, const char* destination,
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index 92b4a94..355326d 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -354,27 +354,38 @@
         return -ENOBUFS;  // Cannot happen; parsePrefix only supports IPv4 and IPv6.
     }
 
-    // If an interface was specified, find the ifindex.
+    uint8_t type = RTN_UNICAST;
     uint32_t ifindex;
-    if (interface != OIF_NONE) {
-        ifindex = if_nametoindex(interface);
-        if (!ifindex) {
-            ALOGE("cannot find interface %s", interface);
-            return -ENODEV;
-        }
-    }
-
-    // If a nexthop was specified, parse it as the same family as the prefix.
     uint8_t rawNexthop[sizeof(in6_addr)];
-    if (nexthop && inet_pton(family, nexthop, rawNexthop) <= 0) {
-        ALOGE("inet_pton failed for nexthop %s", nexthop);
-        return -EINVAL;
+
+    if (nexthop && !strcmp(nexthop, "unreachable")) {
+        type = RTN_UNREACHABLE;
+        // 'interface' is likely non-NULL, as the caller (modifyRoute()) likely used it to lookup
+        // the table number. But it's an error to specify an interface ("dev ...") or a nexthop for
+        // unreachable routes, so nuke them. (IPv6 allows them to be specified; IPv4 doesn't.)
+        interface = OIF_NONE;
+        nexthop = NULL;
+    } else {
+        // If an interface was specified, find the ifindex.
+        if (interface != OIF_NONE) {
+            ifindex = if_nametoindex(interface);
+            if (!ifindex) {
+                ALOGE("cannot find interface %s", interface);
+                return -ENODEV;
+            }
+        }
+
+        // If a nexthop was specified, parse it as the same family as the prefix.
+        if (nexthop && inet_pton(family, nexthop, rawNexthop) <= 0) {
+            ALOGE("inet_pton failed for nexthop %s", nexthop);
+            return -EINVAL;
+        }
     }
 
     // Assemble a rtmsg and put it in an array of iovec structures.
     rtmsg route = {
         .rtm_protocol = RTPROT_STATIC,
-        .rtm_type = RTN_UNICAST,
+        .rtm_type = type,
         .rtm_family = family,
         .rtm_dst_len = prefixLength,
     };
diff --git a/server/RouteController.h b/server/RouteController.h
index 3d00a66..b21bc77 100644
--- a/server/RouteController.h
+++ b/server/RouteController.h
@@ -66,6 +66,8 @@
     static int removeInterfaceFromDefaultNetwork(const char* interface,
                                                  Permission permission) WARN_UNUSED_RESULT;
 
+    // |nexthop| can be NULL (to indicate a directly-connected route), "unreachable" (to indicate a
+    // route that's blocked) or a regular IP address.
     static int addRoute(const char* interface, const char* destination, const char* nexthop,
                         TableType tableType) WARN_UNUSED_RESULT;
     static int removeRoute(const char* interface, const char* destination, const char* nexthop,