BandwidthController.cpp revision c8dc63b14f5bcef34a23061c17d3bbe66df51429
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 <errno.h> 26#include <fcntl.h> 27#include <stdio.h> 28#include <stdlib.h> 29#include <string.h> 30 31#include <sys/socket.h> 32#include <sys/stat.h> 33#include <sys/types.h> 34#include <sys/wait.h> 35 36#include <linux/netlink.h> 37#include <linux/rtnetlink.h> 38#include <linux/pkt_sched.h> 39 40#define LOG_TAG "BandwidthController" 41#include <cutils/log.h> 42#include <cutils/properties.h> 43#include <logwrap/logwrap.h> 44 45#include "NetdConstants.h" 46#include "BandwidthController.h" 47 48/* Alphabetical */ 49#define ALERT_IPT_TEMPLATE "%s %s -m quota2 ! --quota %lld --name %s" 50const int BandwidthController::ALERT_RULE_POS_IN_COSTLY_CHAIN = 4; 51const char BandwidthController::ALERT_GLOBAL_NAME[] = "globalAlert"; 52const char* BandwidthController::LOCAL_INPUT = "bw_INPUT"; 53const char* BandwidthController::LOCAL_FORWARD = "bw_FORWARD"; 54const char* BandwidthController::LOCAL_OUTPUT = "bw_OUTPUT"; 55const char* BandwidthController::LOCAL_RAW_PREROUTING = "bw_raw_PREROUTING"; 56const char* BandwidthController::LOCAL_MANGLE_POSTROUTING = "bw_mangle_POSTROUTING"; 57const int BandwidthController::MAX_CMD_ARGS = 32; 58const int BandwidthController::MAX_CMD_LEN = 1024; 59const int BandwidthController::MAX_IFACENAME_LEN = 64; 60const int BandwidthController::MAX_IPT_OUTPUT_LINE_LEN = 256; 61 62/** 63 * Some comments about the rules: 64 * * Ordering 65 * - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains. 66 * E.g. "-I bw_INPUT -i rmnet0 --jump costly" 67 * - quota'd rules in the costly chain should be before penalty_box lookups. 68 * - the qtaguid counting is done at the end of the bw_INPUT/bw_OUTPUT user chains. 69 * 70 * * global quota vs per interface quota 71 * - global quota for all costly interfaces uses a single costly chain: 72 * . initial rules 73 * iptables -N costly_shared 74 * iptables -I bw_INPUT -i iface0 --jump costly_shared 75 * iptables -I bw_OUTPUT -o iface0 --jump costly_shared 76 * iptables -I costly_shared -m quota \! --quota 500000 \ 77 * --jump REJECT --reject-with icmp-net-prohibited 78 * iptables -A costly_shared --jump penalty_box 79 * 80 * . adding a new iface to this, E.g.: 81 * iptables -I bw_INPUT -i iface1 --jump costly_shared 82 * iptables -I bw_OUTPUT -o iface1 --jump costly_shared 83 * 84 * - quota per interface. This is achieve by having "costly" chains per quota. 85 * E.g. adding a new costly interface iface0 with its own quota: 86 * iptables -N costly_iface0 87 * iptables -I bw_INPUT -i iface0 --jump costly_iface0 88 * iptables -I bw_OUTPUT -o iface0 --jump costly_iface0 89 * iptables -A costly_iface0 -m quota \! --quota 500000 \ 90 * --jump REJECT --reject-with icmp-net-prohibited 91 * iptables -A costly_iface0 --jump penalty_box 92 * 93 * * penalty_box handling: 94 * - only one penalty_box for all interfaces 95 * E.g Adding an app: 96 * iptables -A penalty_box -m owner --uid-owner app_3 \ 97 * --jump REJECT --reject-with icmp-net-prohibited 98 */ 99const char *BandwidthController::IPT_FLUSH_COMMANDS[] = { 100 /* 101 * Cleanup rules. 102 * Should normally include costly_<iface>, but we rely on the way they are setup 103 * to allow coexistance. 104 */ 105 "-F bw_INPUT", 106 "-F bw_OUTPUT", 107 "-F bw_FORWARD", 108 "-F penalty_box", 109 "-F costly_shared", 110 111 "-t raw -F bw_raw_PREROUTING", 112 "-t mangle -F bw_mangle_POSTROUTING", 113}; 114 115/* The cleanup commands assume flushing has been done. */ 116const char *BandwidthController::IPT_CLEANUP_COMMANDS[] = { 117 "-X penalty_box", 118 "-X costly_shared", 119}; 120 121const char *BandwidthController::IPT_SETUP_COMMANDS[] = { 122 "-N costly_shared", 123 "-N penalty_box", 124}; 125 126const char *BandwidthController::IPT_BASIC_ACCOUNTING_COMMANDS[] = { 127 "-A bw_INPUT -m owner --socket-exists", /* This is a tracking rule. */ 128 129 "-A bw_OUTPUT -m owner --socket-exists", /* This is a tracking rule. */ 130 131 "-A costly_shared --jump penalty_box", 132 133 "-t raw -A bw_raw_PREROUTING -m owner --socket-exists", /* This is a tracking rule. */ 134 "-t mangle -A bw_mangle_POSTROUTING -m owner --socket-exists", /* This is a tracking rule. */ 135}; 136 137BandwidthController::BandwidthController(void) { 138} 139 140int BandwidthController::runIpxtablesCmd(const char *cmd, IptRejectOp rejectHandling, 141 IptFailureLog failureHandling) { 142 int res = 0; 143 144 ALOGV("runIpxtablesCmd(cmd=%s)", cmd); 145 res |= runIptablesCmd(cmd, rejectHandling, IptIpV4, failureHandling); 146 res |= runIptablesCmd(cmd, rejectHandling, IptIpV6, failureHandling); 147 return res; 148} 149 150int BandwidthController::StrncpyAndCheck(char *buffer, const char *src, size_t buffSize) { 151 152 memset(buffer, '\0', buffSize); // strncpy() is not filling leftover with '\0' 153 strncpy(buffer, src, buffSize); 154 return buffer[buffSize - 1]; 155} 156 157int BandwidthController::runIptablesCmd(const char *cmd, IptRejectOp rejectHandling, 158 IptIpVer iptVer, IptFailureLog failureHandling) { 159 char buffer[MAX_CMD_LEN]; 160 const char *argv[MAX_CMD_ARGS]; 161 int argc = 0; 162 char *next = buffer; 163 char *tmp; 164 int res; 165 int status = 0; 166 167 std::string fullCmd = cmd; 168 169 if (rejectHandling == IptRejectAdd) { 170 fullCmd += " --jump REJECT --reject-with"; 171 switch (iptVer) { 172 case IptIpV4: 173 fullCmd += " icmp-net-prohibited"; 174 break; 175 case IptIpV6: 176 fullCmd += " icmp6-adm-prohibited"; 177 break; 178 } 179 } 180 181 fullCmd.insert(0, " "); 182 fullCmd.insert(0, iptVer == IptIpV4 ? IPTABLES_PATH : IP6TABLES_PATH); 183 184 if (StrncpyAndCheck(buffer, fullCmd.c_str(), sizeof(buffer))) { 185 ALOGE("iptables command too long"); 186 return -1; 187 } 188 189 argc = 0; 190 while ((tmp = strsep(&next, " "))) { 191 argv[argc++] = tmp; 192 if (argc >= MAX_CMD_ARGS) { 193 ALOGE("iptables argument overflow"); 194 return -1; 195 } 196 } 197 198 argv[argc] = NULL; 199 res = android_fork_execvp(argc, (char **)argv, &status, false, 200 failureHandling == IptFailShow); 201 res = res || !WIFEXITED(status) || WEXITSTATUS(status); 202 if (res && failureHandling == IptFailShow) { 203 ALOGE("runIptablesCmd(): res=%d status=%d failed %s", res, status, 204 fullCmd.c_str()); 205 } 206 return res; 207} 208 209int BandwidthController::setupIptablesHooks(void) { 210 211 /* Some of the initialCommands are allowed to fail */ 212 runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*), 213 IPT_FLUSH_COMMANDS, RunCmdFailureOk); 214 215 runCommands(sizeof(IPT_CLEANUP_COMMANDS) / sizeof(char*), 216 IPT_CLEANUP_COMMANDS, RunCmdFailureOk); 217 218 runCommands(sizeof(IPT_SETUP_COMMANDS) / sizeof(char*), 219 IPT_SETUP_COMMANDS, RunCmdFailureBad); 220 221 return 0; 222} 223 224int BandwidthController::enableBandwidthControl(bool force) { 225 int res; 226 char value[PROPERTY_VALUE_MAX]; 227 228 if (!force) { 229 property_get("persist.bandwidth.enable", value, "1"); 230 if (!strcmp(value, "0")) 231 return 0; 232 } 233 234 /* Let's pretend we started from scratch ... */ 235 sharedQuotaIfaces.clear(); 236 quotaIfaces.clear(); 237 naughtyAppUids.clear(); 238 globalAlertBytes = 0; 239 globalAlertTetherCount = 0; 240 sharedQuotaBytes = sharedAlertBytes = 0; 241 242 res = runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*), 243 IPT_FLUSH_COMMANDS, RunCmdFailureOk); 244 245 res |= runCommands(sizeof(IPT_BASIC_ACCOUNTING_COMMANDS) / sizeof(char*), 246 IPT_BASIC_ACCOUNTING_COMMANDS, RunCmdFailureBad); 247 248 return res; 249 250} 251 252int BandwidthController::disableBandwidthControl(void) { 253 runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*), 254 IPT_FLUSH_COMMANDS, RunCmdFailureOk); 255 return 0; 256} 257 258int BandwidthController::runCommands(int numCommands, const char *commands[], 259 RunCmdErrHandling cmdErrHandling) { 260 int res = 0; 261 IptFailureLog failureLogging = IptFailShow; 262 if (cmdErrHandling == RunCmdFailureOk) { 263 failureLogging = IptFailHide; 264 } 265 ALOGV("runCommands(): %d commands", numCommands); 266 for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) { 267 res = runIpxtablesCmd(commands[cmdNum], IptRejectNoAdd, failureLogging); 268 if (res && cmdErrHandling != RunCmdFailureOk) 269 return res; 270 } 271 return 0; 272} 273 274std::string BandwidthController::makeIptablesNaughtyCmd(IptOp op, int uid) { 275 std::string res; 276 char *buff; 277 const char *opFlag; 278 279 switch (op) { 280 case IptOpInsert: 281 opFlag = "-I"; 282 break; 283 case IptOpReplace: 284 opFlag = "-R"; 285 break; 286 default: 287 case IptOpDelete: 288 opFlag = "-D"; 289 break; 290 } 291 asprintf(&buff, "%s penalty_box -m owner --uid-owner %d", opFlag, uid); 292 res = buff; 293 free(buff); 294 return res; 295} 296 297int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) { 298 return maninpulateNaughtyApps(numUids, appUids, NaughtyAppOpAdd); 299} 300 301int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) { 302 return maninpulateNaughtyApps(numUids, appUids, NaughtyAppOpRemove); 303} 304 305int BandwidthController::maninpulateNaughtyApps(int numUids, char *appStrUids[], NaughtyAppOp appOp) { 306 char cmd[MAX_CMD_LEN]; 307 int uidNum; 308 const char *failLogTemplate; 309 IptOp op; 310 int appUids[numUids]; 311 std::string naughtyCmd; 312 std::list<int /*uid*/>::iterator it; 313 314 switch (appOp) { 315 case NaughtyAppOpAdd: 316 op = IptOpInsert; 317 failLogTemplate = "Failed to add app uid %d to penalty box."; 318 break; 319 case NaughtyAppOpRemove: 320 op = IptOpDelete; 321 failLogTemplate = "Failed to delete app uid %d from penalty box."; 322 break; 323 default: 324 ALOGE("Unexpected app Op %d", appOp); 325 return -1; 326 } 327 328 for (uidNum = 0; uidNum < numUids; uidNum++) { 329 appUids[uidNum] = atol(appStrUids[uidNum]); 330 if (appUids[uidNum] == 0) { 331 ALOGE(failLogTemplate, appUids[uidNum]); 332 goto fail_parse; 333 } 334 } 335 336 for (uidNum = 0; uidNum < numUids; uidNum++) { 337 int uid = appUids[uidNum]; 338 for (it = naughtyAppUids.begin(); it != naughtyAppUids.end(); it++) { 339 if (*it == uid) 340 break; 341 } 342 bool found = (it != naughtyAppUids.end()); 343 344 if (appOp == NaughtyAppOpRemove) { 345 if (!found) { 346 ALOGE("No such appUid %d to remove", uid); 347 return -1; 348 } 349 naughtyAppUids.erase(it); 350 } else { 351 if (found) { 352 ALOGE("appUid %d exists already", uid); 353 return -1; 354 } 355 naughtyAppUids.push_front(uid); 356 } 357 358 naughtyCmd = makeIptablesNaughtyCmd(op, uid); 359 if (runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd)) { 360 ALOGE(failLogTemplate, uid); 361 goto fail_with_uidNum; 362 } 363 } 364 return 0; 365 366fail_with_uidNum: 367 /* Try to remove the uid that failed in any case*/ 368 naughtyCmd = makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum]); 369 runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd); 370fail_parse: 371 return -1; 372} 373 374std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota) { 375 std::string res; 376 char *buff; 377 const char *opFlag; 378 379 ALOGV("makeIptablesQuotaCmd(%d, %lld)", op, quota); 380 381 switch (op) { 382 case IptOpInsert: 383 opFlag = "-I"; 384 break; 385 case IptOpReplace: 386 opFlag = "-R"; 387 break; 388 default: 389 case IptOpDelete: 390 opFlag = "-D"; 391 break; 392 } 393 394 // The requried IP version specific --jump REJECT ... will be added later. 395 asprintf(&buff, "%s costly_%s -m quota2 ! --quota %lld --name %s", opFlag, costName, quota, 396 costName); 397 res = buff; 398 free(buff); 399 return res; 400} 401 402int BandwidthController::prepCostlyIface(const char *ifn, QuotaType quotaType) { 403 char cmd[MAX_CMD_LEN]; 404 int res = 0, res1, res2; 405 int ruleInsertPos = 1; 406 std::string costString; 407 const char *costCString; 408 409 /* The "-N costly" is created upfront, no need to handle it here. */ 410 switch (quotaType) { 411 case QuotaUnique: 412 costString = "costly_"; 413 costString += ifn; 414 costCString = costString.c_str(); 415 /* 416 * Flush the costly_<iface> is allowed to fail in case it didn't exist. 417 * Creating a new one is allowed to fail in case it existed. 418 * This helps with netd restarts. 419 */ 420 snprintf(cmd, sizeof(cmd), "-F %s", costCString); 421 res1 = runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide); 422 snprintf(cmd, sizeof(cmd), "-N %s", costCString); 423 res2 = runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide); 424 res = (res1 && res2) || (!res1 && !res2); 425 426 snprintf(cmd, sizeof(cmd), "-A %s -j penalty_box", costCString); 427 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 428 break; 429 case QuotaShared: 430 costCString = "costly_shared"; 431 break; 432 default: 433 ALOGE("Unexpected quotatype %d", quotaType); 434 return -1; 435 } 436 437 if (globalAlertBytes) { 438 /* The alert rule comes 1st */ 439 ruleInsertPos = 2; 440 } 441 442 snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString); 443 runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide); 444 445 snprintf(cmd, sizeof(cmd), "-I bw_INPUT %d -i %s --jump %s", ruleInsertPos, ifn, costCString); 446 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 447 448 snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString); 449 runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide); 450 451 snprintf(cmd, sizeof(cmd), "-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, ifn, costCString); 452 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 453 return res; 454} 455 456int BandwidthController::cleanupCostlyIface(const char *ifn, QuotaType quotaType) { 457 char cmd[MAX_CMD_LEN]; 458 int res = 0; 459 std::string costString; 460 const char *costCString; 461 462 switch (quotaType) { 463 case QuotaUnique: 464 costString = "costly_"; 465 costString += ifn; 466 costCString = costString.c_str(); 467 break; 468 case QuotaShared: 469 costCString = "costly_shared"; 470 break; 471 default: 472 ALOGE("Unexpected quotatype %d", quotaType); 473 return -1; 474 } 475 476 snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString); 477 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 478 snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString); 479 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 480 481 /* The "-N costly_shared" is created upfront, no need to handle it here. */ 482 if (quotaType == QuotaUnique) { 483 snprintf(cmd, sizeof(cmd), "-F %s", costCString); 484 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 485 snprintf(cmd, sizeof(cmd), "-X %s", costCString); 486 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 487 } 488 return res; 489} 490 491int BandwidthController::setInterfaceSharedQuota(const char *iface, int64_t maxBytes) { 492 char cmd[MAX_CMD_LEN]; 493 char ifn[MAX_IFACENAME_LEN]; 494 int res = 0; 495 std::string quotaCmd; 496 std::string ifaceName; 497 ; 498 const char *costName = "shared"; 499 std::list<std::string>::iterator it; 500 501 if (!maxBytes) { 502 /* Don't talk about -1, deprecate it. */ 503 ALOGE("Invalid bytes value. 1..max_int64."); 504 return -1; 505 } 506 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 507 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 508 return -1; 509 } 510 ifaceName = ifn; 511 512 if (maxBytes == -1) { 513 return removeInterfaceSharedQuota(ifn); 514 } 515 516 /* Insert ingress quota. */ 517 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) { 518 if (*it == ifaceName) 519 break; 520 } 521 522 if (it == sharedQuotaIfaces.end()) { 523 res |= prepCostlyIface(ifn, QuotaShared); 524 if (sharedQuotaIfaces.empty()) { 525 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 526 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 527 if (res) { 528 ALOGE("Failed set quota rule"); 529 goto fail; 530 } 531 sharedQuotaBytes = maxBytes; 532 } 533 sharedQuotaIfaces.push_front(ifaceName); 534 535 } 536 537 if (maxBytes != sharedQuotaBytes) { 538 res |= updateQuota(costName, maxBytes); 539 if (res) { 540 ALOGE("Failed update quota for %s", costName); 541 goto fail; 542 } 543 sharedQuotaBytes = maxBytes; 544 } 545 return 0; 546 547 fail: 548 /* 549 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse 550 * rules in the kernel to see which ones need cleaning up. 551 * For now callers needs to choose if they want to "ndc bandwidth enable" 552 * which resets everything. 553 */ 554 removeInterfaceSharedQuota(ifn); 555 return -1; 556} 557 558/* It will also cleanup any shared alerts */ 559int BandwidthController::removeInterfaceSharedQuota(const char *iface) { 560 char ifn[MAX_IFACENAME_LEN]; 561 int res = 0; 562 std::string ifaceName; 563 std::list<std::string>::iterator it; 564 const char *costName = "shared"; 565 566 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 567 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 568 return -1; 569 } 570 ifaceName = ifn; 571 572 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) { 573 if (*it == ifaceName) 574 break; 575 } 576 if (it == sharedQuotaIfaces.end()) { 577 ALOGE("No such iface %s to delete", ifn); 578 return -1; 579 } 580 581 res |= cleanupCostlyIface(ifn, QuotaShared); 582 sharedQuotaIfaces.erase(it); 583 584 if (sharedQuotaIfaces.empty()) { 585 std::string quotaCmd; 586 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes); 587 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 588 sharedQuotaBytes = 0; 589 if (sharedAlertBytes) { 590 removeSharedAlert(); 591 sharedAlertBytes = 0; 592 } 593 } 594 return res; 595} 596 597int BandwidthController::setInterfaceQuota(const char *iface, int64_t maxBytes) { 598 char ifn[MAX_IFACENAME_LEN]; 599 int res = 0; 600 std::string ifaceName; 601 const char *costName; 602 std::list<QuotaInfo>::iterator it; 603 std::string quotaCmd; 604 605 if (!maxBytes) { 606 /* Don't talk about -1, deprecate it. */ 607 ALOGE("Invalid bytes value. 1..max_int64."); 608 return -1; 609 } 610 if (maxBytes == -1) { 611 return removeInterfaceQuota(iface); 612 } 613 614 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 615 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 616 return -1; 617 } 618 ifaceName = ifn; 619 costName = iface; 620 621 /* Insert ingress quota. */ 622 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 623 if (it->ifaceName == ifaceName) 624 break; 625 } 626 627 if (it == quotaIfaces.end()) { 628 res |= prepCostlyIface(ifn, QuotaUnique); 629 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 630 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 631 if (res) { 632 ALOGE("Failed set quota rule"); 633 goto fail; 634 } 635 636 quotaIfaces.push_front(QuotaInfo(ifaceName, maxBytes, 0)); 637 638 } else { 639 res |= updateQuota(costName, maxBytes); 640 if (res) { 641 ALOGE("Failed update quota for %s", iface); 642 goto fail; 643 } 644 it->quota = maxBytes; 645 } 646 return 0; 647 648 fail: 649 /* 650 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse 651 * rules in the kernel to see which ones need cleaning up. 652 * For now callers needs to choose if they want to "ndc bandwidth enable" 653 * which resets everything. 654 */ 655 removeInterfaceSharedQuota(ifn); 656 return -1; 657} 658 659int BandwidthController::getInterfaceSharedQuota(int64_t *bytes) { 660 return getInterfaceQuota("shared", bytes); 661} 662 663int BandwidthController::getInterfaceQuota(const char *costName, int64_t *bytes) { 664 FILE *fp; 665 char *fname; 666 int scanRes; 667 668 asprintf(&fname, "/proc/net/xt_quota/%s", costName); 669 fp = fopen(fname, "r"); 670 free(fname); 671 if (!fp) { 672 ALOGE("Reading quota %s failed (%s)", costName, strerror(errno)); 673 return -1; 674 } 675 scanRes = fscanf(fp, "%lld", bytes); 676 ALOGV("Read quota res=%d bytes=%lld", scanRes, *bytes); 677 fclose(fp); 678 return scanRes == 1 ? 0 : -1; 679} 680 681int BandwidthController::removeInterfaceQuota(const char *iface) { 682 683 char ifn[MAX_IFACENAME_LEN]; 684 int res = 0; 685 std::string ifaceName; 686 const char *costName; 687 std::list<QuotaInfo>::iterator it; 688 689 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 690 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 691 return -1; 692 } 693 ifaceName = ifn; 694 costName = iface; 695 696 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 697 if (it->ifaceName == ifaceName) 698 break; 699 } 700 701 if (it == quotaIfaces.end()) { 702 ALOGE("No such iface %s to delete", ifn); 703 return -1; 704 } 705 706 /* This also removes the quota command of CostlyIface chain. */ 707 res |= cleanupCostlyIface(ifn, QuotaUnique); 708 709 quotaIfaces.erase(it); 710 711 return res; 712} 713 714int BandwidthController::updateQuota(const char *quotaName, int64_t bytes) { 715 FILE *fp; 716 char *fname; 717 718 asprintf(&fname, "/proc/net/xt_quota/%s", quotaName); 719 fp = fopen(fname, "w"); 720 free(fname); 721 if (!fp) { 722 ALOGE("Updating quota %s failed (%s)", quotaName, strerror(errno)); 723 return -1; 724 } 725 fprintf(fp, "%lld\n", bytes); 726 fclose(fp); 727 return 0; 728} 729 730int BandwidthController::runIptablesAlertCmd(IptOp op, const char *alertName, int64_t bytes) { 731 int res = 0; 732 const char *opFlag; 733 char *alertQuotaCmd; 734 735 switch (op) { 736 case IptOpInsert: 737 opFlag = "-I"; 738 break; 739 case IptOpReplace: 740 opFlag = "-R"; 741 break; 742 default: 743 case IptOpDelete: 744 opFlag = "-D"; 745 break; 746 } 747 748 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_INPUT", 749 bytes, alertName); 750 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd); 751 free(alertQuotaCmd); 752 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_OUTPUT", 753 bytes, alertName); 754 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd); 755 free(alertQuotaCmd); 756 return res; 757} 758 759int BandwidthController::runIptablesAlertFwdCmd(IptOp op, const char *alertName, int64_t bytes) { 760 int res = 0; 761 const char *opFlag; 762 char *alertQuotaCmd; 763 764 switch (op) { 765 case IptOpInsert: 766 opFlag = "-I"; 767 break; 768 case IptOpReplace: 769 opFlag = "-R"; 770 break; 771 default: 772 case IptOpDelete: 773 opFlag = "-D"; 774 break; 775 } 776 777 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_FORWARD", 778 bytes, alertName); 779 res = runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd); 780 free(alertQuotaCmd); 781 return res; 782} 783 784int BandwidthController::setGlobalAlert(int64_t bytes) { 785 const char *alertName = ALERT_GLOBAL_NAME; 786 int res = 0; 787 788 if (!bytes) { 789 ALOGE("Invalid bytes value. 1..max_int64."); 790 return -1; 791 } 792 if (globalAlertBytes) { 793 res = updateQuota(alertName, bytes); 794 } else { 795 res = runIptablesAlertCmd(IptOpInsert, alertName, bytes); 796 if (globalAlertTetherCount) { 797 ALOGV("setGlobalAlert for %d tether", globalAlertTetherCount); 798 res |= runIptablesAlertFwdCmd(IptOpInsert, alertName, bytes); 799 } 800 } 801 globalAlertBytes = bytes; 802 return res; 803} 804 805int BandwidthController::setGlobalAlertInForwardChain(void) { 806 const char *alertName = ALERT_GLOBAL_NAME; 807 int res = 0; 808 809 globalAlertTetherCount++; 810 ALOGV("setGlobalAlertInForwardChain(): %d tether", globalAlertTetherCount); 811 812 /* 813 * If there is no globalAlert active we are done. 814 * If there is an active globalAlert but this is not the 1st 815 * tether, we are also done. 816 */ 817 if (!globalAlertBytes || globalAlertTetherCount != 1) { 818 return 0; 819 } 820 821 /* We only add the rule if this was the 1st tether added. */ 822 res = runIptablesAlertFwdCmd(IptOpInsert, alertName, globalAlertBytes); 823 return res; 824} 825 826int BandwidthController::removeGlobalAlert(void) { 827 828 const char *alertName = ALERT_GLOBAL_NAME; 829 int res = 0; 830 831 if (!globalAlertBytes) { 832 ALOGE("No prior alert set"); 833 return -1; 834 } 835 res = runIptablesAlertCmd(IptOpDelete, alertName, globalAlertBytes); 836 if (globalAlertTetherCount) { 837 res |= runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes); 838 } 839 globalAlertBytes = 0; 840 return res; 841} 842 843int BandwidthController::removeGlobalAlertInForwardChain(void) { 844 int res = 0; 845 const char *alertName = ALERT_GLOBAL_NAME; 846 847 if (!globalAlertTetherCount) { 848 ALOGE("No prior alert set"); 849 return -1; 850 } 851 852 globalAlertTetherCount--; 853 /* 854 * If there is no globalAlert active we are done. 855 * If there is an active globalAlert but there are more 856 * tethers, we are also done. 857 */ 858 if (!globalAlertBytes || globalAlertTetherCount >= 1) { 859 return 0; 860 } 861 862 /* We only detete the rule if this was the last tether removed. */ 863 res = runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes); 864 return res; 865} 866 867int BandwidthController::setSharedAlert(int64_t bytes) { 868 if (!sharedQuotaBytes) { 869 ALOGE("Need to have a prior shared quota set to set an alert"); 870 return -1; 871 } 872 if (!bytes) { 873 ALOGE("Invalid bytes value. 1..max_int64."); 874 return -1; 875 } 876 return setCostlyAlert("shared", bytes, &sharedAlertBytes); 877} 878 879int BandwidthController::removeSharedAlert(void) { 880 return removeCostlyAlert("shared", &sharedAlertBytes); 881} 882 883int BandwidthController::setInterfaceAlert(const char *iface, int64_t bytes) { 884 std::list<QuotaInfo>::iterator it; 885 886 if (!bytes) { 887 ALOGE("Invalid bytes value. 1..max_int64."); 888 return -1; 889 } 890 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 891 if (it->ifaceName == iface) 892 break; 893 } 894 895 if (it == quotaIfaces.end()) { 896 ALOGE("Need to have a prior interface quota set to set an alert"); 897 return -1; 898 } 899 900 return setCostlyAlert(iface, bytes, &it->alert); 901} 902 903int BandwidthController::removeInterfaceAlert(const char *iface) { 904 std::list<QuotaInfo>::iterator it; 905 906 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 907 if (it->ifaceName == iface) 908 break; 909 } 910 911 if (it == quotaIfaces.end()) { 912 ALOGE("No prior alert set for interface %s", iface); 913 return -1; 914 } 915 916 return removeCostlyAlert(iface, &it->alert); 917} 918 919int BandwidthController::setCostlyAlert(const char *costName, int64_t bytes, int64_t *alertBytes) { 920 char *alertQuotaCmd; 921 char *chainNameAndPos; 922 int res = 0; 923 char *alertName; 924 925 if (!bytes) { 926 ALOGE("Invalid bytes value. 1..max_int64."); 927 return -1; 928 } 929 asprintf(&alertName, "%sAlert", costName); 930 if (*alertBytes) { 931 res = updateQuota(alertName, *alertBytes); 932 } else { 933 asprintf(&chainNameAndPos, "costly_%s %d", costName, ALERT_RULE_POS_IN_COSTLY_CHAIN); 934 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "-I", chainNameAndPos, bytes, alertName); 935 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd); 936 free(alertQuotaCmd); 937 free(chainNameAndPos); 938 } 939 *alertBytes = bytes; 940 free(alertName); 941 return res; 942} 943 944int BandwidthController::removeCostlyAlert(const char *costName, int64_t *alertBytes) { 945 char *alertQuotaCmd; 946 char *chainName; 947 char *alertName; 948 int res = 0; 949 950 asprintf(&alertName, "%sAlert", costName); 951 if (!*alertBytes) { 952 ALOGE("No prior alert set for %s alert", costName); 953 return -1; 954 } 955 956 asprintf(&chainName, "costly_%s", costName); 957 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "-D", chainName, *alertBytes, alertName); 958 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd); 959 free(alertQuotaCmd); 960 free(chainName); 961 962 *alertBytes = 0; 963 free(alertName); 964 return res; 965} 966 967/* 968 * Parse the ptks and bytes out of: 969 * Chain FORWARD (policy RETURN 0 packets, 0 bytes) 970 * pkts bytes target prot opt in out source destination 971 * 0 0 RETURN all -- rmnet0 wlan0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED 972 * 0 0 DROP all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0 state INVALID 973 * 0 0 RETURN all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0 974 * 975 */ 976int BandwidthController::parseForwardChainStats(TetherStats &stats, FILE *fp, 977 std::string &extraProcessingInfo) { 978 int res; 979 char lineBuffer[MAX_IPT_OUTPUT_LINE_LEN]; 980 char iface0[MAX_IPT_OUTPUT_LINE_LEN]; 981 char iface1[MAX_IPT_OUTPUT_LINE_LEN]; 982 char rest[MAX_IPT_OUTPUT_LINE_LEN]; 983 984 char *buffPtr; 985 int64_t packets, bytes; 986 987 while (NULL != (buffPtr = fgets(lineBuffer, MAX_IPT_OUTPUT_LINE_LEN, fp))) { 988 /* Clean up, so a failed parse can still print info */ 989 iface0[0] = iface1[0] = rest[0] = packets = bytes = 0; 990 res = sscanf(buffPtr, "%lld %lld RETURN all -- %s %s 0.%s", 991 &packets, &bytes, iface0, iface1, rest); 992 ALOGV("parse res=%d iface0=<%s> iface1=<%s> pkts=%lld bytes=%lld rest=<%s> orig line=<%s>", res, 993 iface0, iface1, packets, bytes, rest, buffPtr); 994 extraProcessingInfo += buffPtr; 995 996 if (res != 5) { 997 continue; 998 } 999 if ((stats.ifaceIn == iface0) && (stats.ifaceOut == iface1)) { 1000 ALOGV("iface_in=%s iface_out=%s rx_bytes=%lld rx_packets=%lld ", iface0, iface1, bytes, packets); 1001 stats.rxPackets = packets; 1002 stats.rxBytes = bytes; 1003 } else if ((stats.ifaceOut == iface0) && (stats.ifaceIn == iface1)) { 1004 ALOGV("iface_in=%s iface_out=%s tx_bytes=%lld tx_packets=%lld ", iface1, iface0, bytes, packets); 1005 stats.txPackets = packets; 1006 stats.txBytes = bytes; 1007 } 1008 } 1009 /* Failure if rx or tx was not found */ 1010 return (stats.rxBytes == -1 || stats.txBytes == -1) ? -1 : 0; 1011} 1012 1013 1014char *BandwidthController::TetherStats::getStatsLine(void) { 1015 char *msg; 1016 asprintf(&msg, "%s %s %lld %lld %lld %lld", ifaceIn.c_str(), ifaceOut.c_str(), 1017 rxBytes, rxPackets, txBytes, txPackets); 1018 return msg; 1019} 1020 1021int BandwidthController::getTetherStats(TetherStats &stats, std::string &extraProcessingInfo) { 1022 int res; 1023 std::string fullCmd; 1024 FILE *iptOutput; 1025 const char *cmd; 1026 1027 if (stats.rxBytes != -1 || stats.txBytes != -1) { 1028 ALOGE("Unexpected input stats. Byte counts should be -1."); 1029 return -1; 1030 } 1031 1032 /* 1033 * Why not use some kind of lib to talk to iptables? 1034 * Because the only libs are libiptc and libip6tc in iptables, and they are 1035 * not easy to use. They require the known iptables match modules to be 1036 * preloaded/linked, and require apparently a lot of wrapper code to get 1037 * the wanted info. 1038 */ 1039 fullCmd = IPTABLES_PATH; 1040 fullCmd += " -nvx -L natctrl_FORWARD"; 1041 iptOutput = popen(fullCmd.c_str(), "r"); 1042 if (!iptOutput) { 1043 ALOGE("Failed to run %s err=%s", fullCmd.c_str(), strerror(errno)); 1044 extraProcessingInfo += "Failed to run iptables."; 1045 return -1; 1046 } 1047 res = parseForwardChainStats(stats, iptOutput, extraProcessingInfo); 1048 pclose(iptOutput); 1049 1050 /* Currently NatController doesn't do ipv6 tethering, so we are done. */ 1051 return res; 1052} 1053