OSDN Git Service

Merge "Host exemption now handles premarked sockets"
[android-x86/system-netd.git] / NatController.cpp
index 8ec5d64..b2a0e64 100644 (file)
  * limitations under the License.
  */
 
+#define LOG_NDEBUG 0
+
 #include <stdlib.h>
-#include <sys/wait.h>
 #include <errno.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 #include <fcntl.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <string.h>
+#include <cutils/properties.h>
 
 #define LOG_TAG "NatController"
 #include <cutils/log.h>
+#include <logwrap/logwrap.h>
 
 #include "NatController.h"
+#include "SecondaryTableController.h"
+#include "NetdConstants.h"
 
-extern "C" int logwrap(int argc, const char **argv, int background);
-
-static char IPTABLES_PATH[] = "/system/bin/iptables";
-static char OEM_SCRIPT_PATH[] = "/system/bin/oem-iptables-init.sh";
-
-NatController::NatController() : mOemChainsExist(false) {
-    natCount = 0;
-
-    setDefaults();
+const char* NatController::LOCAL_FORWARD = "natctrl_FORWARD";
+const char* NatController::LOCAL_NAT_POSTROUTING = "natctrl_nat_POSTROUTING";
+const char* NatController::LOCAL_TETHER_COUNTERS_CHAIN = "natctrl_tether_counters";
 
-    if (0 == access(OEM_SCRIPT_PATH, R_OK | X_OK)) {
-        // The call to oemCleanupHooks() is superfluous when done on bootup,
-        // but is needed for the case where netd has crashed/stopped and is
-        // restarted.
-        if (!oemCleanupHooks() && !oemSetupHooks() && !oemInitChains()) {
-            mOemChainsExist = true;
-        }
-    }
+NatController::NatController(SecondaryTableController *ctrl) {
+    secondaryTableCtrl = ctrl;
 }
 
 NatController::~NatController() {
 }
 
-int NatController::runIptablesCmd(const char *cmd) {
-    char buffer[255];
-
-    strncpy(buffer, cmd, sizeof(buffer)-1);
-
-    const char *args[16];
-    char *next = buffer;
-    char *tmp;
+struct CommandsAndArgs {
+    /* The array size doesn't really matter as the compiler will barf if too many initializers are specified. */
+    const char *cmd[32];
+    bool checkRes;
+};
+
+int NatController::runCmd(int argc, const char **argv) {
+    int res;
+
+    res = android_fork_execvp(argc, (char **)argv, NULL, false, false);
+
+#if !LOG_NDEBUG
+    std::string full_cmd = argv[0];
+    argc--; argv++;
+    /*
+     * HACK: Sometimes runCmd() is called with a ridcously large value (32)
+     * and it works because the argv[] contains a NULL after the last
+     * true argv. So here we use the NULL argv[] to terminate when the argc
+     * is horribly wrong, and argc for the normal cases.
+     */
+    for (; argc && argv[0]; argc--, argv++) {
+        full_cmd += " ";
+        full_cmd += argv[0];
+    }
+    ALOGV("runCmd(%s) res=%d", full_cmd.c_str(), res);
+#endif
+    return res;
+}
 
-    args[0] = IPTABLES_PATH;
-    args[1] = "--verbose";
-    int i = 2;
+int NatController::setupIptablesHooks() {
+    int res;
+    res = setDefaults();
+    if (res < 0) {
+        return res;
+    }
 
-    while ((tmp = strsep(&next, " "))) {
-        args[i++] = tmp;
-        if (i == 16) {
-            LOGE("iptables argument overflow");
-            errno = E2BIG;
-            return -1;
+    struct CommandsAndArgs defaultCommands[] = {
+        /*
+         * Chain for tethering counters.
+         * This chain is reached via --goto, and then RETURNS.
+         */
+        {{IPTABLES_PATH, "-F", LOCAL_TETHER_COUNTERS_CHAIN,}, 0},
+        {{IPTABLES_PATH, "-X", LOCAL_TETHER_COUNTERS_CHAIN,}, 0},
+        {{IPTABLES_PATH, "-N", LOCAL_TETHER_COUNTERS_CHAIN,}, 1},
+    };
+    for (unsigned int cmdNum = 0; cmdNum < ARRAY_SIZE(defaultCommands); cmdNum++) {
+        if (runCmd(ARRAY_SIZE(defaultCommands[cmdNum].cmd), defaultCommands[cmdNum].cmd) &&
+            defaultCommands[cmdNum].checkRes) {
+                return -1;
         }
     }
-    args[i] = NULL;
 
-    return logwrap(i, args, 0);
+    return 0;
 }
 
 int NatController::setDefaults() {
+    /*
+     * The following only works because:
+     *  - the defaultsCommands[].cmd array is padded with NULL, and
+     *  - the 1st argc of runCmd() will just be the max for the CommandsAndArgs[].cmd, and
+     *  - internally it will be memcopied to an array and terminated with a NULL.
+     */
+    struct CommandsAndArgs defaultCommands[] = {
+        {{IPTABLES_PATH, "-F", LOCAL_FORWARD,}, 1},
+        {{IPTABLES_PATH, "-A", LOCAL_FORWARD, "-j", "DROP"}, 1},
+        {{IPTABLES_PATH, "-t", "nat", "-F", LOCAL_NAT_POSTROUTING}, 1},
+        {{IP_PATH, "rule", "flush"}, 0},
+        {{IP_PATH, "-6", "rule", "flush"}, 0},
+        {{IP_PATH, "rule", "add", "from", "all", "lookup", "default", "prio", "32767"}, 0},
+        {{IP_PATH, "rule", "add", "from", "all", "lookup", "main", "prio", "32766"}, 0},
+        {{IP_PATH, "-6", "rule", "add", "from", "all", "lookup", "default", "prio", "32767"}, 0},
+        {{IP_PATH, "-6", "rule", "add", "from", "all", "lookup", "main", "prio", "32766"}, 0},
+        {{IP_PATH, "route", "flush", "cache"}, 0},
+    };
+    for (unsigned int cmdNum = 0; cmdNum < ARRAY_SIZE(defaultCommands); cmdNum++) {
+        if (runCmd(ARRAY_SIZE(defaultCommands[cmdNum].cmd), defaultCommands[cmdNum].cmd) &&
+            defaultCommands[cmdNum].checkRes) {
+                return -1;
+        }
+    }
 
-    if (runIptablesCmd("-P INPUT ACCEPT"))
-        return -1;
-    if (runIptablesCmd("-P OUTPUT ACCEPT"))
-        return -1;
-    if (runIptablesCmd("-P FORWARD DROP"))
-        return -1;
-    if (runIptablesCmd("-F FORWARD"))
-        return -1;
-
-    if (runIptablesCmd("-t nat -F PREROUTING"))
-        return -1;
-    if (runIptablesCmd("-t nat -F OUTPUT"))
-        return -1;
-    if (runIptablesCmd("-t nat -F POSTROUTING"))
-        return -1;
+    natCount = 0;
 
     return 0;
 }
 
-int NatController::oemSetupHooks() {
-    // Order is important!
-    // -N to create the chain (no-op if already exist).
-    // -D to delete any pre-existing jump rule, to prevent dupes (no-op if doesn't exist)
-    // -I to insert our jump rule into the default chain
+bool NatController::checkInterface(const char *iface) {
+    if (strlen(iface) > IFNAMSIZ) return false;
+    return true;
+}
 
-    runIptablesCmd("-N oem_out");
-    runIptablesCmd("-D OUTPUT -j oem_out");
-    if (runIptablesCmd("-I OUTPUT -j oem_out"))
-        return -1;
+int NatController::routesOp(bool add, const char *intIface, const char *extIface, char **argv, int addrCount) {
+    int tableNumber = secondaryTableCtrl->findTableNumber(extIface);
+    int ret = 0;
+
+    if (tableNumber != -1) {
+        for (int i = 0; i < addrCount; i++) {
+            if (add) {
+                ret |= secondaryTableCtrl->modifyFromRule(tableNumber, ADD, argv[5+i]);
+                ret |= secondaryTableCtrl->modifyLocalRoute(tableNumber, ADD, intIface, argv[5+i]);
+            } else {
+                ret |= secondaryTableCtrl->modifyLocalRoute(tableNumber, DEL, intIface, argv[5+i]);
+                ret |= secondaryTableCtrl->modifyFromRule(tableNumber, DEL, argv[5+i]);
+            }
+        }
+        const char *cmd[] = {
+                IP_PATH,
+                "route",
+                "flush",
+                "cache"
+        };
+        runCmd(ARRAY_SIZE(cmd), cmd);
+    }
+    return ret;
+}
 
-    runIptablesCmd("-N oem_fwd");
-    runIptablesCmd("-D FORWARD -j oem_fwd");
-    if (runIptablesCmd("-I FORWARD -j oem_fwd"))
-        return -1;
+//  0    1       2       3       4            5
+// nat enable intface extface addrcnt nated-ipaddr/prelength
+int NatController::enableNat(const int argc, char **argv) {
+    int i;
+    int addrCount = atoi(argv[4]);
+    const char *intIface = argv[2];
+    const char *extIface = argv[3];
+    int tableNumber;
+
+    ALOGV("enableNat(intIface=<%s>, extIface=<%s>)",intIface, extIface);
 
-    runIptablesCmd("-t nat -N oem_nat_pre");
-    runIptablesCmd("-t nat -D PREROUTING -j oem_nat_pre");
-    if (runIptablesCmd("-t nat -I PREROUTING -j oem_nat_pre"))
+    if (!checkInterface(intIface) || !checkInterface(extIface)) {
+        ALOGE("Invalid interface specified");
+        errno = ENODEV;
         return -1;
+    }
 
-    return 0;
-}
+    /* Bug: b/9565268. "enableNat wlan0 wlan0". For now we fail until java-land is fixed */
+    if (!strcmp(intIface, extIface)) {
+        ALOGE("Duplicate interface specified: %s %s", intIface, extIface);
+        errno = EINVAL;
+        return -1;
+    }
 
-int NatController::oemCleanupHooks() {
-    // Order is important!
-    // -D to remove ref to the chain
-    // -F to empty the chain
-    // -X to delete the chain
+    if (argc < 5 + addrCount) {
+        ALOGE("Missing Argument");
+        errno = EINVAL;
+        return -1;
+    }
+    if (routesOp(true, intIface, extIface, argv, addrCount)) {
+        ALOGE("Error setting route rules");
+        routesOp(false, intIface, extIface, argv, addrCount);
+        errno = ENODEV;
+        return -1;
+    }
 
-    runIptablesCmd("-D OUTPUT -j oem_out");
-    runIptablesCmd("-F oem_out");
-    runIptablesCmd("-X oem_out");
+    // add this if we are the first added nat
+    if (natCount == 0) {
+        const char *cmd[] = {
+                IPTABLES_PATH,
+                "-t",
+                "nat",
+                "-A",
+                LOCAL_NAT_POSTROUTING,
+                "-o",
+                extIface,
+                "-j",
+                "MASQUERADE"
+        };
+        if (runCmd(ARRAY_SIZE(cmd), cmd)) {
+            ALOGE("Error seting postroute rule: iface=%s", extIface);
+            // unwind what's been done, but don't care about success - what more could we do?
+            routesOp(false, intIface, extIface, argv, addrCount);
+            setDefaults();
+            return -1;
+        }
+    }
 
-    runIptablesCmd("-D FORWARD -j oem_fwd");
-    runIptablesCmd("-F oem_fwd");
-    runIptablesCmd("-X oem_fwd");
 
-    runIptablesCmd("-t nat -D PREROUTING -j oem_nat_pre");
-    runIptablesCmd("-t nat -F oem_nat_pre");
-    runIptablesCmd("-t nat -X oem_nat_pre");
+    if (setForwardRules(true, intIface, extIface) != 0) {
+        ALOGE("Error setting forward rules");
+        routesOp(false, intIface, extIface, argv, addrCount);
+        if (natCount == 0) {
+            setDefaults();
+        }
+        errno = ENODEV;
+        return -1;
+    }
 
+    /* Always make sure the drop rule is at the end */
+    const char *cmd1[] = {
+            IPTABLES_PATH,
+            "-D",
+            LOCAL_FORWARD,
+            "-j",
+            "DROP"
+    };
+    runCmd(ARRAY_SIZE(cmd1), cmd1);
+    const char *cmd2[] = {
+            IPTABLES_PATH,
+            "-A",
+            LOCAL_FORWARD,
+            "-j",
+            "DROP"
+    };
+    runCmd(ARRAY_SIZE(cmd2), cmd2);
+
+    natCount++;
     return 0;
 }
 
-// This method should only be called when netd starts up.  The OEM chains are
-// intended to be static, so there's no need to flush and recreate them every
-// time setDefaults() is called.
-int NatController::oemInitChains() {
-    int ret = system(OEM_SCRIPT_PATH);
-    if ((-1 == ret) || (0 != WEXITSTATUS(ret))) {
-        LOGE("%s failed: %s", OEM_SCRIPT_PATH, strerror(errno));
+int NatController::setTetherCountingRules(bool add, const char *intIface, const char *extIface) {
+
+    /* We only ever add tethering quota rules so that they stick. */
+    if (!add) {
+        return 0;
+    }
+    char *quota_name, *proc_path;
+    int quota_fd;
+    asprintf(&quota_name, "%s_%s", intIface, extIface);
+
+    asprintf(&proc_path, "/proc/net/xt_quota/%s", quota_name);
+    quota_fd = open(proc_path, O_RDONLY);
+    if (quota_fd >= 0) {
+        /* quota for iface pair already exists */
+        free(proc_path);
+        free(quota_name);
+        return 0;
+    }
+    close(quota_fd);
+    free(proc_path);
+
+    const char *cmd2b[] = {
+            IPTABLES_PATH,
+            "-A",
+            LOCAL_TETHER_COUNTERS_CHAIN,
+            "-i",
+            intIface,
+            "-o",
+            extIface,
+            "-m",
+            "quota2",
+            "--name",
+            quota_name,
+            "--grow",
+            "-j",
+          "RETURN"
+    };
+
+    if (runCmd(ARRAY_SIZE(cmd2b), cmd2b) && add) {
+        free(quota_name);
         return -1;
     }
+    free(quota_name);
+
+    asprintf(&quota_name, "%s_%s", extIface, intIface);
+    asprintf(&proc_path, "/proc/net/xt_quota/%s", quota_name);
+    quota_fd = open(proc_path, O_RDONLY);
+    if (quota_fd >= 0) {
+        /* quota for iface pair already exists */
+        free(proc_path);
+        free(quota_name);
+        return 0;
+    }
+    close(quota_fd);
+    free(proc_path);
+
+    const char *cmd3b[] = {
+            IPTABLES_PATH,
+            "-A",
+            LOCAL_TETHER_COUNTERS_CHAIN,
+            "-i",
+            extIface,
+            "-o",
+            intIface,
+            "-m",
+            "quota2",
+            "--name",
+            quota_name,
+            "--grow",
+            "-j",
+            "RETURN"
+    };
+
+    if (runCmd(ARRAY_SIZE(cmd3b), cmd3b) && add) {
+        // unwind what's been done, but don't care about success - what more could we do?
+        free(quota_name);
+        return -1;
+    }
+    free(quota_name);
     return 0;
 }
 
-bool NatController::interfaceExists(const char *iface) {
-    // XXX: Implement this
-    return true;
-}
-
-int NatController::doNatCommands(const char *intIface, const char *extIface, bool add) {
-    char cmd[255];
-
-    // handle decrement to 0 case (do reset to defaults) and erroneous dec below 0
-    if (add == false) {
-        if (natCount <= 1) {
-            int ret = setDefaults();
-            if (ret == 0) {
-                natCount=0;
-            }
-            if (mOemChainsExist)
-                oemSetupHooks();
-            return ret;
-        }
-    }
-
-    if (!interfaceExists(intIface) || !interfaceExists (extIface)) {
-        LOGE("Invalid interface specified");
-        errno = ENODEV;
+int NatController::setForwardRules(bool add, const char *intIface, const char *extIface) {
+    const char *cmd1[] = {
+            IPTABLES_PATH,
+            add ? "-A" : "-D",
+            LOCAL_FORWARD,
+            "-i",
+            extIface,
+            "-o",
+            intIface,
+            "-m",
+            "state",
+            "--state",
+            "ESTABLISHED,RELATED",
+            "-g",
+            LOCAL_TETHER_COUNTERS_CHAIN
+    };
+    int rc = 0;
+
+    if (runCmd(ARRAY_SIZE(cmd1), cmd1) && add) {
         return -1;
     }
 
-    snprintf(cmd, sizeof(cmd),
-             "-%s FORWARD -i %s -o %s -m state --state ESTABLISHED,RELATED -j ACCEPT",
-             (add ? "A" : "D"),
-             extIface, intIface);
-    if (runIptablesCmd(cmd)) {
-        return -1;
+    const char *cmd2[] = {
+            IPTABLES_PATH,
+            add ? "-A" : "-D",
+            LOCAL_FORWARD,
+            "-i",
+            intIface,
+            "-o",
+            extIface,
+            "-m",
+            "state",
+            "--state",
+            "INVALID",
+            "-j",
+            "DROP"
+    };
+
+    const char *cmd3[] = {
+            IPTABLES_PATH,
+            add ? "-A" : "-D",
+            LOCAL_FORWARD,
+            "-i",
+            intIface,
+            "-o",
+            extIface,
+            "-g",
+            LOCAL_TETHER_COUNTERS_CHAIN
+    };
+
+    if (runCmd(ARRAY_SIZE(cmd2), cmd2) && add) {
+        // bail on error, but only if adding
+        rc = -1;
+        goto err_invalid_drop;
     }
 
-    snprintf(cmd, sizeof(cmd), "-%s FORWARD -i %s -o %s -j ACCEPT", (add ? "A" : "D"),
-            intIface, extIface);
-    if (runIptablesCmd(cmd)) {
+    if (runCmd(ARRAY_SIZE(cmd3), cmd3) && add) {
         // unwind what's been done, but don't care about success - what more could we do?
-        snprintf(cmd, sizeof(cmd),
-                 "-%s FORWARD -i %s -o %s -m state --state ESTABLISHED,RELATED -j ACCEPT",
-                 (!add ? "A" : "D"),
-                 extIface, intIface);
-        return -1;
+        rc = -1;
+        goto err_return;
     }
 
-    // add this if we are the first added nat
-    if (add && natCount == 0) {
-        snprintf(cmd, sizeof(cmd), "-t nat -A POSTROUTING -o %s -j MASQUERADE", extIface);
-        if (runIptablesCmd(cmd)) {
-            // unwind what's been done, but don't care about success - what more could we do?
-            setDefaults();;
-            oemSetupHooks();
-            return -1;
-        }
+    if (setTetherCountingRules(add, intIface, extIface) && add) {
+        rc = -1;
+        goto err_return;
     }
 
-    if (add) {
-        natCount++;
-    } else {
-        natCount--;
-    }
     return 0;
-}
 
-int NatController::enableNat(const char *intIface, const char *extIface) {
-    return doNatCommands(intIface, extIface, true);
+err_return:
+    cmd2[1] = "-D";
+    runCmd(ARRAY_SIZE(cmd2), cmd2);
+err_invalid_drop:
+    cmd1[1] = "-D";
+    runCmd(ARRAY_SIZE(cmd1), cmd1);
+    return rc;
 }
 
-int NatController::disableNat(const char *intIface, const char *extIface) {
-    return doNatCommands(intIface, extIface, false);
+// nat disable intface extface
+//  0    1       2       3       4            5
+// nat enable intface extface addrcnt nated-ipaddr/prelength
+int NatController::disableNat(const int argc, char **argv) {
+    int i;
+    int addrCount = atoi(argv[4]);
+    const char *intIface = argv[2];
+    const char *extIface = argv[3];
+    int tableNumber;
+
+    if (!checkInterface(intIface) || !checkInterface(extIface)) {
+        ALOGE("Invalid interface specified");
+        errno = ENODEV;
+        return -1;
+    }
+
+    if (argc < 5 + addrCount) {
+        ALOGE("Missing Argument");
+        errno = EINVAL;
+        return -1;
+    }
+
+    setForwardRules(false, intIface, extIface);
+    routesOp(false, intIface, extIface, argv, addrCount);
+    if (--natCount <= 0) {
+        // handle decrement to 0 case (do reset to defaults) and erroneous dec below 0
+        setDefaults();
+    }
+    return 0;
 }