netd: bandwidth: tethering global alert support

Now, when nat is enabled/disabled it will let the bandwidthcontroller
know that it might need to add/remove the matching global alert into
the tethering rules in the FORWARD chain of iptables.

Bug: 5336638
Change-Id: I1843f3f6601f371537f754a31db792e054b36a1d
diff --git a/BandwidthController.cpp b/BandwidthController.cpp
index 1761145..f0a856e 100644
--- a/BandwidthController.cpp
+++ b/BandwidthController.cpp
@@ -48,6 +48,7 @@
 /* Alphabetical */
 const char BandwidthController::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;
@@ -217,6 +218,7 @@
     quotaIfaces.clear();
     naughtyAppUids.clear();
     globalAlertBytes = 0;
+    globalAlertTetherCount = 0;
     sharedQuotaBytes = sharedAlertBytes = 0;
 
 
@@ -698,9 +700,35 @@
     return res;
 }
 
-int BandwidthController::setGlobalAlert(int64_t bytes) {
+int BandwidthController::runIptablesAlertFwdCmd(IptOp op, const char *alertName, int64_t bytes) {
+    int res = 0;
+    const char *opFlag;
+    const char *ifaceLimiting;
     char *alertQuotaCmd;
-    const char *alertName = "globalAlert";
+
+    switch (op) {
+    case IptOpInsert:
+        opFlag = "-I";
+        break;
+    case IptOpReplace:
+        opFlag = "-R";
+        break;
+    default:
+    case IptOpDelete:
+        opFlag = "-D";
+        break;
+    }
+
+    ifaceLimiting = "! -i lo+";
+    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "FORWARD",
+        bytes, alertName, alertName);
+    res = runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd);
+    free(alertQuotaCmd);
+    return res;
+}
+
+int BandwidthController::setGlobalAlert(int64_t bytes) {
+    const char *alertName = ALERT_GLOBAL_NAME;
     int res = 0;
 
     if (!bytes) {
@@ -711,15 +739,39 @@
         res = updateQuota(alertName, bytes);
     } else {
         res = runIptablesAlertCmd(IptOpInsert, alertName, bytes);
+        if (globalAlertTetherCount) {
+            LOGV("setGlobalAlert for %d tether", globalAlertTetherCount);
+            res |= runIptablesAlertFwdCmd(IptOpInsert, alertName, bytes);
+        }
     }
     globalAlertBytes = bytes;
     return res;
 }
 
-int BandwidthController::removeGlobalAlert(void) {
-    char *alertQuotaCmd;
+int BandwidthController::setGlobalAlertInForwardChain(void) {
+    const char *alertName = ALERT_GLOBAL_NAME;
+    int res = 0;
 
-    const char *alertName = "globalAlert";
+    globalAlertTetherCount++;
+    LOGV("setGlobalAlertInForwardChain(): %d tether", globalAlertTetherCount);
+
+    /*
+     * If there is no globalAlert active we are done.
+     * If there is an active globalAlert but this is not the 1st
+     * tether, we are also done.
+     */
+    if (!globalAlertBytes || globalAlertTetherCount != 1) {
+        return 0;
+    }
+
+    /* We only add the rule if this was the 1st tether added. */
+    res = runIptablesAlertFwdCmd(IptOpInsert, alertName, globalAlertBytes);
+    return res;
+}
+
+int BandwidthController::removeGlobalAlert(void) {
+
+    const char *alertName = ALERT_GLOBAL_NAME;
     int res = 0;
 
     if (!globalAlertBytes) {
@@ -727,10 +779,37 @@
         return -1;
     }
     res = runIptablesAlertCmd(IptOpDelete, alertName, globalAlertBytes);
+    if (globalAlertTetherCount) {
+        res |= runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes);
+    }
     globalAlertBytes = 0;
     return res;
 }
 
+int BandwidthController::removeGlobalAlertInForwardChain(void) {
+    int res = 0;
+    const char *alertName = ALERT_GLOBAL_NAME;
+
+    if (!globalAlertTetherCount) {
+        LOGE("No prior alert set");
+        return -1;
+    }
+
+    globalAlertTetherCount--;
+    /*
+     * If there is no globalAlert active we are done.
+     * If there is an active globalAlert but there are more
+     * tethers, we are also done.
+     */
+    if (!globalAlertBytes || globalAlertTetherCount >= 1) {
+        return 0;
+    }
+
+    /* We only detete the rule if this was the last tether removed. */
+    res = runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes);
+    return res;
+}
+
 int BandwidthController::setSharedAlert(int64_t bytes) {
     if (!sharedQuotaBytes) {
         LOGE("Need to have a prior shared quota set to set an alert");
diff --git a/BandwidthController.h b/BandwidthController.h
index 401609f..861c63e 100644
--- a/BandwidthController.h
+++ b/BandwidthController.h
@@ -62,6 +62,8 @@
 
     int setGlobalAlert(int64_t bytes);
     int removeGlobalAlert(void);
+    int setGlobalAlertInForwardChain(void);
+    int removeGlobalAlertInForwardChain(void);
 
     int setSharedAlert(int64_t bytes);
     int removeSharedAlert(void);
@@ -101,6 +103,7 @@
     std::string makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota);
 
     int runIptablesAlertCmd(IptOp op, const char *alertName, int64_t bytes);
+    int runIptablesAlertFwdCmd(IptOp op, const char *alertName, int64_t bytes);
 
     /* Runs for both ipv4 and ipv6 iptables */
     int runCommands(int numCommands, const char *commands[], RunCmdErrHandling cmdErrHandling);
@@ -128,6 +131,16 @@
     int64_t sharedQuotaBytes;
     int64_t sharedAlertBytes;
     int64_t globalAlertBytes;
+    /*
+     * This tracks the number of tethers setup.
+     * The FORWARD chain is updated in the following cases:
+     *  - The 1st time a globalAlert is setup and there are tethers setup.
+     *  - Anytime a globalAlert is removed and there are tethers setup.
+     *  - The 1st tether is setup and there is a globalAlert active.
+     *  - The last tether is removed and there is a globalAlert active.
+     */
+    int globalAlertTetherCount;
+
     std::list<QuotaInfo> quotaIfaces;
     std::list<int /*appUid*/> naughtyAppUids;
 
@@ -139,6 +152,7 @@
     /* Alphabetical */
     static const char ALERT_IPT_TEMPLATE[];
     static const int  ALERT_RULE_POS_IN_COSTLY_CHAIN;
+    static const char ALERT_GLOBAL_NAME[];
     static const char IP6TABLES_PATH[];
     static const char IPTABLES_PATH[];
     static const int  MAX_CMD_ARGS;
diff --git a/CommandListener.cpp b/CommandListener.cpp
index 68a1309..21efadd 100644
--- a/CommandListener.cpp
+++ b/CommandListener.cpp
@@ -582,8 +582,16 @@
 
     if (!strcmp(argv[1], "enable")) {
         rc = sNatCtrl->enableNat(argv[2], argv[3]);
+        if(!rc) {
+            /* Ignore ifaces for now. */
+            rc = sBandwidthCtrl->setGlobalAlertInForwardChain();
+        }
     } else if (!strcmp(argv[1], "disable")) {
         rc = sNatCtrl->disableNat(argv[2], argv[3]);
+        if(!rc) {
+            /* Ignore ifaces for now. */
+            rc = sBandwidthCtrl->removeGlobalAlertInForwardChain();
+        }
     } else {
         cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown nat cmd", false);
         return 0;
@@ -1047,6 +1055,17 @@
         return 0;
 
     }
+    if (!strcmp(argv[1], "debugsettetherglobalalert") || !strcmp(argv[1], "dstga")) {
+        if (argc != 4) {
+            sendGenericSyntaxError(cli, "debugsettetherglobalalert <interface0> <interface1>");
+            return 0;
+        }
+        /* We ignore the interfaces for now. */
+        int rc = sBandwidthCtrl->setGlobalAlertInForwardChain();
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
     if (!strcmp(argv[1], "removeglobalalert") || !strcmp(argv[1], "rga")) {
         if (argc != 2) {
             sendGenericSyntaxError(cli, "removeglobalalert");
@@ -1057,6 +1076,17 @@
         return 0;
 
     }
+    if (!strcmp(argv[1], "debugremovetetherglobalalert") || !strcmp(argv[1], "drtga")) {
+        if (argc != 4) {
+            sendGenericSyntaxError(cli, "debugremovetetherglobalalert <interface0> <interface1>");
+            return 0;
+        }
+        /* We ignore the interfaces for now. */
+        int rc = sBandwidthCtrl->removeGlobalAlertInForwardChain();
+        sendGenericOkFail(cli, rc);
+        return 0;
+
+    }
     if (!strcmp(argv[1], "setsharedalert") || !strcmp(argv[1], "ssa")) {
         if (argc != 3) {
             sendGenericSyntaxError(cli, "setsharedalert <bytes>");