netd: Idletimer vs Nat vs Bandwidth controllers

* modified iptables users to work in controller specific custom chains.
 - each controller only works withing his own custom chains and not the
  top level ones (INPUT, OUTPUT, FORWARD, POSTROUTING,...)
 - CommandListener now invokes setupIptablesHooks() for each controller
   once. That is the only time they are allowed to access the top-level
   chains.

* Added idletimer controller.
  From https://android-git.corp.google.com/g/#/c/180769/2
 - supported commands
   . ndc idletimer enable
   . ndc idletimer add <iface> <timeout>
   . ndc idletimer remove <iface> <timeout_used_during_add>
 There is a framework change elsewhere that receives netlink messages.

Signed-off-by: Ashish Sharma <ashishsharma@google.com>
Signed-off-by: JP Abgrall <jpa@google.com>
Change-Id: Ia57450c09166ce20f21d1e3b49047ef1e98f2a3d
diff --git a/BandwidthController.cpp b/BandwidthController.cpp
index fa94d30..5233f3e 100644
--- a/BandwidthController.cpp
+++ b/BandwidthController.cpp
@@ -44,15 +44,13 @@
 extern "C" int logwrap(int argc, const char **argv);
 extern "C" int system_nosh(const char *command);
 
+#include "NetdConstants.h"
 #include "BandwidthController.h"
-#include "oem_iptables_hook.h"
 
 /* Alphabetical */
 #define ALERT_IPT_TEMPLATE "%s %s %s -m quota2 ! --quota %lld --name %s"
 const int  BandwidthController::ALERT_RULE_POS_IN_COSTLY_CHAIN = 4;
 const char BandwidthController::ALERT_GLOBAL_NAME[] = "globalAlert";
-const char BandwidthController::IP6TABLES_PATH[] = "/system/bin/ip6tables";
-const char BandwidthController::IPTABLES_PATH[] = "/system/bin/iptables";
 const int  BandwidthController::MAX_CMD_ARGS = 32;
 const int  BandwidthController::MAX_CMD_LEN = 1024;
 const int  BandwidthController::MAX_IFACENAME_LEN = 64;
@@ -64,29 +62,29 @@
  * Some comments about the rules:
  *  * Ordering
  *    - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains.
- *      E.g. "-I INPUT -i rmnet0 --goto costly"
+ *      E.g. "-I INPUT -i rmnet0 --jump costly"
  *    - quota'd rules in the costly chain should be before penalty_box lookups.
  *
  * * global quota vs per interface quota
  *   - global quota for all costly interfaces uses a single costly chain:
  *    . initial rules
  *      iptables -N costly_shared
- *      iptables -I INPUT -i iface0 --goto costly_shared
- *      iptables -I OUTPUT -o iface0 --goto costly_shared
+ *      iptables -I INPUT -i iface0 --jump costly_shared
+ *      iptables -I OUTPUT -o iface0 --jump costly_shared
  *      iptables -I costly_shared -m quota \! --quota 500000 \
  *          --jump REJECT --reject-with icmp-net-prohibited
  *      iptables -A costly_shared --jump penalty_box
  *      iptables -A costly_shared -m owner --socket-exists
  *
  *    . adding a new iface to this, E.g.:
- *      iptables -I INPUT -i iface1 --goto costly_shared
- *      iptables -I OUTPUT -o iface1 --goto costly_shared
+ *      iptables -I INPUT -i iface1 --jump costly_shared
+ *      iptables -I OUTPUT -o iface1 --jump costly_shared
  *
  *   - quota per interface. This is achieve by having "costly" chains per quota.
  *     E.g. adding a new costly interface iface0 with its own quota:
  *      iptables -N costly_iface0
- *      iptables -I INPUT -i iface0 --goto costly_iface0
- *      iptables -I OUTPUT -o iface0 --goto costly_iface0
+ *      iptables -I INPUT -i iface0 --jump costly_iface0
+ *      iptables -I OUTPUT -o iface0 --jump costly_iface0
  *      iptables -A costly_iface0 -m quota \! --quota 500000 \
  *          --jump REJECT --reject-with icmp-net-prohibited
  *      iptables -A costly_iface0 --jump penalty_box
@@ -98,48 +96,61 @@
  *    iptables -A penalty_box -m owner --uid-owner app_3 \
  *        --jump REJECT --reject-with icmp-net-prohibited
  */
-const char *BandwidthController::IPT_CLEANUP_COMMANDS[] = {
-    /* Cleanup rules. */
-    "-F",
-    "-t raw -F",
-    /* TODO: If at some point we need more user chains than here, then we will need
-     * a different cleanup approach.
+const char *BandwidthController::IPT_FLUSH_COMMANDS[] = {
+    /*
+     * Cleanup rules.
+     * Should normally include costly_<iface>, but we rely on the way they are setup
+     * to allow coexistance.
      */
-    "-X",  /* Should normally only be costly_shared, penalty_box, and costly_<iface>  */
+    "-F bw_INPUT",
+    "-F bw_OUTPUT",
+    "-F bw_FORWARD",
+    "-F penalty_box",
+    "-F costly_shared",
+};
+
+/* The cleanup commands assume flushing has been done. */
+const char *BandwidthController::IPT_CLEANUP_COMMANDS[] = {
+    /* Delete hooks to custom chains. */
+    "-D INPUT -j bw_INPUT",
+    "-D OUTPUT -j bw_OUTPUT",
+    "-D FORWARD -j bw_FORWARD",
+    "-X bw_INPUT",
+    "-X bw_OUTPUT",
+    "-X bw_FORWARD",
+    "-X penalty_box",
+    "-X costly_shared",
 };
 
 const char *BandwidthController::IPT_SETUP_COMMANDS[] = {
     /* Created needed chains. */
+    "-N bw_INPUT",
+    "-A INPUT -j bw_INPUT",
+
+    "-N bw_OUTPUT",
+    "-A OUTPUT -j bw_OUTPUT",
+
+    "-N bw_FORWARD",
+    "-I FORWARD -j bw_FORWARD",
+
     "-N costly_shared",
     "-N penalty_box",
 };
 
 const char *BandwidthController::IPT_BASIC_ACCOUNTING_COMMANDS[] = {
-    "-F INPUT",
-    "-A INPUT -i lo --jump ACCEPT",
-    "-A INPUT -m owner --socket-exists", /* This is a tracking rule. */
+    "-A bw_INPUT -i lo --jump RETURN",
+    "-A bw_INPUT -m owner --socket-exists", /* This is a tracking rule. */
 
-    "-F OUTPUT",
-    "-A OUTPUT -o lo --jump ACCEPT",
-    "-A OUTPUT -m owner --socket-exists", /* This is a tracking rule. */
+    "-A bw_OUTPUT -o lo --jump RETURN",
+    "-A bw_OUTPUT -m owner --socket-exists", /* This is a tracking rule. */
 
-    "-F costly_shared",
     "-A costly_shared --jump penalty_box",
     "-A costly_shared -m owner --socket-exists", /* This is a tracking rule. */
-    /* TODO(jpa): Figure out why iptables doesn't correctly return from this
-     * chain. For now, hack the chain exit with an ACCEPT.
-     */
-    "-A costly_shared --jump ACCEPT",
 };
 
 BandwidthController::BandwidthController(void) {
     char value[PROPERTY_VALUE_MAX];
 
-    property_get("persist.bandwidth.enable", value, "0");
-    if (!strcmp(value, "1")) {
-        enableBandwidthControl();
-    }
-
     property_get("persist.bandwidth.uselogwrap", value, "0");
     useLogwrapCall = !strcmp(value, "1");
 }
@@ -212,8 +223,31 @@
     return res;
 }
 
-int BandwidthController::enableBandwidthControl(void) {
+int BandwidthController::setupIptablesHooks(void) {
+
+    /* Some of the initialCommands are allowed to fail */
+    runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*),
+            IPT_FLUSH_COMMANDS, RunCmdFailureOk);
+
+    runCommands(sizeof(IPT_CLEANUP_COMMANDS) / sizeof(char*),
+            IPT_CLEANUP_COMMANDS, RunCmdFailureOk);
+
+    runCommands(sizeof(IPT_SETUP_COMMANDS) / sizeof(char*),
+            IPT_SETUP_COMMANDS, RunCmdFailureBad);
+
+    return 0;
+
+}
+
+int BandwidthController::enableBandwidthControl(bool force) {
     int res;
+    char value[PROPERTY_VALUE_MAX];
+
+    if (!force) {
+            property_get("persist.bandwidth.enable", value, "1");
+            if (!strcmp(value, "0"))
+                    return 0;
+    }
 
     /* Let's pretend we started from scratch ... */
     sharedQuotaIfaces.clear();
@@ -223,26 +257,19 @@
     globalAlertTetherCount = 0;
     sharedQuotaBytes = sharedAlertBytes = 0;
 
+    res = runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*),
+            IPT_FLUSH_COMMANDS, RunCmdFailureOk);
 
-    /* Some of the initialCommands are allowed to fail */
-    runCommands(sizeof(IPT_CLEANUP_COMMANDS) / sizeof(char*),
-            IPT_CLEANUP_COMMANDS, RunCmdFailureOk);
-    runCommands(sizeof(IPT_SETUP_COMMANDS) / sizeof(char*),
-            IPT_SETUP_COMMANDS, RunCmdFailureOk);
-    res = runCommands(sizeof(IPT_BASIC_ACCOUNTING_COMMANDS) / sizeof(char*),
+    res |= runCommands(sizeof(IPT_BASIC_ACCOUNTING_COMMANDS) / sizeof(char*),
             IPT_BASIC_ACCOUNTING_COMMANDS, RunCmdFailureBad);
 
-    setupOemIptablesHook();
-
     return res;
 
 }
 
 int BandwidthController::disableBandwidthControl(void) {
-    /* The IPT_CLEANUP_COMMANDS are allowed to fail. */
-    runCommands(sizeof(IPT_CLEANUP_COMMANDS) / sizeof(char*),
-            IPT_CLEANUP_COMMANDS, RunCmdFailureOk);
-    setupOemIptablesHook();
+    runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*),
+            IPT_FLUSH_COMMANDS, RunCmdFailureOk);
     return 0;
 }
 
@@ -252,10 +279,10 @@
     ALOGV("runCommands(): %d commands", numCommands);
     for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) {
         res = runIpxtablesCmd(commands[cmdNum], IptRejectNoAdd);
-        if (res && cmdErrHandling != RunCmdFailureBad)
+        if (res && cmdErrHandling != RunCmdFailureOk)
             return res;
     }
-    return cmdErrHandling == RunCmdFailureBad ? res : 0;
+    return 0;
 }
 
 std::string BandwidthController::makeIptablesNaughtyCmd(IptOp op, int uid) {
@@ -306,6 +333,9 @@
         op = IptOpDelete;
         failLogTemplate = "Failed to delete app uid %d from penalty box.";
         break;
+    default:
+        ALOGE("Unexpected app Op %d", appOp);
+        return -1;
     }
 
     for (uidNum = 0; uidNum < numUids; uidNum++) {
@@ -363,7 +393,7 @@
 
 int BandwidthController::prepCostlyIface(const char *ifn, QuotaType quotaType) {
     char cmd[MAX_CMD_LEN];
-    int res = 0;
+    int res = 0, res1, res2;
     int ruleInsertPos = 1;
     std::string costString;
     const char *costCString;
@@ -374,30 +404,45 @@
         costString = "costly_";
         costString += ifn;
         costCString = costString.c_str();
+        /*
+         * Flush the costly_<iface> is allowed to fail in case it didn't exist.
+         * Creating a new one is allowed to fail in case it existed.
+         * This helps with netd restarts.
+         */
+        snprintf(cmd, sizeof(cmd), "-F %s", costCString);
+        res1 = runIpxtablesCmd(cmd, IptRejectNoAdd);
         snprintf(cmd, sizeof(cmd), "-N %s", costCString);
-        res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
+        res2 = runIpxtablesCmd(cmd, IptRejectNoAdd);
+        res = (res1 && res2) || (!res1 && !res2);
+
         snprintf(cmd, sizeof(cmd), "-A %s -j penalty_box", costCString);
         res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
         snprintf(cmd, sizeof(cmd), "-A %s -m owner --socket-exists", costCString);
         res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
-        /* TODO(jpa): Figure out why iptables doesn't correctly return from this
-         * chain. For now, hack the chain exit with an ACCEPT.
-         */
-        snprintf(cmd, sizeof(cmd), "-A %s --jump ACCEPT", costCString);
-        res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
         break;
     case QuotaShared:
         costCString = "costly_shared";
         break;
+    default:
+        ALOGE("Unexpected quotatype %d", quotaType);
+        return -1;
     }
 
     if (globalAlertBytes) {
         /* The alert rule comes 1st */
         ruleInsertPos = 2;
     }
-    snprintf(cmd, sizeof(cmd), "-I INPUT %d -i %s --goto %s", ruleInsertPos, ifn, costCString);
+
+    snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString);
+    runIpxtablesCmd(cmd, IptRejectNoAdd);
+
+    snprintf(cmd, sizeof(cmd), "-I bw_INPUT %d -i %s --jump %s", ruleInsertPos, ifn, costCString);
     res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
-    snprintf(cmd, sizeof(cmd), "-I OUTPUT %d -o %s --goto %s", ruleInsertPos, ifn, costCString);
+
+    snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString);
+    runIpxtablesCmd(cmd, IptRejectNoAdd);
+
+    snprintf(cmd, sizeof(cmd), "-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, ifn, costCString);
     res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
     return res;
 }
@@ -417,11 +462,14 @@
     case QuotaShared:
         costCString = "costly_shared";
         break;
+    default:
+        ALOGE("Unexpected quotatype %d", quotaType);
+        return -1;
     }
 
-    snprintf(cmd, sizeof(cmd), "-D INPUT -i %s --goto %s", ifn, costCString);
+    snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString);
     res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
-    snprintf(cmd, sizeof(cmd), "-D OUTPUT -o %s --goto %s", ifn, costCString);
+    snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString);
     res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
 
     /* The "-N costly_shared" is created upfront, no need to handle it here. */
@@ -693,12 +741,12 @@
     }
 
     ifaceLimiting = "! -i lo+";
-    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "INPUT",
+    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "bw_INPUT",
         bytes, alertName);
     res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd);
     free(alertQuotaCmd);
     ifaceLimiting = "! -o lo+";
-    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "OUTPUT",
+    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "bw_OUTPUT",
         bytes, alertName);
     res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd);
     free(alertQuotaCmd);
@@ -725,7 +773,7 @@
     }
 
     ifaceLimiting = "! -i lo+";
-    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "FORWARD",
+    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "bw_FORWARD",
         bytes, alertName);
     res = runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd);
     free(alertQuotaCmd);
@@ -917,11 +965,11 @@
 
 /*
  * Parse the ptks and bytes out of:
- * Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
+ * Chain FORWARD (policy RETURN 0 packets, 0 bytes)
  *     pkts      bytes target     prot opt in     out     source               destination
- *        0        0 ACCEPT     all  --  rmnet0 wlan0   0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
+ *        0        0 RETURN     all  --  rmnet0 wlan0   0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
  *        0        0 DROP       all  --  wlan0  rmnet0  0.0.0.0/0            0.0.0.0/0            state INVALID
- *        0        0 ACCEPT     all  --  wlan0  rmnet0  0.0.0.0/0            0.0.0.0/0
+ *        0        0 RETURN     all  --  wlan0  rmnet0  0.0.0.0/0            0.0.0.0/0
  *
  */
 int BandwidthController::parseForwardChainStats(TetherStats &stats, FILE *fp,
@@ -938,7 +986,7 @@
     while (NULL != (buffPtr = fgets(lineBuffer, MAX_IPT_OUTPUT_LINE_LEN, fp))) {
         /* Clean up, so a failed parse can still print info */
         iface0[0] = iface1[0] = rest[0] = packets = bytes = 0;
-        res = sscanf(buffPtr, "%lld %lld ACCEPT all -- %s %s 0.%s",
+        res = sscanf(buffPtr, "%lld %lld RETURN all -- %s %s 0.%s",
                 &packets, &bytes, iface0, iface1, rest);
         ALOGV("parse res=%d iface0=<%s> iface1=<%s> pkts=%lld bytes=%lld rest=<%s> orig line=<%s>", res,
              iface0, iface1, packets, bytes, rest, buffPtr);
@@ -988,7 +1036,7 @@
      * the wanted info.
      */
     fullCmd = IPTABLES_PATH;
-    fullCmd += " -nvx -L FORWARD";
+    fullCmd += " -nvx -L natctrl_FORWARD";
     iptOutput = popen(fullCmd.c_str(), "r");
     if (!iptOutput) {
             ALOGE("Failed to run %s err=%s", fullCmd.c_str(), strerror(errno));