BandwidthController.cpp revision a9f802c23f4c2c53fa1065b75f712ce46f384c3a
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 160 LOGD("runIptablesCmd(): %s %s", argv[0], fullCmd.c_str()); 161 if (StrncpyAndCheck(buffer, fullCmd.c_str(), sizeof(buffer))) { 162 LOGE("iptables command too long"); 163 return -1; 164 } 165 166 while ((tmp = strsep(&next, " "))) { 167 argv[argc++] = tmp; 168 if (argc >= MAX_CMD_ARGS) { 169 LOGE("iptables argument overflow"); 170 return -1; 171 } 172 } 173 174 argv[argc] = NULL; 175 /* TODO(jpa): Once this stabilizes, remove logwrap() as it tends to wedge netd 176 * Then just talk directly to the kernel via rtnetlink. 177 */ 178 return logwrap(argc, argv, 0); 179} 180 181int BandwidthController::enableBandwidthControl(void) { 182 int res; 183 /* Some of the initialCommands are allowed to fail */ 184 runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, RunCmdFailureOk); 185 runCommands(sizeof(setupCommands) / sizeof(char*), setupCommands, RunCmdFailureOk); 186 res = runCommands(sizeof(basicAccountingCommands) / sizeof(char*), basicAccountingCommands, RunCmdFailureBad); 187 return res; 188 189} 190 191int BandwidthController::disableBandwidthControl(void) { 192 /* The cleanupCommands are allowed to fail. */ 193 runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, RunCmdFailureOk); 194 return 0; 195} 196 197int BandwidthController::runCommands(int numCommands, const char *commands[], RunCmdErrHandling cmdErrHandling) { 198 int res = 0; 199 LOGD("runCommands(): %d commands", numCommands); 200 for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) { 201 res = runIpxtablesCmd(commands[cmdNum], IptRejectNoAdd); 202 if (res && cmdErrHandling != RunCmdFailureBad) 203 return res; 204 } 205 return cmdErrHandling == RunCmdFailureBad ? res : 0; 206} 207 208std::string BandwidthController::makeIptablesNaughtyCmd(IptOp op, int uid) { 209 std::string res; 210 char *convBuff; 211 212 switch (op) { 213 case IptOpInsert: 214 res = "-I"; 215 break; 216 case IptOpReplace: 217 res = "-R"; 218 break; 219 default: 220 case IptOpDelete: 221 res = "-D"; 222 break; 223 } 224 res += " penalty_box"; 225 asprintf(&convBuff, "%d", uid); 226 res += " -m owner --uid-owner "; 227 res += convBuff; 228 free(convBuff); 229 return res; 230} 231 232int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) { 233 return maninpulateNaughtyApps(numUids, appUids, NaughtyAppOpAdd); 234} 235 236int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) { 237 return maninpulateNaughtyApps(numUids, appUids, NaughtyAppOpRemove); 238} 239 240int BandwidthController::maninpulateNaughtyApps(int numUids, char *appStrUids[], NaughtyAppOp appOp) { 241 char cmd[MAX_CMD_LEN]; 242 int uidNum; 243 const char *failLogTemplate; 244 IptOp op; 245 int appUids[numUids]; 246 std::string naughtyCmd; 247 switch (appOp) { 248 case NaughtyAppOpAdd: 249 op = IptOpInsert; 250 failLogTemplate = "Failed to add app uid %d to penalty box."; 251 break; 252 case NaughtyAppOpRemove: 253 op = IptOpDelete; 254 failLogTemplate = "Failed to delete app uid %d from penalty box."; 255 break; 256 } 257 258 for (uidNum = 0; uidNum < numUids; uidNum++) { 259 appUids[uidNum] = atol(appStrUids[uidNum]); 260 if (appUids[uidNum] == 0) { 261 LOGE(failLogTemplate, appUids[uidNum]); 262 goto fail_parse; 263 } 264 } 265 266 for (uidNum = 0; uidNum < numUids; uidNum++) { 267 naughtyCmd = makeIptablesNaughtyCmd(op, appUids[uidNum]); 268 if (runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd)) { 269 LOGE(failLogTemplate, appUids[uidNum]); 270 goto fail_with_uidNum; 271 } 272 } 273 return 0; 274 275fail_with_uidNum: 276 /* Try to remove the uid that failed in any case*/ 277 naughtyCmd = makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum]); 278 runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd); 279fail_parse: 280 return -1; 281} 282 283std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota) { 284 std::string res; 285 char convBuff[21]; // log10(2^64) ~ 20 286 287 LOGD("makeIptablesQuotaCmd(%d, %llu)", op, quota); 288 289 switch (op) { 290 case IptOpInsert: 291 res = "-I"; 292 break; 293 case IptOpReplace: 294 res = "-R"; 295 break; 296 default: 297 case IptOpDelete: 298 res = "-D"; 299 break; 300 } 301 res += " costly"; 302 if (costName) { 303 res += "_"; 304 res += costName; 305 } 306 sprintf(convBuff, "%lld", quota); 307 /* TODO(jpa): Use -m quota2 --name " + costName + " ! --quota " 308 * once available. 309 */ 310 res += " -m quota ! --quota "; 311 res += convBuff; 312 ; 313 // The requried --jump REJECT ... will be added later. 314 return res; 315} 316 317int BandwidthController::prepCostlyIface(const char *ifn, QuotaType quotaType) { 318 char cmd[MAX_CMD_LEN]; 319 int res = 0; 320 std::string costString; 321 const char *costCString; 322 323 costString = "costly"; 324 /* The "-N costly" is created upfront, no need to handle it here. */ 325 switch (quotaType) { 326 case QuotaUnique: 327 costString += "_"; 328 costString += ifn; 329 costCString = costString.c_str(); 330 snprintf(cmd, sizeof(cmd), "-N %s", costCString); 331 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 332 snprintf(cmd, sizeof(cmd), "-A %s -j penalty_box", costCString); 333 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 334 snprintf(cmd, sizeof(cmd), "-A %s -m owner --socket-exists", costCString); 335 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 336 /* TODO(jpa): Figure out why iptables doesn't correctly return from this 337 * chain. For now, hack the chain exit with an ACCEPT. 338 */ 339 snprintf(cmd, sizeof(cmd), "-A %s --jump ACCEPT", costCString); 340 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 341 break; 342 case QuotaShared: 343 costCString = costString.c_str(); 344 break; 345 } 346 347 snprintf(cmd, sizeof(cmd), "-I INPUT -i %s --goto %s", ifn, costCString); 348 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 349 snprintf(cmd, sizeof(cmd), "-I OUTPUT -o %s --goto %s", ifn, costCString); 350 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 351 return res; 352} 353 354int BandwidthController::cleanupCostlyIface(const char *ifn, QuotaType quotaType) { 355 char cmd[MAX_CMD_LEN]; 356 int res = 0; 357 std::string costString; 358 const char *costCString; 359 360 costString = "costly"; 361 switch (quotaType) { 362 case QuotaUnique: 363 costString += "_"; 364 costString += ifn; 365 costCString = costString.c_str(); 366 break; 367 case QuotaShared: 368 costCString = costString.c_str(); 369 break; 370 } 371 372 snprintf(cmd, sizeof(cmd), "-D INPUT -i %s --goto %s", ifn, costCString); 373 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 374 snprintf(cmd, sizeof(cmd), "-D OUTPUT -o %s --goto %s", ifn, costCString); 375 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 376 377 /* The "-N costly" is created upfront, no need to handle it here. */ 378 if (quotaType == QuotaUnique) { 379 snprintf(cmd, sizeof(cmd), "-F %s", costCString); 380 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 381 snprintf(cmd, sizeof(cmd), "-X %s", costCString); 382 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 383 } 384 return res; 385} 386 387int BandwidthController::setInterfaceSharedQuota(const char *iface, int64_t maxBytes) { 388 char cmd[MAX_CMD_LEN]; 389 char ifn[MAX_IFACENAME_LEN]; 390 int res = 0; 391 std::string quotaCmd; 392 std::string ifaceName;; 393 const char *costName = NULL; /* Shared quota */ 394 std::list<std::string>::iterator it; 395 396 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 397 LOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 398 return -1; 399 } 400 ifaceName = ifn; 401 402 if (maxBytes == -1) { 403 return removeInterfaceSharedQuota(ifn); 404 } 405 406 /* Insert ingress quota. */ 407 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) { 408 if (*it == ifaceName) 409 break; 410 } 411 412 if (it == sharedQuotaIfaces.end()) { 413 res |= prepCostlyIface(ifn, QuotaShared); 414 if (sharedQuotaIfaces.empty()) { 415 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 416 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 417 if (res) { 418 LOGE("Failed set quota rule."); 419 goto fail; 420 } 421 sharedQuotaBytes = maxBytes; 422 } 423 sharedQuotaIfaces.push_front(ifaceName); 424 425 } 426 427 if (maxBytes != sharedQuotaBytes) { 428 /* Instead of replacing, which requires being aware of the rules in 429 * the kernel, we just add a new one, then delete the older one. 430 */ 431 432 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 433 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 434 435 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes); 436 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 437 438 if (res) { 439 LOGE("Failed replace quota rule."); 440 goto fail; 441 } 442 sharedQuotaBytes = maxBytes; 443 } 444 return 0; 445 446 fail: 447 /* 448 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse 449 * rules in the kernel to see which ones need cleaning up. 450 * For now callers needs to choose if they want to "ndc bandwidth enable" 451 * which resets everything. 452 */ 453 removeInterfaceSharedQuota(ifn); 454 return -1; 455} 456 457int BandwidthController::removeInterfaceSharedQuota(const char *iface) { 458 char ifn[MAX_IFACENAME_LEN]; 459 int res = 0; 460 std::string ifaceName; 461 std::list<std::string>::iterator it; 462 463 if(StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 464 LOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 465 return -1; 466 } 467 ifaceName =ifn; 468 469 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) { 470 if (*it == ifaceName) 471 break; 472 } 473 if (it == sharedQuotaIfaces.end()) { 474 LOGE("No such iface %s to delete.", ifn); 475 return -1; 476 } 477 478 res |= cleanupCostlyIface(ifn, QuotaShared); 479 sharedQuotaIfaces.erase(it); 480 481 if (sharedQuotaIfaces.empty()) { 482 std::string quotaCmd; 483 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, NULL, sharedQuotaBytes); 484 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 485 sharedQuotaBytes = -1; 486 } 487 488 return res; 489} 490 491int BandwidthController::setInterfaceQuota(const char *iface, int64_t maxBytes) { 492 char ifn[MAX_IFACENAME_LEN]; 493 int res = 0; 494 std::string ifaceName; 495 const char *costName; 496 std::list<QuotaInfo>::iterator it; 497 std::string quotaCmd; 498 499 if (maxBytes == -1) { 500 return removeInterfaceQuota(iface); 501 } 502 503 if(StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 504 LOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 505 return -1; 506 } 507 ifaceName = ifn; 508 costName = iface; 509 510 511 /* Insert ingress quota. */ 512 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 513 if (it->first == ifaceName) 514 break; 515 } 516 517 if (it == quotaIfaces.end()) { 518 res |= prepCostlyIface(ifn, QuotaUnique); 519 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 520 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 521 if (res) { 522 LOGE("Failed set quota rule."); 523 goto fail; 524 } 525 526 quotaIfaces.push_front(QuotaInfo(ifaceName, maxBytes)); 527 528 } else { 529 /* Instead of replacing, which requires being aware of the rules in 530 * the kernel, we just add a new one, then delete the older one. 531 */ 532 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 533 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 534 535 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, it->second); 536 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 537 538 if (res) { 539 LOGE("Failed replace quota rule."); 540 goto fail; 541 } 542 it->second = maxBytes; 543 } 544 return 0; 545 546 fail: 547 /* 548 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse 549 * rules in the kernel to see which ones need cleaning up. 550 * For now callers needs to choose if they want to "ndc bandwidth enable" 551 * which resets everything. 552 */ 553 removeInterfaceSharedQuota(ifn); 554 return -1; 555} 556 557int BandwidthController::removeInterfaceQuota(const char *iface) { 558 559 char ifn[MAX_IFACENAME_LEN]; 560 int res = 0; 561 std::string ifaceName; 562 const char *costName; 563 std::list<QuotaInfo>::iterator it; 564 565 if(StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 566 LOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 567 return -1; 568 } 569 ifaceName = ifn; 570 costName = iface; 571 572 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) { 573 if (it->first == ifaceName) 574 break; 575 } 576 577 if (it == quotaIfaces.end()) { 578 LOGE("No such iface %s to delete.", ifn); 579 return -1; 580 } 581 582 /* This also removes the quota command of CostlyIface chain. */ 583 res |= cleanupCostlyIface(ifn, QuotaUnique); 584 585 quotaIfaces.erase(it); 586 587 return res; 588} 589