Refactor: Encapsulate permissions and interfaces into a Network class.

Currently, there's a lot of logic in NetworkController surrounding events such
as interface addition/removal, network creation/destruction and default network
change, because these events are interwined. For example, adding an interface
means also adding a corresponding default network rule if the interface is being
added to the current default network.

When we introduce VPNs into this mix, things will get hairy real quick for all
this logic in NetworkController.

In this refactor, we introduce an abstract base class Network which supports
adding and removing interfaces. The main concrete implementation of this is
PhysicalNetwork, which allows setting permissions and "default network" state.

Since we've moved network permissions into the above class, and user permissions
into NetworkController, PermissionsController is unused and has been removed.

Also fix a few bugs in RouteController:
+ Use uidEnd correctly.
+ Check for all error cases in inet_pton.
+ Check the return value of android_fork_execvp() correctly.
+ The "return cmd1() && cmd2()" pattern is wrong. Rewrite that code.

Also (non-functional changes):
+ Remove instantiations of RouteController. It has static methods only.
+ Reorder some blocks in CommandListener so that the most frequent commands are
  checked first.
+ Remove unused paramError() and clearNetworkPreference().
+ Change all return codes to int (negative errno) wherever applicable.
+ Add WARN_UNUSED_RESULT everywhere.
+ Cleanup some style in RouteController and NetworkController.
+ Use uid_t instead of unsigned for user IDs.
+ Add clearer log messages at the source of failures.
+ Add a check for when fwmark bits are set without corresponding mask bits.

Bug: 15409918

Change-Id: Ibba78b0850160f9f3d17d476f16331a6db0025d1
diff --git a/server/Android.mk b/server/Android.mk
index c2cca8e..33a0be6 100644
--- a/server/Android.mk
+++ b/server/Android.mk
@@ -54,9 +54,10 @@
         NetdConstants.cpp \
         NetlinkHandler.cpp \
         NetlinkManager.cpp \
+        Network.cpp \
         NetworkController.cpp \
         Permission.cpp \
-        PermissionsController.cpp \
+        PhysicalNetwork.cpp \
         PppController.cpp \
         ResolverController.cpp \
         RouteController.cpp \
diff --git a/server/CommandListener.cpp b/server/CommandListener.cpp
index 2b6d348..bd60785 100644
--- a/server/CommandListener.cpp
+++ b/server/CommandListener.cpp
@@ -44,7 +44,6 @@
 #include "oem_iptables_hook.h"
 #include "NetdConstants.h"
 #include "FirewallController.h"
-#include "PermissionsController.h"
 #include "RouteController.h"
 
 #include <string>
@@ -68,8 +67,6 @@
 
 }  // namespace
 
-PermissionsController* CommandListener::sPermissionsController = NULL;
-RouteController* CommandListener::sRouteController = NULL;
 NetworkController *CommandListener::sNetCtrl = NULL;
 TetherController *CommandListener::sTetherCtrl = NULL;
 NatController *CommandListener::sNatCtrl = NULL;
@@ -175,12 +172,8 @@
     registerCmd(new ClatdCmd());
     registerCmd(new NetworkCommand());
 
-    if (!sPermissionsController)
-        sPermissionsController = new PermissionsController();
-    if (!sRouteController)
-        sRouteController = new RouteController();
     if (!sNetCtrl)
-        sNetCtrl = new NetworkController(sPermissionsController, sRouteController);
+        sNetCtrl = new NetworkController();
     if (!sSecondaryTableCtrl)
         sSecondaryTableCtrl = new SecondaryTableController(sNetCtrl);
     if (!sTetherCtrl)
@@ -247,7 +240,9 @@
 
     sSecondaryTableCtrl->setupIptablesHooks();
 
-    sRouteController->Init();
+    if (int ret = RouteController::Init()) {
+        ALOGE("failed to initialize RouteController (%s)", strerror(-ret));
+    }
 }
 
 CommandListener::InterfaceCmd::InterfaceCmd() :
@@ -1563,12 +1558,9 @@
     return 0;
 }
 
-int CommandListener::NetworkCommand::paramError(SocketClient* client, const char* message) {
-    client->sendMsg(ResponseCode::CommandParameterError, message, false);
-    return 0;
-}
-
-int CommandListener::NetworkCommand::operationError(SocketClient* client, const char* message) {
+int CommandListener::NetworkCommand::operationError(SocketClient* client, const char* message,
+                                                    int ret) {
+    errno = -ret;
     client->sendMsg(ResponseCode::OperationFailed, message, true);
     return 0;
 }
@@ -1583,6 +1575,71 @@
         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]
+    if (!strcmp(argv[1], "route")) {
+        if (argc < 6 || argc > 9) {
+            return syntaxError(client, "Incorrect number of arguments");
+        }
+
+        int nextArg = 2;
+        bool legacy = false;
+        uid_t uid = 0;
+        if (!strcmp(argv[nextArg], "legacy")) {
+            ++nextArg;
+            legacy = true;
+            uid = strtoul(argv[nextArg++], NULL, 0);
+        }
+
+        bool add = false;
+        if (!strcmp(argv[nextArg], "add")) {
+            add = true;
+        } else if (strcmp(argv[nextArg], "remove")) {
+            return syntaxError(client, "Unknown argument");
+        }
+        ++nextArg;
+
+        // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
+        unsigned netId = strtoul(argv[nextArg++], NULL, 0);
+        const char* interface = argv[nextArg++];
+        const char* destination = argv[nextArg++];
+        const char* nexthop = argc > nextArg ? argv[nextArg] : NULL;
+
+        int ret;
+        if (add) {
+            ret = sNetCtrl->addRoute(netId, interface, destination, nexthop, legacy, uid);
+        } else {
+            ret = sNetCtrl->removeRoute(netId, interface, destination, nexthop, legacy, uid);
+        }
+        if (ret) {
+            return operationError(client, add ? "addRoute() failed" : "removeRoute() failed", ret);
+        }
+
+        return success(client);
+    }
+
+    //    0         1         2         3
+    // network   addiface  <netId> <interface>
+    // network removeiface <netId> <interface>
+    if (!strcmp(argv[1], "addiface") || !strcmp(argv[1], "removeiface")) {
+        if (argc != 4) {
+            return syntaxError(client, "Missing argument");
+        }
+        // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
+        unsigned netId = strtoul(argv[2], NULL, 0);
+        if (!strcmp(argv[1], "addiface")) {
+            if (int ret = sNetCtrl->addInterfaceToNetwork(netId, argv[3])) {
+                return operationError(client, "addInterfaceToNetwork() failed", ret);
+            }
+        } else {
+            if (int ret = sNetCtrl->removeInterfaceFromNetwork(netId, argv[3])) {
+                return operationError(client, "removeInterfaceFromNetwork() failed", ret);
+            }
+        }
+        return success(client);
+    }
+
     //    0      1       2          3
     // network create <netId> [<permission> ...]
     if (!strcmp(argv[1], "create")) {
@@ -1596,8 +1653,8 @@
         if (nextArg != argc) {
             return syntaxError(client, "Unknown trailing argument(s)");
         }
-        if (!sNetCtrl->createNetwork(netId, permission)) {
-            return operationError(client, "createNetwork() failed");
+        if (int ret = sNetCtrl->createNetwork(netId, permission)) {
+            return operationError(client, "createNetwork() failed", ret);
         }
         return success(client);
     }
@@ -1610,32 +1667,31 @@
         }
         // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
         unsigned netId = strtoul(argv[2], NULL, 0);
-        if (!sNetCtrl->destroyNetwork(netId)) {
-            return operationError(client, "destroyNetwork() failed");
+        if (int ret = sNetCtrl->destroyNetwork(netId)) {
+            return operationError(client, "destroyNetwork() failed", ret);
         }
         return success(client);
     }
 
-    //    0         1         2         3
-    // network   addiface  <netId> <interface>
-    // network removeiface <netId> <interface>
-    if (!strcmp(argv[1], "addiface") || !strcmp(argv[1], "removeiface")) {
-        if (argc != 4) {
+    //    0       1      2      3
+    // network default  set  <netId>
+    // network default clear
+    if (!strcmp(argv[1], "default")) {
+        if (argc < 3) {
             return syntaxError(client, "Missing argument");
         }
-        // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
-        unsigned netId = strtoul(argv[2], NULL, 0);
-        int ret;
-        if (!strcmp(argv[1], "addiface")) {
-            if ((ret = sNetCtrl->addInterfaceToNetwork(netId, argv[3])) != 0) {
-                errno = -ret;
-                return operationError(client, "addInterfaceToNetwork() failed");
+        unsigned netId = NETID_UNSET;
+        if (!strcmp(argv[2], "set")) {
+            if (argc < 4) {
+                return syntaxError(client, "Missing netId");
             }
-        } else {
-            if ((ret = sNetCtrl->removeInterfaceFromNetwork(netId, argv[3])) != 0) {
-                errno = -ret;
-                return operationError(client, "removeInterfaceFromNetwork() failed");
-            }
+            // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
+            netId = strtoul(argv[3], NULL, 0);
+        } else if (strcmp(argv[2], "clear")) {
+            return syntaxError(client, "Unknown argument");
+        }
+        if (int ret = sNetCtrl->setDefaultNetwork(netId)) {
+            return operationError(client, "setDefaultNetwork() failed", ret);
         }
         return success(client);
     }
@@ -1669,12 +1725,10 @@
             return syntaxError(client, "Missing id");
         }
         if (!strcmp(argv[2], "user")) {
-            sNetCtrl->setPermissionForUser(permission, ids);
+            sNetCtrl->setPermissionForUsers(permission, ids);
         } else if (!strcmp(argv[2], "network")) {
-            int ret = sNetCtrl->setPermissionForNetwork(permission, ids);
-            if (ret) {
-                errno = -ret;
-                return operationError(client, "setPermissionForNetwork() failed");
+            if (int ret = sNetCtrl->setPermissionForNetworks(permission, ids)) {
+                return operationError(client, "setPermissionForNetworks() failed", ret);
             }
         } else {
             return syntaxError(client, "Unknown argument");
@@ -1682,74 +1736,6 @@
         return success(client);
     }
 
-    //    0       1      2      3
-    // network default  set  <netId>
-    // network default clear
-    if (!strcmp(argv[1], "default")) {
-        if (argc < 3) {
-            return syntaxError(client, "Missing argument");
-        }
-        unsigned netId = NETID_UNSET;
-        if (!strcmp(argv[2], "set")) {
-            if (argc < 4) {
-                return syntaxError(client, "Missing netId");
-            }
-            // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
-            netId = strtoul(argv[3], NULL, 0);
-        } else if (strcmp(argv[2], "clear")) {
-            return syntaxError(client, "Unknown argument");
-        }
-        if (!sNetCtrl->setDefaultNetwork(netId)) {
-            return operationError(client, "setDefaultNetwork() failed");
-        }
-        return success(client);
-    }
-
-    //    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]
-    if (!strcmp(argv[1], "route")) {
-        if (argc < 6 || argc > 9) {
-            return syntaxError(client, "Incorrect number of arguments");
-        }
-
-        int nextArg = 2;
-        bool legacy = false;
-        unsigned uid = 0;
-        if (!strcmp(argv[nextArg], "legacy")) {
-            ++nextArg;
-            legacy = true;
-            uid = strtoul(argv[nextArg++], NULL, 0);
-        }
-
-        bool add = false;
-        if (!strcmp(argv[nextArg], "add")) {
-            add = true;
-        } else if (strcmp(argv[nextArg], "remove")) {
-            return syntaxError(client, "Unknown argument");
-        }
-        ++nextArg;
-
-        // strtoul() returns 0 on errors, which is fine because 0 is an invalid netId.
-        unsigned netId = strtoul(argv[nextArg++], NULL, 0);
-        const char* interface = argv[nextArg++];
-        const char* destination = argv[nextArg++];
-        const char* nexthop = argc > nextArg ? argv[nextArg] : NULL;
-
-        int ret;
-        if (add) {
-            ret = sNetCtrl->addRoute(netId, interface, destination, nexthop, legacy, uid);
-        } else {
-            ret = sNetCtrl->removeRoute(netId, interface, destination, nexthop, legacy, uid);
-        }
-        if (ret != 0) {
-            errno = -ret;
-            return operationError(client, add ? "addRoute() failed" : "removeRoute() failed");
-        }
-
-        return success(client);
-    }
-
     // network vpn create <netId> [owner_uid]
     // network vpn destroy <netId>
     // network <bind|unbind> <netId> <uid1> .. <uidN>
diff --git a/server/CommandListener.h b/server/CommandListener.h
index ada79eb..78fcdab 100644
--- a/server/CommandListener.h
+++ b/server/CommandListener.h
@@ -33,9 +33,6 @@
 #include "FirewallController.h"
 #include "ClatdController.h"
 
-class PermissionsController;
-class RouteController;
-
 class CommandListener : public FrameworkListener {
     static TetherController *sTetherCtrl;
     static NatController *sNatCtrl;
@@ -48,11 +45,9 @@
     static SecondaryTableController *sSecondaryTableCtrl;
     static FirewallController *sFirewallCtrl;
     static ClatdController *sClatdCtrl;
-    static RouteController* sRouteController;
 
 public:
     static NetworkController *sNetCtrl;
-    static PermissionsController* sPermissionsController;
 
     CommandListener();
     virtual ~CommandListener() {}
@@ -157,8 +152,7 @@
         int runCommand(SocketClient* client, int argc, char** argv);
     private:
         int syntaxError(SocketClient* cli, const char* message);
-        int paramError(SocketClient* cli, const char* message);
-        int operationError(SocketClient* cli, const char* message);
+        int operationError(SocketClient* cli, const char* message, int ret);
         int success(SocketClient* cli);
     };
 };
diff --git a/server/DnsProxyListener.cpp b/server/DnsProxyListener.cpp
index 3a8e475..3fcb5bd 100644
--- a/server/DnsProxyListener.cpp
+++ b/server/DnsProxyListener.cpp
@@ -39,14 +39,10 @@
 #include "DnsProxyListener.h"
 #include "NetdConstants.h"
 #include "NetworkController.h"
-#include "PermissionsController.h"
 #include "ResponseCode.h"
 
-DnsProxyListener::DnsProxyListener(const NetworkController* netCtrl,
-        const PermissionsController* permCtrl) :
-                 FrameworkListener("dnsproxyd"),
-                 mNetCtrl(netCtrl),
-                 mPermCtrl(permCtrl) {
+DnsProxyListener::DnsProxyListener(const NetworkController* netCtrl) :
+        FrameworkListener("dnsproxyd"), mNetCtrl(netCtrl) {
     registerCmd(new GetAddrInfoCmd(this));
     registerCmd(new GetHostByAddrCmd(this));
     registerCmd(new GetHostByNameCmd(this));
@@ -58,7 +54,7 @@
     // If netd's UID is forced into a VPN that isn't the intended network,
     // use VPN protect bit to force it into the desired network.
     fwmark.protectedFromVpn = mNetCtrl->getNetwork(getuid(), netId, true) != netId;
-    fwmark.permission = mPermCtrl->getPermissionForUser(c->getUid());
+    fwmark.permission = mNetCtrl->getPermissionForUser(c->getUid());
     return fwmark.intValue;
 }
 
diff --git a/server/DnsProxyListener.h b/server/DnsProxyListener.h
index 936d6aa..f5624e8 100644
--- a/server/DnsProxyListener.h
+++ b/server/DnsProxyListener.h
@@ -22,16 +22,14 @@
 #include "NetdCommand.h"
 
 class NetworkController;
-class PermissionsController;
 
 class DnsProxyListener : public FrameworkListener {
 public:
-    DnsProxyListener(const NetworkController* netCtrl, const PermissionsController* permCtrl);
+    explicit DnsProxyListener(const NetworkController* netCtrl);
     virtual ~DnsProxyListener() {}
 
 private:
     const NetworkController *mNetCtrl;
-    const PermissionsController *mPermCtrl;
     class GetAddrInfoCmd : public NetdCommand {
     public:
         GetAddrInfoCmd(const DnsProxyListener* dnsProxyListener);
diff --git a/server/FwmarkServer.cpp b/server/FwmarkServer.cpp
index b5a5872..60ee852 100644
--- a/server/FwmarkServer.cpp
+++ b/server/FwmarkServer.cpp
@@ -19,17 +19,13 @@
 #include "Fwmark.h"
 #include "FwmarkCommand.h"
 #include "NetworkController.h"
-#include "PermissionsController.h"
 #include "resolv_netid.h"
 
 #include <sys/socket.h>
 #include <unistd.h>
 
-FwmarkServer::FwmarkServer(NetworkController* networkController,
-                           PermissionsController* permissionsController)
-        : SocketListener("fwmarkd", true),
-          mNetworkController(networkController),
-          mPermissionsController(permissionsController) {
+FwmarkServer::FwmarkServer(NetworkController* networkController) :
+        SocketListener("fwmarkd", true), mNetworkController(networkController) {
 }
 
 bool FwmarkServer::onDataAvailable(SocketClient* client) {
@@ -95,13 +91,13 @@
         return -errno;
     }
 
-    fwmark.permission = mPermissionsController->getPermissionForUser(client->getUid());
+    fwmark.permission = mNetworkController->getPermissionForUser(client->getUid());
 
     switch (command.cmdId) {
         case FwmarkCommand::ON_ACCEPT: {
             // Called after a socket accept(). The kernel would've marked the netId into the socket
             // already, so we just need to check permissions here.
-            if (!mPermissionsController->isUserPermittedOnNetwork(client->getUid(), fwmark.netId)) {
+            if (!mNetworkController->isUserPermittedOnNetwork(client->getUid(), fwmark.netId)) {
                 return -EPERM;
             }
             break;
@@ -130,8 +126,8 @@
                 if (!mNetworkController->isValidNetwork(command.netId)) {
                     return -ENONET;
                 }
-                if (!mPermissionsController->isUserPermittedOnNetwork(client->getUid(),
-                                                                      command.netId)) {
+                if (!mNetworkController->isUserPermittedOnNetwork(client->getUid(),
+                                                                  command.netId)) {
                     return -EPERM;
                 }
             }
diff --git a/server/FwmarkServer.h b/server/FwmarkServer.h
index 04fb280..54cbc74 100644
--- a/server/FwmarkServer.h
+++ b/server/FwmarkServer.h
@@ -20,12 +20,10 @@
 #include <sysutils/SocketListener.h>
 
 class NetworkController;
-class PermissionsController;
 
 class FwmarkServer : public SocketListener {
 public:
-    FwmarkServer(NetworkController* networkController,
-                 PermissionsController* permissionsController);
+    explicit FwmarkServer(NetworkController* networkController);
 
 private:
     // Overridden from SocketListener:
@@ -35,7 +33,6 @@
     int processClient(SocketClient* client, int* fd);
 
     NetworkController* const mNetworkController;
-    PermissionsController* const mPermissionsController;
 };
 
 #endif  // NETD_SERVER_FWMARK_SERVER_H
diff --git a/server/NetdConstants.h b/server/NetdConstants.h
index a05b6d6..4291bbc 100644
--- a/server/NetdConstants.h
+++ b/server/NetdConstants.h
@@ -47,4 +47,6 @@
 #define UINT32_STRLEN _INT_STRLEN(UINT32_MAX)
 #define UINT32_HEX_STRLEN sizeof("0x12345678")
 
+#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
+
 #endif
diff --git a/server/Network.cpp b/server/Network.cpp
new file mode 100644
index 0000000..5c4bd0e
--- /dev/null
+++ b/server/Network.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Network.h"
+
+#define LOG_TAG "Netd"
+#include "log/log.h"
+
+Network::Network(unsigned netId) : mNetId(netId) {
+}
+
+Network::~Network() {
+    if (!mInterfaces.empty()) {
+        ALOGE("deleting network with netId %u without clearing its interfaces", mNetId);
+    }
+}
+
+bool Network::hasInterface(const std::string& interface) const {
+    return mInterfaces.find(interface) != mInterfaces.end();
+}
+
+int Network::clearInterfaces() {
+    while (!mInterfaces.empty()) {
+        // Make a copy of the string, so removeInterface() doesn't lose its parameter when it
+        // removes the string from the set.
+        std::string interface = *mInterfaces.begin();
+        if (int ret = removeInterface(interface)) {
+            return ret;
+        }
+    }
+    return 0;
+}
diff --git a/server/Network.h b/server/Network.h
new file mode 100644
index 0000000..e5c929d
--- /dev/null
+++ b/server/Network.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NETD_SERVER_NETWORK_H
+#define NETD_SERVER_NETWORK_H
+
+#include "NetdConstants.h"
+
+#include <set>
+#include <string>
+
+// A Network represents a collection of interfaces participating as a single administrative unit.
+class Network {
+public:
+    explicit Network(unsigned netId);
+
+    // You MUST ensure that no interfaces are still assigned to this network, say by calling
+    // clearInterfaces(), before deleting it. This is because interface removal may fail. If we
+    // automatically removed interfaces in the destructor, you wouldn't know if it failed.
+    virtual ~Network();
+
+    bool hasInterface(const std::string& interface) const;
+
+    // These return 0 on success or negative errno on failure.
+    virtual int addInterface(const std::string& interface) WARN_UNUSED_RESULT = 0;
+    virtual int removeInterface(const std::string& interface) WARN_UNUSED_RESULT = 0;
+    int clearInterfaces() WARN_UNUSED_RESULT;
+
+protected:
+    const unsigned mNetId;
+    std::set<std::string> mInterfaces;
+};
+
+#endif  // NETD_SERVER_NETWORK_H
diff --git a/server/NetworkController.cpp b/server/NetworkController.cpp
index af95c1e..be28c5b 100644
--- a/server/NetworkController.cpp
+++ b/server/NetworkController.cpp
@@ -32,35 +32,22 @@
 
 #include "NetworkController.h"
 
-#include "PermissionsController.h"
+#include "PhysicalNetwork.h"
 #include "RouteController.h"
 
-#define LOG_TAG "NetworkController"
-
-#include <sys/socket.h>
-#include <linux/if.h>
-
-#include "cutils/log.h"
+#define LOG_TAG "Netd"
+#include "log/log.h"
 #include "resolv_netid.h"
 
 namespace {
 
 // Keep these in sync with ConnectivityService.java.
-const unsigned int MIN_NET_ID = 10;
-const unsigned int MAX_NET_ID = 65535;
+const unsigned MIN_NET_ID = 10;
+const unsigned MAX_NET_ID = 65535;
 
 }  // namespace
 
-NetworkController::NetworkController(PermissionsController* permissionsController,
-                                     RouteController* routeController)
-        : mDefaultNetId(NETID_UNSET),
-          mPermissionsController(permissionsController),
-          mRouteController(routeController) {
-}
-
-void NetworkController::clearNetworkPreference() {
-    android::RWLock::AutoWLock lock(mRWLock);
-    mUidMap.clear();
+NetworkController::NetworkController() : mDefaultNetId(NETID_UNSET) {
 }
 
 unsigned NetworkController::getDefaultNetwork() const {
@@ -68,312 +55,258 @@
     return mDefaultNetId;
 }
 
-bool NetworkController::setDefaultNetwork(unsigned newNetId) {
-    // newNetId must be either NETID_UNSET or a valid network. If it's NETID_UNSET, the caller is
-    // asking for there to be no default network, which is a request we support.
-    if (newNetId != NETID_UNSET && !isValidNetwork(newNetId)) {
-        ALOGE("invalid netId %u", newNetId);
-        errno = EINVAL;
-        return false;
+int NetworkController::setDefaultNetwork(unsigned netId) {
+    android::RWLock::AutoWLock lock(mRWLock);
+
+    if (netId == mDefaultNetId) {
+        return 0;
     }
 
-    unsigned oldNetId;
-    {
-        android::RWLock::AutoWLock lock(mRWLock);
-        oldNetId = mDefaultNetId;
-        mDefaultNetId = newNetId;
-    }
-
-    if (oldNetId == newNetId) {
-        return true;
-    }
-
-    bool status = true;
-    Permission permission;
-    InterfaceRange range;
-
-    // Add default network rules for the new netId.
-    permission = mPermissionsController->getPermissionForNetwork(newNetId);
-    range = mNetIdToInterfaces.equal_range(newNetId);
-    for (InterfaceIteratorConst iter = range.first; iter != range.second; ++iter) {
-        if (!mRouteController->addToDefaultNetwork(iter->second.c_str(), permission)) {
-            ALOGE("failed to add interface %s to default netId %u", iter->second.c_str(), newNetId);
-            status = false;
+    if (netId != NETID_UNSET) {
+        auto iter = mPhysicalNetworks.find(netId);
+        if (iter == mPhysicalNetworks.end()) {
+            ALOGE("invalid netId %u", netId);
+            return -EINVAL;
+        }
+        if (int ret = iter->second->addAsDefault()) {
+            return ret;
         }
     }
 
-    // Remove the old default network rules.
-    permission = mPermissionsController->getPermissionForNetwork(oldNetId);
-    range = mNetIdToInterfaces.equal_range(oldNetId);
-    for (InterfaceIteratorConst iter = range.first; iter != range.second; ++iter) {
-        if (!mRouteController->removeFromDefaultNetwork(iter->second.c_str(), permission)) {
-            ALOGE("failed to remove interface %s from default netId %u", iter->second.c_str(),
-                  oldNetId);
-            status = false;
+    if (mDefaultNetId != NETID_UNSET) {
+        auto iter = mPhysicalNetworks.find(mDefaultNetId);
+        if (iter == mPhysicalNetworks.end()) {
+            ALOGE("cannot find previously set default network with netId %u", mDefaultNetId);
+            return -ESRCH;
+        }
+        if (int ret = iter->second->removeAsDefault()) {
+            return ret;
         }
     }
 
-    return status;
+    mDefaultNetId = netId;
+    return 0;
 }
 
-bool NetworkController::setNetworkForUidRange(int uid_start, int uid_end, unsigned netId,
-                                              bool forward_dns) {
-    if (uid_start > uid_end || !isValidNetwork(netId)) {
+bool NetworkController::setNetworkForUidRange(uid_t uidStart, uid_t uidEnd, unsigned netId,
+                                              bool forwardDns) {
+    if (uidStart > uidEnd || !isValidNetwork(netId)) {
         errno = EINVAL;
         return false;
     }
 
     android::RWLock::AutoWLock lock(mRWLock);
-    for (std::list<UidEntry>::iterator it = mUidMap.begin(); it != mUidMap.end(); ++it) {
-        if (it->uid_start != uid_start || it->uid_end != uid_end || it->netId != netId)
-            continue;
-        it->forward_dns = forward_dns;
-        return true;
+    for (UidEntry& entry : mUidMap) {
+        if (entry.uidStart == uidStart && entry.uidEnd == uidEnd && entry.netId == netId) {
+            entry.forwardDns = forwardDns;
+            return true;
+        }
     }
 
-    mUidMap.push_front(UidEntry(uid_start, uid_end, netId, forward_dns));
+    mUidMap.push_front(UidEntry(uidStart, uidEnd, netId, forwardDns));
     return true;
 }
 
-bool NetworkController::clearNetworkForUidRange(int uid_start, int uid_end, unsigned netId) {
-    if (uid_start > uid_end || !isValidNetwork(netId)) {
+bool NetworkController::clearNetworkForUidRange(uid_t uidStart, uid_t uidEnd, unsigned netId) {
+    if (uidStart > uidEnd || !isValidNetwork(netId)) {
         errno = EINVAL;
         return false;
     }
 
     android::RWLock::AutoWLock lock(mRWLock);
-    for (std::list<UidEntry>::iterator it = mUidMap.begin(); it != mUidMap.end(); ++it) {
-        if (it->uid_start != uid_start || it->uid_end != uid_end || it->netId != netId)
-            continue;
-        mUidMap.erase(it);
-        return true;
+    for (auto iter = mUidMap.begin(); iter != mUidMap.end(); ++iter) {
+        if (iter->uidStart == uidStart && iter->uidEnd == uidEnd && iter->netId == netId) {
+            mUidMap.erase(iter);
+            return true;
+        }
     }
 
     errno = ENOENT;
     return false;
 }
 
-unsigned NetworkController::getNetwork(int uid, unsigned requested_netId, bool for_dns) const {
+unsigned NetworkController::getNetwork(uid_t uid, unsigned requestedNetId, bool forDns) const {
     android::RWLock::AutoRLock lock(mRWLock);
-    for (std::list<UidEntry>::const_iterator it = mUidMap.begin(); it != mUidMap.end(); ++it) {
-        if (uid < it->uid_start || it->uid_end < uid)
-            continue;
-        if (for_dns && !it->forward_dns)
-            break;
-        return it->netId;
+    for (const UidEntry& entry : mUidMap) {
+        if (entry.uidStart <= uid && uid <= entry.uidEnd) {
+            if (forDns && !entry.forwardDns) {
+                break;
+            }
+            return entry.netId;
+        }
     }
-    if (mValidNetworks.find(requested_netId) != mValidNetworks.end())
-        return requested_netId;
-    return mDefaultNetId;
+    return getNetworkLocked(requestedNetId) ? requestedNetId : mDefaultNetId;
 }
 
 unsigned NetworkController::getNetworkId(const char* interface) const {
-    for (InterfaceIteratorConst iter = mNetIdToInterfaces.begin(); iter != mNetIdToInterfaces.end();
-         ++iter) {
-        if (iter->second == interface) {
-            return iter->first;
+    android::RWLock::AutoRLock lock(mRWLock);
+    for (const auto& entry : mPhysicalNetworks) {
+        if (entry.second->hasInterface(interface)) {
+            return entry.first;
         }
     }
     return NETID_UNSET;
 }
 
-bool NetworkController::createNetwork(unsigned netId, Permission permission) {
+bool NetworkController::isValidNetwork(unsigned netId) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    return getNetworkLocked(netId);
+}
+
+int NetworkController::createNetwork(unsigned netId, Permission permission) {
     if (netId < MIN_NET_ID || netId > MAX_NET_ID) {
         ALOGE("invalid netId %u", netId);
-        errno = EINVAL;
-        return false;
-    }
-
-    {
-        android::RWLock::AutoWLock lock(mRWLock);
-        if (!mValidNetworks.insert(netId).second) {
-            ALOGE("duplicate netId %u", netId);
-            errno = EEXIST;
-            return false;
-        }
-    }
-
-    mPermissionsController->setPermissionForNetwork(permission, netId);
-    return true;
-}
-
-int NetworkController::addInterfaceToNetwork(unsigned netId, const char* interface) {
-    if (!isValidNetwork(netId) || !interface) {
-        ALOGE("invalid netId %u or interface null", netId);
         return -EINVAL;
     }
 
-    unsigned existingNetId = getNetworkId(interface);
-    if (existingNetId != NETID_UNSET) {
-        ALOGE("interface %s already assigned to netId %u", interface, existingNetId);
-        return -EBUSY;
+    if (isValidNetwork(netId)) {
+        ALOGE("duplicate netId %u", netId);
+        return -EEXIST;
     }
 
-    int ret;
-    Permission permission = mPermissionsController->getPermissionForNetwork(netId);
-    if ((ret = mRouteController->addInterfaceToNetwork(netId, interface, permission)) != 0) {
-        ALOGE("failed to add interface %s to netId %u", interface, netId);
+    PhysicalNetwork* physicalNetwork = new PhysicalNetwork(netId);
+    if (int ret = physicalNetwork->setPermission(permission)) {
+        ALOGE("inconceivable! setPermission cannot fail on an empty network");
+        delete physicalNetwork;
         return ret;
     }
 
-    mNetIdToInterfaces.insert(std::pair<unsigned, std::string>(netId, interface));
-
-    if (netId == getDefaultNetwork() &&
-            (ret = mRouteController->addToDefaultNetwork(interface, permission)) != 0) {
-        ALOGE("failed to add interface %s to default netId %u", interface, netId);
-        return ret;
-    }
-
+    android::RWLock::AutoWLock lock(mRWLock);
+    mPhysicalNetworks[netId] = physicalNetwork;
     return 0;
 }
 
-int NetworkController::removeInterfaceFromNetwork(unsigned netId, const char* interface) {
-    if (!isValidNetwork(netId) || !interface) {
-        ALOGE("invalid netId %u or interface null", netId);
-        return -EINVAL;
-    }
-
-    int ret = -ENOENT;
-    InterfaceRange range = mNetIdToInterfaces.equal_range(netId);
-    for (InterfaceIterator iter = range.first; iter != range.second; ++iter) {
-        if (iter->second == interface) {
-            mNetIdToInterfaces.erase(iter);
-            ret = 0;
-            break;
-        }
-    }
-
-    if (ret) {
-        ALOGE("interface %s not assigned to netId %u", interface, netId);
-        return ret;
-    }
-
-    Permission permission = mPermissionsController->getPermissionForNetwork(netId);
-    if (netId == getDefaultNetwork() &&
-            (ret = mRouteController->removeFromDefaultNetwork(interface, permission)) != 0) {
-        ALOGE("failed to remove interface %s from default netId %u", interface, netId);
-        return ret;
-    }
-
-    if ((ret = mRouteController->removeInterfaceFromNetwork(netId, interface, permission)) != 0) {
-        ALOGE("failed to remove interface %s from netId %u", interface, netId);
-        return ret;
-    }
-
-    return 0;
-}
-
-bool NetworkController::destroyNetwork(unsigned netId) {
+int NetworkController::destroyNetwork(unsigned netId) {
     if (!isValidNetwork(netId)) {
         ALOGE("invalid netId %u", netId);
-        errno = EINVAL;
-        return false;
+        return -EINVAL;
     }
 
     // TODO: ioctl(SIOCKILLADDR, ...) to kill all sockets on the old network.
 
-    bool status = true;
-
-    InterfaceRange range = mNetIdToInterfaces.equal_range(netId);
-    for (InterfaceIteratorConst iter = range.first; iter != range.second; ) {
-        char interface[IFNAMSIZ];
-        strncpy(interface, iter->second.c_str(), sizeof(interface));
-        interface[sizeof(interface) - 1] = 0;
-        ++iter;
-        if (!removeInterfaceFromNetwork(netId, interface)) {
-            status = false;
+    android::RWLock::AutoWLock lock(mRWLock);
+    Network* network = getNetworkLocked(netId);
+    if (int ret = network->clearInterfaces()) {
+        return ret;
+    }
+    if (mDefaultNetId == netId) {
+        PhysicalNetwork* physicalNetwork = static_cast<PhysicalNetwork*>(network);
+        if (int ret = physicalNetwork->removeAsDefault()) {
+            ALOGE("inconceivable! removeAsDefault cannot fail on an empty network");
+            return ret;
         }
+        mDefaultNetId = NETID_UNSET;
     }
-
-    if (netId == getDefaultNetwork()) {
-        setDefaultNetwork(NETID_UNSET);
-    }
-
-    {
-        android::RWLock::AutoWLock lock(mRWLock);
-        mValidNetworks.erase(netId);
-    }
-
-    mPermissionsController->setPermissionForNetwork(PERMISSION_NONE, netId);
-
+    mPhysicalNetworks.erase(netId);
+    delete network;
     _resolv_delete_cache_for_net(netId);
-    return status;
-}
-
-void NetworkController::setPermissionForUser(Permission permission,
-                                             const std::vector<unsigned>& uid) {
-    for (size_t i = 0; i < uid.size(); ++i) {
-        mPermissionsController->setPermissionForUser(permission, uid[i]);
-    }
-}
-
-int NetworkController::setPermissionForNetwork(Permission newPermission,
-                                               const std::vector<unsigned>& netId) {
-    for (size_t i = 0; i < netId.size(); ++i) {
-        if (!isValidNetwork(netId[i])) {
-            ALOGE("invalid netId %u", netId[i]);
-            return -EINVAL;
-        }
-
-        Permission oldPermission = mPermissionsController->getPermissionForNetwork(netId[i]);
-        if (oldPermission == newPermission) {
-            continue;
-        }
-
-        // TODO: ioctl(SIOCKILLADDR, ...) to kill sockets on the network that don't have
-        // newPermission.
-
-        InterfaceRange range = mNetIdToInterfaces.equal_range(netId[i]);
-        for (InterfaceIteratorConst iter = range.first; iter != range.second; ++iter) {
-            int ret = mRouteController->modifyNetworkPermission(netId[i], iter->second.c_str(),
-                                                                oldPermission, newPermission);
-            if (ret) {
-                ALOGE("failed to change permission on interface %s of netId %u from %x to %x",
-                      iter->second.c_str(), netId[i], oldPermission, newPermission);
-                return ret;
-            }
-        }
-
-        mPermissionsController->setPermissionForNetwork(newPermission, netId[i]);
-    }
-
     return 0;
 }
 
-int NetworkController::addRoute(unsigned netId, const char* interface, const char* destination,
-                                const char* nexthop, bool legacy, unsigned uid) {
-    return modifyRoute(netId, interface, destination, nexthop, true, legacy, uid);
-}
-
-int NetworkController::removeRoute(unsigned netId, const char* interface, const char* destination,
-                                   const char* nexthop, bool legacy, unsigned uid) {
-    return modifyRoute(netId, interface, destination, nexthop, false, legacy, uid);
-}
-
-bool NetworkController::isValidNetwork(unsigned netId) const {
-    if (netId == NETID_UNSET) {
-        return false;
-    }
-
-    android::RWLock::AutoRLock lock(mRWLock);
-    return mValidNetworks.find(netId) != mValidNetworks.end();
-}
-
-int NetworkController::modifyRoute(unsigned netId, const char* interface, const char* destination,
-                                   const char* nexthop, bool add, bool legacy, unsigned uid) {
+int NetworkController::addInterfaceToNetwork(unsigned netId, const char* interface) {
     if (!isValidNetwork(netId)) {
         ALOGE("invalid netId %u", netId);
         return -EINVAL;
     }
 
-    if (getNetworkId(interface) != netId) {
-        ALOGE("netId %u has no such interface %s", netId, interface);
+    unsigned existingNetId = getNetworkId(interface);
+    if (existingNetId != NETID_UNSET && existingNetId != netId) {
+        ALOGE("interface %s already assigned to netId %u", interface, existingNetId);
+        return -EBUSY;
+    }
+
+    android::RWLock::AutoWLock lock(mRWLock);
+    return getNetworkLocked(netId)->addInterface(interface);
+}
+
+int NetworkController::removeInterfaceFromNetwork(unsigned netId, const char* interface) {
+    if (!isValidNetwork(netId)) {
+        ALOGE("invalid netId %u", netId);
+        return -EINVAL;
+    }
+
+    android::RWLock::AutoWLock lock(mRWLock);
+    return getNetworkLocked(netId)->removeInterface(interface);
+}
+
+Permission NetworkController::getPermissionForUser(uid_t uid) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    auto iter = mUsers.find(uid);
+    return iter != mUsers.end() ? iter->second : PERMISSION_NONE;
+}
+
+void NetworkController::setPermissionForUsers(Permission permission,
+                                              const std::vector<uid_t>& uids) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    for (uid_t uid : uids) {
+        if (permission == PERMISSION_NONE) {
+            mUsers.erase(uid);
+        } else {
+            mUsers[uid] = permission;
+        }
+    }
+}
+
+bool NetworkController::isUserPermittedOnNetwork(uid_t uid, unsigned netId) const {
+    android::RWLock::AutoRLock lock(mRWLock);
+    auto userIter = mUsers.find(uid);
+    Permission userPermission = (userIter != mUsers.end() ? userIter->second : PERMISSION_NONE);
+    auto networkIter = mPhysicalNetworks.find(netId);
+    if (networkIter == mPhysicalNetworks.end()) {
+        return false;
+    }
+    Permission networkPermission = networkIter->second->getPermission();
+    return (userPermission & networkPermission) == networkPermission;
+}
+
+int NetworkController::setPermissionForNetworks(Permission permission,
+                                                const std::vector<unsigned>& netIds) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    for (unsigned netId : netIds) {
+        auto iter = mPhysicalNetworks.find(netId);
+        if (iter == mPhysicalNetworks.end()) {
+            ALOGE("invalid netId %u", netId);
+            return -EINVAL;
+        }
+
+        // TODO: ioctl(SIOCKILLADDR, ...) to kill socets on the network that don't have permission.
+
+        if (int ret = iter->second->setPermission(permission)) {
+            return ret;
+        }
+    }
+    return 0;
+}
+
+int NetworkController::addRoute(unsigned netId, const char* interface, const char* destination,
+                                const char* nexthop, bool legacy, uid_t uid) {
+    return modifyRoute(netId, interface, destination, nexthop, true, legacy, uid);
+}
+
+int NetworkController::removeRoute(unsigned netId, const char* interface, const char* destination,
+                                   const char* nexthop, bool legacy, uid_t uid) {
+    return modifyRoute(netId, interface, destination, nexthop, false, legacy, uid);
+}
+
+Network* NetworkController::getNetworkLocked(unsigned netId) const {
+    auto physicalNetworkIter = mPhysicalNetworks.find(netId);
+    if (physicalNetworkIter != mPhysicalNetworks.end()) {
+        return physicalNetworkIter->second;
+    }
+    return NULL;
+}
+
+int NetworkController::modifyRoute(unsigned netId, const char* interface, const char* destination,
+                                   const char* nexthop, bool add, bool legacy, uid_t uid) {
+    unsigned existingNetId = getNetworkId(interface);
+    if (netId == NETID_UNSET || existingNetId != netId) {
+        ALOGE("interface %s assigned to netId %u, not %u", interface, existingNetId, netId);
         return -ENOENT;
     }
 
     RouteController::TableType tableType;
     if (legacy) {
-        if (mPermissionsController->getPermissionForUser(uid) & PERMISSION_CONNECTIVITY_INTERNAL) {
+        if (getPermissionForUser(uid) & PERMISSION_CONNECTIVITY_INTERNAL) {
             tableType = RouteController::PRIVILEGED_LEGACY;
         } else {
             tableType = RouteController::LEGACY;
@@ -382,10 +315,11 @@
         tableType = RouteController::INTERFACE;
     }
 
-    return add ? mRouteController->addRoute(interface, destination, nexthop, tableType, uid) :
-                 mRouteController->removeRoute(interface, destination, nexthop, tableType, uid);
+    return add ? RouteController::addRoute(interface, destination, nexthop, tableType, uid) :
+                 RouteController::removeRoute(interface, destination, nexthop, tableType, uid);
 }
 
-NetworkController::UidEntry::UidEntry(int start, int end, unsigned netId, bool forward_dns)
-    : uid_start(start), uid_end(end), netId(netId), forward_dns(forward_dns) {
+NetworkController::UidEntry::UidEntry(uid_t uidStart, uid_t uidEnd, unsigned netId,
+                                      bool forwardDns) :
+        uidStart(uidStart), uidEnd(uidEnd), netId(netId), forwardDns(forwardDns) {
 }
diff --git a/server/NetworkController.h b/server/NetworkController.h
index 869f593..04a5ace 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -17,20 +17,18 @@
 #ifndef NETD_SERVER_NETWORK_CONTROLLER_H
 #define NETD_SERVER_NETWORK_CONTROLLER_H
 
+#include "NetdConstants.h"
 #include "Permission.h"
+
 #include "utils/RWLock.h"
 
 #include <list>
 #include <map>
-#include <set>
-#include <stddef.h>
-#include <stdint.h>
-#include <string>
-#include <utility>
+#include <sys/types.h>
 #include <vector>
 
-class PermissionsController;
-class RouteController;
+class Network;
+class PhysicalNetwork;
 
 /*
  * Keeps track of default, per-pid, and per-uid-range network selection, as
@@ -40,72 +38,62 @@
  */
 class NetworkController {
 public:
-    NetworkController(PermissionsController* permissionsController,
-                      RouteController* routeController);
+    NetworkController();
 
-    void clearNetworkPreference();
     unsigned getDefaultNetwork() const;
-    bool setDefaultNetwork(unsigned netId);
-    bool setNetworkForUidRange(int uid_start, int uid_end, unsigned netId, bool forward_dns);
-    bool clearNetworkForUidRange(int uid_start, int uid_end, unsigned netId);
+    int setDefaultNetwork(unsigned netId) WARN_UNUSED_RESULT;
 
-    // Order of preference: UID-specific, requested_netId, PID-specific, default.
-    // Specify NETID_UNSET for requested_netId if the default network is preferred.
-    // for_dns indicates if we're querrying the netId for a DNS request.  This avoids sending DNS
+    bool setNetworkForUidRange(uid_t uidStart, uid_t uidEnd, unsigned netId, bool forwardDns);
+    bool clearNetworkForUidRange(uid_t uidStart, uid_t uidEnd, unsigned netId);
+
+    // Order of preference: UID-specific, requestedNetId, default.
+    // Specify NETID_UNSET for requestedNetId if the default network is preferred.
+    // forDns indicates if we're querying the netId for a DNS request. This avoids sending DNS
     // requests to VPNs without DNS servers.
-    unsigned getNetwork(int uid, unsigned requested_netId, bool for_dns) const;
-
+    unsigned getNetwork(uid_t uid, unsigned requestedNetId, bool forDns) const;
     unsigned getNetworkId(const char* interface) const;
+    bool isValidNetwork(unsigned netId) const;
 
-    bool createNetwork(unsigned netId, Permission permission);
-    bool destroyNetwork(unsigned netId);
-    int addInterfaceToNetwork(unsigned netId, const char* interface);
-    int removeInterfaceFromNetwork(unsigned netId, const char* interface);
+    int createNetwork(unsigned netId, Permission permission) WARN_UNUSED_RESULT;
+    int destroyNetwork(unsigned netId) WARN_UNUSED_RESULT;
 
-    void setPermissionForUser(Permission permission, const std::vector<unsigned>& uid);
-    int setPermissionForNetwork(Permission permission, const std::vector<unsigned>& netId);
+    int addInterfaceToNetwork(unsigned netId, const char* interface) WARN_UNUSED_RESULT;
+    int removeInterfaceFromNetwork(unsigned netId, const char* interface) WARN_UNUSED_RESULT;
+
+    Permission getPermissionForUser(uid_t uid) const;
+    void setPermissionForUsers(Permission permission, const std::vector<uid_t>& uids);
+    bool isUserPermittedOnNetwork(uid_t uid, unsigned netId) const;
+    int setPermissionForNetworks(Permission permission,
+                                 const std::vector<unsigned>& netIds) WARN_UNUSED_RESULT;
 
     // 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,
-                 const char* nexthop, bool legacy, unsigned uid);
+                 const char* nexthop, bool legacy, uid_t uid) WARN_UNUSED_RESULT;
     int removeRoute(unsigned netId, const char* interface, const char* destination,
-                    const char* nexthop, bool legacy, unsigned uid);
-
-    bool isValidNetwork(unsigned netId) const;
+                    const char* nexthop, bool legacy, uid_t uid) WARN_UNUSED_RESULT;
 
 private:
-    typedef std::multimap<unsigned, std::string>::const_iterator InterfaceIteratorConst;
-    typedef std::multimap<unsigned, std::string>::iterator InterfaceIterator;
-    typedef std::pair<InterfaceIterator, InterfaceIterator> InterfaceRange;
+    Network* getNetworkLocked(unsigned netId) const;
 
     int modifyRoute(unsigned netId, const char* interface, const char* destination,
-                    const char* nexthop, bool add, bool legacy, unsigned uid);
+                    const char* nexthop, bool add, bool legacy, uid_t uid) WARN_UNUSED_RESULT;
 
     struct UidEntry {
-        int uid_start;
-        int uid_end;
-        unsigned netId;
-        bool forward_dns;
-        UidEntry(int uid_start, int uid_end, unsigned netId, bool forward_dns);
+        const uid_t uidStart;
+        const uid_t uidEnd;
+        const unsigned netId;
+        bool forwardDns;
+
+        UidEntry(uid_t uidStart, uid_t uidEnd, unsigned netId, bool forwardDns);
     };
 
-    // mRWLock guards all accesses to mUidMap, mDefaultNetId and mValidNetworks.
+    // mRWLock guards all accesses to mUidMap, mDefaultNetId, mPhysicalNetworks and mUsers.
     mutable android::RWLock mRWLock;
     std::list<UidEntry> mUidMap;
     unsigned mDefaultNetId;
-    std::set<unsigned> mValidNetworks;
-
-    PermissionsController* const mPermissionsController;
-    RouteController* const mRouteController;
-
-    // Maps a netId to all its interfaces.
-    //
-    // We need to know interface names to configure incoming packet marking and because routing
-    // tables are associated with interfaces and not with netIds.
-    //
-    // An interface may belong to at most one netId, but a netId may have multiple interfaces.
-    std::multimap<unsigned, std::string> mNetIdToInterfaces;
+    std::map<unsigned, PhysicalNetwork*> mPhysicalNetworks;  // Map keys are NetIds.
+    std::map<uid_t, Permission> mUsers;
 };
 
 #endif  // NETD_SERVER_NETWORK_CONTROLLER_H
diff --git a/server/PermissionsController.cpp b/server/PermissionsController.cpp
deleted file mode 100644
index 63c80dc..0000000
--- a/server/PermissionsController.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "PermissionsController.h"
-
-namespace {
-
-Permission get(const std::map<unsigned, Permission>& map, unsigned id) {
-    std::map<unsigned, Permission>::const_iterator iter = map.find(id);
-    return iter != map.end() ? iter->second : PERMISSION_NONE;
-}
-
-void set(std::map<unsigned, Permission>* map, Permission permission, unsigned id) {
-    if (permission == PERMISSION_NONE) {
-        map->erase(id);
-    } else {
-        (*map)[id] = permission;
-    }
-}
-
-}  // namespace
-
-Permission PermissionsController::getPermissionForUser(unsigned uid) const {
-    android::RWLock::AutoRLock lock(mRWLock);
-    return get(mUsers, uid);
-}
-
-void PermissionsController::setPermissionForUser(Permission permission, unsigned uid) {
-    android::RWLock::AutoWLock lock(mRWLock);
-    set(&mUsers, permission, uid);
-}
-
-Permission PermissionsController::getPermissionForNetwork(unsigned netId) const {
-    android::RWLock::AutoRLock lock(mRWLock);
-    return get(mNetworks, netId);
-}
-
-void PermissionsController::setPermissionForNetwork(Permission permission, unsigned netId) {
-    android::RWLock::AutoWLock lock(mRWLock);
-    set(&mNetworks, permission, netId);
-}
-
-bool PermissionsController::isUserPermittedOnNetwork(unsigned uid, unsigned netId) const {
-    android::RWLock::AutoRLock lock(mRWLock);
-    Permission userPermission = get(mUsers, uid);
-    Permission networkPermission = get(mNetworks, netId);
-    return (userPermission & networkPermission) == networkPermission;
-}
diff --git a/server/PermissionsController.h b/server/PermissionsController.h
deleted file mode 100644
index c2a014d..0000000
--- a/server/PermissionsController.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef NETD_SERVER_PERMISSIONS_CONTROLLER_H
-#define NETD_SERVER_PERMISSIONS_CONTROLLER_H
-
-#include "Permission.h"
-#include "utils/RWLock.h"
-
-#include <map>
-
-class PermissionsController {
-public:
-    Permission getPermissionForUser(unsigned uid) const;
-    void setPermissionForUser(Permission permission, unsigned uid);
-
-    Permission getPermissionForNetwork(unsigned netId) const;
-    void setPermissionForNetwork(Permission permission, unsigned netId);
-
-    bool isUserPermittedOnNetwork(unsigned uid, unsigned netId) const;
-
-private:
-    mutable android::RWLock mRWLock;
-    std::map<unsigned, Permission> mUsers;
-    std::map<unsigned, Permission> mNetworks;
-};
-
-#endif  // NETD_SERVER_PERMISSIONS_CONTROLLER_H
diff --git a/server/PhysicalNetwork.cpp b/server/PhysicalNetwork.cpp
new file mode 100644
index 0000000..2e9d71d
--- /dev/null
+++ b/server/PhysicalNetwork.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PhysicalNetwork.h"
+
+#include "RouteController.h"
+
+#define LOG_TAG "Netd"
+#include "log/log.h"
+
+namespace {
+
+WARN_UNUSED_RESULT int addToDefault(unsigned netId, const std::string& interface,
+                                    Permission permission) {
+    if (int ret = RouteController::addToDefaultNetwork(interface.c_str(), permission)) {
+        ALOGE("failed to add interface %s to default netId %u", interface.c_str(), netId);
+        return ret;
+    }
+    return 0;
+}
+
+WARN_UNUSED_RESULT int removeFromDefault(unsigned netId, const std::string& interface,
+                                         Permission permission) {
+    if (int ret = RouteController::removeFromDefaultNetwork(interface.c_str(), permission)) {
+        ALOGE("failed to remove interface %s from default netId %u", interface.c_str(), netId);
+        return ret;
+    }
+    return 0;
+}
+
+}  // namespace
+
+PhysicalNetwork::PhysicalNetwork(unsigned netId) :
+        Network(netId), mPermission(PERMISSION_NONE), mIsDefault(false) {
+}
+
+PhysicalNetwork::~PhysicalNetwork() {
+}
+
+Permission PhysicalNetwork::getPermission() const {
+    return mPermission;
+}
+
+int PhysicalNetwork::setPermission(Permission permission) {
+    if (permission == mPermission) {
+        return 0;
+    }
+    for (const std::string& interface : mInterfaces) {
+        if (int ret = RouteController::modifyNetworkPermission(mNetId, interface.c_str(),
+                                                               mPermission, permission)) {
+            ALOGE("failed to change permission on interface %s of netId %u from %x to %x",
+                  interface.c_str(), mNetId, mPermission, permission);
+            return ret;
+        }
+    }
+    if (mIsDefault) {
+        for (const std::string& interface : mInterfaces) {
+            if (int ret = addToDefault(mNetId, interface, permission)) {
+                return ret;
+            }
+            if (int ret = removeFromDefault(mNetId, interface, mPermission)) {
+                return ret;
+            }
+        }
+    }
+    mPermission = permission;
+    return 0;
+}
+
+int PhysicalNetwork::addAsDefault() {
+    if (mIsDefault) {
+        return 0;
+    }
+    for (const std::string& interface : mInterfaces) {
+        if (int ret = addToDefault(mNetId, interface, mPermission)) {
+            return ret;
+        }
+    }
+    mIsDefault = true;
+    return 0;
+}
+
+int PhysicalNetwork::removeAsDefault() {
+    if (!mIsDefault) {
+        return 0;
+    }
+    for (const std::string& interface : mInterfaces) {
+        if (int ret = removeFromDefault(mNetId, interface, mPermission)) {
+            return ret;
+        }
+    }
+    mIsDefault = false;
+    return 0;
+}
+
+int PhysicalNetwork::addInterface(const std::string& interface) {
+    if (hasInterface(interface)) {
+        return 0;
+    }
+    if (int ret = RouteController::addInterfaceToNetwork(mNetId, interface.c_str(), mPermission)) {
+        ALOGE("failed to add interface %s to netId %u", interface.c_str(), mNetId);
+        return ret;
+    }
+    if (mIsDefault) {
+        if (int ret = addToDefault(mNetId, interface, mPermission)) {
+            return ret;
+        }
+    }
+    mInterfaces.insert(interface);
+    return 0;
+}
+
+int PhysicalNetwork::removeInterface(const std::string& interface) {
+    if (!hasInterface(interface)) {
+        return 0;
+    }
+    if (int ret = RouteController::removeInterfaceFromNetwork(mNetId, interface.c_str(),
+                                                              mPermission)) {
+        ALOGE("failed to remove interface %s from netId %u", interface.c_str(), mNetId);
+        return ret;
+    }
+    if (mIsDefault) {
+        if (int ret = removeFromDefault(mNetId, interface, mPermission)) {
+            return ret;
+        }
+    }
+    mInterfaces.erase(interface);
+    return 0;
+}
diff --git a/server/PhysicalNetwork.h b/server/PhysicalNetwork.h
new file mode 100644
index 0000000..215c8b0
--- /dev/null
+++ b/server/PhysicalNetwork.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NETD_SERVER_PHYSICAL_NETWORK_H
+#define NETD_SERVER_PHYSICAL_NETWORK_H
+
+#include "Network.h"
+#include "Permission.h"
+
+class PhysicalNetwork : public Network {
+public:
+    explicit PhysicalNetwork(unsigned netId);
+    virtual ~PhysicalNetwork();
+
+    // These refer to permissions that apps must have in order to use this network.
+    Permission getPermission() const;
+    int setPermission(Permission permission) WARN_UNUSED_RESULT;
+
+    int addAsDefault() WARN_UNUSED_RESULT;
+    int removeAsDefault() WARN_UNUSED_RESULT;
+
+private:
+    int addInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+    int removeInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+
+    Permission mPermission;
+    bool mIsDefault;
+};
+
+#endif  // NETD_SERVER_PHYSICAL_NETWORK_H
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index 90a9399..d0e1461 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -17,27 +17,20 @@
 #include "RouteController.h"
 
 #include "Fwmark.h"
-#include "NetdConstants.h"
+
+#define LOG_TAG "Netd"
+#include "log/log.h"
+#include "logwrap/logwrap.h"
 
 #include <arpa/inet.h>
-#include <errno.h>
 #include <linux/fib_rules.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <logwrap/logwrap.h>
 #include <map>
-#include <netinet/in.h>
 #include <net/if.h>
-#include <sys/socket.h>
-#include <sys/uio.h>
-#include <unistd.h>
-
-// Avoids "non-constant-expression cannot be narrowed from type 'unsigned int' to 'unsigned short'"
-// warnings when using RTA_LENGTH(x) inside static initializers (even when x is already uint16_t).
-#define U16_RTA_LENGTH(x) static_cast<uint16_t>(RTA_LENGTH((x)))
 
 namespace {
 
+// BEGIN CONSTANTS --------------------------------------------------------------------------------
+
 const uint32_t RULE_PRIORITY_PRIVILEGED_LEGACY     = 11000;
 const uint32_t RULE_PRIORITY_PER_NETWORK_EXPLICIT  = 13000;
 const uint32_t RULE_PRIORITY_PER_NETWORK_INTERFACE = 14000;
@@ -50,8 +43,6 @@
 const uint32_t RULE_PRIORITY_UNREACHABLE           = 21000;
 #endif
 
-const uid_t INVALID_UID = static_cast<uid_t>(-1);
-
 // TODO: These should be turned into per-UID tables once the kernel supports UID-based routing.
 const int ROUTE_TABLE_PRIVILEGED_LEGACY = RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX - 901;
 const int ROUTE_TABLE_LEGACY            = RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX - 902;
@@ -66,9 +57,39 @@
              "Android-specific FRA_UID_{START,END} values also assigned in Linux uapi. "
              "Check that these values match what the kernel does and then update this assertion.");
 
+const uid_t INVALID_UID = static_cast<uid_t>(-1);
+
 const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
 const uint16_t NETLINK_CREATE_REQUEST_FLAGS = NETLINK_REQUEST_FLAGS | NLM_F_CREATE | NLM_F_EXCL;
 
+const sockaddr_nl NETLINK_ADDRESS = {AF_NETLINK, 0, 0, 0};
+
+const uint8_t AF_FAMILIES[] = {AF_INET, AF_INET6};
+
+const char* const IP_VERSIONS[] = {"-4", "-6"};
+
+// Avoids "non-constant-expression cannot be narrowed from type 'unsigned int' to 'unsigned short'"
+// warnings when using RTA_LENGTH(x) inside static initializers (even when x is already uint16_t).
+constexpr uint16_t U16_RTA_LENGTH(uint16_t x) {
+    return RTA_LENGTH(x);
+}
+
+// These are practically const, but can't be declared so, because they are used to initialize
+// non-const pointers ("void* iov_base") in iovec arrays.
+rtattr FRATTR_PRIORITY  = { U16_RTA_LENGTH(sizeof(uint32_t)), FRA_PRIORITY };
+rtattr FRATTR_TABLE     = { U16_RTA_LENGTH(sizeof(uint32_t)), FRA_TABLE };
+rtattr FRATTR_FWMARK    = { U16_RTA_LENGTH(sizeof(uint32_t)), FRA_FWMARK };
+rtattr FRATTR_FWMASK    = { U16_RTA_LENGTH(sizeof(uint32_t)), FRA_FWMASK };
+rtattr FRATTR_UID_START = { U16_RTA_LENGTH(sizeof(uid_t)),    FRA_UID_START };
+rtattr FRATTR_UID_END   = { U16_RTA_LENGTH(sizeof(uid_t)),    FRA_UID_END };
+
+rtattr RTATTR_TABLE     = { U16_RTA_LENGTH(sizeof(uint32_t)), RTA_TABLE };
+rtattr RTATTR_OIF       = { U16_RTA_LENGTH(sizeof(uint32_t)), RTA_OIF };
+
+uint8_t PADDING_BUFFER[RTA_ALIGNTO] = {0, 0, 0, 0};
+
+// END CONSTANTS ----------------------------------------------------------------------------------
+
 std::map<std::string, uint32_t> interfaceToIndex;
 
 uint32_t getRouteTableForInterface(const char* interface) {
@@ -79,8 +100,9 @@
         // If the interface goes away if_nametoindex() will return 0 but we still need to know
         // the index so we can remove the rules and routes.
         std::map<std::string, uint32_t>::iterator it = interfaceToIndex.find(interface);
-        if (it != interfaceToIndex.end())
+        if (it != interfaceToIndex.end()) {
             index = it->second;
+        }
     }
     return index ? index + RouteController::ROUTE_TABLE_OFFSET_FROM_INDEX : 0;
 }
@@ -89,7 +111,7 @@
 // |iov| is an array of struct iovec that contains the netlink message payload.
 // The netlink header is generated by this function based on |action| and |flags|.
 // Returns -errno if there was an error or if the kernel reported an error.
-int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec* iov, int iovlen) {
+WARN_UNUSED_RESULT int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec* iov, int iovlen) {
     nlmsghdr nlmsg = {
         .nlmsg_type = action,
         .nlmsg_flags = flags,
@@ -106,18 +128,23 @@
         nlmsgerr err;
     } response;
 
-    sockaddr_nl kernel = {AF_NETLINK, 0, 0, 0};
     int sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
     if (sock != -1 &&
-            connect(sock, reinterpret_cast<sockaddr*>(&kernel), sizeof(kernel)) != -1 &&
+            connect(sock, reinterpret_cast<const sockaddr*>(&NETLINK_ADDRESS),
+                    sizeof(NETLINK_ADDRESS)) != -1 &&
             writev(sock, iov, iovlen) != -1 &&
             (ret = recv(sock, &response, sizeof(response), 0)) != -1) {
         if (ret == sizeof(response)) {
             ret = response.err.error;  // Netlink errors are negative errno.
+            if (ret) {
+                ALOGE("netlink response contains error (%s)", strerror(-ret));
+            }
         } else {
+            ALOGE("bad netlink response message size (%d != %u)", ret, sizeof(response));
             ret = -EBADMSG;
         }
     } else {
+        ALOGE("netlink socket/connect/writev/recv failed (%s)", strerror(errno));
         ret = -errno;
     }
 
@@ -137,24 +164,32 @@
 // + If |interface| is non-NULL, the rule matches the specified outgoing interface.
 //
 // Returns 0 on success or negative errno on failure.
-int modifyIpRule(uint16_t action, uint32_t priority, uint32_t table, uint32_t fwmark, uint32_t mask,
-                 const char* interface, uid_t uidStart, uid_t uidEnd) {
+WARN_UNUSED_RESULT int modifyIpRule(uint16_t action, uint32_t priority, uint32_t table,
+                                    uint32_t fwmark, uint32_t mask, const char* interface,
+                                    uid_t uidStart, uid_t uidEnd) {
+    // Ensure that if you set a bit in the fwmark, it's not being ignored by the mask.
+    if (fwmark & ~mask) {
+        ALOGE("mask 0x%x does not select all the bits set in fwmark 0x%x", mask, fwmark);
+        return -ERANGE;
+    }
+
     // The interface name must include exactly one terminating NULL and be properly padded, or older
     // kernels will refuse to delete rules.
-    uint8_t padding[RTA_ALIGNTO] = {0, 0, 0, 0};
     uint16_t paddingLength = 0;
     size_t interfaceLength = 0;
     char oifname[IFNAMSIZ];
     if (interface) {
         interfaceLength = strlcpy(oifname, interface, IFNAMSIZ) + 1;
         if (interfaceLength > IFNAMSIZ) {
+            ALOGE("interface name too long (%u > %u)", interfaceLength, IFNAMSIZ);
             return -ENAMETOOLONG;
         }
         paddingLength = RTA_SPACE(interfaceLength) - RTA_LENGTH(interfaceLength);
     }
 
     // Either both start and end UID must be specified, or neither.
-    if ((uidStart == INVALID_UID) != (uidStart == INVALID_UID)) {
+    if ((uidStart == INVALID_UID) != (uidEnd == INVALID_UID)) {
+        ALOGE("incompatible start and end UIDs (%u vs %u)", uidStart, uidEnd);
         return -EUSERS;
     }
     bool isUidRule = (uidStart != INVALID_UID);
@@ -164,40 +199,32 @@
         .action = static_cast<uint8_t>(table ? FR_ACT_TO_TBL : FR_ACT_UNREACHABLE),
     };
 
-    rtattr fraPriority  = { U16_RTA_LENGTH(sizeof(priority)),  FRA_PRIORITY };
-    rtattr fraTable     = { U16_RTA_LENGTH(sizeof(table)),     FRA_TABLE };
-    rtattr fraFwmark    = { U16_RTA_LENGTH(sizeof(fwmark)),    FRA_FWMARK };
-    rtattr fraFwmask    = { U16_RTA_LENGTH(sizeof(mask)),      FRA_FWMASK };
-    rtattr fraOifname   = { U16_RTA_LENGTH(interfaceLength),   FRA_OIFNAME };
-    rtattr fraUidStart  = { U16_RTA_LENGTH(sizeof(uidStart)),  FRA_UID_START };
-    rtattr fraUidEnd    = { U16_RTA_LENGTH(sizeof(uidEnd)),    FRA_UID_END };
+    rtattr fraOifname = { U16_RTA_LENGTH(interfaceLength), FRA_OIFNAME };
 
     iovec iov[] = {
-        { NULL,          0 },
-        { &rule,         sizeof(rule) },
-        { &fraPriority,  sizeof(fraPriority) },
-        { &priority,     sizeof(priority) },
-        { &fraTable,     table ? sizeof(fraTable) : 0 },
-        { &table,        table ? sizeof(table) : 0 },
-        { &fraFwmark,    mask ? sizeof(fraFwmark) : 0 },
-        { &fwmark,       mask ? sizeof(fwmark) : 0 },
-        { &fraFwmask,    mask ? sizeof(fraFwmask) : 0 },
-        { &mask,         mask ? sizeof(mask) : 0 },
-        { &fraUidStart,  isUidRule ? sizeof(fraUidStart) : 0 },
-        { &uidStart,     isUidRule ? sizeof(uidStart) : 0 },
-        { &fraUidEnd,    isUidRule ? sizeof(fraUidEnd) : 0 },
-        { &uidEnd,       isUidRule ? sizeof(uidEnd) : 0 },
-        { &fraOifname,   interface ? sizeof(fraOifname) : 0 },
-        { oifname,       interfaceLength },
-        { padding,       paddingLength },
+        { NULL,              0 },
+        { &rule,             sizeof(rule) },
+        { &FRATTR_PRIORITY,  sizeof(FRATTR_PRIORITY) },
+        { &priority,         sizeof(priority) },
+        { &FRATTR_TABLE,     table ? sizeof(FRATTR_TABLE) : 0 },
+        { &table,            table ? sizeof(table) : 0 },
+        { &FRATTR_FWMARK,    mask ? sizeof(FRATTR_FWMARK) : 0 },
+        { &fwmark,           mask ? sizeof(fwmark) : 0 },
+        { &FRATTR_FWMASK,    mask ? sizeof(FRATTR_FWMASK) : 0 },
+        { &mask,             mask ? sizeof(mask) : 0 },
+        { &FRATTR_UID_START, isUidRule ? sizeof(FRATTR_UID_START) : 0 },
+        { &uidStart,         isUidRule ? sizeof(uidStart) : 0 },
+        { &FRATTR_UID_END,   isUidRule ? sizeof(FRATTR_UID_END) : 0 },
+        { &uidEnd,           isUidRule ? sizeof(uidEnd) : 0 },
+        { &fraOifname,       interface ? sizeof(fraOifname) : 0 },
+        { oifname,           interfaceLength },
+        { PADDING_BUFFER,    paddingLength },
     };
 
     uint16_t flags = (action == RTM_NEWRULE) ? NETLINK_CREATE_REQUEST_FLAGS : NETLINK_REQUEST_FLAGS;
-    uint8_t family[] = {AF_INET, AF_INET6};
-    for (size_t i = 0; i < ARRAY_SIZE(family); ++i) {
-        rule.family = family[i];
-        int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov));
-        if (ret) {
+    for (size_t i = 0; i < ARRAY_SIZE(AF_FAMILIES); ++i) {
+        rule.family = AF_FAMILIES[i];
+        if (int ret = sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov))) {
             return ret;
         }
     }
@@ -205,30 +232,29 @@
     return 0;
 }
 
-int modifyIpRule(uint16_t action, uint32_t priority, uint32_t table,
-                 uint32_t fwmark, uint32_t mask, const char* interface) {
-    return modifyIpRule(action, priority, table, fwmark, mask, interface, INVALID_UID, INVALID_UID);
-}
-
 // Adds or deletes an IPv4 or IPv6 route.
 // Returns 0 on success or negative errno on failure.
-int modifyIpRoute(uint16_t action, uint32_t table, const char* interface, const char* destination,
-                  const char* nexthop) {
+WARN_UNUSED_RESULT int modifyIpRoute(uint16_t action, uint32_t table, const char* interface,
+                                     const char* destination, const char* nexthop) {
     // At least the destination must be non-null.
     if (!destination) {
+        ALOGE("null destination");
         return -EFAULT;
     }
 
     // Parse the prefix.
     uint8_t rawAddress[sizeof(in6_addr)];
-    uint8_t family, prefixLength;
+    uint8_t family;
+    uint8_t prefixLength;
     int rawLength = parsePrefix(destination, &family, rawAddress, sizeof(rawAddress),
                                 &prefixLength);
     if (rawLength < 0) {
+        ALOGE("parsePrefix failed for destination %s (%s)", destination, strerror(-rawLength));
         return rawLength;
     }
 
     if (static_cast<size_t>(rawLength) > sizeof(rawAddress)) {
+        ALOGE("impossible! address too long (%d vs %u)", rawLength, sizeof(rawAddress));
         return -ENOBUFS;  // Cannot happen; parsePrefix only supports IPv4 and IPv6.
     }
 
@@ -237,68 +263,68 @@
     if (interface) {
         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)) {
+    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 rtmsg = {
+    rtmsg route = {
         .rtm_protocol = RTPROT_STATIC,
         .rtm_type = RTN_UNICAST,
         .rtm_family = family,
         .rtm_dst_len = prefixLength,
     };
 
-    rtattr rtaTable   = { U16_RTA_LENGTH(sizeof(table)),    RTA_TABLE };
-    rtattr rtaOif     = { U16_RTA_LENGTH(sizeof(ifindex)),  RTA_OIF };
-    rtattr rtaDst     = { U16_RTA_LENGTH(rawLength),        RTA_DST };
-    rtattr rtaGateway = { U16_RTA_LENGTH(rawLength),        RTA_GATEWAY };
+    rtattr rtaDst     = { U16_RTA_LENGTH(rawLength), RTA_DST };
+    rtattr rtaGateway = { U16_RTA_LENGTH(rawLength), RTA_GATEWAY };
 
     iovec iov[] = {
-        { NULL,         0 },
-        { &rtmsg,       sizeof(rtmsg) },
-        { &rtaTable,    sizeof(rtaTable) },
-        { &table,       sizeof(table) },
-        { &rtaDst,      sizeof(rtaDst) },
-        { rawAddress,   static_cast<size_t>(rawLength) },
-        { &rtaOif,      interface ? sizeof(rtaOif) : 0 },
-        { &ifindex,     interface ? sizeof(ifindex) : 0 },
-        { &rtaGateway,  nexthop ? sizeof(rtaGateway) : 0 },
-        { rawNexthop,   nexthop ? static_cast<size_t>(rawLength) : 0 },
+        { NULL,          0 },
+        { &route,        sizeof(route) },
+        { &RTATTR_TABLE, sizeof(RTATTR_TABLE) },
+        { &table,        sizeof(table) },
+        { &rtaDst,       sizeof(rtaDst) },
+        { rawAddress,    static_cast<size_t>(rawLength) },
+        { &RTATTR_OIF,   interface ? sizeof(RTATTR_OIF) : 0 },
+        { &ifindex,      interface ? sizeof(ifindex) : 0 },
+        { &rtaGateway,   nexthop ? sizeof(rtaGateway) : 0 },
+        { rawNexthop,    nexthop ? static_cast<size_t>(rawLength) : 0 },
     };
 
-    uint16_t flags = (action == RTM_NEWROUTE) ? NETLINK_CREATE_REQUEST_FLAGS : NETLINK_REQUEST_FLAGS;
+    uint16_t flags = (action == RTM_NEWROUTE) ? NETLINK_CREATE_REQUEST_FLAGS :
+                                                NETLINK_REQUEST_FLAGS;
     return sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov));
 }
 
-int modifyPerNetworkRules(unsigned netId, const char* interface, Permission permission, bool add,
-                          bool modifyIptables) {
+WARN_UNUSED_RESULT int modifyPerNetworkRules(unsigned netId, const char* interface,
+                                             Permission permission, bool add, bool modifyIptables) {
     uint32_t table = getRouteTableForInterface(interface);
     if (!table) {
+        ALOGE("cannot find interface %s", interface);
         return -ESRCH;
     }
 
     uint16_t action = add ? RTM_NEWRULE : RTM_DELRULE;
-    int ret;
 
     Fwmark fwmark;
-    fwmark.permission = permission;
-
     Fwmark mask;
-    mask.permission = permission;
 
     // A rule to route traffic based on a chosen outgoing interface.
     //
     // Supports apps that use SO_BINDTODEVICE or IP_PKTINFO options and the kernel that already
     // knows the outgoing interface (typically for link-local communications).
-    if ((ret = modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_INTERFACE, table, fwmark.intValue,
-                            mask.intValue, interface)) != 0) {
+    fwmark.permission = permission;
+    mask.permission = permission;
+    if (int ret = modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_INTERFACE, table, fwmark.intValue,
+                               mask.intValue, interface, INVALID_UID, INVALID_UID)) {
         return ret;
     }
 
@@ -309,8 +335,8 @@
     // network stay on that network even if the default network changes.
     fwmark.netId = netId;
     mask.netId = FWMARK_NET_ID_MASK;
-    if ((ret = modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_NORMAL, table, fwmark.intValue,
-                            mask.intValue, NULL)) != 0) {
+    if (int ret = modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_NORMAL, table, fwmark.intValue,
+                               mask.intValue, NULL, INVALID_UID, INVALID_UID)) {
         return ret;
     }
 
@@ -318,12 +344,13 @@
     //
     // Supports apps that use the multinetwork APIs to restrict their traffic to a network.
     //
-    // We don't really need to check the permission bits of the fwmark here, as they would've been
-    // checked at the time the netId was set into the fwmark, but we do so to be consistent.
+    // Even though we check permissions at the time we set a netId into the fwmark of a socket, we
+    // still need to check it again in the rules here, because a network's permissions may have been
+    // updated via modifyNetworkPermission().
     fwmark.explicitlySelected = true;
     mask.explicitlySelected = true;
-    if ((ret = modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_EXPLICIT, table, fwmark.intValue,
-                            mask.intValue, NULL)) != 0) {
+    if (int ret = modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_EXPLICIT, table, fwmark.intValue,
+                               mask.intValue, NULL, INVALID_UID, INVALID_UID)) {
         return ret;
     }
 
@@ -335,11 +362,11 @@
     // + Mark sockets that accept connections from this interface so that the connection stays on
     //   the same interface.
     if (modifyIptables) {
-        const char* iptablesAction = add ? "-A" : "-D";
         char markString[UINT32_HEX_STRLEN];
         snprintf(markString, sizeof(markString), "0x%x", netId);
-        if (execIptables(V4V6, "-t", "mangle", iptablesAction, "INPUT", "-i", interface,
+        if (execIptables(V4V6, "-t", "mangle", add ? "-A" : "-D", "INPUT", "-i", interface,
                          "-j", "MARK", "--set-mark", markString, NULL)) {
+            ALOGE("failed to change iptables rule that sets incoming packet mark");
             return -EREMOTEIO;
         }
     }
@@ -347,29 +374,33 @@
     return 0;
 }
 
-int modifyDefaultNetworkRules(const char* interface, Permission permission, uint16_t action) {
+WARN_UNUSED_RESULT int modifyDefaultNetworkRules(const char* interface, Permission permission,
+                                                 uint16_t action) {
     uint32_t table = getRouteTableForInterface(interface);
     if (!table) {
+        ALOGE("cannot find interface %s", interface);
         return -ESRCH;
     }
 
     Fwmark fwmark;
-    fwmark.netId = 0;
-    fwmark.permission = permission;
-
     Fwmark mask;
+
+    fwmark.netId = 0;
     mask.netId = FWMARK_NET_ID_MASK;
+
+    fwmark.permission = permission;
     mask.permission = permission;
 
     return modifyIpRule(action, RULE_PRIORITY_DEFAULT_NETWORK, table, fwmark.intValue,
-                        mask.intValue, NULL);
+                        mask.intValue, NULL, INVALID_UID, INVALID_UID);
 }
 
-// Adds or removes an IPv4 or IPv6 route to the specified table and, if it's directly-connected
+// Adds or removes an IPv4 or IPv6 route to the specified table and, if it's a directly-connected
 // route, to the main table as well.
 // Returns 0 on success or negative errno on failure.
-int modifyRoute(const char* interface, const char* destination, const char* nexthop,
-                uint16_t action, RouteController::TableType tableType, unsigned /* uid */) {
+WARN_UNUSED_RESULT int modifyRoute(const char* interface, const char* destination,
+                                   const char* nexthop, uint16_t action,
+                                   RouteController::TableType tableType, uid_t /*uid*/) {
     uint32_t table = 0;
     switch (tableType) {
         case RouteController::INTERFACE: {
@@ -388,6 +419,7 @@
         }
     }
     if (!table) {
+        ALOGE("cannot find table for interface %s and tableType %d", interface, tableType);
         return -ESRCH;
     }
 
@@ -407,7 +439,7 @@
         // A failure with action == ADD && errno == EEXIST means that the route already exists in
         // the main table, perhaps because the kernel added it automatically as part of adding the
         // IP address to the interface. Ignore this, but complain about everything else.
-        if (ret != 0 && !(action == RTM_NEWROUTE && ret == -EEXIST)) {
+        if (ret && !(action == RTM_NEWROUTE && ret == -EEXIST)) {
             return ret;
         }
     }
@@ -415,52 +447,58 @@
     return 0;
 }
 
-bool flushRoutes(const char* interface) {
+// Returns 0 on success or negative errno on failure.
+WARN_UNUSED_RESULT int flushRoutes(const char* interface) {
     uint32_t table = getRouteTableForInterface(interface);
     if (!table) {
-        return false;
+        ALOGE("cannot find interface %s", interface);
+        return -ESRCH;
     }
     interfaceToIndex.erase(interface);
 
     char tableString[UINT32_STRLEN];
     snprintf(tableString, sizeof(tableString), "%u", table);
 
-    const char* version[] = {"-4", "-6"};
-    for (size_t i = 0; i < ARRAY_SIZE(version); ++i) {
+    for (size_t i = 0; i < ARRAY_SIZE(IP_VERSIONS); ++i) {
         const char* argv[] = {
             IP_PATH,
-            version[i],
+            IP_VERSIONS[i],
             "route"
             "flush",
             "table",
             tableString,
         };
-        int argc = ARRAY_SIZE(argv);
-
-        if (!android_fork_execvp(argc, const_cast<char**>(argv), NULL, false, false)) {
-            return false;
+        if (android_fork_execvp(ARRAY_SIZE(argv), const_cast<char**>(argv), NULL, false, false)) {
+            ALOGE("failed to flush routes");
+            return -EREMOTEIO;
         }
     }
 
-    return true;
+    return 0;
 }
 
 }  // namespace
 
-void RouteController::Init() {
+int RouteController::Init() {
+    Fwmark fwmark;
+    Fwmark mask;
+
     // Add a new rule to look up the 'main' table, with the same selectors as the "default network"
     // rule, but with a lower priority. Since the default network rule points to a table with a
     // default route, the rule we're adding will never be used for normal routing lookups. However,
     // the kernel may fall-through to it to find directly-connected routes when it validates that a
     // nexthop (in a route being added) is reachable.
-    Fwmark fwmark;
+    //
+    // TODO: This isn't true if the default network requires non-zero permissions. In that case, an
+    // app without those permissions may still be able to access directly-connected routes, since
+    // it won't match the default network rule. Fix this by only allowing the root UID (as a proxy
+    // for the kernel) to lookup this main table rule.
     fwmark.netId = 0;
-
-    Fwmark mask;
     mask.netId = FWMARK_NET_ID_MASK;
-
-    modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_MAIN, RT_TABLE_MAIN, fwmark.intValue, mask.intValue,
-                 NULL);
+    if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_MAIN, RT_TABLE_MAIN, fwmark.intValue,
+                               mask.intValue, NULL, INVALID_UID, INVALID_UID)) {
+        return ret;
+    }
 
     // Add rules to allow lookup of legacy routes.
     //
@@ -471,21 +509,28 @@
 
     fwmark.explicitlySelected = false;
     mask.explicitlySelected = true;
-
-    modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LEGACY, ROUTE_TABLE_LEGACY, fwmark.intValue,
-                 mask.intValue, NULL);
+    if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_LEGACY, ROUTE_TABLE_LEGACY,
+                               fwmark.intValue, mask.intValue, NULL, INVALID_UID, INVALID_UID)) {
+        return ret;
+    }
 
     fwmark.permission = PERMISSION_CONNECTIVITY_INTERNAL;
     mask.permission = PERMISSION_CONNECTIVITY_INTERNAL;
 
-    modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_PRIVILEGED_LEGACY, ROUTE_TABLE_PRIVILEGED_LEGACY,
-                 fwmark.intValue, mask.intValue, NULL);
+    if (int ret = modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_PRIVILEGED_LEGACY,
+                               ROUTE_TABLE_PRIVILEGED_LEGACY, fwmark.intValue, mask.intValue, NULL,
+                               INVALID_UID, INVALID_UID)) {
+        return ret;
+    }
 
 // TODO: Uncomment once we are sure everything works.
 #if 0
     // Add a rule to preempt the pre-defined "from all lookup main" rule. This ensures that packets
     // that are already marked with a specific NetId don't fall-through to the main table.
-    modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_UNREACHABLE, 0, 0, 0, NULL);
+    return modifyIpRule(RTM_NEWRULE, RULE_PRIORITY_UNREACHABLE, 0, 0, 0, NULL, INVALID_UID,
+                        INVALID_UID);
+#else
+    return 0;
 #endif
 }
 
@@ -496,15 +541,19 @@
 
 int RouteController::removeInterfaceFromNetwork(unsigned netId, const char* interface,
                                                 Permission permission) {
-    return modifyPerNetworkRules(netId, interface, permission, false, true) &&
-           flushRoutes(interface);
+    if (int ret = modifyPerNetworkRules(netId, interface, permission, false, true)) {
+        return ret;
+    }
+    return flushRoutes(interface);
 }
 
 int RouteController::modifyNetworkPermission(unsigned netId, const char* interface,
                                              Permission oldPermission, Permission newPermission) {
     // Add the new rules before deleting the old ones, to avoid race conditions.
-    return modifyPerNetworkRules(netId, interface, newPermission, true, false) &&
-           modifyPerNetworkRules(netId, interface, oldPermission, false, false);
+    if (int ret = modifyPerNetworkRules(netId, interface, newPermission, true, false)) {
+        return ret;
+    }
+    return modifyPerNetworkRules(netId, interface, oldPermission, false, false);
 }
 
 int RouteController::addToDefaultNetwork(const char* interface, Permission permission) {
@@ -515,12 +564,12 @@
     return modifyDefaultNetworkRules(interface, permission, RTM_DELRULE);
 }
 
-int RouteController::addRoute(const char* interface, const char* destination,
-                              const char* nexthop, TableType tableType, unsigned uid) {
+int RouteController::addRoute(const char* interface, const char* destination, const char* nexthop,
+                              TableType tableType, uid_t uid) {
     return modifyRoute(interface, destination, nexthop, RTM_NEWROUTE, tableType, uid);
 }
 
 int RouteController::removeRoute(const char* interface, const char* destination,
-                                 const char* nexthop, TableType tableType, unsigned uid) {
+                                 const char* nexthop, TableType tableType, uid_t uid) {
     return modifyRoute(interface, destination, nexthop, RTM_DELROUTE, tableType, uid);
 }
diff --git a/server/RouteController.h b/server/RouteController.h
index b826e08..a54adae 100644
--- a/server/RouteController.h
+++ b/server/RouteController.h
@@ -17,8 +17,11 @@
 #ifndef NETD_SERVER_ROUTE_CONTROLLER_H
 #define NETD_SERVER_ROUTE_CONTROLLER_H
 
+#include "NetdConstants.h"
 #include "Permission.h"
 
+#include <sys/types.h>
+
 class RouteController {
 public:
     // How the routing table number is determined for route modification requests.
@@ -30,21 +33,25 @@
 
     static const int ROUTE_TABLE_OFFSET_FROM_INDEX = 1000;
 
-    static void Init();
+    static int Init() WARN_UNUSED_RESULT;
 
-    static int addInterfaceToNetwork(unsigned netId, const char* interface, Permission permission);
+    static int addInterfaceToNetwork(unsigned netId, const char* interface,
+                                     Permission permission) WARN_UNUSED_RESULT;
     static int removeInterfaceFromNetwork(unsigned netId, const char* interface,
-                                          Permission permission);
-    static int modifyNetworkPermission(unsigned netId, const char* interface,
-                                       Permission oldPermission, Permission newPermission);
+                                          Permission permission) WARN_UNUSED_RESULT;
 
-    static int addToDefaultNetwork(const char* interface, Permission permission);
-    static int removeFromDefaultNetwork(const char* interface, Permission permission);
+    static int modifyNetworkPermission(unsigned netId, const char* interface,
+                                       Permission oldPermission,
+                                       Permission newPermission) WARN_UNUSED_RESULT;
+
+    static int addToDefaultNetwork(const char* interface, Permission permission) WARN_UNUSED_RESULT;
+    static int removeFromDefaultNetwork(const char* interface,
+                                        Permission permission) WARN_UNUSED_RESULT;
 
     static int addRoute(const char* interface, const char* destination, const char* nexthop,
-                        TableType tableType, unsigned uid);
+                        TableType tableType, uid_t uid) WARN_UNUSED_RESULT;
     static int removeRoute(const char* interface, const char* destination, const char* nexthop,
-                           TableType tableType, unsigned uid);
+                           TableType tableType, uid_t uid) WARN_UNUSED_RESULT;
 };
 
 #endif  // NETD_SERVER_ROUTE_CONTROLLER_H
diff --git a/server/main.cpp b/server/main.cpp
index 11d4d40..6af1e4e 100644
--- a/server/main.cpp
+++ b/server/main.cpp
@@ -66,7 +66,7 @@
     // Set local DNS mode, to prevent bionic from proxying
     // back to this service, recursively.
     setenv("ANDROID_DNS_MODE", "local", 1);
-    dpl = new DnsProxyListener(CommandListener::sNetCtrl, CommandListener::sPermissionsController);
+    dpl = new DnsProxyListener(CommandListener::sNetCtrl);
     if (dpl->startListener()) {
         ALOGE("Unable to start DnsProxyListener (%s)", strerror(errno));
         exit(1);
@@ -78,8 +78,7 @@
         exit(1);
     }
 
-    fwmarkServer = new FwmarkServer(CommandListener::sNetCtrl,
-                                    CommandListener::sPermissionsController);
+    fwmarkServer = new FwmarkServer(CommandListener::sNetCtrl);
     if (fwmarkServer->startListener()) {
         ALOGE("Unable to start FwmarkServer (%s)", strerror(errno));
         exit(1);