OSDN Git Service

system/netd: bandwidth management initial support (uid+tag stats)
authorJP Abgrall <jpa@google.com>
Thu, 16 Jun 2011 01:37:39 +0000 (18:37 -0700)
committerJP Abgrall <jpa@google.com>
Thu, 16 Jun 2011 01:37:39 +0000 (18:37 -0700)
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

Android.mk
BandwidthController.cpp [new file with mode: 0644]
BandwidthController.h [new file with mode: 0644]
CommandListener.cpp
CommandListener.h

index a75c5ff..c4748d2 100644 (file)
@@ -10,27 +10,32 @@ LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:=                                      \
-                  main.cpp                             \
+                  BandwidthController.cpp              \
                   CommandListener.cpp                  \
                   DnsProxyListener.cpp                 \
+                  NatController.cpp                    \
                   NetdCommand.cpp                      \
-                  NetlinkManager.cpp                   \
                   NetlinkHandler.cpp                   \
-                  logwrapper.c                         \
-                  TetherController.cpp                 \
-                  NatController.cpp                    \
-                  PppController.cpp                    \
+                  NetlinkManager.cpp                   \
                   PanController.cpp                    \
+                  PppController.cpp                    \
                   SoftapController.cpp                 \
+                  TetherController.cpp                 \
+                  ThrottleController.cpp               \
                   UsbController.cpp                    \
-                  ThrottleController.cpp
+                  logwrapper.c                         \
+                  main.cpp                             \
+
+
 
 LOCAL_MODULE:= netd
 
 LOCAL_C_INCLUDES := $(KERNEL_HEADERS) \
                     $(LOCAL_PATH)/../bluetooth/bluedroid/include \
                     $(LOCAL_PATH)/../bluetooth/bluez-clean-headers \
-                    external/openssl/include
+                    external/openssl/include \
+                    external/stlport/stlport \
+                    bionic
 
 LOCAL_CFLAGS :=
 ifdef WIFI_DRIVER_FW_STA_PATH
@@ -40,7 +45,7 @@ ifdef WIFI_DRIVER_FW_AP_PATH
 LOCAL_CFLAGS += -DWIFI_DRIVER_FW_AP_PATH=\"$(WIFI_DRIVER_FW_AP_PATH)\"
 endif
 
-LOCAL_SHARED_LIBRARIES := libsysutils libcutils libnetutils libcrypto
+LOCAL_SHARED_LIBRARIES := libstlport libsysutils libcutils libnetutils libcrypto
 
 ifeq ($(BOARD_HAVE_BLUETOOTH),true)
   LOCAL_SHARED_LIBRARIES := $(LOCAL_SHARED_LIBRARIES) libbluedroid
diff --git a/BandwidthController.cpp b/BandwidthController.cpp
new file mode 100644 (file)
index 0000000..f859215
--- /dev/null
@@ -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;
+}
diff --git a/BandwidthController.h b/BandwidthController.h
new file mode 100644 (file)
index 0000000..db57208
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+#ifndef _BANDWIDTH_CONTROLLER_H
+#define _BANDWIDTH_CONTROLLER_H
+
+#include <list>
+#include <string>
+
+class BandwidthController {
+public:
+       BandwidthController();
+       int enableBandwidthControl(void);
+       int disableBandwidthControl(void);
+       int setInterfaceQuota(const char *iface, int64_t bytes);
+
+protected:
+       int runCommands(const char *commands[], int numCommands,
+                       bool allowFailure = false);
+       int removeQuota(const char *iface);
+       std::list<std::string /*ifaceName*/> ifaceRules;
+
+private:
+       static const char *cleanupCommands[];
+       static const char *setupCommands[];
+       static const char *basicAccountingCommands[];
+       static const int MAX_CMD_LEN;
+       static const int MAX_IFACENAME_LEN;
+       static const int MAX_CMD_ARGS;
+       static const char IPTABLES_PATH[];
+
+       static int runIptablesCmd(const char *cmd);
+};
+
+#endif
index d8094d5..40380a0 100644 (file)
@@ -33,6 +33,7 @@
 #include "CommandListener.h"
 #include "ResponseCode.h"
 #include "ThrottleController.h"
+#include "BandwidthController.h"
 
 
 extern "C" int ifc_init(void);
@@ -51,6 +52,7 @@ PppController *CommandListener::sPppCtrl = NULL;
 PanController *CommandListener::sPanCtrl = NULL;
 SoftapController *CommandListener::sSoftapCtrl = NULL;
 UsbController *CommandListener::sUsbCtrl = NULL;
+BandwidthController * CommandListener::sBandwidthCtrl = NULL;
 
 CommandListener::CommandListener() :
                  FrameworkListener("netd") {
@@ -63,6 +65,7 @@ CommandListener::CommandListener() :
     registerCmd(new PanCmd());
     registerCmd(new SoftapCmd());
     registerCmd(new UsbCmd());
+    registerCmd(new BandwidthControlCmd());
 
     if (!sTetherCtrl)
         sTetherCtrl = new TetherController();
@@ -76,6 +79,8 @@ CommandListener::CommandListener() :
         sSoftapCtrl = new SoftapController();
     if (!sUsbCtrl)
         sUsbCtrl = new UsbController();
+    if (!sBandwidthCtrl)
+        sBandwidthCtrl = new BandwidthController();
 }
 
 CommandListener::InterfaceCmd::InterfaceCmd() :
@@ -337,6 +342,7 @@ int CommandListener::InterfaceCmd::runCommand(SocketClient *cli,
     return 0;
 }
 
+
 CommandListener::ListTtysCmd::ListTtysCmd() :
                  NetdCommand("list_ttys") {
 }
@@ -697,7 +703,7 @@ int CommandListener::UsbCmd::runCommand(SocketClient *cli, int argc, char **argv
     if (!rc) {
         cli->sendMsg(ResponseCode::CommandOkay, "Usb operation succeeded", false);
     } else {
-        cli->sendMsg(ResponseCode::OperationFailed, "Softap operation failed", true);
+        cli->sendMsg(ResponseCode::OperationFailed, "Usb operation failed", true);
     }
 
     return 0;
@@ -743,3 +749,39 @@ int CommandListener::readInterfaceCounters(const char *iface, unsigned long *rx,
     *tx = 0;
     return 0;
 }
+
+CommandListener::BandwidthControlCmd::BandwidthControlCmd() :
+       NetdCommand("bandwidth") {
+}
+
+int CommandListener::BandwidthControlCmd::runCommand(SocketClient *cli,
+                                                     int argc, char **argv) {
+    int rc = 0;
+    if (argc < 2) {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing argument", false);
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "enable")) {
+        rc = sBandwidthCtrl->enableBandwidthControl();
+    } else if (!strcmp(argv[1], "disable")) {
+        rc = sBandwidthCtrl->disableBandwidthControl();
+    } else if (!strcmp(argv[1], "setquota")) {
+            if (argc != 4) {
+                    cli->sendMsg(ResponseCode::CommandSyntaxError,
+                                 "Usage: bandwidth setquota <interface> <bytes>", false);
+                    return 0;
+            }
+            rc = sBandwidthCtrl->setInterfaceQuota(argv[2], atoll(argv[3]));
+    } else {
+            cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown bandwidth cmd", false);
+            return 0;
+    }
+
+    if (!rc) {
+        cli->sendMsg(ResponseCode::CommandOkay, "Bandwidth command succeeeded", false);
+    } else {
+        cli->sendMsg(ResponseCode::OperationFailed, "Bandwidth command failed", true);
+    }
+    return 0;
+}
index 2f40474..b12da7c 100644 (file)
@@ -26,6 +26,7 @@
 #include "PanController.h"
 #include "SoftapController.h"
 #include "UsbController.h"
+#include "BandwidthController.h"
 
 class CommandListener : public FrameworkListener {
     static TetherController *sTetherCtrl;
@@ -34,6 +35,7 @@ class CommandListener : public FrameworkListener {
     static PanController *sPanCtrl;
     static SoftapController *sSoftapCtrl;
     static UsbController *sUsbCtrl;
+    static BandwidthController *sBandwidthCtrl;
 
 public:
     CommandListener();
@@ -105,6 +107,13 @@ private:
         virtual ~PanCmd() {}
         int runCommand(SocketClient *c, int argc, char ** argv);
     };
+
+    class BandwidthControlCmd : public NetdCommand {
+    public:
+        BandwidthControlCmd();
+        virtual ~BandwidthControlCmd() {}
+        int runCommand(SocketClient *c, int argc, char ** argv);
+    };
 };
 
 #endif