system/netd: bandwidth management initial support (uid+tag stats)

This is a minimalistic version to get accounting of data going
through tagged socket per uid.

When netd starts up the BandwidthController, it will look at the
properties for
   persist.bandwidth.enable=1
and enabled it.

It needs the kernel with the xt_qtaguid + iptables/netfilter goodness.
stlport is ok to use.

The "owner" netfilter module used is actually our xt_qtaguid that acts as it
(just until we get around to talking directly the to kernel).

Once
  "ndc bandwidth enable"
is invoked all traffic is counted against the UIDs receiving/sending it.
This allows BlockGuard.java to "tag" sockets and see stats for the tags.

Data shows up in
  /proc/net/xt_qtaguid/stats

  /proc/net/xt_qtaguid/iface_stat/<iface>/
     rx_packets_tcp
     rx_bytes_tcp
     ...
There is no <uid>/...

Supported commands:
 - "ndc bandwidth enable"
   will setup the needed iptable entries to track tag/uid.
 - "ndc bandwidth disable"
   will remove the iptable entries.
 - "ndc bandwidth setquota <iface> <value>"
   will set a quota on the iface.
   Once quota is reached, packets are rejected.
   With the correct kernel, rejects are turned in socket errors.

TODO
----
 * make bandwidth controller cooperate with tethering.
   - they both manipulate the iptables.

Change-Id: Ieb9e7c60ef8c974e99828f7833065d59b2922bf3
diff --git a/BandwidthController.cpp b/BandwidthController.cpp
new file mode 100644
index 0000000..f859215
--- /dev/null
+++ b/BandwidthController.cpp
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/pkt_sched.h>
+
+#define LOG_TAG "BandwidthController"
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+extern "C" int logwrap(int argc, const char **argv, int background);
+
+#include "BandwidthController.h"
+
+
+const int BandwidthController::MAX_CMD_LEN = 255;
+const int BandwidthController::MAX_IFACENAME_LEN = 64;
+const int BandwidthController::MAX_CMD_ARGS = 32;
+const char BandwidthController::IPTABLES_PATH[] = "/system/bin/iptables";
+
+
+/**
+ * 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"
+ *    - 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
+ *      iptables -I INPUT -i iface0 --goto costly
+ *      iptables -I OUTPUT -o iface0 --goto costly
+ *      iptables -I costly -m quota \! --quota 500000 --jump REJECT --reject-with icmp-net-prohibited
+ *      iptables -A costly                            --jump penalty_box
+ *      iptables -A costly -m owner --socket-exists
+ *    . adding a new iface to this, E.g.:
+ *      iptables -I INPUT -i iface1 --goto costly
+ *      iptables -I OUTPUT -o iface1 --goto costly
+ *
+ *   - 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 -A costly_iface0 -m quota \! --quota 500000 --jump REJECT --reject-with icmp-net-prohibited
+ *      iptables -A costly_iface0                            --jump penalty_box
+ *      iptables -A costly_iface0 -m owner --socket-exists
+ *
+ * * penalty_box handling:
+ *  - only one penalty_box for all interfaces
+ *   E.g  Adding an app:
+ *    iptables -A penalty_box -m owner --uid-owner app_3 --jump REJECT --reject-with icmp-net-prohibited
+ */
+const char *BandwidthController::cleanupCommands[] = {
+    /* Cleanup rules. */
+    "-F",
+    "-t raw -F",
+    "-X costly",
+    "-X penalty_box",
+};
+
+const char *BandwidthController::setupCommands[] = {
+    /* Created needed chains. */
+    "-N costly",
+    "-N penalty_box",
+};
+
+const char *BandwidthController::basicAccountingCommands[] = {
+    "-F INPUT",
+    "-A INPUT -i lo --jump ACCEPT",
+    "-A 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. */
+
+    "-F costly",
+    "-A costly --jump penalty_box",
+    "-A costly -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 --jump ACCEPT",
+};
+
+
+BandwidthController::BandwidthController(void) {
+
+    char value[PROPERTY_VALUE_MAX];
+
+    property_get("persist.bandwidth.enable", value, "0");
+    if (!strcmp(value, "1")) {
+        enableBandwidthControl();
+    }
+
+}
+
+int BandwidthController::runIptablesCmd(const char *cmd) {
+    char buffer[MAX_CMD_LEN];
+
+    LOGD("About to run: iptables %s", cmd);
+
+    strncpy(buffer, cmd, sizeof(buffer)-1);
+
+    const char *argv[MAX_CMD_ARGS];
+    char *next = buffer;
+    char *tmp;
+
+    argv[0] = IPTABLES_PATH;
+    int argc = 1;
+
+    while ((tmp = strsep(&next, " "))) {
+        argv[argc++] = tmp;
+        if (argc == MAX_CMD_ARGS) {
+            LOGE("iptables argument overflow");
+            errno = E2BIG;
+            return -1;
+        }
+    }
+    argv[argc] = NULL;
+    /* TODO(jpa): Once this stabilizes, remove logwrap() as it tends to wedge netd
+     * Then just talk directly to the kernel via rtnetlink.
+     */
+    return logwrap(argc, argv, 0);
+}
+
+
+int BandwidthController::enableBandwidthControl(void) {
+        /* Some of the initialCommands are allowed to fail */
+        runCommands(cleanupCommands, sizeof(cleanupCommands)/sizeof(char*), true);
+        runCommands(setupCommands, sizeof(setupCommands)/sizeof(char*), true);
+        return runCommands(basicAccountingCommands,
+                           sizeof(basicAccountingCommands)/sizeof(char*));
+
+}
+
+int BandwidthController::disableBandwidthControl(void) {
+        /* The cleanupCommands are allowed to fail */
+        runCommands(cleanupCommands, sizeof(cleanupCommands)/sizeof(char*), true);
+        return 0;
+}
+
+int BandwidthController::runCommands(const char *commands[], int numCommands, bool allowFailure) {
+        int res = 0;
+        LOGD("runCommands(): %d commands", numCommands);
+        for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) {
+                res = runIptablesCmd(commands[cmdNum]);
+                if(res && !allowFailure) return res;
+        }
+        return allowFailure?res:0;
+}
+
+
+int BandwidthController::setInterfaceQuota(const char *iface,
+                                           int64_t maxBytes) {
+    char cmd[MAX_CMD_LEN];
+    char ifn[MAX_IFACENAME_LEN];
+    int res;
+
+    memset(ifn, 0, sizeof(ifn));
+    strncpy(ifn, iface, sizeof(ifn)-1);
+
+    if (maxBytes == -1) {
+        return removeQuota(ifn);
+    }
+
+    /* Insert ingress quota. */
+    std::string ifaceName(ifn);
+    std::list<std::string>::iterator it;
+    int pos;
+    for (pos=1, it = ifaceRules.begin(); it != ifaceRules.end(); it++, pos++) {
+            if (*it == ifaceName)
+                    break;
+    }
+    if (it != ifaceRules.end()) {
+            snprintf(cmd, sizeof(cmd), "-R INPUT %d -i %s --goto costly", pos, ifn);
+            res = runIptablesCmd(cmd);
+            snprintf(cmd, sizeof(cmd), "-R OUTPUT %d -o %s --goto costly", pos, ifn);
+            res |= runIptablesCmd(cmd);
+            snprintf(cmd, sizeof(cmd), "-R costly %d -m quota ! --quota %lld"
+                    " --jump REJECT --reject-with icmp-net-prohibited",
+                    pos, maxBytes);
+            res |= runIptablesCmd(cmd);
+            if (res) {
+                    LOGE("Failed set quota rule.");
+                    goto fail;
+            }
+    } else {
+            pos = 1;
+            snprintf(cmd, sizeof(cmd), "-I INPUT -i %s --goto costly", ifn);
+            res = runIptablesCmd(cmd);
+            snprintf(cmd, sizeof(cmd), "-I OUTPUT -o %s --goto costly", ifn);
+            res |= runIptablesCmd(cmd);
+            snprintf(cmd, sizeof(cmd), "-I costly -m quota ! --quota %lld"
+                    " --jump REJECT --reject-with icmp-net-prohibited",
+                    maxBytes);
+            res |= runIptablesCmd(cmd);
+            if (res) {
+                    LOGE("Failed set quota rule.");
+                    goto fail;
+            }
+            ifaceRules.push_front(ifaceName);
+    }
+    return 0;
+fail:
+    /*
+     * Failure tends to be that the rules have been messed up.
+     * For now cleanup all the rules.
+     * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse
+     * rules in the kernel to see which ones need cleaning up.
+     */
+    runCommands(basicAccountingCommands,
+                sizeof(basicAccountingCommands)/sizeof(char*), true);
+    removeQuota(ifn);
+    return -1;
+}
+
+int BandwidthController::removeQuota(const char *iface) {
+    char cmd[MAX_CMD_LEN];
+    char ifn[MAX_IFACENAME_LEN];
+    int res;
+
+    memset(ifn, 0, sizeof(ifn));
+    strncpy(ifn, iface, sizeof(ifn)-1);
+
+    std::string ifaceName(ifn);
+    std::list<std::string>::iterator it;
+
+    int pos;
+    for (pos=1, it = ifaceRules.begin(); it != ifaceRules.end(); it++, pos++) {
+            if (*it == ifaceName)
+                    break;
+    }
+    if(it == ifaceRules.end()) {
+            LOGE("No such iface %s to delete.", ifn);
+            return -1;
+    }
+    ifaceRules.erase(it);
+    snprintf(cmd, sizeof(cmd), "--delete INPUT -i %s --goto costly", ifn);
+    res = runIptablesCmd(cmd);
+    snprintf(cmd, sizeof(cmd), "--delete OUTPUT -o %s --goto costly", ifn);
+    res |= runIptablesCmd(cmd);
+    // Don't use rule-matching for this one. Quota is the remaining one.
+    snprintf(cmd, sizeof(cmd), "--delete costly %d", pos);
+    res |= runIptablesCmd(cmd);
+    return res;
+}