1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // #define LOG_NDEBUG 0 18 19 /* 20 * The CommandListener, FrameworkListener don't allow for 21 * multiple calls in parallel to reach the BandwidthController. 22 * If they ever were to allow it, then netd/ would need some tweaking. 23 */ 24 25 #include <ctype.h> 26 #include <errno.h> 27 #include <fcntl.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <string> 32 #include <vector> 33 34 #define __STDC_FORMAT_MACROS 1 35 #include <inttypes.h> 36 37 #include <sys/socket.h> 38 #include <sys/stat.h> 39 #include <sys/types.h> 40 #include <sys/wait.h> 41 42 #include <linux/netlink.h> 43 #include <linux/rtnetlink.h> 44 #include <linux/pkt_sched.h> 45 46 #include "android-base/stringprintf.h" 47 #include "android-base/strings.h" 48 #define LOG_TAG "BandwidthController" 49 #include <cutils/log.h> 50 #include <cutils/properties.h> 51 #include <logwrap/logwrap.h> 52 53 #include <netdutils/Syscalls.h> 54 #include "BandwidthController.h" 55 #include "Controllers.h" 56 #include "FirewallController.h" /* For makeCriticalCommands */ 57 #include "Fwmark.h" 58 #include "NetdConstants.h" 59 #include "bpf/BpfUtils.h" 60 61 /* Alphabetical */ 62 #define ALERT_IPT_TEMPLATE "%s %s -m quota2 ! --quota %" PRId64" --name %s\n" 63 const char BandwidthController::LOCAL_INPUT[] = "bw_INPUT"; 64 const char BandwidthController::LOCAL_FORWARD[] = "bw_FORWARD"; 65 const char BandwidthController::LOCAL_OUTPUT[] = "bw_OUTPUT"; 66 const char BandwidthController::LOCAL_RAW_PREROUTING[] = "bw_raw_PREROUTING"; 67 const char BandwidthController::LOCAL_MANGLE_POSTROUTING[] = "bw_mangle_POSTROUTING"; 68 69 auto BandwidthController::iptablesRestoreFunction = execIptablesRestoreWithOutput; 70 71 using android::base::Join; 72 using android::base::StringAppendF; 73 using android::base::StringPrintf; 74 using android::bpf::XT_BPF_EGRESS_PROG_PATH; 75 using android::bpf::XT_BPF_INGRESS_PROG_PATH; 76 using android::netdutils::StatusOr; 77 using android::netdutils::UniqueFile; 78 79 namespace { 80 81 const char ALERT_GLOBAL_NAME[] = "globalAlert"; 82 const std::string NEW_CHAIN_COMMAND = "-N "; 83 84 const char NAUGHTY_CHAIN[] = "bw_penalty_box"; 85 const char NICE_CHAIN[] = "bw_happy_box"; 86 87 /** 88 * Some comments about the rules: 89 * * Ordering 90 * - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains. 91 * E.g. "-I bw_INPUT -i rmnet0 --jump costly" 92 * - quota'd rules in the costly chain should be before bw_penalty_box lookups. 93 * - the qtaguid counting is done at the end of the bw_INPUT/bw_OUTPUT user chains. 94 * 95 * * global quota vs per interface quota 96 * - global quota for all costly interfaces uses a single costly chain: 97 * . initial rules 98 * iptables -N bw_costly_shared 99 * iptables -I bw_INPUT -i iface0 --jump bw_costly_shared 100 * iptables -I bw_OUTPUT -o iface0 --jump bw_costly_shared 101 * iptables -I bw_costly_shared -m quota \! --quota 500000 \ 102 * --jump REJECT --reject-with icmp-net-prohibited 103 * iptables -A bw_costly_shared --jump bw_penalty_box 104 * iptables -A bw_penalty_box --jump bw_happy_box 105 * iptables -A bw_happy_box --jump bw_data_saver 106 * 107 * . adding a new iface to this, E.g.: 108 * iptables -I bw_INPUT -i iface1 --jump bw_costly_shared 109 * iptables -I bw_OUTPUT -o iface1 --jump bw_costly_shared 110 * 111 * - quota per interface. This is achieve by having "costly" chains per quota. 112 * E.g. adding a new costly interface iface0 with its own quota: 113 * iptables -N bw_costly_iface0 114 * iptables -I bw_INPUT -i iface0 --jump bw_costly_iface0 115 * iptables -I bw_OUTPUT -o iface0 --jump bw_costly_iface0 116 * iptables -A bw_costly_iface0 -m quota \! --quota 500000 \ 117 * --jump REJECT --reject-with icmp-port-unreachable 118 * iptables -A bw_costly_iface0 --jump bw_penalty_box 119 * 120 * * Penalty box, happy box and data saver. 121 * - bw_penalty box is a blacklist of apps that are rejected. 122 * - bw_happy_box is a whitelist of apps. It always includes all system apps 123 * - bw_data_saver implements data usage restrictions. 124 * - Via the UI the user can add and remove apps from the whitelist and 125 * blacklist, and turn on/off data saver. 126 * - The blacklist takes precedence over the whitelist and the whitelist 127 * takes precedence over data saver. 128 * 129 * * bw_penalty_box handling: 130 * - only one bw_penalty_box for all interfaces 131 * E.g Adding an app: 132 * iptables -I bw_penalty_box -m owner --uid-owner app_3 \ 133 * --jump REJECT --reject-with icmp-port-unreachable 134 * 135 * * bw_happy_box handling: 136 * - The bw_happy_box comes after the penalty box. 137 * E.g Adding a happy app, 138 * iptables -I bw_happy_box -m owner --uid-owner app_3 \ 139 * --jump RETURN 140 * 141 * * bw_data_saver handling: 142 * - The bw_data_saver comes after the happy box. 143 * Enable data saver: 144 * iptables -R 1 bw_data_saver --jump REJECT --reject-with icmp-port-unreachable 145 * Disable data saver: 146 * iptables -R 1 bw_data_saver --jump RETURN 147 */ 148 149 const std::string COMMIT_AND_CLOSE = "COMMIT\n"; 150 const std::string HAPPY_BOX_WHITELIST_COMMAND = StringPrintf( 151 "-I bw_happy_box -m owner --uid-owner %d-%d --jump RETURN", 0, MAX_SYSTEM_UID); 152 153 static const std::vector<std::string> IPT_FLUSH_COMMANDS = { 154 /* 155 * Cleanup rules. 156 * Should normally include bw_costly_<iface>, but we rely on the way they are setup 157 * to allow coexistance. 158 */ 159 "*filter", 160 ":bw_INPUT -", 161 ":bw_OUTPUT -", 162 ":bw_FORWARD -", 163 ":bw_happy_box -", 164 ":bw_penalty_box -", 165 ":bw_data_saver -", 166 ":bw_costly_shared -", 167 "COMMIT", 168 "*raw", 169 ":bw_raw_PREROUTING -", 170 "COMMIT", 171 "*mangle", 172 ":bw_mangle_POSTROUTING -", 173 COMMIT_AND_CLOSE 174 }; 175 176 static const uint32_t uidBillingMask = Fwmark::getUidBillingMask(); 177 178 /** 179 * Basic commands for creation of hooks into data accounting and data boxes. 180 * 181 * Included in these commands are rules to prevent the double-counting of IPsec 182 * packets. The general overview is as follows: 183 * > All interface counters (counted in PREROUTING, POSTROUTING) must be 184 * completely accurate, and count only the outer packet. As such, the inner 185 * packet must be ignored, which is done through the use of two rules: use 186 * of the policy module (for tunnel mode), and VTI interface checks (for 187 * tunnel or transport-in-tunnel mode). The VTI interfaces should be named 188 * ipsec* 189 * > Outbound UID billing can always be done with the outer packets, due to the 190 * ability to always find the correct UID (based on the skb->sk). As such, 191 * the inner packets should be ignored based on the policy module, or the 192 * output interface if a VTI (ipsec+) 193 * > Inbound UDP-encap-ESP packets can be correctly mapped to the UID that 194 * opened the encap socket, and as such, should be billed as early as 195 * possible (for transport mode; tunnel mode usage should be billed to 196 * sending/receiving application). Due to the inner packet being 197 * indistinguishable from the inner packet of ESP, a uidBillingDone mark 198 * has to be applied to prevent counting a second time. 199 * > Inbound ESP has no socket, and as such must be accounted later. ESP 200 * protocol packets are skipped via a blanket rule. 201 * > Note that this solution is asymmetrical. Adding the VTI or policy matcher 202 * ignore rule in the input chain would actually break the INPUT chain; 203 * Those rules are designed to ignore inner packets, and in the tunnel 204 * mode UDP, or any ESP case, we would not have billed the outer packet. 205 * 206 * See go/ipsec-data-accounting for more information. 207 */ 208 209 const std::vector<std::string> getBasicAccountingCommands() { 210 bool useBpf = BandwidthController::getBpfStatsStatus(); 211 const std::vector<std::string> ipt_basic_accounting_commands = { 212 "*filter", 213 // Prevents IPSec double counting (ESP and UDP-encap-ESP respectively) 214 "-A bw_INPUT -p esp -j RETURN", 215 StringPrintf("-A bw_INPUT -m mark --mark 0x%x/0x%x -j RETURN", 216 uidBillingMask, uidBillingMask), 217 "-A bw_INPUT -m owner --socket-exists", /* This is a tracking rule. */ 218 StringPrintf("-A bw_INPUT -j MARK --or-mark 0x%x", uidBillingMask), 219 220 // Prevents IPSec double counting (Tunnel mode and Transport mode, 221 // respectively) 222 "-A bw_OUTPUT -o " IPSEC_IFACE_PREFIX "+ -j RETURN", 223 "-A bw_OUTPUT -m policy --pol ipsec --dir out -j RETURN", 224 "-A bw_OUTPUT -m owner --socket-exists", /* This is a tracking rule. */ 225 226 "-A bw_costly_shared --jump bw_penalty_box", 227 "-A bw_penalty_box --jump bw_happy_box", 228 "-A bw_happy_box --jump bw_data_saver", 229 "-A bw_data_saver -j RETURN", 230 HAPPY_BOX_WHITELIST_COMMAND, 231 "COMMIT", 232 233 "*raw", 234 // Prevents IPSec double counting (Tunnel mode and Transport mode, 235 // respectively) 236 "-A bw_raw_PREROUTING -i " IPSEC_IFACE_PREFIX "+ -j RETURN", 237 "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN", 238 "-A bw_raw_PREROUTING -m owner --socket-exists", /* This is a tracking rule. */ 239 useBpf ? StringPrintf("-A bw_raw_PREROUTING -m bpf --object-pinned %s", 240 XT_BPF_INGRESS_PROG_PATH):"", 241 "COMMIT", 242 243 "*mangle", 244 // Prevents IPSec double counting (Tunnel mode and Transport mode, 245 // respectively) 246 "-A bw_mangle_POSTROUTING -o " IPSEC_IFACE_PREFIX "+ -j RETURN", 247 "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN", 248 "-A bw_mangle_POSTROUTING -m owner --socket-exists", /* This is a tracking rule. */ 249 StringPrintf("-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x%x", 250 uidBillingMask), // Clear the mark before sending this packet 251 useBpf ? StringPrintf("-A bw_mangle_POSTROUTING -m bpf --object-pinned %s", 252 XT_BPF_EGRESS_PROG_PATH):"", 253 COMMIT_AND_CLOSE 254 }; 255 return ipt_basic_accounting_commands; 256 } 257 258 259 std::vector<std::string> toStrVec(int num, char* strs[]) { 260 std::vector<std::string> tmp; 261 for (int i = 0; i < num; ++i) { 262 tmp.emplace_back(strs[i]); 263 } 264 return tmp; 265 } 266 267 } // namespace 268 269 bool BandwidthController::getBpfStatsStatus() { 270 return (access(XT_BPF_INGRESS_PROG_PATH, F_OK) != -1) && 271 (access(XT_BPF_EGRESS_PROG_PATH, F_OK) != -1); 272 } 273 274 BandwidthController::BandwidthController() { 275 } 276 277 void BandwidthController::flushCleanTables(bool doClean) { 278 /* Flush and remove the bw_costly_<iface> tables */ 279 flushExistingCostlyTables(doClean); 280 281 std::string commands = Join(IPT_FLUSH_COMMANDS, '\n'); 282 iptablesRestoreFunction(V4V6, commands, nullptr); 283 } 284 285 int BandwidthController::setupIptablesHooks() { 286 /* flush+clean is allowed to fail */ 287 flushCleanTables(true); 288 return 0; 289 } 290 291 int BandwidthController::enableBandwidthControl(bool force) { 292 char value[PROPERTY_VALUE_MAX]; 293 294 if (!force) { 295 property_get("persist.bandwidth.enable", value, "1"); 296 if (!strcmp(value, "0")) 297 return 0; 298 } 299 300 /* Let's pretend we started from scratch ... */ 301 mSharedQuotaIfaces.clear(); 302 mQuotaIfaces.clear(); 303 mGlobalAlertBytes = 0; 304 mGlobalAlertTetherCount = 0; 305 mSharedQuotaBytes = mSharedAlertBytes = 0; 306 307 flushCleanTables(false); 308 309 std::string commands = Join(getBasicAccountingCommands(), '\n'); 310 return iptablesRestoreFunction(V4V6, commands, nullptr); 311 } 312 313 int BandwidthController::disableBandwidthControl() { 314 315 flushCleanTables(false); 316 return 0; 317 } 318 319 std::string BandwidthController::makeDataSaverCommand(IptablesTarget target, bool enable) { 320 std::string cmd; 321 const char *chainName = "bw_data_saver"; 322 const char *op = jumpToString(enable ? IptJumpReject : IptJumpReturn); 323 std::string criticalCommands = enable ? 324 FirewallController::makeCriticalCommands(target, chainName) : ""; 325 StringAppendF(&cmd, 326 "*filter\n" 327 ":%s -\n" 328 "%s" 329 "-A %s%s\n" 330 "COMMIT\n", chainName, criticalCommands.c_str(), chainName, op); 331 return cmd; 332 } 333 334 int BandwidthController::enableDataSaver(bool enable) { 335 int ret = iptablesRestoreFunction(V4, makeDataSaverCommand(V4, enable), nullptr); 336 ret |= iptablesRestoreFunction(V6, makeDataSaverCommand(V6, enable), nullptr); 337 return ret; 338 } 339 340 int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) { 341 return manipulateSpecialApps(toStrVec(numUids, appUids), NAUGHTY_CHAIN, 342 IptJumpReject, IptOpInsert); 343 } 344 345 int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) { 346 return manipulateSpecialApps(toStrVec(numUids, appUids), NAUGHTY_CHAIN, 347 IptJumpReject, IptOpDelete); 348 } 349 350 int BandwidthController::addNiceApps(int numUids, char *appUids[]) { 351 return manipulateSpecialApps(toStrVec(numUids, appUids), NICE_CHAIN, 352 IptJumpReturn, IptOpInsert); 353 } 354 355 int BandwidthController::removeNiceApps(int numUids, char *appUids[]) { 356 return manipulateSpecialApps(toStrVec(numUids, appUids), NICE_CHAIN, 357 IptJumpReturn, IptOpDelete); 358 } 359 360 int BandwidthController::manipulateSpecialApps(const std::vector<std::string>& appStrUids, 361 const std::string& chain, IptJumpOp jumpHandling, 362 IptOp op) { 363 std::string cmd = "*filter\n"; 364 for (const auto& appStrUid : appStrUids) { 365 StringAppendF(&cmd, "%s %s -m owner --uid-owner %s%s\n", opToString(op), chain.c_str(), 366 appStrUid.c_str(), jumpToString(jumpHandling)); 367 } 368 StringAppendF(&cmd, "COMMIT\n"); 369 return iptablesRestoreFunction(V4V6, cmd, nullptr); 370 } 371 372 int BandwidthController::setInterfaceSharedQuota(const std::string& iface, int64_t maxBytes) { 373 int res = 0; 374 std::string quotaCmd; 375 constexpr char cost[] = "shared"; 376 constexpr char chain[] = "bw_costly_shared"; 377 378 if (!maxBytes) { 379 /* Don't talk about -1, deprecate it. */ 380 ALOGE("Invalid bytes value. 1..max_int64."); 381 return -1; 382 } 383 if (!isIfaceName(iface)) 384 return -1; 385 386 if (maxBytes == -1) { 387 return removeInterfaceSharedQuota(iface); 388 } 389 390 auto it = mSharedQuotaIfaces.find(iface); 391 392 if (it == mSharedQuotaIfaces.end()) { 393 const int ruleInsertPos = (mGlobalAlertBytes) ? 2 : 1; 394 std::vector<std::string> cmds = { 395 "*filter", 396 StringPrintf("-I bw_INPUT %d -i %s --jump %s", ruleInsertPos, iface.c_str(), chain), 397 StringPrintf("-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, iface.c_str(), chain), 398 StringPrintf("-A bw_FORWARD -i %s --jump %s", iface.c_str(), chain), 399 StringPrintf("-A bw_FORWARD -o %s --jump %s", iface.c_str(), chain), 400 }; 401 if (mSharedQuotaIfaces.empty()) { 402 cmds.push_back(StringPrintf("-I %s -m quota2 ! --quota %" PRId64 403 " --name %s --jump REJECT", 404 chain, maxBytes, cost)); 405 } 406 cmds.push_back("COMMIT\n"); 407 408 res |= iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr); 409 if (res) { 410 ALOGE("Failed set quota rule"); 411 removeInterfaceSharedQuota(iface); 412 return -1; 413 } 414 mSharedQuotaBytes = maxBytes; 415 mSharedQuotaIfaces.insert(iface); 416 } 417 418 if (maxBytes != mSharedQuotaBytes) { 419 res |= updateQuota(cost, maxBytes); 420 if (res) { 421 ALOGE("Failed update quota for %s", cost); 422 removeInterfaceSharedQuota(iface); 423 return -1; 424 } 425 mSharedQuotaBytes = maxBytes; 426 } 427 return 0; 428 } 429 430 /* It will also cleanup any shared alerts */ 431 int BandwidthController::removeInterfaceSharedQuota(const std::string& iface) { 432 constexpr char cost[] = "shared"; 433 constexpr char chain[] = "bw_costly_shared"; 434 435 if (!isIfaceName(iface)) 436 return -1; 437 438 auto it = mSharedQuotaIfaces.find(iface); 439 440 if (it == mSharedQuotaIfaces.end()) { 441 ALOGE("No such iface %s to delete", iface.c_str()); 442 return -1; 443 } 444 445 std::vector<std::string> cmds = { 446 "*filter", 447 StringPrintf("-D bw_INPUT -i %s --jump %s", iface.c_str(), chain), 448 StringPrintf("-D bw_OUTPUT -o %s --jump %s", iface.c_str(), chain), 449 StringPrintf("-D bw_FORWARD -i %s --jump %s", iface.c_str(), chain), 450 StringPrintf("-D bw_FORWARD -o %s --jump %s", iface.c_str(), chain), 451 }; 452 if (mSharedQuotaIfaces.size() == 1) { 453 cmds.push_back(StringPrintf("-D %s -m quota2 ! --quota %" PRIu64 454 " --name %s --jump REJECT", 455 chain, mSharedQuotaBytes, cost)); 456 } 457 cmds.push_back("COMMIT\n"); 458 459 if (iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr) != 0) { 460 ALOGE("Failed to remove shared quota on %s", iface.c_str()); 461 return -1; 462 } 463 464 int res = 0; 465 mSharedQuotaIfaces.erase(it); 466 if (mSharedQuotaIfaces.empty()) { 467 mSharedQuotaBytes = 0; 468 if (mSharedAlertBytes) { 469 res = removeSharedAlert(); 470 if (res == 0) { 471 mSharedAlertBytes = 0; 472 } 473 } 474 } 475 476 return res; 477 478 } 479 480 int BandwidthController::setInterfaceQuota(const std::string& iface, int64_t maxBytes) { 481 const std::string& cost = iface; 482 483 if (!isIfaceName(iface)) 484 return -1; 485 486 if (!maxBytes) { 487 /* Don't talk about -1, deprecate it. */ 488 ALOGE("Invalid bytes value. 1..max_int64."); 489 return -1; 490 } 491 if (maxBytes == -1) { 492 return removeInterfaceQuota(iface); 493 } 494 495 /* Insert ingress quota. */ 496 auto it = mQuotaIfaces.find(iface); 497 498 if (it != mQuotaIfaces.end()) { 499 if (updateQuota(cost, maxBytes) != 0) { 500 ALOGE("Failed update quota for %s", iface.c_str()); 501 removeInterfaceQuota(iface); 502 return -1; 503 } 504 it->second.quota = maxBytes; 505 return 0; 506 } 507 508 const std::string chain = "bw_costly_" + iface; 509 const int ruleInsertPos = (mGlobalAlertBytes) ? 2 : 1; 510 std::vector<std::string> cmds = { 511 "*filter", 512 StringPrintf(":%s -", chain.c_str()), 513 StringPrintf("-A %s -j bw_penalty_box", chain.c_str()), 514 StringPrintf("-I bw_INPUT %d -i %s --jump %s", ruleInsertPos, iface.c_str(), 515 chain.c_str()), 516 StringPrintf("-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, iface.c_str(), 517 chain.c_str()), 518 StringPrintf("-A bw_FORWARD -i %s --jump %s", iface.c_str(), chain.c_str()), 519 StringPrintf("-A bw_FORWARD -o %s --jump %s", iface.c_str(), chain.c_str()), 520 StringPrintf("-A %s -m quota2 ! --quota %" PRId64 " --name %s --jump REJECT", 521 chain.c_str(), maxBytes, cost.c_str()), 522 "COMMIT\n", 523 }; 524 525 if (iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr) != 0) { 526 ALOGE("Failed set quota rule"); 527 removeInterfaceQuota(iface); 528 return -1; 529 } 530 531 mQuotaIfaces[iface] = QuotaInfo{maxBytes, 0}; 532 return 0; 533 } 534 535 int BandwidthController::getInterfaceSharedQuota(int64_t *bytes) { 536 return getInterfaceQuota("shared", bytes); 537 } 538 539 int BandwidthController::getInterfaceQuota(const std::string& iface, int64_t* bytes) { 540 const auto& sys = android::netdutils::sSyscalls.get(); 541 const std::string fname = "/proc/net/xt_quota/" + iface; 542 543 if (!isIfaceName(iface)) return -1; 544 545 StatusOr<UniqueFile> file = sys.fopen(fname, "re"); 546 if (!isOk(file)) { 547 ALOGE("Reading quota %s failed (%s)", iface.c_str(), toString(file).c_str()); 548 return -1; 549 } 550 auto rv = sys.fscanf(file.value().get(), "%" SCNd64, bytes); 551 if (!isOk(rv)) { 552 ALOGE("Reading quota %s failed (%s)", iface.c_str(), toString(rv).c_str()); 553 return -1; 554 } 555 ALOGV("Read quota res=%d bytes=%" PRId64, rv.value(), *bytes); 556 return rv.value() == 1 ? 0 : -1; 557 } 558 559 int BandwidthController::removeInterfaceQuota(const std::string& iface) { 560 if (!isIfaceName(iface)) 561 return -1; 562 563 auto it = mQuotaIfaces.find(iface); 564 565 if (it == mQuotaIfaces.end()) { 566 ALOGE("No such iface %s to delete", iface.c_str()); 567 return -1; 568 } 569 570 const std::string chain = "bw_costly_" + iface; 571 std::vector<std::string> cmds = { 572 "*filter", 573 StringPrintf("-D bw_INPUT -i %s --jump %s", iface.c_str(), chain.c_str()), 574 StringPrintf("-D bw_OUTPUT -o %s --jump %s", iface.c_str(), chain.c_str()), 575 StringPrintf("-D bw_FORWARD -i %s --jump %s", iface.c_str(), chain.c_str()), 576 StringPrintf("-D bw_FORWARD -o %s --jump %s", iface.c_str(), chain.c_str()), 577 StringPrintf("-F %s", chain.c_str()), 578 StringPrintf("-X %s", chain.c_str()), 579 "COMMIT\n", 580 }; 581 582 const int res = iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr); 583 584 if (res == 0) { 585 mQuotaIfaces.erase(it); 586 } 587 588 return res; 589 } 590 591 int BandwidthController::updateQuota(const std::string& quotaName, int64_t bytes) { 592 const auto& sys = android::netdutils::sSyscalls.get(); 593 const std::string fname = "/proc/net/xt_quota/" + quotaName; 594 595 if (!isIfaceName(quotaName)) { 596 ALOGE("updateQuota: Invalid quotaName \"%s\"", quotaName.c_str()); 597 return -1; 598 } 599 600 StatusOr<UniqueFile> file = sys.fopen(fname, "we"); 601 if (!isOk(file)) { 602 ALOGE("Updating quota %s failed (%s)", quotaName.c_str(), toString(file).c_str()); 603 return -1; 604 } 605 sys.fprintf(file.value().get(), "%" PRId64 "\n", bytes); 606 return 0; 607 } 608 609 int BandwidthController::runIptablesAlertCmd(IptOp op, const std::string& alertName, 610 int64_t bytes) { 611 const char *opFlag = opToString(op); 612 std::string alertQuotaCmd = "*filter\n"; 613 614 // TODO: consider using an alternate template for the delete that does not include the --quota 615 // value. This code works because the --quota value is ignored by deletes 616 StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_INPUT", bytes, 617 alertName.c_str()); 618 StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_OUTPUT", bytes, 619 alertName.c_str()); 620 StringAppendF(&alertQuotaCmd, "COMMIT\n"); 621 622 return iptablesRestoreFunction(V4V6, alertQuotaCmd, nullptr); 623 } 624 625 int BandwidthController::runIptablesAlertFwdCmd(IptOp op, const std::string& alertName, 626 int64_t bytes) { 627 const char *opFlag = opToString(op); 628 std::string alertQuotaCmd = "*filter\n"; 629 StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_FORWARD", bytes, 630 alertName.c_str()); 631 StringAppendF(&alertQuotaCmd, "COMMIT\n"); 632 633 return iptablesRestoreFunction(V4V6, alertQuotaCmd, nullptr); 634 } 635 636 int BandwidthController::setGlobalAlert(int64_t bytes) { 637 const char *alertName = ALERT_GLOBAL_NAME; 638 int res = 0; 639 640 if (!bytes) { 641 ALOGE("Invalid bytes value. 1..max_int64."); 642 return -1; 643 } 644 if (mGlobalAlertBytes) { 645 res = updateQuota(alertName, bytes); 646 } else { 647 res = runIptablesAlertCmd(IptOpInsert, alertName, bytes); 648 if (mGlobalAlertTetherCount) { 649 ALOGV("setGlobalAlert for %d tether", mGlobalAlertTetherCount); 650 res |= runIptablesAlertFwdCmd(IptOpInsert, alertName, bytes); 651 } 652 } 653 mGlobalAlertBytes = bytes; 654 return res; 655 } 656 657 int BandwidthController::setGlobalAlertInForwardChain() { 658 const char *alertName = ALERT_GLOBAL_NAME; 659 int res = 0; 660 661 mGlobalAlertTetherCount++; 662 ALOGV("setGlobalAlertInForwardChain(): %d tether", mGlobalAlertTetherCount); 663 664 /* 665 * If there is no globalAlert active we are done. 666 * If there is an active globalAlert but this is not the 1st 667 * tether, we are also done. 668 */ 669 if (!mGlobalAlertBytes || mGlobalAlertTetherCount != 1) { 670 return 0; 671 } 672 673 /* We only add the rule if this was the 1st tether added. */ 674 res = runIptablesAlertFwdCmd(IptOpInsert, alertName, mGlobalAlertBytes); 675 return res; 676 } 677 678 int BandwidthController::removeGlobalAlert() { 679 680 const char *alertName = ALERT_GLOBAL_NAME; 681 int res = 0; 682 683 if (!mGlobalAlertBytes) { 684 ALOGE("No prior alert set"); 685 return -1; 686 } 687 res = runIptablesAlertCmd(IptOpDelete, alertName, mGlobalAlertBytes); 688 if (mGlobalAlertTetherCount) { 689 res |= runIptablesAlertFwdCmd(IptOpDelete, alertName, mGlobalAlertBytes); 690 } 691 mGlobalAlertBytes = 0; 692 return res; 693 } 694 695 int BandwidthController::removeGlobalAlertInForwardChain() { 696 int res = 0; 697 const char *alertName = ALERT_GLOBAL_NAME; 698 699 if (!mGlobalAlertTetherCount) { 700 ALOGE("No prior alert set"); 701 return -1; 702 } 703 704 mGlobalAlertTetherCount--; 705 /* 706 * If there is no globalAlert active we are done. 707 * If there is an active globalAlert but there are more 708 * tethers, we are also done. 709 */ 710 if (!mGlobalAlertBytes || mGlobalAlertTetherCount >= 1) { 711 return 0; 712 } 713 714 /* We only detete the rule if this was the last tether removed. */ 715 res = runIptablesAlertFwdCmd(IptOpDelete, alertName, mGlobalAlertBytes); 716 return res; 717 } 718 719 int BandwidthController::setSharedAlert(int64_t bytes) { 720 if (!mSharedQuotaBytes) { 721 ALOGE("Need to have a prior shared quota set to set an alert"); 722 return -1; 723 } 724 if (!bytes) { 725 ALOGE("Invalid bytes value. 1..max_int64."); 726 return -1; 727 } 728 return setCostlyAlert("shared", bytes, &mSharedAlertBytes); 729 } 730 731 int BandwidthController::removeSharedAlert() { 732 return removeCostlyAlert("shared", &mSharedAlertBytes); 733 } 734 735 int BandwidthController::setInterfaceAlert(const std::string& iface, int64_t bytes) { 736 if (!isIfaceName(iface)) { 737 ALOGE("setInterfaceAlert: Invalid iface \"%s\"", iface.c_str()); 738 return -1; 739 } 740 741 if (!bytes) { 742 ALOGE("Invalid bytes value. 1..max_int64."); 743 return -1; 744 } 745 auto it = mQuotaIfaces.find(iface); 746 747 if (it == mQuotaIfaces.end()) { 748 ALOGE("Need to have a prior interface quota set to set an alert"); 749 return -1; 750 } 751 752 return setCostlyAlert(iface, bytes, &it->second.alert); 753 } 754 755 int BandwidthController::removeInterfaceAlert(const std::string& iface) { 756 if (!isIfaceName(iface)) { 757 ALOGE("removeInterfaceAlert: Invalid iface \"%s\"", iface.c_str()); 758 return -1; 759 } 760 761 auto it = mQuotaIfaces.find(iface); 762 763 if (it == mQuotaIfaces.end()) { 764 ALOGE("No prior alert set for interface %s", iface.c_str()); 765 return -1; 766 } 767 768 return removeCostlyAlert(iface, &it->second.alert); 769 } 770 771 int BandwidthController::setCostlyAlert(const std::string& costName, int64_t bytes, 772 int64_t* alertBytes) { 773 int res = 0; 774 775 if (!isIfaceName(costName)) { 776 ALOGE("setCostlyAlert: Invalid costName \"%s\"", costName.c_str()); 777 return -1; 778 } 779 780 if (!bytes) { 781 ALOGE("Invalid bytes value. 1..max_int64."); 782 return -1; 783 } 784 785 std::string alertName = costName + "Alert"; 786 std::string chainName = "bw_costly_" + costName; 787 if (*alertBytes) { 788 res = updateQuota(alertName, *alertBytes); 789 } else { 790 std::vector<std::string> commands = { 791 "*filter\n", 792 StringPrintf(ALERT_IPT_TEMPLATE, "-A", chainName.c_str(), bytes, alertName.c_str()), 793 "COMMIT\n" 794 }; 795 res = iptablesRestoreFunction(V4V6, Join(commands, ""), nullptr); 796 if (res) { 797 ALOGE("Failed to set costly alert for %s", costName.c_str()); 798 } 799 } 800 if (res == 0) { 801 *alertBytes = bytes; 802 } 803 return res; 804 } 805 806 int BandwidthController::removeCostlyAlert(const std::string& costName, int64_t* alertBytes) { 807 if (!isIfaceName(costName)) { 808 ALOGE("removeCostlyAlert: Invalid costName \"%s\"", costName.c_str()); 809 return -1; 810 } 811 812 if (!*alertBytes) { 813 ALOGE("No prior alert set for %s alert", costName.c_str()); 814 return -1; 815 } 816 817 std::string alertName = costName + "Alert"; 818 std::string chainName = "bw_costly_" + costName; 819 std::vector<std::string> commands = { 820 "*filter\n", 821 StringPrintf(ALERT_IPT_TEMPLATE, "-D", chainName.c_str(), *alertBytes, alertName.c_str()), 822 "COMMIT\n" 823 }; 824 if (iptablesRestoreFunction(V4V6, Join(commands, ""), nullptr) != 0) { 825 ALOGE("Failed to remove costly alert %s", costName.c_str()); 826 return -1; 827 } 828 829 *alertBytes = 0; 830 return 0; 831 } 832 833 void BandwidthController::flushExistingCostlyTables(bool doClean) { 834 std::string fullCmd = "*filter\n-S\nCOMMIT\n"; 835 std::string ruleList; 836 837 /* Only lookup ip4 table names as ip6 will have the same tables ... */ 838 if (int ret = iptablesRestoreFunction(V4, fullCmd, &ruleList)) { 839 ALOGE("Failed to list existing costly tables ret=%d", ret); 840 return; 841 } 842 /* ... then flush/clean both ip4 and ip6 iptables. */ 843 parseAndFlushCostlyTables(ruleList, doClean); 844 } 845 846 void BandwidthController::parseAndFlushCostlyTables(const std::string& ruleList, bool doRemove) { 847 std::stringstream stream(ruleList); 848 std::string rule; 849 std::vector<std::string> clearCommands = { "*filter" }; 850 std::string chainName; 851 852 // Find and flush all rules starting with "-N bw_costly_<iface>" except "-N bw_costly_shared". 853 while (std::getline(stream, rule, '\n')) { 854 if (rule.find(NEW_CHAIN_COMMAND) != 0) continue; 855 chainName = rule.substr(NEW_CHAIN_COMMAND.size()); 856 ALOGV("parse chainName=<%s> orig line=<%s>", chainName.c_str(), rule.c_str()); 857 858 if (chainName.find("bw_costly_") != 0 || chainName == std::string("bw_costly_shared")) { 859 continue; 860 } 861 862 clearCommands.push_back(StringPrintf(":%s -", chainName.c_str())); 863 if (doRemove) { 864 clearCommands.push_back(StringPrintf("-X %s", chainName.c_str())); 865 } 866 } 867 868 if (clearCommands.size() == 1) { 869 // No rules found. 870 return; 871 } 872 873 clearCommands.push_back("COMMIT\n"); 874 iptablesRestoreFunction(V4V6, Join(clearCommands, '\n'), nullptr); 875 } 876 877 inline const char *BandwidthController::opToString(IptOp op) { 878 switch (op) { 879 case IptOpInsert: 880 return "-I"; 881 case IptOpDelete: 882 return "-D"; 883 } 884 } 885 886 inline const char *BandwidthController::jumpToString(IptJumpOp jumpHandling) { 887 /* 888 * Must be careful what one rejects with, as upper layer protocols will just 889 * keep on hammering the device until the number of retries are done. 890 * For port-unreachable (default), TCP should consider as an abort (RFC1122). 891 */ 892 switch (jumpHandling) { 893 case IptJumpNoAdd: 894 return ""; 895 case IptJumpReject: 896 return " --jump REJECT"; 897 case IptJumpReturn: 898 return " --jump RETURN"; 899 } 900 } 901