BandwidthController.cpp revision 4a5f5ca3c9e07fc3e6feca2afde07f41a8a64f11
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 39 40const int BandwidthController::MAX_CMD_LEN = 255; 41const int BandwidthController::MAX_IFACENAME_LEN = 64; 42const int BandwidthController::MAX_CMD_ARGS = 32; 43const char BandwidthController::IPTABLES_PATH[] = "/system/bin/iptables"; 44 45 46/** 47 * Some comments about the rules: 48 * * Ordering 49 * - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains. 50 * E.g. "-I INPUT -i rmnet0 --goto costly" 51 * - quota'd rules in the costly chain should be before penalty_box lookups. 52 * 53 * * global quota vs per interface quota 54 * - global quota for all costly interfaces uses a single costly chain: 55 * . initial rules 56 * iptables -N costly 57 * iptables -I INPUT -i iface0 --goto costly 58 * iptables -I OUTPUT -o iface0 --goto costly 59 * iptables -I costly -m quota \! --quota 500000 --jump REJECT --reject-with icmp-net-prohibited 60 * iptables -A costly --jump penalty_box 61 * iptables -A costly -m owner --socket-exists 62 * . adding a new iface to this, E.g.: 63 * iptables -I INPUT -i iface1 --goto costly 64 * iptables -I OUTPUT -o iface1 --goto costly 65 * 66 * - quota per interface. This is achieve by having "costly" chains per quota. 67 * E.g. adding a new costly interface iface0 with its own quota: 68 * iptables -N costly_iface0 69 * iptables -I INPUT -i iface0 --goto costly_iface0 70 * iptables -I OUTPUT -o iface0 --goto costly_iface0 71 * iptables -A costly_iface0 -m quota \! --quota 500000 --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 --jump REJECT --reject-with icmp-net-prohibited 79 */ 80const char *BandwidthController::cleanupCommands[] = { 81 /* Cleanup rules. */ 82 "-F", 83 "-t raw -F", 84 "-X costly", 85 "-X penalty_box", 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 112 113BandwidthController::BandwidthController(void) { 114 115 char value[PROPERTY_VALUE_MAX]; 116 117 property_get("persist.bandwidth.enable", value, "0"); 118 if (!strcmp(value, "1")) { 119 enableBandwidthControl(); 120 } 121 122} 123 124int BandwidthController::runIptablesCmd(const char *cmd) { 125 char buffer[MAX_CMD_LEN]; 126 127 LOGD("About to run: iptables %s", cmd); 128 129 strncpy(buffer, cmd, sizeof(buffer)-1); 130 131 const char *argv[MAX_CMD_ARGS]; 132 char *next = buffer; 133 char *tmp; 134 135 argv[0] = IPTABLES_PATH; 136 int argc = 1; 137 138 while ((tmp = strsep(&next, " "))) { 139 argv[argc++] = tmp; 140 if (argc == MAX_CMD_ARGS) { 141 LOGE("iptables argument overflow"); 142 errno = E2BIG; 143 return -1; 144 } 145 } 146 argv[argc] = NULL; 147 /* TODO(jpa): Once this stabilizes, remove logwrap() as it tends to wedge netd 148 * Then just talk directly to the kernel via rtnetlink. 149 */ 150 return logwrap(argc, argv, 0); 151} 152 153 154int BandwidthController::enableBandwidthControl(void) { 155 /* Some of the initialCommands are allowed to fail */ 156 runCommands(cleanupCommands, sizeof(cleanupCommands)/sizeof(char*), true); 157 runCommands(setupCommands, sizeof(setupCommands)/sizeof(char*), true); 158 return runCommands(basicAccountingCommands, 159 sizeof(basicAccountingCommands)/sizeof(char*)); 160 161} 162 163int BandwidthController::disableBandwidthControl(void) { 164 /* The cleanupCommands are allowed to fail */ 165 runCommands(cleanupCommands, sizeof(cleanupCommands)/sizeof(char*), true); 166 return 0; 167} 168 169int BandwidthController::runCommands(const char *commands[], int numCommands, bool allowFailure) { 170 int res = 0; 171 LOGD("runCommands(): %d commands", numCommands); 172 for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) { 173 res = runIptablesCmd(commands[cmdNum]); 174 if(res && !allowFailure) return res; 175 } 176 return allowFailure?res:0; 177} 178 179 180int BandwidthController::setInterfaceQuota(const char *iface, 181 int64_t maxBytes) { 182 char cmd[MAX_CMD_LEN]; 183 char ifn[MAX_IFACENAME_LEN]; 184 int res; 185 186 memset(ifn, 0, sizeof(ifn)); 187 strncpy(ifn, iface, sizeof(ifn)-1); 188 189 if (maxBytes == -1) { 190 return removeQuota(ifn); 191 } 192 193 /* Insert ingress quota. */ 194 std::string ifaceName(ifn); 195 std::list<std::string>::iterator it; 196 int pos; 197 for (pos=1, it = ifaceRules.begin(); it != ifaceRules.end(); it++, pos++) { 198 if (*it == ifaceName) 199 break; 200 } 201 if (it != ifaceRules.end()) { 202 snprintf(cmd, sizeof(cmd), "-R INPUT %d -i %s --goto costly", pos, ifn); 203 res = runIptablesCmd(cmd); 204 snprintf(cmd, sizeof(cmd), "-R OUTPUT %d -o %s --goto costly", pos, ifn); 205 res |= runIptablesCmd(cmd); 206 snprintf(cmd, sizeof(cmd), "-R costly %d -m quota ! --quota %lld" 207 " --jump REJECT --reject-with icmp-net-prohibited", 208 pos, maxBytes); 209 res |= runIptablesCmd(cmd); 210 if (res) { 211 LOGE("Failed set quota rule."); 212 goto fail; 213 } 214 } else { 215 pos = 1; 216 snprintf(cmd, sizeof(cmd), "-I INPUT -i %s --goto costly", ifn); 217 res = runIptablesCmd(cmd); 218 snprintf(cmd, sizeof(cmd), "-I OUTPUT -o %s --goto costly", ifn); 219 res |= runIptablesCmd(cmd); 220 snprintf(cmd, sizeof(cmd), "-I costly -m quota ! --quota %lld" 221 " --jump REJECT --reject-with icmp-net-prohibited", 222 maxBytes); 223 res |= runIptablesCmd(cmd); 224 if (res) { 225 LOGE("Failed set quota rule."); 226 goto fail; 227 } 228 ifaceRules.push_front(ifaceName); 229 } 230 return 0; 231fail: 232 /* 233 * Failure tends to be that the rules have been messed up. 234 * For now cleanup all the rules. 235 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse 236 * rules in the kernel to see which ones need cleaning up. 237 */ 238 runCommands(basicAccountingCommands, 239 sizeof(basicAccountingCommands)/sizeof(char*), true); 240 removeQuota(ifn); 241 return -1; 242} 243 244int BandwidthController::removeQuota(const char *iface) { 245 char cmd[MAX_CMD_LEN]; 246 char ifn[MAX_IFACENAME_LEN]; 247 int res; 248 249 memset(ifn, 0, sizeof(ifn)); 250 strncpy(ifn, iface, sizeof(ifn)-1); 251 252 std::string ifaceName(ifn); 253 std::list<std::string>::iterator it; 254 255 int pos; 256 for (pos=1, it = ifaceRules.begin(); it != ifaceRules.end(); it++, pos++) { 257 if (*it == ifaceName) 258 break; 259 } 260 if(it == ifaceRules.end()) { 261 LOGE("No such iface %s to delete.", ifn); 262 return -1; 263 } 264 ifaceRules.erase(it); 265 snprintf(cmd, sizeof(cmd), "--delete INPUT -i %s --goto costly", ifn); 266 res = runIptablesCmd(cmd); 267 snprintf(cmd, sizeof(cmd), "--delete OUTPUT -o %s --goto costly", ifn); 268 res |= runIptablesCmd(cmd); 269 // Don't use rule-matching for this one. Quota is the remaining one. 270 snprintf(cmd, sizeof(cmd), "--delete costly %d", pos); 271 res |= runIptablesCmd(cmd); 272 return res; 273} 274