* 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("a_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("a_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;
}