BandwidthController.cpp revision bfa7466b328101a8b04807f26e85c84526c9a774
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_shared 55 * iptables -I INPUT -i iface0 --goto costly_shared 56 * iptables -I OUTPUT -o iface0 --goto costly_shared 57 * iptables -I costly_shared -m quota \! --quota 500000 \ 58 * --jump REJECT --reject-with icmp-net-prohibited 59 * iptables -A costly_shared --jump penalty_box 60 * iptables -A costly_shared -m owner --socket-exists 61 * . adding a new iface to this, E.g.: 62 * iptables -I INPUT -i iface1 --goto costly_shared 63 * iptables -I OUTPUT -o iface1 --goto costly_shared 64 * 65 * - quota per interface. This is achieve by having "costly" chains per quota. 66 * E.g. adding a new costly interface iface0 with its own quota: 67 * iptables -N costly_iface0 68 * iptables -I INPUT -i iface0 --goto costly_iface0 69 * iptables -I OUTPUT -o iface0 --goto costly_iface0 70 * iptables -A costly_iface0 -m quota \! --quota 500000 \ 71 * --jump REJECT --reject-with icmp-net-prohibited 72 * iptables -A costly_iface0 --jump penalty_box 73 * iptables -A costly_iface0 -m owner --socket-exists 74 * 75 * * penalty_box handling: 76 * - only one penalty_box for all interfaces 77 * E.g Adding an app: 78 * iptables -A penalty_box -m owner --uid-owner app_3 \ 79 * --jump REJECT --reject-with icmp-net-prohibited 80 */ 81const char *BandwidthController::cleanupCommands[] = { 82 /* Cleanup rules. */ 83 "-F", 84 "-t raw -F", 85 /* TODO: If at some point we need more user chains than here, then we will need 86 * a different cleanup approach. 87 */ 88 "-X", /* Should normally only be costly_shared, penalty_box, and costly_<iface> */ 89}; 90 91const char *BandwidthController::setupCommands[] = { 92 /* Created needed chains. */ 93 "-N costly_shared", 94 "-N penalty_box", 95}; 96 97const char *BandwidthController::basicAccountingCommands[] = { 98 "-F INPUT", 99 "-A INPUT -i lo --jump ACCEPT", 100 "-A INPUT -m owner --socket-exists", /* This is a tracking rule. */ 101 102 "-F OUTPUT", 103 "-A OUTPUT -o lo --jump ACCEPT", 104 "-A OUTPUT -m owner --socket-exists", /* This is a tracking rule. */ 105 106 "-F costly_shared", 107 "-A costly_shared --jump penalty_box", 108 "-A costly_shared -m owner --socket-exists", /* This is a tracking rule. */ 109 /* TODO(jpa): Figure out why iptables doesn't correctly return from this 110 * chain. For now, hack the chain exit with an ACCEPT. 111 */ 112 "-A costly_shared --jump ACCEPT", 113}; 114 115BandwidthController::BandwidthController(void) { 116 117 char value[PROPERTY_VALUE_MAX]; 118 119 property_get("persist.bandwidth.enable", value, "0"); 120 if (!strcmp(value, "1")) { 121 enableBandwidthControl(); 122 } 123 124} 125 126int BandwidthController::runIpxtablesCmd(const char *cmd, IptRejectOp rejectHandling) { 127 int res = 0; 128 LOGD("runIpxtablesCmd(cmd=%s)", cmd); 129 res |= runIptablesCmd(cmd, rejectHandling, IptIpV4); 130 res |= runIptablesCmd(cmd, rejectHandling, IptIpV6); 131 return res; 132} 133 134int BandwidthController::StrncpyAndCheck(char *buffer, const char *src, size_t buffSize) { 135 136 memset(buffer, '\0', buffSize); // strncpy() is not filling leftover with '\0' 137 strncpy(buffer, src, buffSize); 138 return buffer[buffSize - 1]; 139} 140 141int BandwidthController::runIptablesCmd(const char *cmd, IptRejectOp rejectHandling, IptIpVer iptVer) { 142 char buffer[MAX_CMD_LEN]; 143 const char *argv[MAX_CMD_ARGS]; 144 int argc = 0; 145 char *next = buffer; 146 char *tmp; 147 148 std::string fullCmd = cmd; 149 150 if (rejectHandling == IptRejectAdd) { 151 fullCmd += " --jump REJECT --reject-with"; 152 switch (iptVer) { 153 case IptIpV4: 154 fullCmd += " icmp-net-prohibited"; 155 break; 156 case IptIpV6: 157 fullCmd += " icmp6-adm-prohibited"; 158 break; 159 } 160 } 161 162 argc = 0; 163 argv[argc++] = iptVer == IptIpV4 ? IPTABLES_PATH : IP6TABLES_PATH; 164 165 LOGD("runIptablesCmd(): %s %s", argv[0], fullCmd.c_str()); 166 if (StrncpyAndCheck(buffer, fullCmd.c_str(), sizeof(buffer))) { 167 LOGE("iptables command too long"); 168 return -1; 169 } 170 171 while ((tmp = strsep(&next, " "))) { 172 argv[argc++] = tmp; 173 if (argc >= MAX_CMD_ARGS) { 174 LOGE("iptables argument overflow"); 175 return -1; 176 } 177 } 178 179 argv[argc] = NULL; 180 /* TODO(jpa): Once this stabilizes, remove logwrap() as it tends to wedge netd 181 * Then just talk directly to the kernel via rtnetlink. 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 return res; 235} 236 237int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) { 238 return maninpulateNaughtyApps(numUids, appUids, NaughtyAppOpAdd); 239} 240 241int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) { 242 return maninpulateNaughtyApps(numUids, appUids, NaughtyAppOpRemove); 243} 244 245int BandwidthController::maninpulateNaughtyApps(int numUids, char *appStrUids[], NaughtyAppOp appOp) { 246 char cmd[MAX_CMD_LEN]; 247 int uidNum; 248 const char *failLogTemplate; 249 IptOp op; 250 int appUids[numUids]; 251 std::string naughtyCmd; 252 switch (appOp) { 253 case NaughtyAppOpAdd: 254 op = IptOpInsert; 255 failLogTemplate = "Failed to add app uid %d to penalty box."; 256 break; 257 case NaughtyAppOpRemove: 258 op = IptOpDelete; 259 failLogTemplate = "Failed to delete app uid %d from penalty box."; 260 break; 261 } 262 263 for (uidNum = 0; uidNum < numUids; uidNum++) { 264 appUids[uidNum] = atol(appStrUids[uidNum]); 265 if (appUids[uidNum] == 0) { 266 LOGE(failLogTemplate, appUids[uidNum]); 267 goto fail_parse; 268 } 269 } 270 271 for (uidNum = 0; uidNum < numUids; uidNum++) { 272 naughtyCmd = makeIptablesNaughtyCmd(op, appUids[uidNum]); 273 if (runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd)) { 274 LOGE(failLogTemplate, appUids[uidNum]); 275 goto fail_with_uidNum; 276 } 277 } 278 return 0; 279 280fail_with_uidNum: 281 /* Try to remove the uid that failed in any case*/ 282 naughtyCmd = makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum]); 283 runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd); 284fail_parse: 285 return -1; 286} 287 288std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota) { 289 std::string res; 290 char *convBuff; 291 292 LOGD("makeIptablesQuotaCmd(%d, %llu)", op, quota); 293 294 switch (op) { 295 case IptOpInsert: 296 res = "-I"; 297 break; 298 case IptOpReplace: 299 res = "-R"; 300 break; 301 default: 302 case IptOpDelete: 303 res = "-D"; 304 break; 305 } 306 res += " costly_"; 307 res += costName; 308 asprintf(&convBuff, "%lld", quota); 309 res += " -m quota2 --name "; 310 res += costName; 311 res += " ! --quota "; 312 res += convBuff; 313 free(convBuff); 314 // The requried IP version specific --jump REJECT ... will be added later. 315 return res; 316} 317 318int BandwidthController::prepCostlyIface(const char *ifn, QuotaType quotaType) { 319 char cmd[MAX_CMD_LEN]; 320 int res = 0; 321 std::string costString; 322 const char *costCString; 323 324 /* The "-N costly" is created upfront, no need to handle it here. */ 325 switch (quotaType) { 326 case QuotaUnique: 327 costString = "costly_"; 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 = "costly_shared"; 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 switch (quotaType) { 361 case QuotaUnique: 362 costString = "costly_"; 363 costString += ifn; 364 costCString = costString.c_str(); 365 break; 366 case QuotaShared: 367 costCString = "costly_shared"; 368 break; 369 } 370 371 snprintf(cmd, sizeof(cmd), "-D INPUT -i %s --goto %s", ifn, costCString); 372 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 373 snprintf(cmd, sizeof(cmd), "-D OUTPUT -o %s --goto %s", ifn, costCString); 374 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 375 376 /* The "-N costly_shared" is created upfront, no need to handle it here. */ 377 if (quotaType == QuotaUnique) { 378 snprintf(cmd, sizeof(cmd), "-F %s", costCString); 379 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 380 snprintf(cmd, sizeof(cmd), "-X %s", costCString); 381 res |= runIpxtablesCmd(cmd, IptRejectNoAdd); 382 } 383 return res; 384} 385 386int BandwidthController::setInterfaceSharedQuota(const char *iface, int64_t maxBytes) { 387 char cmd[MAX_CMD_LEN]; 388 char ifn[MAX_IFACENAME_LEN]; 389 int res = 0; 390 std::string quotaCmd; 391 std::string ifaceName;; 392 const char *costName = "shared"; 393 std::list<std::string>::iterator it; 394 395 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) { 396 LOGE("Interface name longer than %d", MAX_IFACENAME_LEN); 397 return -1; 398 } 399 ifaceName = ifn; 400 401 if (maxBytes == -1) { 402 return removeInterfaceSharedQuota(ifn); 403 } 404 405 /* Insert ingress quota. */ 406 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) { 407 if (*it == ifaceName) 408 break; 409 } 410 411 if (it == sharedQuotaIfaces.end()) { 412 res |= prepCostlyIface(ifn, QuotaShared); 413 if (sharedQuotaIfaces.empty()) { 414 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 415 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 416 if (res) { 417 LOGE("Failed set quota rule."); 418 goto fail; 419 } 420 sharedQuotaBytes = maxBytes; 421 } 422 sharedQuotaIfaces.push_front(ifaceName); 423 424 } 425 426 if (maxBytes != sharedQuotaBytes) { 427 /* Instead of replacing, which requires being aware of the rules in 428 * the kernel, we just add a new one, then delete the older one. 429 */ 430 431 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes); 432 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 433 434 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes); 435 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd); 436 437 if (res) { 438 LOGE("Failed replace quota rule."); 439 goto fail; 440 } 441 sharedQuotaBytes = maxBytes; 442 } 443 return 0; 444 445 fail: 446 /* 447 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse 448 * rules in the kernel to see which ones need cleaning up. 449 * For now callers needs to choose if they want to "ndc bandwidth enable" 450 * which resets everything. 451 */ 452 removeInterfaceSharedQuota(ifn); 453 return -1; 454} 455 456int BandwidthController::removeInterfaceSharedQuota(const char *iface) { 457 char ifn[MAX_IFACENAME_LEN]; 458 int res = 0; 459 std::string ifaceName; 460 std::list<std::string>::iterator it; 461 const char *costName = "shared"; 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, costName, 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