Add UID range support to VPNs.

This adds the necessary routing rules.

Future CLs will add the ability to select the right netId for connect(),
setNetworkForSocket(), DNS resolutions, etc.

Bug: 15409918
Change-Id: I88a67660d49cecda834dd72ab947fbfed250f09d
diff --git a/server/Android.mk b/server/Android.mk
index 99795a2..541644e 100644
--- a/server/Android.mk
+++ b/server/Android.mk
@@ -64,6 +64,7 @@
         SecondaryTableController.cpp \
         SoftapController.cpp \
         TetherController.cpp \
+        UidRanges.cpp \
         VirtualNetwork.cpp \
         main.cpp \
         oem_iptables_hook.cpp \
diff --git a/server/CommandListener.cpp b/server/CommandListener.cpp
index fd99555..7e408f0 100644
--- a/server/CommandListener.cpp
+++ b/server/CommandListener.cpp
@@ -45,6 +45,7 @@
 #include "NetdConstants.h"
 #include "FirewallController.h"
 #include "RouteController.h"
+#include "UidRanges.h"
 
 #include <string>
 #include <vector>
@@ -1746,8 +1747,33 @@
         return success(client);
     }
 
-    // network <bind|unbind> <netId> <uid1> .. <uidN>
-    //     -- uid range can be specified as "uidX-uidY"
+    //    0      1     2       3           4
+    // network users  add   <netId> [<uid>[-<uid>]] ...
+    // network users remove <netId> [<uid>[-<uid>]] ...
+    if (!strcmp(argv[1], "users")) {
+        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[3], NULL, 0);
+        UidRanges uidRanges;
+        if (!uidRanges.parseFrom(argc - 4, argv + 4)) {
+            return syntaxError(client, "Invalid UIDs");
+        }
+        if (!strcmp(argv[2], "add")) {
+            if (int ret = sNetCtrl->addUsersToNetwork(netId, uidRanges)) {
+                return operationError(client, "addUsersToNetwork() failed", ret);
+            }
+        } else if (!strcmp(argv[2], "remove")) {
+            if (int ret = sNetCtrl->removeUsersFromNetwork(netId, uidRanges)) {
+                return operationError(client, "removeUsersFromNetwork() failed", ret);
+            }
+        } else {
+            return syntaxError(client, "Unknown argument");
+        }
+        return success(client);
+    }
+
     // TODO:
     //   o tethering
     //   o p2p
diff --git a/server/NetdConstants.h b/server/NetdConstants.h
index 4291bbc..7b894a8 100644
--- a/server/NetdConstants.h
+++ b/server/NetdConstants.h
@@ -49,4 +49,6 @@
 
 #define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
 
+const uid_t INVALID_UID = static_cast<uid_t>(-1);
+
 #endif
diff --git a/server/NetworkController.cpp b/server/NetworkController.cpp
index 8a51bcb..0ce82c3 100644
--- a/server/NetworkController.cpp
+++ b/server/NetworkController.cpp
@@ -302,6 +302,32 @@
     return 0;
 }
 
+int NetworkController::addUsersToNetwork(unsigned netId, const UidRanges& uidRanges) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    auto iter = mVirtualNetworks.find(netId);
+    if (iter == mVirtualNetworks.end()) {
+        ALOGE("invalid netId %u", netId);
+        return -EINVAL;
+    }
+    if (int ret = iter->second->addUsers(uidRanges)) {
+        return ret;
+    }
+    return 0;
+}
+
+int NetworkController::removeUsersFromNetwork(unsigned netId, const UidRanges& uidRanges) {
+    android::RWLock::AutoWLock lock(mRWLock);
+    auto iter = mVirtualNetworks.find(netId);
+    if (iter == mVirtualNetworks.end()) {
+        ALOGE("invalid netId %u", netId);
+        return -EINVAL;
+    }
+    if (int ret = iter->second->removeUsers(uidRanges)) {
+        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);
diff --git a/server/NetworkController.h b/server/NetworkController.h
index 7f572d6..cc6e953 100644
--- a/server/NetworkController.h
+++ b/server/NetworkController.h
@@ -29,6 +29,7 @@
 
 class Network;
 class PhysicalNetwork;
+class UidRanges;
 class VirtualNetwork;
 
 /*
@@ -68,6 +69,9 @@
     int setPermissionForNetworks(Permission permission,
                                  const std::vector<unsigned>& netIds) WARN_UNUSED_RESULT;
 
+    int addUsersToNetwork(unsigned netId, const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+    int removeUsersFromNetwork(unsigned netId, const UidRanges& uidRanges) 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,
diff --git a/server/RouteController.cpp b/server/RouteController.cpp
index 2d8e75f..30bdae8 100644
--- a/server/RouteController.cpp
+++ b/server/RouteController.cpp
@@ -17,6 +17,7 @@
 #include "RouteController.h"
 
 #include "Fwmark.h"
+#include "UidRanges.h"
 
 #define LOG_TAG "Netd"
 #include "log/log.h"
@@ -58,8 +59,6 @@
              "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;
 
@@ -305,12 +304,33 @@
     return sendNetlinkRequest(action, flags, iov, ARRAY_SIZE(iov));
 }
 
-WARN_UNUSED_RESULT int modifyPerNetworkRules(unsigned netId, const char* interface,
-                                             Permission permission, bool add, bool modifyIptables) {
-    uint32_t table = getRouteTableForInterface(interface);
+WARN_UNUSED_RESULT int modifyIncomingPacketMark(unsigned netId, const char* interface, bool add) {
+    // An iptables rule to mark incoming packets on a network with the netId of the network.
+    //
+    // This is so that the kernel can:
+    // + Use the right fwmark for (and thus correctly route) replies (e.g.: TCP RST, ICMP errors,
+    //   ping replies).
+    // + Mark sockets that accept connections from this interface so that the connection stays on
+    //   the same interface.
+    char markString[UINT32_HEX_STRLEN];
+    snprintf(markString, sizeof(markString), "0x%x", netId);
+    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;
+    }
+    return 0;
+}
+
+WARN_UNUSED_RESULT int modifyPerNetworkRules(unsigned netId, const char* interface, uint32_t table,
+                                             Permission permission, uid_t uidStart, uid_t uidEnd,
+                                             bool add, bool modifyIptables) {
     if (!table) {
-        ALOGE("cannot find interface %s", interface);
-        return -ESRCH;
+        table = getRouteTableForInterface(interface);
+        if (!table) {
+            ALOGE("cannot find interface %s", interface);
+            return -ESRCH;
+        }
     }
 
     uint16_t action = add ? RTM_NEWRULE : RTM_DELRULE;
@@ -325,7 +345,7 @@
     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)) {
+                               mask.intValue, interface, uidStart, uidEnd)) {
         return ret;
     }
 
@@ -337,7 +357,7 @@
     fwmark.netId = netId;
     mask.netId = FWMARK_NET_ID_MASK;
     if (int ret = modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_NORMAL, table, fwmark.intValue,
-                               mask.intValue, NULL, INVALID_UID, INVALID_UID)) {
+                               mask.intValue, NULL, uidStart, uidEnd)) {
         return ret;
     }
 
@@ -351,70 +371,83 @@
     fwmark.explicitlySelected = true;
     mask.explicitlySelected = true;
     if (int ret = modifyIpRule(action, RULE_PRIORITY_PER_NETWORK_EXPLICIT, table, fwmark.intValue,
-                               mask.intValue, NULL, INVALID_UID, INVALID_UID)) {
+                               mask.intValue, NULL, uidStart, uidEnd)) {
         return ret;
     }
 
-    // An iptables rule to mark incoming packets on a network with the netId of the network.
-    //
-    // This is so that the kernel can:
-    // + Use the right fwmark for (and thus correctly route) replies (e.g.: TCP RST, ICMP errors,
-    //   ping replies).
-    // + Mark sockets that accept connections from this interface so that the connection stays on
-    //   the same interface.
     if (modifyIptables) {
-        char markString[UINT32_HEX_STRLEN];
-        snprintf(markString, sizeof(markString), "0x%x", netId);
-        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;
+        if (int ret = modifyIncomingPacketMark(netId, interface, add)) {
+            return ret;
         }
     }
 
     return 0;
 }
 
-WARN_UNUSED_RESULT int modifyVpnRules(unsigned netId, const char* interface, uint16_t action) {
+// Adds or removes rules for VPNs that affect UIDs in |uidRanges|. If |modifyInterfaceBasedRules|
+// is true, also modifies the rules that are based only on the |interface| and not on |uidRanges|.
+// When adding or removing an interface to the VPN, set it to true. When adding or removing UIDs
+// without changing the VPN's interfaces, set it to false.
+WARN_UNUSED_RESULT int modifyVpnRules(unsigned netId, const char* interface,
+                                      const UidRanges& uidRanges, bool add,
+                                      bool modifyInterfaceBasedRules) {
     uint32_t table = getRouteTableForInterface(interface);
     if (!table) {
         ALOGE("cannot find interface %s", interface);
         return -ESRCH;
     }
 
+    uint16_t action = add ? RTM_NEWRULE : RTM_DELRULE;
+
     Fwmark fwmark;
     Fwmark mask;
 
-    // A rule to route all traffic from a given set of UIDs to go over the VPN.
-    //
-    // Notice that this rule doesn't use the netId. I.e., no matter what netId the user's socket may
-    // have, if they are subject to this VPN, their traffic has to go through it. Allows the traffic
-    // to bypass the VPN if the protectedFromVpn bit is set.
-    //
-    // TODO: Actually implement the "from a set of UIDs" part.
     fwmark.protectedFromVpn = false;
     mask.protectedFromVpn = true;
-    if (int ret = modifyIpRule(action, RULE_PRIORITY_SECURE_VPN, table, fwmark.intValue,
-                               mask.intValue, NULL, INVALID_UID, INVALID_UID)) {
-        return ret;
+
+    for (const std::pair<uid_t, uid_t>& range : uidRanges.getRanges()) {
+        if (int ret = modifyPerNetworkRules(netId, interface, table, PERMISSION_NONE, range.first,
+                                            range.second, add, false)) {
+            return ret;
+        }
+
+        // A rule to route all traffic from a given set of UIDs to go over the VPN.
+        //
+        // Notice that this rule doesn't use the netId. I.e., no matter what netId the user's socket
+        // may have, if they are subject to this VPN, their traffic has to go through it. Allows the
+        // traffic to bypass the VPN if the protectedFromVpn bit is set.
+        if (int ret = modifyIpRule(action, RULE_PRIORITY_SECURE_VPN, table, fwmark.intValue,
+                                   mask.intValue, NULL, range.first, range.second)) {
+            return ret;
+        }
     }
 
-    // A rule to allow privileged apps to send traffic over this VPN even if they are not part of
-    // the target set of UIDs.
-    //
-    // This is needed for DnsProxyListener to correctly resolve a request for a user who is in the
-    // target set, but where the DnsProxyListener itself is not.
-    fwmark.protectedFromVpn = false;
-    mask.protectedFromVpn = false;
+    if (modifyInterfaceBasedRules) {
+        if (int ret = modifyIncomingPacketMark(netId, interface, add)) {
+            return ret;
+        }
 
-    fwmark.netId = netId;
-    mask.netId = FWMARK_NET_ID_MASK;
+        // A rule to allow privileged apps to send traffic over this VPN even if they are not part
+        // of the target set of UIDs.
+        //
+        // This is needed for DnsProxyListener to correctly resolve a request for a user who is in
+        // the target set, but where the DnsProxyListener itself is not.
+        fwmark.protectedFromVpn = false;
+        mask.protectedFromVpn = false;
 
-    fwmark.permission = PERMISSION_CONNECTIVITY_INTERNAL;
-    mask.permission = PERMISSION_CONNECTIVITY_INTERNAL;
+        fwmark.netId = netId;
+        mask.netId = FWMARK_NET_ID_MASK;
 
-    return modifyIpRule(action, RULE_PRIORITY_SECURE_VPN, table, fwmark.intValue, mask.intValue,
-                        NULL, INVALID_UID, INVALID_UID);
+        fwmark.permission = PERMISSION_CONNECTIVITY_INTERNAL;
+        mask.permission = PERMISSION_CONNECTIVITY_INTERNAL;
+
+        if (int ret = modifyIpRule(action, RULE_PRIORITY_SECURE_VPN, table, fwmark.intValue,
+                                   mask.intValue, NULL, INVALID_UID, INVALID_UID)) {
+            return ret;
+        }
+    }
+
+    return 0;
 }
 
 WARN_UNUSED_RESULT int modifyDefaultNetworkRules(const char* interface, Permission permission,
@@ -579,29 +612,27 @@
 
 int RouteController::addInterfaceToNetwork(unsigned netId, const char* interface,
                                            Permission permission) {
-    return modifyPerNetworkRules(netId, interface, permission, true, true);
+    return modifyPerNetworkRules(netId, interface, 0, permission, INVALID_UID, INVALID_UID, true,
+                                 true);
 }
 
 int RouteController::removeInterfaceFromNetwork(unsigned netId, const char* interface,
                                                 Permission permission) {
-    if (int ret = modifyPerNetworkRules(netId, interface, permission, false, true)) {
+    if (int ret = modifyPerNetworkRules(netId, interface, 0, permission, INVALID_UID, INVALID_UID,
+                                        false, true)) {
         return ret;
     }
     return flushRoutes(interface);
 }
 
-int RouteController::addInterfaceToVpn(unsigned netId, const char* interface) {
-    if (int ret = modifyPerNetworkRules(netId, interface, PERMISSION_NONE, true, true)) {
-        return ret;
-    }
-    return modifyVpnRules(netId, interface, RTM_NEWRULE);
+int RouteController::addInterfaceToVpn(unsigned netId, const char* interface,
+                                       const UidRanges& uidRanges) {
+    return modifyVpnRules(netId, interface, uidRanges, true, true);
 }
 
-int RouteController::removeInterfaceFromVpn(unsigned netId, const char* interface) {
-    if (int ret = modifyPerNetworkRules(netId, interface, PERMISSION_NONE, false, true)) {
-        return ret;
-    }
-    if (int ret = modifyVpnRules(netId, interface, RTM_DELRULE)) {
+int RouteController::removeInterfaceFromVpn(unsigned netId, const char* interface,
+                                            const UidRanges& uidRanges) {
+    if (int ret = modifyVpnRules(netId, interface, uidRanges, false, true)) {
         return ret;
     }
     return flushRoutes(interface);
@@ -610,10 +641,22 @@
 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.
-    if (int ret = modifyPerNetworkRules(netId, interface, newPermission, true, false)) {
+    if (int ret = modifyPerNetworkRules(netId, interface, 0, newPermission, INVALID_UID,
+                                        INVALID_UID, true, false)) {
         return ret;
     }
-    return modifyPerNetworkRules(netId, interface, oldPermission, false, false);
+    return modifyPerNetworkRules(netId, interface, 0, oldPermission, INVALID_UID, INVALID_UID,
+                                 false, false);
+}
+
+int RouteController::addUsersToVpn(unsigned netId, const char* interface,
+                                   const UidRanges& uidRanges) {
+    return modifyVpnRules(netId, interface, uidRanges, true, false);
+}
+
+int RouteController::removeUsersFromVpn(unsigned netId, const char* interface,
+                                        const UidRanges& uidRanges) {
+    return modifyVpnRules(netId, interface, uidRanges, false, false);
 }
 
 int RouteController::addToDefaultNetwork(const char* interface, Permission permission) {
diff --git a/server/RouteController.h b/server/RouteController.h
index c6cd760..9162023 100644
--- a/server/RouteController.h
+++ b/server/RouteController.h
@@ -22,6 +22,8 @@
 
 #include <sys/types.h>
 
+class UidRanges;
+
 class RouteController {
 public:
     // How the routing table number is determined for route modification requests.
@@ -40,8 +42,10 @@
     static int removeInterfaceFromNetwork(unsigned netId, const char* interface,
                                           Permission permission) WARN_UNUSED_RESULT;
 
-    static int addInterfaceToVpn(unsigned netId, const char* interface) WARN_UNUSED_RESULT;
-    static int removeInterfaceFromVpn(unsigned netId, const char* interface) WARN_UNUSED_RESULT;
+    static int addInterfaceToVpn(unsigned netId, const char* interface,
+                                 const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+    static int removeInterfaceFromVpn(unsigned netId, const char* interface,
+                                      const UidRanges& uidRanges) WARN_UNUSED_RESULT;
 
     static int modifyNetworkPermission(unsigned netId, const char* interface,
                                        Permission oldPermission,
@@ -51,6 +55,11 @@
     static int removeFromDefaultNetwork(const char* interface,
                                         Permission permission) WARN_UNUSED_RESULT;
 
+    static int addUsersToVpn(unsigned netId, const char* interface,
+                             const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+    static int removeUsersFromVpn(unsigned netId, const char* interface,
+                                  const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+
     static int addRoute(const char* interface, const char* destination, const char* nexthop,
                         TableType tableType, uid_t uid) WARN_UNUSED_RESULT;
     static int removeRoute(const char* interface, const char* destination, const char* nexthop,
diff --git a/server/UidRanges.cpp b/server/UidRanges.cpp
new file mode 100644
index 0000000..d752cbf
--- /dev/null
+++ b/server/UidRanges.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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 "UidRanges.h"
+
+#include "NetdConstants.h"
+
+#include <stdlib.h>
+
+const std::vector<std::pair<uid_t, uid_t>>& UidRanges::getRanges() const {
+    return mRanges;
+}
+
+bool UidRanges::parseFrom(int argc, char* argv[]) {
+    mRanges.clear();
+    for (int i = 0; i < argc; ++i) {
+        if (!*argv[i]) {
+            // The UID string is empty.
+            return false;
+        }
+        char* endPtr;
+        uid_t uidStart = strtoul(argv[i], &endPtr, 0);
+        uid_t uidEnd;
+        if (!*endPtr) {
+            // Found a single UID. The range contains just the one UID.
+            uidEnd = uidStart;
+        } else if (*endPtr == '-') {
+            if (!*++endPtr) {
+                // Unexpected end of string.
+                return false;
+            }
+            uidEnd = strtoul(endPtr, &endPtr, 0);
+            if (*endPtr) {
+                // Illegal trailing chars.
+                return false;
+            }
+            if (uidEnd < uidStart) {
+                // Invalid order.
+                return false;
+            }
+        } else {
+            // Not a single uid, not a range. Found some other illegal char.
+            return false;
+        }
+        if (uidStart == INVALID_UID || uidEnd == INVALID_UID) {
+            // Invalid UIDs.
+            return false;
+        }
+        mRanges.push_back(std::pair<uid_t, uid_t>(uidStart, uidEnd));
+    }
+    std::sort(mRanges.begin(), mRanges.end());
+    return true;
+}
+
+void UidRanges::add(const UidRanges& other) {
+    auto middle = mRanges.insert(mRanges.end(), other.mRanges.begin(), other.mRanges.end());
+    std::inplace_merge(mRanges.begin(), middle, mRanges.end());
+}
+
+void UidRanges::remove(const UidRanges& other) {
+    auto end = std::set_difference(mRanges.begin(), mRanges.end(), other.mRanges.begin(),
+                                   other.mRanges.end(), mRanges.begin());
+    mRanges.erase(end, mRanges.end());
+}
diff --git a/server/UidRanges.h b/server/UidRanges.h
new file mode 100644
index 0000000..88685b4
--- /dev/null
+++ b/server/UidRanges.h
@@ -0,0 +1,37 @@
+/*
+ * 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_UID_RANGES_H
+#define NETD_SERVER_UID_RANGES_H
+
+#include <sys/types.h>
+#include <utility>
+#include <vector>
+
+class UidRanges {
+public:
+    const std::vector<std::pair<uid_t, uid_t>>& getRanges() const;
+
+    bool parseFrom(int argc, char* argv[]);
+
+    void add(const UidRanges& other);
+    void remove(const UidRanges& other);
+
+private:
+    std::vector<std::pair<uid_t, uid_t>> mRanges;
+};
+
+#endif  // NETD_SERVER_UID_RANGES_H
diff --git a/server/VirtualNetwork.cpp b/server/VirtualNetwork.cpp
index 11998da..bc94d00 100644
--- a/server/VirtualNetwork.cpp
+++ b/server/VirtualNetwork.cpp
@@ -31,7 +31,7 @@
     if (hasInterface(interface)) {
         return 0;
     }
-    if (int ret = RouteController::addInterfaceToVpn(mNetId, interface.c_str())) {
+    if (int ret = RouteController::addInterfaceToVpn(mNetId, interface.c_str(), mUidRanges)) {
         ALOGE("failed to add interface %s to VPN netId %u", interface.c_str(), mNetId);
         return ret;
     }
@@ -43,10 +43,32 @@
     if (!hasInterface(interface)) {
         return 0;
     }
-    if (int ret = RouteController::removeInterfaceFromVpn(mNetId, interface.c_str())) {
+    if (int ret = RouteController::removeInterfaceFromVpn(mNetId, interface.c_str(), mUidRanges)) {
         ALOGE("failed to remove interface %s from VPN netId %u", interface.c_str(), mNetId);
         return ret;
     }
     mInterfaces.erase(interface);
     return 0;
 }
+
+int VirtualNetwork::addUsers(const UidRanges& uidRanges) {
+    for (const std::string& interface : mInterfaces) {
+        if (int ret = RouteController::addUsersToVpn(mNetId, interface.c_str(), uidRanges)) {
+            ALOGE("failed to add users on interface %s of netId %u", interface.c_str(), mNetId);
+            return ret;
+        }
+    }
+    mUidRanges.add(uidRanges);
+    return 0;
+}
+
+int VirtualNetwork::removeUsers(const UidRanges& uidRanges) {
+    for (const std::string& interface : mInterfaces) {
+        if (int ret = RouteController::removeUsersFromVpn(mNetId, interface.c_str(), uidRanges)) {
+            ALOGE("failed to remove users on interface %s of netId %u", interface.c_str(), mNetId);
+            return ret;
+        }
+    }
+    mUidRanges.remove(uidRanges);
+    return 0;
+}
diff --git a/server/VirtualNetwork.h b/server/VirtualNetwork.h
index cb412bb..fad28fd 100644
--- a/server/VirtualNetwork.h
+++ b/server/VirtualNetwork.h
@@ -18,15 +18,21 @@
 #define NETD_SERVER_VIRTUAL_NETWORK_H
 
 #include "Network.h"
+#include "UidRanges.h"
 
 class VirtualNetwork : public Network {
 public:
     VirtualNetwork(unsigned netId, uid_t ownerUid);
     virtual ~VirtualNetwork();
 
+    int addUsers(const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+    int removeUsers(const UidRanges& uidRanges) WARN_UNUSED_RESULT;
+
 private:
     int addInterface(const std::string& interface) override WARN_UNUSED_RESULT;
     int removeInterface(const std::string& interface) override WARN_UNUSED_RESULT;
+
+    UidRanges mUidRanges;
 };
 
 #endif  // NETD_SERVER_VIRTUAL_NETWORK_H