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