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/Android.mk b/Android.mk
index a75c5ff..c4748d2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -10,27 +10,32 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- main.cpp \
+ BandwidthController.cpp \
CommandListener.cpp \
DnsProxyListener.cpp \
- NetdCommand.cpp \
- NetlinkManager.cpp \
- NetlinkHandler.cpp \
- logwrapper.c \
- TetherController.cpp \
NatController.cpp \
- PppController.cpp \
+ NetdCommand.cpp \
+ NetlinkHandler.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 @@
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 <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
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 <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
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 @@
PanController *CommandListener::sPanCtrl = NULL;
SoftapController *CommandListener::sSoftapCtrl = NULL;
UsbController *CommandListener::sUsbCtrl = NULL;
+BandwidthController * CommandListener::sBandwidthCtrl = NULL;
CommandListener::CommandListener() :
FrameworkListener("netd") {
@@ -63,6 +65,7 @@
registerCmd(new PanCmd());
registerCmd(new SoftapCmd());
registerCmd(new UsbCmd());
+ registerCmd(new BandwidthControlCmd());
if (!sTetherCtrl)
sTetherCtrl = new TetherController();
@@ -76,6 +79,8 @@
sSoftapCtrl = new SoftapController();
if (!sUsbCtrl)
sUsbCtrl = new UsbController();
+ if (!sBandwidthCtrl)
+ sBandwidthCtrl = new BandwidthController();
}
CommandListener::InterfaceCmd::InterfaceCmd() :
@@ -337,6 +342,7 @@
return 0;
}
+
CommandListener::ListTtysCmd::ListTtysCmd() :
NetdCommand("list_ttys") {
}
@@ -697,7 +703,7 @@
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 @@
*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;
+}
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 @@
static PanController *sPanCtrl;
static SoftapController *sSoftapCtrl;
static UsbController *sUsbCtrl;
+ static BandwidthController *sBandwidthCtrl;
public:
CommandListener();
@@ -105,6 +107,13 @@
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