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