BandwidthController.cpp revision b1d24094c2c5d48bbb3dfad4a0551ff0bf77ce6c
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 std::list<int /*uid*/>::iterator it; 350 351 switch (appOp) { 352 case NaughtyAppOpAdd: 353 op = IptOpInsert; 354 failLogTemplate = "Failed to add app uid %d to penalty box."; 355 break; 356 case NaughtyAppOpRemove: 357 op = IptOpDelete; 358 failLogTemplate = "Failed to delete app uid %d from penalty box."; 359 break; 360 default: 361 ALOGE("Unexpected app Op %d", appOp); 362 return -1; 363 } 364 365 for (uidNum = 0; uidNum < numUids; uidNum++) { 366 appUids[uidNum] = atol(appStrUids[uidNum]); 367 if (appUids[uidNum] == 0) { 368 ALOGE(failLogTemplate, appUids[uidNum]); 369 goto fail_parse; 370 } 371 } 372 373 for (uidNum = 0; uidNum < numUids; uidNum++) { 374 int uid = appUids[uidNum]; 375 for (it = naughtyAppUids.begin(); it != naughtyAppUids.end(); it++) { 376 if (*it == uid) 377 break; 378 } 379 bool found = (it != naughtyAppUids.end()); 380 381 if (appOp == NaughtyAppOpRemove) { 382 if (!found) { 383 ALOGE("No such appUid %d to remove", uid); 384 return -1; 385 } 386 naughtyAppUids.erase(it); 387 } else { 388 if (found) { 389 ALOGE("appUid %d exists already", uid); 390 return -1; 391 } 392 naughtyAppUids.push_front(uid); 393 } 394 395 naughtyCmd = makeIptablesNaughtyCmd(op, uid); 396 if (runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd)) { 397 ALOGE(failLogTemplate, uid); 398 goto fail_with_uidNum; 399 } 400 } 401 return 0; 402 403fail_with_uidNum: 404 /* Try to remove the uid that failed in any case*/ 405 naughtyCmd = makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum]); 406 runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd); 407fail_parse: 408 return -1; 409} 410 411std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota) { 412 std::string res; 413 char *buff; 414 const char *opFlag; 415 416 ALOGV("makeIptablesQuotaCmd(%d, %lld)", op, quota); 417 418 switch (op) { 419 case IptOpInsert: 420 opFlag = "-I"; 421 break; 422 case IptOpReplace: 423 opFlag = "-R"; 424 break; 425 default: 426 case IptOpDelete: 427 opFlag = "-D"; 428 break; 429 } 430 431 // The requried IP version specific --jump REJECT ... will be added later. 432 asprintf(&buff, "%s costly_%s -m quota2 ! --quota %lld --name %s", opFlag, costName, quota, 433 costName); 434 res = buff; 435 free(buff); 436 return res; 437} 438 439int BandwidthController::prepCostlyIface(const char *ifn, QuotaType quotaType) { 440 char cmd[MAX_CMD_LEN]; 441 int res = 0, res1, res2; 442 int ruleInsertPos = 1; 443 std::string costString; 444 const char *costCString; 445 446 /* The "-N costly" is created upfront, no need to handle it here. */ 447 switch (quotaType) { 448 case QuotaUnique: 449 costString = "costly_"; 450 costString += ifn; 451 costCString = costString.c_str(); 452 /* 453 * Flush the costly_<iface> is allowed to fail in case it didn't exist. 454 * Creating a new one is allowed to fail in case it existed. 455 * This helps with netd restarts. 456 */ 457 snprintf(cmd, sizeof(cmd), "-F %s", costCString); 458 res1 = runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide); 459 snprintf(cmd, sizeof(cmd), "-N %s", costCString); 460 res2 = runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide); 461 res = (res1 && res2) || (!res1 && !res2); 462 463 snprintf(cmd, sizeof(cmd), "-A %s -j penalty_box", costCString); 464 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 465 snprintf(cmd, sizeof(cmd), "-A %s -m owner --socket-exists", costCString); 466 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 467 break; 468 case QuotaShared: 469 costCString = "costly_shared"; 470 break; 471 default: 472 ALOGE("Unexpected quotatype %d", quotaType); 473 return -1; 474 } 475 476 if (globalAlertBytes) { 477 /* The alert rule comes 1st */ 478 ruleInsertPos = 2; 479 } 480 481 snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString); 482 runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide); 483 484 snprintf(cmd, sizeof(cmd), "-I bw_INPUT %d -i %s --jump %s", ruleInsertPos, ifn, costCString); 485 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 486 487 snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString); 488 runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide); 489 490 snprintf(cmd, sizeof(cmd), "-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, ifn, costCString); 491 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 492 return res; 493} 494 495int BandwidthController::cleanupCostlyIface(const char *ifn, QuotaType quotaType) { 496 char cmd[MAX_CMD_LEN]; 497 int res = 0; 498 std::string costString; 499 const char *costCString; 500 501 switch (quotaType) { 502 case QuotaUnique: 503 costString = "costly_"; 504 costString += ifn; 505 costCString = costString.c_str(); 506 break; 507 case QuotaShared: 508 costCString = "costly_shared"; 509 break; 510 default: 511 ALOGE("Unexpected quotatype %d", quotaType); 512 return -1; 513 } 514 515 snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString); 516 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 517 snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString); 518 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 519 520 /* The "-N costly_shared" is created upfront, no need to handle it here. */ 521 if (quotaType == QuotaUnique) { 522 snprintf(cmd, sizeof(cmd), "-F %s", costCString); 523 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 524 snprintf(cmd, sizeof(cmd), "-X %s", costCString); 525 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 526 } 527 return res; 528} 529 530int BandwidthController::setInterfaceSharedQuota(const char *iface, int64_t maxBytes) { 531 char cmd[MAX_CMD_LEN]; 532 char ifn[MAX_IFACENAME_LEN]; 533 int res = 0; 534 std::string quotaCmd; 535 std::string ifaceName; 536 ; 537 const char *costName = "shared"; 538 std::list<std::string>::iterator it; 539 540 if (!maxBytes) { 541 /* Don't talk about -1, deprecate it. */ 542 ALOGE("Invalid bytes value. 1..max_int64."); 543 return -1; 544 } 545 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 546 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 547 return -1; 548 } 549 ifaceName = ifn; 550 551 if (maxBytes == -1) { 552 return removeInterfaceSharedQuota(ifn); 553 } 554 555 /* Insert ingress quota. */ 556 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) { 557 if (*it == ifaceName) 558 break; 559 } 560 561 if (it == sharedQuotaIfaces.end()) { 562 res |= prepCostlyIface(ifn, QuotaShared); 563 if (sharedQuotaIfaces.empty()) { 564 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 565 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 566 if (res) { 567 ALOGE("Failed set quota rule"); 568 goto fail; 569 } 570 sharedQuotaBytes = maxBytes; 571 } 572 sharedQuotaIfaces.push_front(ifaceName); 573 574 } 575 576 if (maxBytes != sharedQuotaBytes) { 577 res |= updateQuota(costName, maxBytes); 578 if (res) { 579 ALOGE("Failed update quota for %s", costName); 580 goto fail; 581 } 582 sharedQuotaBytes = maxBytes; 583 } 584 return 0; 585 586 fail: 587 /* 588 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse 589 * rules in the kernel to see which ones need cleaning up. 590 * For now callers needs to choose if they want to "ndc bandwidth enable" 591 * which resets everything. 592 */ 593 removeInterfaceSharedQuota(ifn); 594 return -1; 595} 596 597/* It will also cleanup any shared alerts */ 598int BandwidthController::removeInterfaceSharedQuota(const char *iface) { 599 char ifn[MAX_IFACENAME_LEN]; 600 int res = 0; 601 std::string ifaceName; 602 std::list<std::string>::iterator it; 603 const char *costName = "shared"; 604 605 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 606 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 607 return -1; 608 } 609 ifaceName = ifn; 610 611 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) { 612 if (*it == ifaceName) 613 break; 614 } 615 if (it == sharedQuotaIfaces.end()) { 616 ALOGE("No such iface %s to delete", ifn); 617 return -1; 618 } 619 620 res |= cleanupCostlyIface(ifn, QuotaShared); 621 sharedQuotaIfaces.erase(it); 622 623 if (sharedQuotaIfaces.empty()) { 624 std::string quotaCmd; 625 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes); 626 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 627 sharedQuotaBytes = 0; 628 if (sharedAlertBytes) { 629 removeSharedAlert(); 630 sharedAlertBytes = 0; 631 } 632 } 633 return res; 634} 635 636int BandwidthController::setInterfaceQuota(const char *iface, int64_t maxBytes) { 637 char ifn[MAX_IFACENAME_LEN]; 638 int res = 0; 639 std::string ifaceName; 640 const char *costName; 641 std::list<QuotaInfo>::iterator it; 642 std::string quotaCmd; 643 644 if (!maxBytes) { 645 /* Don't talk about -1, deprecate it. */ 646 ALOGE("Invalid bytes value. 1..max_int64."); 647 return -1; 648 } 649 if (maxBytes == -1) { 650 return removeInterfaceQuota(iface); 651 } 652 653 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 654 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 655 return -1; 656 } 657 ifaceName = ifn; 658 costName = iface; 659 660 /* Insert ingress quota. */ 661 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 662 if (it->ifaceName == ifaceName) 663 break; 664 } 665 666 if (it == quotaIfaces.end()) { 667 res |= prepCostlyIface(ifn, QuotaUnique); 668 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 669 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 670 if (res) { 671 ALOGE("Failed set quota rule"); 672 goto fail; 673 } 674 675 quotaIfaces.push_front(QuotaInfo(ifaceName, maxBytes, 0)); 676 677 } else { 678 res |= updateQuota(costName, maxBytes); 679 if (res) { 680 ALOGE("Failed update quota for %s", iface); 681 goto fail; 682 } 683 it->quota = maxBytes; 684 } 685 return 0; 686 687 fail: 688 /* 689 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse 690 * rules in the kernel to see which ones need cleaning up. 691 * For now callers needs to choose if they want to "ndc bandwidth enable" 692 * which resets everything. 693 */ 694 removeInterfaceSharedQuota(ifn); 695 return -1; 696} 697 698int BandwidthController::getInterfaceSharedQuota(int64_t *bytes) { 699 return getInterfaceQuota("shared", bytes); 700} 701 702int BandwidthController::getInterfaceQuota(const char *costName, int64_t *bytes) { 703 FILE *fp; 704 char *fname; 705 int scanRes; 706 707 asprintf(&fname, "/proc/net/xt_quota/%s", costName); 708 fp = fopen(fname, "r"); 709 free(fname); 710 if (!fp) { 711 ALOGE("Reading quota %s failed (%s)", costName, strerror(errno)); 712 return -1; 713 } 714 scanRes = fscanf(fp, "%lld", bytes); 715 ALOGV("Read quota res=%d bytes=%lld", scanRes, *bytes); 716 fclose(fp); 717 return scanRes == 1 ? 0 : -1; 718} 719 720int BandwidthController::removeInterfaceQuota(const char *iface) { 721 722 char ifn[MAX_IFACENAME_LEN]; 723 int res = 0; 724 std::string ifaceName; 725 const char *costName; 726 std::list<QuotaInfo>::iterator it; 727 728 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 729 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 730 return -1; 731 } 732 ifaceName = ifn; 733 costName = iface; 734 735 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 736 if (it->ifaceName == ifaceName) 737 break; 738 } 739 740 if (it == quotaIfaces.end()) { 741 ALOGE("No such iface %s to delete", ifn); 742 return -1; 743 } 744 745 /* This also removes the quota command of CostlyIface chain. */ 746 res |= cleanupCostlyIface(ifn, QuotaUnique); 747 748 quotaIfaces.erase(it); 749 750 return res; 751} 752 753int BandwidthController::updateQuota(const char *quotaName, int64_t bytes) { 754 FILE *fp; 755 char *fname; 756 757 asprintf(&fname, "/proc/net/xt_quota/%s", quotaName); 758 fp = fopen(fname, "w"); 759 free(fname); 760 if (!fp) { 761 ALOGE("Updating quota %s failed (%s)", quotaName, strerror(errno)); 762 return -1; 763 } 764 fprintf(fp, "%lld\n", bytes); 765 fclose(fp); 766 return 0; 767} 768 769int BandwidthController::runIptablesAlertCmd(IptOp op, const char *alertName, int64_t bytes) { 770 int res = 0; 771 const char *opFlag; 772 const char *ifaceLimiting; 773 char *alertQuotaCmd; 774 775 switch (op) { 776 case IptOpInsert: 777 opFlag = "-I"; 778 break; 779 case IptOpReplace: 780 opFlag = "-R"; 781 break; 782 default: 783 case IptOpDelete: 784 opFlag = "-D"; 785 break; 786 } 787 788 ifaceLimiting = "! -i lo+"; 789 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "bw_INPUT", 790 bytes, alertName); 791 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd); 792 free(alertQuotaCmd); 793 ifaceLimiting = "! -o lo+"; 794 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "bw_OUTPUT", 795 bytes, alertName); 796 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd); 797 free(alertQuotaCmd); 798 return res; 799} 800 801int BandwidthController::runIptablesAlertFwdCmd(IptOp op, const char *alertName, int64_t bytes) { 802 int res = 0; 803 const char *opFlag; 804 const char *ifaceLimiting; 805 char *alertQuotaCmd; 806 807 switch (op) { 808 case IptOpInsert: 809 opFlag = "-I"; 810 break; 811 case IptOpReplace: 812 opFlag = "-R"; 813 break; 814 default: 815 case IptOpDelete: 816 opFlag = "-D"; 817 break; 818 } 819 820 ifaceLimiting = "! -i lo+"; 821 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "bw_FORWARD", 822 bytes, alertName); 823 res = runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd); 824 free(alertQuotaCmd); 825 return res; 826} 827 828int BandwidthController::setGlobalAlert(int64_t bytes) { 829 const char *alertName = ALERT_GLOBAL_NAME; 830 int res = 0; 831 832 if (!bytes) { 833 ALOGE("Invalid bytes value. 1..max_int64."); 834 return -1; 835 } 836 if (globalAlertBytes) { 837 res = updateQuota(alertName, bytes); 838 } else { 839 res = runIptablesAlertCmd(IptOpInsert, alertName, bytes); 840 if (globalAlertTetherCount) { 841 ALOGV("setGlobalAlert for %d tether", globalAlertTetherCount); 842 res |= runIptablesAlertFwdCmd(IptOpInsert, alertName, bytes); 843 } 844 } 845 globalAlertBytes = bytes; 846 return res; 847} 848 849int BandwidthController::setGlobalAlertInForwardChain(void) { 850 const char *alertName = ALERT_GLOBAL_NAME; 851 int res = 0; 852 853 globalAlertTetherCount++; 854 ALOGV("setGlobalAlertInForwardChain(): %d tether", globalAlertTetherCount); 855 856 /* 857 * If there is no globalAlert active we are done. 858 * If there is an active globalAlert but this is not the 1st 859 * tether, we are also done. 860 */ 861 if (!globalAlertBytes || globalAlertTetherCount != 1) { 862 return 0; 863 } 864 865 /* We only add the rule if this was the 1st tether added. */ 866 res = runIptablesAlertFwdCmd(IptOpInsert, alertName, globalAlertBytes); 867 return res; 868} 869 870int BandwidthController::removeGlobalAlert(void) { 871 872 const char *alertName = ALERT_GLOBAL_NAME; 873 int res = 0; 874 875 if (!globalAlertBytes) { 876 ALOGE("No prior alert set"); 877 return -1; 878 } 879 res = runIptablesAlertCmd(IptOpDelete, alertName, globalAlertBytes); 880 if (globalAlertTetherCount) { 881 res |= runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes); 882 } 883 globalAlertBytes = 0; 884 return res; 885} 886 887int BandwidthController::removeGlobalAlertInForwardChain(void) { 888 int res = 0; 889 const char *alertName = ALERT_GLOBAL_NAME; 890 891 if (!globalAlertTetherCount) { 892 ALOGE("No prior alert set"); 893 return -1; 894 } 895 896 globalAlertTetherCount--; 897 /* 898 * If there is no globalAlert active we are done. 899 * If there is an active globalAlert but there are more 900 * tethers, we are also done. 901 */ 902 if (!globalAlertBytes || globalAlertTetherCount >= 1) { 903 return 0; 904 } 905 906 /* We only detete the rule if this was the last tether removed. */ 907 res = runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes); 908 return res; 909} 910 911int BandwidthController::setSharedAlert(int64_t bytes) { 912 if (!sharedQuotaBytes) { 913 ALOGE("Need to have a prior shared quota set to set an alert"); 914 return -1; 915 } 916 if (!bytes) { 917 ALOGE("Invalid bytes value. 1..max_int64."); 918 return -1; 919 } 920 return setCostlyAlert("shared", bytes, &sharedAlertBytes); 921} 922 923int BandwidthController::removeSharedAlert(void) { 924 return removeCostlyAlert("shared", &sharedAlertBytes); 925} 926 927int BandwidthController::setInterfaceAlert(const char *iface, int64_t bytes) { 928 std::list<QuotaInfo>::iterator it; 929 930 if (!bytes) { 931 ALOGE("Invalid bytes value. 1..max_int64."); 932 return -1; 933 } 934 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 935 if (it->ifaceName == iface) 936 break; 937 } 938 939 if (it == quotaIfaces.end()) { 940 ALOGE("Need to have a prior interface quota set to set an alert"); 941 return -1; 942 } 943 944 return setCostlyAlert(iface, bytes, &it->alert); 945} 946 947int BandwidthController::removeInterfaceAlert(const char *iface) { 948 std::list<QuotaInfo>::iterator it; 949 950 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 951 if (it->ifaceName == iface) 952 break; 953 } 954 955 if (it == quotaIfaces.end()) { 956 ALOGE("No prior alert set for interface %s", iface); 957 return -1; 958 } 959 960 return removeCostlyAlert(iface, &it->alert); 961} 962 963int BandwidthController::setCostlyAlert(const char *costName, int64_t bytes, int64_t *alertBytes) { 964 char *alertQuotaCmd; 965 char *chainNameAndPos; 966 int res = 0; 967 char *alertName; 968 969 if (!bytes) { 970 ALOGE("Invalid bytes value. 1..max_int64."); 971 return -1; 972 } 973 asprintf(&alertName, "%sAlert", costName); 974 if (*alertBytes) { 975 res = updateQuota(alertName, *alertBytes); 976 } else { 977 asprintf(&chainNameAndPos, "costly_%s %d", costName, ALERT_RULE_POS_IN_COSTLY_CHAIN); 978 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "", "-I", chainNameAndPos, bytes, alertName); 979 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd); 980 free(alertQuotaCmd); 981 free(chainNameAndPos); 982 } 983 *alertBytes = bytes; 984 free(alertName); 985 return res; 986} 987 988int BandwidthController::removeCostlyAlert(const char *costName, int64_t *alertBytes) { 989 char *alertQuotaCmd; 990 char *chainName; 991 char *alertName; 992 int res = 0; 993 994 asprintf(&alertName, "%sAlert", costName); 995 if (!*alertBytes) { 996 ALOGE("No prior alert set for %s alert", costName); 997 return -1; 998 } 999 1000 asprintf(&chainName, "costly_%s", costName); 1001 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "", "-D", chainName, *alertBytes, alertName); 1002 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd); 1003 free(alertQuotaCmd); 1004 free(chainName); 1005 1006 *alertBytes = 0; 1007 free(alertName); 1008 return res; 1009} 1010 1011/* 1012 * Parse the ptks and bytes out of: 1013 * Chain FORWARD (policy RETURN 0 packets, 0 bytes) 1014 * pkts bytes target prot opt in out source destination 1015 * 0 0 RETURN all -- rmnet0 wlan0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED 1016 * 0 0 DROP all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0 state INVALID 1017 * 0 0 RETURN all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0 1018 * 1019 */ 1020int BandwidthController::parseForwardChainStats(TetherStats &stats, FILE *fp, 1021 std::string &extraProcessingInfo) { 1022 int res; 1023 char lineBuffer[MAX_IPT_OUTPUT_LINE_LEN]; 1024 char iface0[MAX_IPT_OUTPUT_LINE_LEN]; 1025 char iface1[MAX_IPT_OUTPUT_LINE_LEN]; 1026 char rest[MAX_IPT_OUTPUT_LINE_LEN]; 1027 1028 char *buffPtr; 1029 int64_t packets, bytes; 1030 1031 while (NULL != (buffPtr = fgets(lineBuffer, MAX_IPT_OUTPUT_LINE_LEN, fp))) { 1032 /* Clean up, so a failed parse can still print info */ 1033 iface0[0] = iface1[0] = rest[0] = packets = bytes = 0; 1034 res = sscanf(buffPtr, "%lld %lld RETURN all -- %s %s 0.%s", 1035 &packets, &bytes, iface0, iface1, rest); 1036 ALOGV("parse res=%d iface0=<%s> iface1=<%s> pkts=%lld bytes=%lld rest=<%s> orig line=<%s>", res, 1037 iface0, iface1, packets, bytes, rest, buffPtr); 1038 extraProcessingInfo += buffPtr; 1039 1040 if (res != 5) { 1041 continue; 1042 } 1043 if ((stats.ifaceIn == iface0) && (stats.ifaceOut == iface1)) { 1044 ALOGV("iface_in=%s iface_out=%s rx_bytes=%lld rx_packets=%lld ", iface0, iface1, bytes, packets); 1045 stats.rxPackets = packets; 1046 stats.rxBytes = bytes; 1047 } else if ((stats.ifaceOut == iface0) && (stats.ifaceIn == iface1)) { 1048 ALOGV("iface_in=%s iface_out=%s tx_bytes=%lld tx_packets=%lld ", iface1, iface0, bytes, packets); 1049 stats.txPackets = packets; 1050 stats.txBytes = bytes; 1051 } 1052 } 1053 /* Failure if rx or tx was not found */ 1054 return (stats.rxBytes == -1 || stats.txBytes == -1) ? -1 : 0; 1055} 1056 1057 1058char *BandwidthController::TetherStats::getStatsLine(void) { 1059 char *msg; 1060 asprintf(&msg, "%s %s %lld %lld %lld %lld", ifaceIn.c_str(), ifaceOut.c_str(), 1061 rxBytes, rxPackets, txBytes, txPackets); 1062 return msg; 1063} 1064 1065int BandwidthController::getTetherStats(TetherStats &stats, std::string &extraProcessingInfo) { 1066 int res; 1067 std::string fullCmd; 1068 FILE *iptOutput; 1069 const char *cmd; 1070 1071 if (stats.rxBytes != -1 || stats.txBytes != -1) { 1072 ALOGE("Unexpected input stats. Byte counts should be -1."); 1073 return -1; 1074 } 1075 1076 /* 1077 * Why not use some kind of lib to talk to iptables? 1078 * Because the only libs are libiptc and libip6tc in iptables, and they are 1079 * not easy to use. They require the known iptables match modules to be 1080 * preloaded/linked, and require apparently a lot of wrapper code to get 1081 * the wanted info. 1082 */ 1083 fullCmd = IPTABLES_PATH; 1084 fullCmd += " -nvx -L natctrl_FORWARD"; 1085 iptOutput = popen(fullCmd.c_str(), "r"); 1086 if (!iptOutput) { 1087 ALOGE("Failed to run %s err=%s", fullCmd.c_str(), strerror(errno)); 1088 extraProcessingInfo += "Failed to run iptables."; 1089 return -1; 1090 } 1091 res = parseForwardChainStats(stats, iptOutput, extraProcessingInfo); 1092 pclose(iptOutput); 1093 1094 /* Currently NatController doesn't do ipv6 tethering, so we are done. */ 1095 return res; 1096} 1097