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