BandwidthController.cpp revision 26e0d49fa743d7881104196a9eda733bd2aac92f
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#include <stdlib.h> 18#include <fcntl.h> 19#include <string.h> 20 21#include <sys/socket.h> 22#include <sys/stat.h> 23#include <sys/types.h> 24#include <sys/wait.h> 25 26#include <linux/netlink.h> 27#include <linux/rtnetlink.h> 28#include <linux/pkt_sched.h> 29 30#define LOG_TAG "BandwidthController" 31#include <cutils/log.h> 32#include <cutils/properties.h> 33 34extern "C" int logwrap(int argc, const char **argv, int background); 35 36#include "BandwidthController.h" 37 38const int BandwidthController::MAX_CMD_LEN = 1024; 39const int BandwidthController::MAX_IFACENAME_LEN = 64; 40const int BandwidthController::MAX_CMD_ARGS = 32; 41const char BandwidthController::IPTABLES_PATH[] = "/system/bin/iptables"; 42const char BandwidthController::IP6TABLES_PATH[] = "/system/bin/ip6tables"; 43 44/** 45 * Some comments about the rules: 46 * * Ordering 47 * - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains. 48 * E.g. "-I INPUT -i rmnet0 --goto costly" 49 * - quota'd rules in the costly chain should be before penalty_box lookups. 50 * 51 * * global quota vs per interface quota 52 * - global quota for all costly interfaces uses a single costly chain: 53 * . initial rules 54 * iptables -N costly 55 * iptables -I INPUT -i iface0 --goto costly 56 * iptables -I OUTPUT -o iface0 --goto costly 57 * iptables -I costly -m quota \! --quota 500000 --jump REJECT --reject-with icmp-net-prohibited 58 * iptables -A costly --jump penalty_box 59 * iptables -A costly -m owner --socket-exists 60 * . adding a new iface to this, E.g.: 61 * iptables -I INPUT -i iface1 --goto costly 62 * iptables -I OUTPUT -o iface1 --goto costly 63 * 64 * - quota per interface. This is achieve by having "costly" chains per quota. 65 * E.g. adding a new costly interface iface0 with its own quota: 66 * iptables -N costly_iface0 67 * iptables -I INPUT -i iface0 --goto costly_iface0 68 * iptables -I OUTPUT -o iface0 --goto costly_iface0 69 * iptables -A costly_iface0 -m quota \! --quota 500000 --jump REJECT --reject-with icmp-net-prohibited 70 * iptables -A costly_iface0 --jump penalty_box 71 * iptables -A costly_iface0 -m owner --socket-exists 72 * 73 * * penalty_box handling: 74 * - only one penalty_box for all interfaces 75 * E.g Adding an app: 76 * iptables -A penalty_box -m owner --uid-owner app_3 --jump REJECT --reject-with icmp-net-prohibited 77 */ 78const char *BandwidthController::cleanupCommands[] = { 79 /* Cleanup rules. */ 80 "-F", 81 "-t raw -F", 82 "-X costly", 83 "-X penalty_box", 84}; 85 86const char *BandwidthController::setupCommands[] = { 87 /* Created needed chains. */ 88 "-N costly", 89 "-N penalty_box", 90}; 91 92const char *BandwidthController::basicAccountingCommands[] = { 93 "-F INPUT", 94 "-A INPUT -i lo --jump ACCEPT", 95 "-A INPUT -m owner --socket-exists", /* This is a tracking rule. */ 96 97 "-F OUTPUT", 98 "-A OUTPUT -o lo --jump ACCEPT", 99 "-A OUTPUT -m owner --socket-exists", /* This is a tracking rule. */ 100 101 "-F costly", 102 "-A costly --jump penalty_box", 103 "-A costly -m owner --socket-exists", /* This is a tracking rule. */ 104 /* TODO(jpa): Figure out why iptables doesn't correctly return from this 105 * chain. For now, hack the chain exit with an ACCEPT. 106 */ 107 "-A costly --jump ACCEPT", 108}; 109 110BandwidthController::BandwidthController(void) { 111 112 char value[PROPERTY_VALUE_MAX]; 113 114 property_get("persist.bandwidth.enable", value, "0"); 115 if (!strcmp(value, "1")) { 116 enableBandwidthControl(); 117 } 118 119} 120 121int BandwidthController::runIpxtablesCmd(const char *cmd, IptRejectOp rejectHandling) { 122 int res = 0; 123 LOGD("runIpxtablesCmd(cmd=%s)", cmd); 124 res |= runIptablesCmd(cmd, rejectHandling, IptIpV4); 125 res |= runIptablesCmd(cmd, rejectHandling, IptIpV6); 126 return res; 127} 128 129int BandwidthController::StrncpyAndCheck(char *buffer, const char *src, size_t buffSize) { 130 131 memset(buffer, '\0', buffSize); // strncpy() is not filling leftover with '\0' 132 strncpy(buffer, src, buffSize); 133 return buffer[buffSize - 1]; 134} 135 136int BandwidthController::runIptablesCmd(const char *cmd, IptRejectOp rejectHandling, IptIpVer iptVer) { 137 char buffer[MAX_CMD_LEN]; 138 const char *argv[MAX_CMD_ARGS]; 139 int argc = 0; 140 char *next = buffer; 141 char *tmp; 142 143 std::string fullCmd = cmd; 144 145 if (rejectHandling == IptRejectAdd) { 146 fullCmd += " --jump REJECT --reject-with"; 147 switch (iptVer) { 148 case IptIpV4: 149 fullCmd += " icmp-net-prohibited"; 150 break; 151 case IptIpV6: 152 fullCmd += " icmp6-adm-prohibited"; 153 break; 154 } 155 } 156 157 argc = 0; 158 argv[argc++] = iptVer == IptIpV4 ? IPTABLES_PATH : IP6TABLES_PATH; 159 LOGD("About to run: %s %s", argv[0], fullCmd.c_str()); 160 161 LOGD("runIpxtablesCmd(): fullCmd.c_str()=%s buffSize=%d", fullCmd.c_str(), sizeof(buffer)); 162 if (StrncpyAndCheck(buffer, fullCmd.c_str(), sizeof(buffer))) { 163 LOGE("iptables command too long"); 164 return -1; 165 } 166 167 while ((tmp = strsep(&next, " "))) { 168 argv[argc++] = tmp; 169 if (argc >= MAX_CMD_ARGS) { 170 LOGE("iptables argument overflow"); 171 return -1; 172 } 173 } 174 175 argv[argc] = NULL; 176 LOGD("runIpxtablesCmd(): argc=%d, argv[argc]=%p", argc, argv[argc]); 177 /* TODO(jpa): Once this stabilizes, remove logwrap() as it tends to wedge netd 178 * Then just talk directly to the kernel via rtnetlink. 179 */ 180 for (int i = 0 ; i < argc; i++) { 181 LOGD("runIpxtablesCmd(): argv[%d]=%p:%s", i, argv[i], argv[i]?:"null"); 182 } 183 return logwrap(argc, argv, 0); 184} 185 186int BandwidthController::enableBandwidthControl(void) { 187 int res; 188 /* Some of the initialCommands are allowed to fail */ 189 runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, RunCmdFailureOk); 190 runCommands(sizeof(setupCommands) / sizeof(char*), setupCommands, RunCmdFailureOk); 191 res = runCommands(sizeof(basicAccountingCommands) / sizeof(char*), basicAccountingCommands, RunCmdFailureBad); 192 return res; 193 194} 195 196int BandwidthController::disableBandwidthControl(void) { 197 /* The cleanupCommands are allowed to fail. */ 198 runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, RunCmdFailureOk); 199 return 0; 200} 201 202int BandwidthController::runCommands(int numCommands, const char *commands[], RunCmdErrHandling cmdErrHandling) { 203 int res = 0; 204 LOGD("runCommands(): %d commands", numCommands); 205 for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) { 206 res = runIpxtablesCmd(commands[cmdNum], IptRejectNoAdd); 207 if (res && cmdErrHandling != RunCmdFailureBad) 208 return res; 209 } 210 return cmdErrHandling == RunCmdFailureBad ? res : 0; 211} 212 213std::string BandwidthController::makeIptablesNaughtyCmd(IptOp op, int uid) { 214 std::string res; 215 char *convBuff; 216 217 switch (op) { 218 case IptOpInsert: 219 res = "-I"; 220 break; 221 case IptOpReplace: 222 res = "-R"; 223 break; 224 default: 225 case IptOpDelete: 226 res = "-D"; 227 break; 228 } 229 res += " penalty_box"; 230 asprintf(&convBuff, "%d", uid); 231 res += " -m owner --uid-owner "; 232 res += convBuff; 233 free(convBuff); 234 LOGD("makeIptablesNaughtyCmd() res=%s", res.c_str()); 235 return res; 236} 237 238int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) { 239 return maninpulateNaughtyApps(numUids, appUids, NaughtyAppOpAdd); 240} 241 242int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) { 243 return maninpulateNaughtyApps(numUids, appUids, NaughtyAppOpRemove); 244} 245 246int BandwidthController::maninpulateNaughtyApps(int numUids, char *appStrUids[], NaughtyAppOp appOp) { 247 char cmd[MAX_CMD_LEN]; 248 int uidNum; 249 const char *failLogTemplate; 250 IptOp op; 251 int appUids[numUids]; 252 std::string naughtyCmd; 253 LOGD("manipulateNaughtyApps()"); 254 switch (appOp) { 255 case NaughtyAppOpAdd: 256 op = IptOpInsert; 257 failLogTemplate = "Failed to add app uid %d to penalty box."; 258 break; 259 case NaughtyAppOpRemove: 260 op = IptOpDelete; 261 failLogTemplate = "Failed to delete app uid %d from penalty box."; 262 break; 263 } 264 265 for (uidNum = 0; uidNum < numUids; uidNum++) { 266 appUids[uidNum] = atol(appStrUids[uidNum]); 267 if (appUids[uidNum] == 0) { 268 LOGE(failLogTemplate, appUids[uidNum]); 269 goto fail_parse; 270 } 271 } 272 LOGD("manipulateNaughtyApps() got the appUids"); 273 274 for (uidNum = 0; uidNum < numUids; uidNum++) { 275 naughtyCmd = makeIptablesNaughtyCmd(op, appUids[uidNum]); 276 if (runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd)) { 277 LOGE(failLogTemplate, appUids[uidNum]); 278 goto fail_with_uidNum; 279 } 280 } 281 return 0; 282 283fail_with_uidNum: 284 /* Try to remove the uid that failed in any case*/ 285 naughtyCmd = makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum]); 286 runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd); 287fail_parse: 288 return -1; 289} 290 291std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota) { 292 std::string res; 293 char convBuff[21]; // log10(2^64) ~ 20 294 295 LOGD("makeIptablesQuotaCmd(%d, %llu)", op, quota); 296 297 switch (op) { 298 case IptOpInsert: 299 res = "-I"; 300 break; 301 case IptOpReplace: 302 res = "-R"; 303 break; 304 default: 305 case IptOpDelete: 306 res = "-D"; 307 break; 308 } 309 res += " costly"; 310 if (costName) { 311 res += "_"; 312 res += costName; 313 } 314 sprintf(convBuff, "%lld", quota); 315 /* TODO(jpa): Use -m quota2 --name " + costName + " ! --quota " 316 * once available. 317 */ 318 res += " -m quota ! --quota "; 319 res += convBuff; 320 ; 321 // The requried --jump REJECT ... will be added later. 322 return res; 323} 324 325int BandwidthController::prepCostlyIface(const char *ifn, QuotaType quotaType) { 326 char cmd[MAX_CMD_LEN]; 327 int res = 0; 328 std::string costString; 329 const char *costCString; 330 331 costString = "costly"; 332 /* The "-N costly" is created upfront, no need to handle it here. */ 333 switch (quotaType) { 334 case QuotaUnique: 335 costString += "_"; 336 costString += ifn; 337 costCString = costString.c_str(); 338 snprintf(cmd, sizeof(cmd), "-N %s", costCString); 339 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 340 snprintf(cmd, sizeof(cmd), "-A %s -j penalty_box", costCString); 341 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 342 snprintf(cmd, sizeof(cmd), "-A %s -m owner --socket-exists", costCString); 343 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 344 /* TODO(jpa): Figure out why iptables doesn't correctly return from this 345 * chain. For now, hack the chain exit with an ACCEPT. 346 */ 347 snprintf(cmd, sizeof(cmd), "-A %s --jump ACCEPT", costCString); 348 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 349 break; 350 case QuotaShared: 351 costCString = costString.c_str(); 352 break; 353 } 354 355 snprintf(cmd, sizeof(cmd), "-I INPUT -i %s --goto %s", ifn, costCString); 356 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 357 snprintf(cmd, sizeof(cmd), "-I OUTPUT -o %s --goto %s", ifn, costCString); 358 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 359 return res; 360} 361 362int BandwidthController::cleanupCostlyIface(const char *ifn, QuotaType quotaType) { 363 char cmd[MAX_CMD_LEN]; 364 int res = 0; 365 std::string costString; 366 const char *costCString; 367 368 costString = "costly"; 369 switch (quotaType) { 370 case QuotaUnique: 371 costString += "_"; 372 costString += ifn; 373 costCString = costString.c_str(); 374 break; 375 case QuotaShared: 376 costCString = costString.c_str(); 377 break; 378 } 379 380 snprintf(cmd, sizeof(cmd), "-D INPUT -i %s --goto %s", ifn, costCString); 381 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 382 snprintf(cmd, sizeof(cmd), "-D OUTPUT -o %s --goto %s", ifn, costCString); 383 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 384 385 /* The "-N costly" is created upfront, no need to handle it here. */ 386 if (quotaType == QuotaUnique) { 387 snprintf(cmd, sizeof(cmd), "-F %s", costCString); 388 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 389 } 390 return res; 391} 392 393int BandwidthController::setInterfaceSharedQuota(const char *iface, int64_t maxBytes) { 394 char cmd[MAX_CMD_LEN]; 395 char ifn[MAX_IFACENAME_LEN]; 396 int res = 0; 397 std::string quotaCmd; 398 std::string ifaceName;; 399 const char *costName = NULL; /* Shared quota */ 400 std::list<std::string>::iterator it; 401 402 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 403 LOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 404 return -1; 405 } 406 ifaceName = ifn; 407 408 if (maxBytes == -1) { 409 return removeInterfaceSharedQuota(ifn); 410 } 411 412 /* Insert ingress quota. */ 413 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) { 414 if (*it == ifaceName) 415 break; 416 } 417 418 if (it == sharedQuotaIfaces.end()) { 419 res |= prepCostlyIface(ifn, QuotaShared); 420 if (sharedQuotaIfaces.empty()) { 421 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 422 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 423 if (res) { 424 LOGE("Failed set quota rule."); 425 goto fail; 426 } 427 sharedQuotaBytes = maxBytes; 428 } 429 sharedQuotaIfaces.push_front(ifaceName); 430 431 } 432 433 if (maxBytes != sharedQuotaBytes) { 434 /* Instead of replacing, which requires being aware of the rules in 435 * the kernel, we just add a new one, then delete the older one. 436 */ 437 438 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 439 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 440 441 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes); 442 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 443 444 if (res) { 445 LOGE("Failed replace quota rule."); 446 goto fail; 447 } 448 sharedQuotaBytes = maxBytes; 449 } 450 return 0; 451 452 fail: 453 /* 454 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse 455 * rules in the kernel to see which ones need cleaning up. 456 * For now callers needs to choose if they want to "ndc bandwidth enable" 457 * which resets everything. 458 */ 459 removeInterfaceSharedQuota(ifn); 460 return -1; 461} 462 463int BandwidthController::removeInterfaceSharedQuota(const char *iface) { 464 char ifn[MAX_IFACENAME_LEN]; 465 int res = 0; 466 std::string ifaceName; 467 std::list<std::string>::iterator it; 468 469 if(StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 470 LOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 471 return -1; 472 } 473 ifaceName =ifn; 474 475 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) { 476 if (*it == ifaceName) 477 break; 478 } 479 if (it == sharedQuotaIfaces.end()) { 480 LOGE("No such iface %s to delete.", ifn); 481 return -1; 482 } 483 484 res |= cleanupCostlyIface(ifn, QuotaShared); 485 sharedQuotaIfaces.erase(it); 486 487 if (sharedQuotaIfaces.empty()) { 488 std::string quotaCmd; 489 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, NULL, sharedQuotaBytes); 490 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 491 sharedQuotaBytes = -1; 492 } 493 494 return res; 495} 496 497int BandwidthController::setInterfaceQuota(const char *iface, int64_t maxBytes) { 498 char ifn[MAX_IFACENAME_LEN]; 499 int res = 0; 500 std::string ifaceName; 501 const char *costName; 502 std::list<QuotaInfo>::iterator it; 503 std::string quotaCmd; 504 505 if (maxBytes == -1) { 506 return removeInterfaceQuota(iface); 507 } 508 509 if(StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 510 LOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 511 return -1; 512 } 513 ifaceName = ifn; 514 costName = iface; 515 516 517 /* Insert ingress quota. */ 518 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 519 if (it->first == ifaceName) 520 break; 521 } 522 523 if (it == quotaIfaces.end()) { 524 res |= prepCostlyIface(ifn, QuotaUnique); 525 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 526 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 527 if (res) { 528 LOGE("Failed set quota rule."); 529 goto fail; 530 } 531 532 quotaIfaces.push_front(QuotaInfo(ifaceName, maxBytes)); 533 534 } else { 535 /* Instead of replacing, which requires being aware of the rules in 536 * the kernel, we just add a new one, then delete the older one. 537 */ 538 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 539 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 540 541 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, it->second); 542 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 543 544 if (res) { 545 LOGE("Failed replace quota rule."); 546 goto fail; 547 } 548 it->second = maxBytes; 549 } 550 return 0; 551 552 fail: 553 /* 554 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse 555 * rules in the kernel to see which ones need cleaning up. 556 * For now callers needs to choose if they want to "ndc bandwidth enable" 557 * which resets everything. 558 */ 559 removeInterfaceSharedQuota(ifn); 560 return -1; 561} 562 563int BandwidthController::removeInterfaceQuota(const char *iface) { 564 565 char ifn[MAX_IFACENAME_LEN]; 566 int res = 0; 567 std::string ifaceName; 568 const char *costName; 569 std::list<QuotaInfo>::iterator it; 570 571 if(StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 572 LOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 573 return -1; 574 } 575 ifaceName = ifn; 576 costName = iface; 577 578 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 579 if (it->first == ifaceName) 580 break; 581 } 582 583 if (it == quotaIfaces.end()) { 584 LOGE("No such iface %s to delete.", ifn); 585 return -1; 586 } 587 588 /* This also removes the quota command of CostlyIface chain. */ 589 res |= cleanupCostlyIface(ifn, QuotaUnique); 590 591 quotaIfaces.erase(it); 592 593 return res; 594} 595