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