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