From: JP Abgrall Date: Thu, 16 Jun 2011 01:37:39 +0000 (-0700) Subject: system/netd: bandwidth management initial support (uid+tag stats) X-Git-Tag: android-x86-7.1-r1~523 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=4a5f5ca3c9e07fc3e6feca2afde07f41a8a64f11;p=android-x86%2Fsystem-netd.git 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// rx_packets_tcp rx_bytes_tcp ... There is no /... 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 " 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/Android.mk b/Android.mk index a75c5ff..c4748d2 100644 --- a/Android.mk +++ b/Android.mk @@ -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 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 +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#define LOG_TAG "BandwidthController" +#include +#include + +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::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::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 index 0000000..db57208 --- /dev/null +++ b/BandwidthController.h @@ -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 +#include + +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 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 diff --git a/CommandListener.cpp b/CommandListener.cpp index d8094d5..40380a0 100644 --- a/CommandListener.cpp +++ b/CommandListener.cpp @@ -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 ", 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; +} diff --git a/CommandListener.h b/CommandListener.h index 2f40474..b12da7c 100644 --- a/CommandListener.h +++ b/CommandListener.h @@ -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