BandwidthController: cleanup ipv4/v6, set/remove multiple quotas.
Regroup the ipv4/ipv6 choice deeper down to avoid copypasted code.
Shared quota accross ifaces.
Single quota per ifaces.
Nothing preventing an iface from have a single and shared quota.
Might be close to having a working combination.
Added commands:
- shared quota
ndc bandwidth setquotas <quotaBytes> <iface> ...
ndc bandwidth setquota <iface> <quotaBytes>
ndc bandwidth removequota <iface>
ndc bandwidth removequotas <iface> ...
- quota per iface
ndc bandwidth setiquota <iface> <quotaBytes>
ndc bandwidth removeiquota <iface>
Change-Id: I370d223da3c8b6e16e8d0a455309ae9e0756a721
diff --git a/BandwidthController.cpp b/BandwidthController.cpp
index 3ccf507..0e71fc7 100644
--- a/BandwidthController.cpp
+++ b/BandwidthController.cpp
@@ -77,23 +77,36 @@
* 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) {
@@ -106,21 +119,47 @@
}
-int BandwidthController::runIptablesCmd(const char *cmd, bool isIp6) {
- char buffer[MAX_CMD_LEN];
+int BandwidthController::runIpxtablesCmd(const char *cmd, bool appendReject) {
+ int res = 0;
+ res |= runIptablesCmd(cmd, appendReject, false);
+ res |= runIptablesCmd(cmd, appendReject, true);
+ return res;
+}
+
+int BandwidthController::runIptablesCmd(const char *cmd, bool appendReject, bool isIp6) {
+ char buffer[MAX_CMD_LEN] = { 0 }; // strncpy() is not filling leftover with '\0'
const char *argv[MAX_CMD_ARGS];
- int argc = 1;
+ int argc, nextArg;
char *next = buffer;
char *tmp;
- argv[0] = isIp6 ? IP6TABLES_PATH : IPTABLES_PATH;
- LOGD("About to run: %s %s", argv[0], cmd);
+ std::string fullCmd = cmd;
+ if (appendReject) {
+ fullCmd += " --jump REJECT --reject-with";
+ if (isIp6) {
+ fullCmd += " icmp6-adm-prohibited";
+ } else {
+ fullCmd += " icmp-net-prohibited";
+ }
+ argc = 4; // --jump ...
+ }
- strncpy(buffer, cmd, sizeof(buffer) - 1);
+ nextArg = 0;
+ argv[nextArg++] = isIp6 ? IP6TABLES_PATH : IPTABLES_PATH;
+ argc++;
+ LOGD("About to run: %s %s", argv[0], fullCmd.c_str());
+
+ strncpy(buffer, fullCmd.c_str(), sizeof(buffer) - 1);
+ if (buffer[sizeof(buffer) - 1]) {
+ LOGE("iptables command too long");
+ errno = E2BIG;
+ return -1;
+ }
while ((tmp = strsep(&next, " "))) {
- argv[argc++] = tmp;
- if (argc == MAX_CMD_ARGS) {
+ argv[nextArg++] = tmp;
+ argc++;
+ if (argc >= MAX_CMD_ARGS) {
LOGE("iptables argument overflow");
errno = E2BIG;
return -1;
@@ -137,14 +176,10 @@
int BandwidthController::enableBandwidthControl(void) {
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);
+ runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, true);
+ runCommands(sizeof(setupCommands) / sizeof(char*), setupCommands, true);
res = runCommands(sizeof(basicAccountingCommands) / sizeof(char*), basicAccountingCommands,
- false, false);
- res |= runCommands(sizeof(basicAccountingCommands) / sizeof(char*), basicAccountingCommands,
- false, true);
+ false);
return res;
}
@@ -152,23 +187,21 @@
int BandwidthController::disableBandwidthControl(void) {
/* 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(int numCommands, const char *commands[], bool allowFailure,
- bool isIp6) {
+int BandwidthController::runCommands(int numCommands, const char *commands[], bool allowFailure) {
int res = 0;
LOGD("runCommands(): %d commands", numCommands);
for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) {
- res = runIptablesCmd(commands[cmdNum], isIp6);
+ res = runIpxtablesCmd(commands[cmdNum], false);
if (res && !allowFailure)
return res;
}
return allowFailure ? res : 0;
}
-std::string BandwidthController::makeIptablesNaughtyCmd(IptOp op, int uid, bool isIp6) {
+std::string BandwidthController::makeIptablesNaughtyCmd(IptOp op, int uid) {
std::string res;
char convBuff[21]; // log10(2^64) ~ 20
@@ -188,12 +221,6 @@
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;
}
@@ -222,13 +249,8 @@
}
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)) {
+ std::string naughtyCmd = makeIptablesNaughtyCmd(op, appUids[uidNum]);
+ if (runIpxtablesCmd(naughtyCmd.c_str(), true)) {
LOGE((doAdd ? addFailedTemplate : deleteFailedTemplate), appUids[uidNum]);
goto fail_with_uidNum;
}
@@ -237,15 +259,16 @@
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);
+ runIpxtablesCmd(makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum]).c_str(), true);
fail_parse: return -1;
}
-std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, char *costName, int64_t quota, bool isIp6) {
+std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, char *costName, int64_t quota) {
std::string res;
char convBuff[21]; // log10(2^64) ~ 20
- LOGD("makeIptablesQuotaCmd(%d, %llu, %d)", op, quota, isIp6);
+
+ LOGD("makeIptablesQuotaCmd(%d, %llu)", op, quota);
+
switch (op) {
case IptOpInsert:
res = "-I";
@@ -260,22 +283,83 @@
}
res += " costly";
if (costName) {
- res += costName;
+ res += "_";
+ res += costName;
}
sprintf(convBuff, "%lld", quota);
+ /* TODO(jpa): Use -m quota2 --name " + costName + " ! --quota "
+ * once available.
+ */
res += " -m quota ! --quota ";
res += convBuff;
;
- res += " --jump REJECT --reject-with";
- if (isIp6) {
- res += " icmp6-adm-prohibited";
+ // The requried --jump REJECT ... will be added later.
+ return res;
+}
+
+int BandwidthController::prepCostlyIface(const char *ifn, bool isShared) {
+ char cmd[MAX_CMD_LEN];
+ int res = 0;
+ std::string costString;
+ const char *costCString;
+
+ costString = "costly";
+ /* The "-N costly" is created upfront, no need to handle it here. */
+ if (!isShared) {
+ costString += "_";
+ costString += ifn;
+ costCString = costString.c_str();
+ snprintf(cmd, sizeof(cmd), "-N %s", costCString);
+ res |= runIpxtablesCmd(cmd, false);
+ snprintf(cmd, sizeof(cmd), "-A %s -j penalty_box", costCString);
+ res |= runIpxtablesCmd(cmd, false);
+ snprintf(cmd, sizeof(cmd), "-A %s -m owner --socket-exists", costCString);
+ res |= runIpxtablesCmd(cmd, false);
+ /* TODO(jpa): Figure out why iptables doesn't correctly return from this
+ * chain. For now, hack the chain exit with an ACCEPT.
+ */
+ snprintf(cmd, sizeof(cmd), "-A %s --jump ACCEPT", costCString);
+ res |= runIpxtablesCmd(cmd, false);
} else {
- res += " icmp-net-prohibited";
+ costCString = costString.c_str();
+ }
+
+ snprintf(cmd, sizeof(cmd), "-I INPUT -i %s --goto %s", ifn, costCString);
+ res |= runIpxtablesCmd(cmd, false);
+ snprintf(cmd, sizeof(cmd), "-I OUTPUT -o %s --goto %s", ifn, costCString);
+ res |= runIpxtablesCmd(cmd, false);
+ return res;
+}
+
+int BandwidthController::cleanupCostlyIface(const char *ifn, bool isShared) {
+ char cmd[MAX_CMD_LEN];
+ int res = 0;
+ std::string costString;
+ const char *costCString;
+
+ costString = "costly";
+ if (!isShared) {
+ costString += "_";
+ costString += ifn;
+ costCString = costString.c_str();
+ } else {
+ costCString = costString.c_str();
+ }
+
+ snprintf(cmd, sizeof(cmd), "-D INPUT -i %s --goto %s", ifn, costCString);
+ res |= runIpxtablesCmd(cmd, false);
+ snprintf(cmd, sizeof(cmd), "-D OUTPUT -o %s --goto %s", ifn, costCString);
+ res |= runIpxtablesCmd(cmd, false);
+
+ /* The "-N costly" is created upfront, no need to handle it here. */
+ if (!isShared) {
+ snprintf(cmd, sizeof(cmd), "-F %s", costCString);
+ res |= runIpxtablesCmd(cmd, false);
}
return res;
}
-int BandwidthController::setInterfaceSharedQuota(int64_t maxBytes, const char *iface) {
+int BandwidthController::setInterfaceSharedQuota(const char *iface, int64_t maxBytes) {
char cmd[MAX_CMD_LEN];
char ifn[MAX_IFACENAME_LEN];
int res = 0;
@@ -287,38 +371,29 @@
return removeInterfaceSharedQuota(ifn);
}
- char *costName = NULL; /* Shared quota */
+ char *costName = NULL; /* Shared quota */
/* Insert ingress quota. */
std::string ifaceName(ifn);
- std::list<QuotaInfo>::iterator it;
- for (it = ifaceRules.begin(); it != ifaceRules.end(); it++) {
- if (it->first == ifaceName)
+ std::list<std::string>::iterator it;
+ for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) {
+ if (*it == ifaceName)
break;
}
- 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()) {
+ if (it == sharedQuotaIfaces.end()) {
+ res |= prepCostlyIface(ifn, true);
+ if (sharedQuotaIfaces.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);
+ quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes);
+ res |= runIpxtablesCmd(quotaCmd.c_str(), true);
if (res) {
LOGE("Failed set quota rule.");
goto fail;
}
sharedQuotaBytes = maxBytes;
}
-
- ifaceRules.push_front(QuotaInfo(ifaceName, maxBytes));
+ sharedQuotaIfaces.push_front(ifaceName);
}
@@ -328,15 +403,11 @@
*/
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(IptOpInsert, costName, maxBytes);
+ res |= runIpxtablesCmd(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);
+ quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes);
+ res |= runIpxtablesCmd(quotaCmd.c_str(), true);
if (res) {
LOGE("Failed replace quota rule.");
@@ -358,7 +429,6 @@
}
int BandwidthController::removeInterfaceSharedQuota(const char *iface) {
- char cmd[MAX_CMD_LEN];
char ifn[MAX_IFACENAME_LEN];
int res = 0;
@@ -366,34 +436,122 @@
strncpy(ifn, iface, sizeof(ifn) - 1);
std::string ifaceName(ifn);
- std::list<QuotaInfo>::iterator it;
+ std::list<std::string>::iterator it;
- for (it = ifaceRules.begin(); it != ifaceRules.end(); it++) {
- if (it->first == ifaceName)
+ for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) {
+ if (*it == ifaceName)
break;
}
- if (it == ifaceRules.end()) {
+ if (it == sharedQuotaIfaces.end()) {
LOGE("No such iface %s to delete.", ifn);
return -1;
}
- snprintf(cmd, sizeof(cmd), "--delete INPUT -i %s --goto costly", ifn);
- res |= runIptablesCmd(cmd);
- res |= runIptablesCmd(cmd, true);
- snprintf(cmd, sizeof(cmd), "--delete OUTPUT -o %s --goto costly", ifn);
- res |= runIptablesCmd(cmd);
- res |= runIptablesCmd(cmd, true);
+ res |= cleanupCostlyIface(ifn, true);
+ sharedQuotaIfaces.erase(it);
- ifaceRules.erase(it);
- if (ifaceRules.empty()) {
+ if (sharedQuotaIfaces.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);
+ quotaCmd = makeIptablesQuotaCmd(IptOpDelete, NULL, sharedQuotaBytes);
+ res |= runIpxtablesCmd(quotaCmd.c_str(), true);
sharedQuotaBytes = -1;
}
return res;
}
+
+int BandwidthController::setInterfaceQuota(const char *iface, int64_t maxBytes) {
+ char ifn[MAX_IFACENAME_LEN];
+ int res = 0;
+
+ memset(ifn, 0, sizeof(ifn));
+ strncpy(ifn, iface, sizeof(ifn) - 1);
+
+ if (maxBytes == -1) {
+ return removeInterfaceQuota(ifn);
+ }
+
+ char *costName = ifn;
+
+ /* Insert ingress quota. */
+ std::string ifaceName(ifn);
+ std::list<QuotaInfo>::iterator it;
+ for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
+ if (it->first == ifaceName)
+ break;
+ }
+
+ if (it == quotaIfaces.end()) {
+
+ res |= prepCostlyIface(ifn, false);
+
+ std::string quotaCmd;
+ quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes);
+ res |= runIpxtablesCmd(quotaCmd.c_str(), true);
+ if (res) {
+ LOGE("Failed set quota rule.");
+ goto fail;
+ }
+
+ quotaIfaces.push_front(QuotaInfo(ifaceName, maxBytes));
+
+ } else {
+ /* 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);
+ res |= runIpxtablesCmd(quotaCmd.c_str(), true);
+
+ quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, it->second);
+ res |= runIpxtablesCmd(quotaCmd.c_str(), true);
+
+ if (res) {
+ LOGE("Failed replace quota rule.");
+ goto fail;
+ }
+ it->second = maxBytes;
+ }
+ return 0;
+
+ fail:
+ /*
+ * 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.
+ */
+ removeInterfaceSharedQuota(ifn);
+ return -1;
+}
+
+int BandwidthController::removeInterfaceQuota(const char *iface) {
+
+ char ifn[MAX_IFACENAME_LEN];
+ int res = 0;
+
+ memset(ifn, 0, sizeof(ifn));
+ strncpy(ifn, iface, sizeof(ifn) - 1);
+
+ char *costName = ifn;
+
+ std::string ifaceName(ifn);
+ std::list<QuotaInfo>::iterator it;
+ for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
+ if (it->first == ifaceName)
+ break;
+ }
+
+ if (it == quotaIfaces.end()) {
+ LOGE("No such iface %s to delete.", ifn);
+ return -1;
+ }
+
+ /* This also removes the quota command of CostlyIface chain. */
+ res |= cleanupCostlyIface(ifn, false);
+
+ quotaIfaces.erase(it);
+
+ return res;
+}