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