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