netd:bandwidth: initial pass at app-rules, and some ipv6.
Adds initial per-app penalty box rules, and prepares for
handling per iface quota.
The following commands work:
* penalty box
ndc bandwidth addnaughtyapps <uid> ...
ndc bandwidth removenaughtyapps <uid> ...
* Shared quota
- add (updates the bytes, if they differ from last time)
ndc bandwidth setquota <iface> <bytes>
ndc bandwidth setquota <iface1> <bytes>
ndc bandwidth setquota <iface2> <bytes>
- remove
ndc bandwidth removequota <iface>
[ oldschool: ndc bandwidth setquota <iface> -1 ]
Change-Id: Ibefc16e81c7713feb47577a9687dcd032dedf06e
diff --git a/BandwidthController.cpp b/BandwidthController.cpp
index f859215..3ccf507 100644
--- a/BandwidthController.cpp
+++ b/BandwidthController.cpp
@@ -36,12 +36,11 @@
#include "BandwidthController.h"
-
-const int BandwidthController::MAX_CMD_LEN = 255;
+const int BandwidthController::MAX_CMD_LEN = 1024;
const int BandwidthController::MAX_IFACENAME_LEN = 64;
const int BandwidthController::MAX_CMD_ARGS = 32;
const char BandwidthController::IPTABLES_PATH[] = "/system/bin/iptables";
-
+const char BandwidthController::IP6TABLES_PATH[] = "/system/bin/ip6tables";
/**
* Some comments about the rules:
@@ -78,37 +77,23 @@
* 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",
-};
+/* Cleanup rules. */
+"-F", "-t raw -F", "-X costly", "-X penalty_box", };
const char *BandwidthController::setupCommands[] = {
- /* Created needed chains. */
- "-N costly",
- "-N penalty_box",
-};
+/* 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. */
+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 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",
-};
-
+ "-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) {
@@ -121,19 +106,17 @@
}
-int BandwidthController::runIptablesCmd(const char *cmd) {
+int BandwidthController::runIptablesCmd(const char *cmd, bool isIp6) {
char buffer[MAX_CMD_LEN];
-
- LOGD("About to run: iptables %s", cmd);
-
- strncpy(buffer, cmd, sizeof(buffer)-1);
-
const char *argv[MAX_CMD_ARGS];
+ int argc = 1;
char *next = buffer;
char *tmp;
- argv[0] = IPTABLES_PATH;
- int argc = 1;
+ argv[0] = isIp6 ? IP6TABLES_PATH : IPTABLES_PATH;
+ LOGD("About to run: %s %s", argv[0], cmd);
+
+ strncpy(buffer, cmd, sizeof(buffer) - 1);
while ((tmp = strsep(&next, " "))) {
argv[argc++] = tmp;
@@ -143,6 +126,7 @@
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.
@@ -150,124 +134,266 @@
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 res;
+ /* Some of the initialCommands are allowed to fail */
+ runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, true, false);
+ runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, true, true);
+ runCommands(sizeof(setupCommands) / sizeof(char*), setupCommands, true, false);
+ runCommands(sizeof(setupCommands) / sizeof(char*), setupCommands, true, true);
+ res = runCommands(sizeof(basicAccountingCommands) / sizeof(char*), basicAccountingCommands,
+ false, false);
+ res |= runCommands(sizeof(basicAccountingCommands) / sizeof(char*), basicAccountingCommands,
+ false, true);
+ return res;
}
int BandwidthController::disableBandwidthControl(void) {
- /* The cleanupCommands are allowed to fail */
- runCommands(cleanupCommands, sizeof(cleanupCommands)/sizeof(char*), true);
- return 0;
+ /* The cleanupCommands are allowed to fail. */
+ runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, true);
+ runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, true, 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;
+int BandwidthController::runCommands(int numCommands, const char *commands[], bool allowFailure,
+ bool isIp6) {
+ int res = 0;
+ LOGD("runCommands(): %d commands", numCommands);
+ for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) {
+ res = runIptablesCmd(commands[cmdNum], isIp6);
+ if (res && !allowFailure)
+ return res;
+ }
+ return allowFailure ? res : 0;
+}
+
+std::string BandwidthController::makeIptablesNaughtyCmd(IptOp op, int uid, bool isIp6) {
+ std::string res;
+ char convBuff[21]; // log10(2^64) ~ 20
+
+ switch (op) {
+ case IptOpInsert:
+ res = "-I";
+ break;
+ case IptOpReplace:
+ res = "-R";
+ break;
+ default:
+ case IptOpDelete:
+ res = "-D";
+ break;
+ }
+ res += " penalty_box";
+ sprintf(convBuff, "%d", uid);
+ res += " -m owner --uid-owner ";
+ res += convBuff;
+ res += " --jump REJECT --reject-with";
+ if (isIp6) {
+ res += " icmp6-adm-prohibited";
+ } else {
+ res += " icmp-net-prohibited";
+ }
+ return res;
+}
+
+int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) {
+ return maninpulateNaughtyApps(numUids, appUids, true);
+}
+
+int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) {
+ return maninpulateNaughtyApps(numUids, appUids, false);
+}
+
+int BandwidthController::maninpulateNaughtyApps(int numUids, char *appStrUids[], bool doAdd) {
+ char cmd[MAX_CMD_LEN];
+ int uidNum;
+ const char *addFailedTemplate = "Failed to add app uid %d to penalty box.";
+ const char *deleteFailedTemplate = "Failed to delete app uid %d from penalty box.";
+ IptOp op = doAdd ? IptOpInsert : IptOpDelete;
+
+ int appUids[numUids];
+ for (uidNum = 0; uidNum < numUids; uidNum++) {
+ appUids[uidNum] = atol(appStrUids[uidNum]);
+ if (appUids[uidNum] == 0) {
+ LOGE((doAdd ? addFailedTemplate : deleteFailedTemplate), appUids[uidNum]);
+ goto fail_parse;
}
- return allowFailure?res:0;
+ }
+
+ for (uidNum = 0; uidNum < numUids; uidNum++) {
+ std::string naughtyCmd = makeIptablesNaughtyCmd(op, appUids[uidNum], false);
+ if (runIptablesCmd(naughtyCmd.c_str())) {
+ LOGE((doAdd ? addFailedTemplate : deleteFailedTemplate), appUids[uidNum]);
+ goto fail_with_uidNum;
+ }
+ naughtyCmd = makeIptablesNaughtyCmd(op, appUids[uidNum], true);
+ if (runIptablesCmd(naughtyCmd.c_str(), true)) {
+ LOGE((doAdd ? addFailedTemplate : deleteFailedTemplate), appUids[uidNum]);
+ goto fail_with_uidNum;
+ }
+ }
+ return 0;
+
+ fail_with_uidNum:
+ /* Try to remove the uid that failed in any case*/
+ runIptablesCmd(makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum], false).c_str());
+ runIptablesCmd(makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum], true).c_str(), true);
+ fail_parse: return -1;
}
+std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, char *costName, int64_t quota, bool isIp6) {
+ std::string res;
+ char convBuff[21]; // log10(2^64) ~ 20
+ LOGD("makeIptablesQuotaCmd(%d, %llu, %d)", op, quota, isIp6);
+ switch (op) {
+ case IptOpInsert:
+ res = "-I";
+ break;
+ case IptOpReplace:
+ res = "-R";
+ break;
+ default:
+ case IptOpDelete:
+ res = "-D";
+ break;
+ }
+ res += " costly";
+ if (costName) {
+ res += costName;
+ }
+ sprintf(convBuff, "%lld", quota);
+ res += " -m quota ! --quota ";
+ res += convBuff;
+ ;
+ res += " --jump REJECT --reject-with";
+ if (isIp6) {
+ res += " icmp6-adm-prohibited";
+ } else {
+ res += " icmp-net-prohibited";
+ }
+ return res;
+}
-int BandwidthController::setInterfaceQuota(const char *iface,
- int64_t maxBytes) {
+int BandwidthController::setInterfaceSharedQuota(int64_t maxBytes, const char *iface) {
char cmd[MAX_CMD_LEN];
char ifn[MAX_IFACENAME_LEN];
- int res;
+ int res = 0;
memset(ifn, 0, sizeof(ifn));
- strncpy(ifn, iface, sizeof(ifn)-1);
+ strncpy(ifn, iface, sizeof(ifn) - 1);
if (maxBytes == -1) {
- return removeQuota(ifn);
+ return removeInterfaceSharedQuota(ifn);
}
+ char *costName = NULL; /* Shared quota */
+
/* 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;
+ std::list<QuotaInfo>::iterator it;
+ for (it = ifaceRules.begin(); it != ifaceRules.end(); it++) {
+ if (it->first == 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 (it == ifaceRules.end()) {
+ snprintf(cmd, sizeof(cmd), "-I INPUT -i %s --goto costly", ifn);
+ res |= runIptablesCmd(cmd);
+ res |= runIptablesCmd(cmd, true);
+ snprintf(cmd, sizeof(cmd), "-I OUTPUT -o %s --goto costly", ifn);
+ res |= runIptablesCmd(cmd);
+ res |= runIptablesCmd(cmd, true);
+
+ if (ifaceRules.empty()) {
+ std::string quotaCmd;
+ quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes, false);
+ res |= runIptablesCmd(quotaCmd.c_str());
+ quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes, true);
+ res |= runIptablesCmd(quotaCmd.c_str(), true);
if (res) {
- LOGE("Failed set quota rule.");
- goto fail;
+ 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);
+ sharedQuotaBytes = maxBytes;
+ }
+
+ ifaceRules.push_front(QuotaInfo(ifaceName, maxBytes));
+
+ }
+
+ if (maxBytes != sharedQuotaBytes) {
+ /* Instead of replacing, which requires being aware of the rules in
+ * the kernel, we just add a new one, then delete the older one.
+ */
+ std::string quotaCmd;
+
+ quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes, false);
+ res |= runIptablesCmd(quotaCmd.c_str());
+ quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes, true);
+ res |= runIptablesCmd(quotaCmd.c_str(), true);
+
+ quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes, false);
+ res |= runIptablesCmd(quotaCmd.c_str());
+ quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes, true);
+ res |= runIptablesCmd(quotaCmd.c_str(), true);
+
+ if (res) {
+ LOGE("Failed replace quota rule.");
+ goto fail;
+ }
+ sharedQuotaBytes = maxBytes;
}
return 0;
-fail:
+
+ 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.
+ * For now callers needs to choose if they want to "ndc bandwidth enable"
+ * which resets everything.
*/
- runCommands(basicAccountingCommands,
- sizeof(basicAccountingCommands)/sizeof(char*), true);
- removeQuota(ifn);
+ removeInterfaceSharedQuota(ifn);
return -1;
}
-int BandwidthController::removeQuota(const char *iface) {
+int BandwidthController::removeInterfaceSharedQuota(const char *iface) {
char cmd[MAX_CMD_LEN];
char ifn[MAX_IFACENAME_LEN];
- int res;
+ int res = 0;
memset(ifn, 0, sizeof(ifn));
- strncpy(ifn, iface, sizeof(ifn)-1);
+ strncpy(ifn, iface, sizeof(ifn) - 1);
std::string ifaceName(ifn);
- std::list<std::string>::iterator it;
+ std::list<QuotaInfo>::iterator it;
- int pos;
- for (pos=1, it = ifaceRules.begin(); it != ifaceRules.end(); it++, pos++) {
- if (*it == ifaceName)
- break;
+ for (it = ifaceRules.begin(); it != ifaceRules.end(); it++) {
+ if (it->first == ifaceName)
+ break;
}
- if(it == ifaceRules.end()) {
- LOGE("No such iface %s to delete.", ifn);
- return -1;
+ 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);
+ res |= runIptablesCmd(cmd);
+ res |= runIptablesCmd(cmd, true);
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);
+ res |= runIptablesCmd(cmd, true);
+
+ ifaceRules.erase(it);
+ if (ifaceRules.empty()) {
+ std::string quotaCmd;
+ quotaCmd = makeIptablesQuotaCmd(IptOpDelete, NULL, sharedQuotaBytes, false);
+ res |= runIptablesCmd(quotaCmd.c_str());
+
+ quotaCmd = makeIptablesQuotaCmd(IptOpDelete, NULL, sharedQuotaBytes, true);
+ res |= runIptablesCmd(quotaCmd.c_str(), true);
+ sharedQuotaBytes = -1;
+ }
+
return res;
}