* If they ever were to allow it, then netd/ would need some tweaking.
*/
+#include <string>
+#include <vector>
+
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <linux/rtnetlink.h>
#include <linux/pkt_sched.h>
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
#define LOG_TAG "BandwidthController"
#include <cutils/log.h>
#include <cutils/properties.h>
/* Alphabetical */
#define ALERT_IPT_TEMPLATE "%s %s -m quota2 ! --quota %" PRId64" --name %s"
-const char BandwidthController::ALERT_GLOBAL_NAME[] = "globalAlert";
const char* BandwidthController::LOCAL_INPUT = "bw_INPUT";
const char* BandwidthController::LOCAL_FORWARD = "bw_FORWARD";
const char* BandwidthController::LOCAL_OUTPUT = "bw_OUTPUT";
const char* BandwidthController::LOCAL_RAW_PREROUTING = "bw_raw_PREROUTING";
const char* BandwidthController::LOCAL_MANGLE_POSTROUTING = "bw_mangle_POSTROUTING";
-const int BandwidthController::MAX_CMD_ARGS = 32;
-const int BandwidthController::MAX_CMD_LEN = 1024;
-const int BandwidthController::MAX_IFACENAME_LEN = 64;
-const int BandwidthController::MAX_IPT_OUTPUT_LINE_LEN = 256;
+
+auto BandwidthController::execFunction = android_fork_execvp;
+auto BandwidthController::popenFunction = popen;
+auto BandwidthController::iptablesRestoreFunction = execIptablesRestore;
+
+namespace {
+
+const char ALERT_GLOBAL_NAME[] = "globalAlert";
+const int MAX_CMD_ARGS = 32;
+const int MAX_CMD_LEN = 1024;
+const int MAX_IFACENAME_LEN = 64;
+const int MAX_IPT_OUTPUT_LINE_LEN = 256;
/**
* Some comments about the rules:
* - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains.
* E.g. "-I bw_INPUT -i rmnet0 --jump costly"
* - quota'd rules in the costly chain should be before bw_penalty_box lookups.
- * - bw_happy_box rejects everything by default.
* - the qtaguid counting is done at the end of the bw_INPUT/bw_OUTPUT user chains.
*
* * global quota vs per interface quota
* iptables -I bw_costly_shared -m quota \! --quota 500000 \
* --jump REJECT --reject-with icmp-net-prohibited
* iptables -A bw_costly_shared --jump bw_penalty_box
- * If the happy box is enabled,
- * iptables -A bw_penalty_box --jump bw_happy_box
+ * iptables -A bw_penalty_box --jump bw_happy_box
+ * iptables -A bw_happy_box --jump bw_data_saver
*
* . adding a new iface to this, E.g.:
* iptables -I bw_INPUT -i iface1 --jump bw_costly_shared
* --jump REJECT --reject-with icmp-port-unreachable
* iptables -A bw_costly_iface0 --jump bw_penalty_box
*
+ * * Penalty box, happy box and data saver.
+ * - bw_penalty box is a blacklist of apps that are rejected.
+ * - bw_happy_box is a whitelist of apps. It always includes all system apps
+ * - bw_data_saver implements data usage restrictions.
+ * - Via the UI the user can add and remove apps from the whitelist and
+ * blacklist, and turn on/off data saver.
+ * - The blacklist takes precedence over the whitelist and the whitelist
+ * takes precedence over data saver.
+ *
* * bw_penalty_box handling:
* - only one bw_penalty_box for all interfaces
- * E.g Adding an app, it has to preserve the appened bw_happy_box, so "-I":
+ * E.g Adding an app:
* iptables -I bw_penalty_box -m owner --uid-owner app_3 \
* --jump REJECT --reject-with icmp-port-unreachable
*
* * bw_happy_box handling:
- * - The bw_happy_box goes at the end of the penalty box.
+ * - The bw_happy_box comes after the penalty box.
* E.g Adding a happy app,
* iptables -I bw_happy_box -m owner --uid-owner app_3 \
* --jump RETURN
+ *
+ * * bw_data_saver handling:
+ * - The bw_data_saver comes after the happy box.
+ * Enable data saver:
+ * iptables -R 1 bw_data_saver --jump REJECT --reject-with icmp-port-unreachable
+ * Disable data saver:
+ * iptables -R 1 bw_data_saver --jump RETURN
*/
-const char *BandwidthController::IPT_FLUSH_COMMANDS[] = {
+
+const std::string COMMIT_AND_CLOSE = "COMMIT\n\x04";
+const std::string DATA_SAVER_ENABLE_COMMAND = "-R bw_data_saver 1";
+const std::string HAPPY_BOX_WHITELIST_COMMAND = android::base::StringPrintf(
+ "-I bw_happy_box -m owner --uid-owner %d-%d --jump RETURN", 0, MAX_SYSTEM_UID);
+
+static const std::vector<std::string> IPT_FLUSH_COMMANDS = {
/*
* Cleanup rules.
* Should normally include bw_costly_<iface>, but we rely on the way they are setup
* to allow coexistance.
*/
- "-F bw_INPUT",
- "-F bw_OUTPUT",
- "-F bw_FORWARD",
- "-F bw_happy_box",
- "-F bw_penalty_box",
- "-F bw_costly_shared",
-
- "-t raw -F bw_raw_PREROUTING",
- "-t mangle -F bw_mangle_POSTROUTING",
+ "*filter",
+ ":bw_INPUT -",
+ ":bw_OUTPUT -",
+ ":bw_FORWARD -",
+ ":bw_happy_box -",
+ ":bw_penalty_box -",
+ ":bw_data_saver -",
+ ":bw_costly_shared -",
+ "COMMIT",
+ "*raw",
+ ":bw_raw_PREROUTING -",
+ "COMMIT",
+ "*mangle",
+ ":bw_mangle_POSTROUTING -",
+ COMMIT_AND_CLOSE
};
-/* The cleanup commands assume flushing has been done. */
-const char *BandwidthController::IPT_CLEANUP_COMMANDS[] = {
- "-X bw_happy_box",
- "-X bw_penalty_box",
- "-X bw_costly_shared",
-};
-
-const char *BandwidthController::IPT_SETUP_COMMANDS[] = {
- "-N bw_happy_box",
- "-N bw_penalty_box",
- "-N bw_costly_shared",
-};
-
-const char *BandwidthController::IPT_BASIC_ACCOUNTING_COMMANDS[] = {
+static const std::vector<std::string> IPT_BASIC_ACCOUNTING_COMMANDS = {
+ "*filter",
"-A bw_INPUT -m owner --socket-exists", /* This is a tracking rule. */
-
"-A bw_OUTPUT -m owner --socket-exists", /* This is a tracking rule. */
-
"-A bw_costly_shared --jump bw_penalty_box",
-
- "-t raw -A bw_raw_PREROUTING -m owner --socket-exists", /* This is a tracking rule. */
- "-t mangle -A bw_mangle_POSTROUTING -m owner --socket-exists", /* This is a tracking rule. */
+ "-A bw_penalty_box --jump bw_happy_box",
+ "-A bw_happy_box --jump bw_data_saver",
+ "-A bw_data_saver -j RETURN",
+ HAPPY_BOX_WHITELIST_COMMAND,
+ "COMMIT",
+
+ "*raw",
+ "-A bw_raw_PREROUTING -m owner --socket-exists", /* This is a tracking rule. */
+ "COMMIT",
+
+ "*mangle",
+ "-A bw_mangle_POSTROUTING -m owner --socket-exists", /* This is a tracking rule. */
+ COMMIT_AND_CLOSE
};
+
+} // namespace
+
BandwidthController::BandwidthController(void) {
}
break;
}
- fullCmd.insert(0, " ");
+ fullCmd.insert(0, " -w ");
fullCmd.insert(0, iptVer == IptIpV4 ? IPTABLES_PATH : IP6TABLES_PATH);
if (StrncpyAndCheck(buffer, fullCmd.c_str(), sizeof(buffer))) {
}
argv[argc] = NULL;
- res = android_fork_execvp(argc, (char **)argv, &status, false,
+ res = execFunction(argc, (char **)argv, &status, false,
failureHandling == IptFailShow);
res = res || !WIFEXITED(status) || WEXITSTATUS(status);
if (res && failureHandling == IptFailShow) {
/* Flush and remove the bw_costly_<iface> tables */
flushExistingCostlyTables(doClean);
- /* Some of the initialCommands are allowed to fail */
- runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*),
- IPT_FLUSH_COMMANDS, RunCmdFailureOk);
-
- if (doClean) {
- runCommands(sizeof(IPT_CLEANUP_COMMANDS) / sizeof(char*),
- IPT_CLEANUP_COMMANDS, RunCmdFailureOk);
- }
+ std::string commands = android::base::Join(IPT_FLUSH_COMMANDS, '\n');
+ iptablesRestoreFunction(V4V6, commands);
}
int BandwidthController::setupIptablesHooks(void) {
-
/* flush+clean is allowed to fail */
flushCleanTables(true);
- runCommands(sizeof(IPT_SETUP_COMMANDS) / sizeof(char*),
- IPT_SETUP_COMMANDS, RunCmdFailureBad);
-
return 0;
}
int BandwidthController::enableBandwidthControl(bool force) {
- int res;
char value[PROPERTY_VALUE_MAX];
if (!force) {
/* Let's pretend we started from scratch ... */
sharedQuotaIfaces.clear();
quotaIfaces.clear();
- naughtyAppUids.clear();
- niceAppUids.clear();
globalAlertBytes = 0;
globalAlertTetherCount = 0;
sharedQuotaBytes = sharedAlertBytes = 0;
flushCleanTables(false);
- res = runCommands(sizeof(IPT_BASIC_ACCOUNTING_COMMANDS) / sizeof(char*),
- IPT_BASIC_ACCOUNTING_COMMANDS, RunCmdFailureBad);
-
- return res;
-
+ std::string commands = android::base::Join(IPT_BASIC_ACCOUNTING_COMMANDS, '\n');
+ return iptablesRestoreFunction(V4V6, commands);
}
int BandwidthController::disableBandwidthControl(void) {
return 0;
}
+int BandwidthController::enableDataSaver(bool enable) {
+ return runIpxtablesCmd(DATA_SAVER_ENABLE_COMMAND.c_str(),
+ enable ? IptJumpReject : IptJumpReturn, IptFailShow);
+}
+
int BandwidthController::runCommands(int numCommands, const char *commands[],
RunCmdErrHandling cmdErrHandling) {
int res = 0;
return res;
}
-int BandwidthController::enableHappyBox(void) {
- char cmd[MAX_CMD_LEN];
- int res = 0;
-
- /*
- * We tentatively delete before adding, which helps recovering
- * from bad states (e.g. netd died).
- */
-
- /* Should not exist, but ignore result if already there. */
- snprintf(cmd, sizeof(cmd), "-N bw_happy_box");
- runIpxtablesCmd(cmd, IptJumpNoAdd);
-
- /* Should be empty, but clear in case something was wrong. */
- niceAppUids.clear();
- snprintf(cmd, sizeof(cmd), "-F bw_happy_box");
- res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
-
- snprintf(cmd, sizeof(cmd), "-D bw_penalty_box -j bw_happy_box");
- runIpxtablesCmd(cmd, IptJumpNoAdd);
- snprintf(cmd, sizeof(cmd), "-A bw_penalty_box -j bw_happy_box");
- res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
-
- /* Reject. Defaulting to prot-unreachable */
- snprintf(cmd, sizeof(cmd), "-D bw_happy_box -j REJECT");
- runIpxtablesCmd(cmd, IptJumpNoAdd);
- snprintf(cmd, sizeof(cmd), "-A bw_happy_box -j REJECT");
- res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
-
- return res;
-}
-
-int BandwidthController::disableHappyBox(void) {
- char cmd[MAX_CMD_LEN];
-
- /* Best effort */
- snprintf(cmd, sizeof(cmd), "-D bw_penalty_box -j bw_happy_box");
- runIpxtablesCmd(cmd, IptJumpNoAdd);
- niceAppUids.clear();
- snprintf(cmd, sizeof(cmd), "-F bw_happy_box");
- runIpxtablesCmd(cmd, IptJumpNoAdd);
- snprintf(cmd, sizeof(cmd), "-X bw_happy_box");
- runIpxtablesCmd(cmd, IptJumpNoAdd);
-
- return 0;
-}
-
int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) {
return manipulateNaughtyApps(numUids, appUids, SpecialAppOpAdd);
}
}
int BandwidthController::manipulateNaughtyApps(int numUids, char *appStrUids[], SpecialAppOp appOp) {
- return manipulateSpecialApps(numUids, appStrUids, "bw_penalty_box", naughtyAppUids, IptJumpReject, appOp);
+ return manipulateSpecialApps(numUids, appStrUids, "bw_penalty_box", IptJumpReject, appOp);
}
int BandwidthController::manipulateNiceApps(int numUids, char *appStrUids[], SpecialAppOp appOp) {
- return manipulateSpecialApps(numUids, appStrUids, "bw_happy_box", niceAppUids, IptJumpReturn, appOp);
+ return manipulateSpecialApps(numUids, appStrUids, "bw_happy_box", IptJumpReturn, appOp);
}
int BandwidthController::manipulateSpecialApps(int numUids, char *appStrUids[],
const char *chain,
- std::list<int /*appUid*/> &specialAppUids,
IptJumpOp jumpHandling, SpecialAppOp appOp) {
int uidNum;
IptOp op;
int appUids[numUids];
std::string iptCmd;
- std::list<int /*uid*/>::iterator it;
switch (appOp) {
case SpecialAppOpAdd:
for (uidNum = 0; uidNum < numUids; uidNum++) {
int uid = appUids[uidNum];
- for (it = specialAppUids.begin(); it != specialAppUids.end(); it++) {
- if (*it == uid)
- break;
- }
- bool found = (it != specialAppUids.end());
-
- if (appOp == SpecialAppOpRemove) {
- if (!found) {
- ALOGE("No such appUid %d to remove", uid);
- return -1;
- }
- specialAppUids.erase(it);
- } else {
- if (found) {
- ALOGE("appUid %d exists already", uid);
- return -1;
- }
- specialAppUids.push_front(uid);
- }
iptCmd = makeIptablesSpecialAppCmd(op, uid, chain);
if (runIpxtablesCmd(iptCmd.c_str(), jumpHandling)) {
snprintf(cmd, sizeof(cmd), "-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, ifn, costCString);
res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+
+ snprintf(cmd, sizeof(cmd), "-D bw_FORWARD -o %s --jump %s", ifn, costCString);
+ runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
+ snprintf(cmd, sizeof(cmd), "-A bw_FORWARD -o %s --jump %s", ifn, costCString);
+ res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+
return res;
}
snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString);
res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
- snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString);
- res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+ for (const auto tableName : {LOCAL_OUTPUT, LOCAL_FORWARD}) {
+ snprintf(cmd, sizeof(cmd), "-D %s -o %s --jump %s", tableName, ifn, costCString);
+ res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
+ }
/* The "-N bw_costly_shared" is created upfront, no need to handle it here. */
if (quotaType == QuotaUnique) {
return -1;
asprintf(&fname, "/proc/net/xt_quota/%s", costName);
- fp = fopen(fname, "r");
+ fp = fopen(fname, "re");
free(fname);
if (!fp) {
ALOGE("Reading quota %s failed (%s)", costName, strerror(errno));
char ifn[MAX_IFACENAME_LEN];
int res = 0;
std::string ifaceName;
- const char *costName;
std::list<QuotaInfo>::iterator it;
if (!isIfaceName(iface))
return -1;
}
ifaceName = ifn;
- costName = iface;
for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
if (it->ifaceName == ifaceName)
}
asprintf(&fname, "/proc/net/xt_quota/%s", quotaName);
- fp = fopen(fname, "w");
+ fp = fopen(fname, "we");
free(fname);
if (!fp) {
ALOGE("Updating quota %s failed (%s)", quotaName, strerror(errno));
* the wanted info.
*/
fullCmd = IPTABLES_PATH;
- fullCmd += " -nvx -L ";
+ fullCmd += " -nvx -w -L ";
fullCmd += NatController::LOCAL_TETHER_COUNTERS_CHAIN;
- iptOutput = popen(fullCmd.c_str(), "r");
+ iptOutput = popenFunction(fullCmd.c_str(), "r");
if (!iptOutput) {
ALOGE("Failed to run %s err=%s", fullCmd.c_str(), strerror(errno));
extraProcessingInfo += "Failed to run iptables.";
/* Only lookup ip4 table names as ip6 will have the same tables ... */
fullCmd = IPTABLES_PATH;
- fullCmd += " -S";
- iptOutput = popen(fullCmd.c_str(), "r");
+ fullCmd += " -w -S";
+ iptOutput = popenFunction(fullCmd.c_str(), "r");
if (!iptOutput) {
ALOGE("Failed to run %s err=%s", fullCmd.c_str(), strerror(errno));
return;