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/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) {