blob: 3ccf50758fda6d56bb0d79a6b5a24c8a14285126 [file] [log] [blame]
JP Abgrall4a5f5ca2011-06-15 18:37:39 -07001/*
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
JP Abgrallfa6f46d2011-06-17 23:17:28 -070039const int BandwidthController::MAX_CMD_LEN = 1024;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -070040const int BandwidthController::MAX_IFACENAME_LEN = 64;
41const int BandwidthController::MAX_CMD_ARGS = 32;
42const char BandwidthController::IPTABLES_PATH[] = "/system/bin/iptables";
JP Abgrallfa6f46d2011-06-17 23:17:28 -070043const char BandwidthController::IP6TABLES_PATH[] = "/system/bin/ip6tables";
JP Abgrall4a5f5ca2011-06-15 18:37:39 -070044
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[] = {
JP Abgrallfa6f46d2011-06-17 23:17:28 -070080/* Cleanup rules. */
81"-F", "-t raw -F", "-X costly", "-X penalty_box", };
JP Abgrall4a5f5ca2011-06-15 18:37:39 -070082
83const char *BandwidthController::setupCommands[] = {
JP Abgrallfa6f46d2011-06-17 23:17:28 -070084/* Created needed chains. */
85"-N costly", "-N penalty_box", };
JP Abgrall4a5f5ca2011-06-15 18:37:39 -070086
JP Abgrallfa6f46d2011-06-17 23:17:28 -070087const char *BandwidthController::basicAccountingCommands[] = { "-F INPUT",
88 "-A INPUT -i lo --jump ACCEPT", "-A INPUT -m owner --socket-exists", /* This is a tracking rule. */
JP Abgrall4a5f5ca2011-06-15 18:37:39 -070089
JP Abgrallfa6f46d2011-06-17 23:17:28 -070090 "-F OUTPUT", "-A OUTPUT -o lo --jump ACCEPT", "-A OUTPUT -m owner --socket-exists", /* This is a tracking rule. */
JP Abgrall4a5f5ca2011-06-15 18:37:39 -070091
JP Abgrallfa6f46d2011-06-17 23:17:28 -070092 "-F costly", "-A costly --jump penalty_box", "-A costly -m owner --socket-exists", /* This is a tracking rule. */
93 /* TODO(jpa): Figure out why iptables doesn't correctly return from this
94 * chain. For now, hack the chain exit with an ACCEPT.
95 */
96 "-A costly --jump ACCEPT", };
JP Abgrall4a5f5ca2011-06-15 18:37:39 -070097
98BandwidthController::BandwidthController(void) {
99
100 char value[PROPERTY_VALUE_MAX];
101
102 property_get("persist.bandwidth.enable", value, "0");
103 if (!strcmp(value, "1")) {
104 enableBandwidthControl();
105 }
106
107}
108
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700109int BandwidthController::runIptablesCmd(const char *cmd, bool isIp6) {
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700110 char buffer[MAX_CMD_LEN];
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700111 const char *argv[MAX_CMD_ARGS];
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700112 int argc = 1;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700113 char *next = buffer;
114 char *tmp;
115
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700116 argv[0] = isIp6 ? IP6TABLES_PATH : IPTABLES_PATH;
117 LOGD("About to run: %s %s", argv[0], cmd);
118
119 strncpy(buffer, cmd, sizeof(buffer) - 1);
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700120
121 while ((tmp = strsep(&next, " "))) {
122 argv[argc++] = tmp;
123 if (argc == MAX_CMD_ARGS) {
124 LOGE("iptables argument overflow");
125 errno = E2BIG;
126 return -1;
127 }
128 }
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700129
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700130 argv[argc] = NULL;
131 /* TODO(jpa): Once this stabilizes, remove logwrap() as it tends to wedge netd
132 * Then just talk directly to the kernel via rtnetlink.
133 */
134 return logwrap(argc, argv, 0);
135}
136
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700137int BandwidthController::enableBandwidthControl(void) {
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700138 int res;
139 /* Some of the initialCommands are allowed to fail */
140 runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, true, false);
141 runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, true, true);
142 runCommands(sizeof(setupCommands) / sizeof(char*), setupCommands, true, false);
143 runCommands(sizeof(setupCommands) / sizeof(char*), setupCommands, true, true);
144 res = runCommands(sizeof(basicAccountingCommands) / sizeof(char*), basicAccountingCommands,
145 false, false);
146 res |= runCommands(sizeof(basicAccountingCommands) / sizeof(char*), basicAccountingCommands,
147 false, true);
148 return res;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700149
150}
151
152int BandwidthController::disableBandwidthControl(void) {
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700153 /* The cleanupCommands are allowed to fail. */
154 runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, true);
155 runCommands(sizeof(cleanupCommands) / sizeof(char*), cleanupCommands, true, true);
156 return 0;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700157}
158
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700159int BandwidthController::runCommands(int numCommands, const char *commands[], bool allowFailure,
160 bool isIp6) {
161 int res = 0;
162 LOGD("runCommands(): %d commands", numCommands);
163 for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) {
164 res = runIptablesCmd(commands[cmdNum], isIp6);
165 if (res && !allowFailure)
166 return res;
167 }
168 return allowFailure ? res : 0;
169}
170
171std::string BandwidthController::makeIptablesNaughtyCmd(IptOp op, int uid, bool isIp6) {
172 std::string res;
173 char convBuff[21]; // log10(2^64) ~ 20
174
175 switch (op) {
176 case IptOpInsert:
177 res = "-I";
178 break;
179 case IptOpReplace:
180 res = "-R";
181 break;
182 default:
183 case IptOpDelete:
184 res = "-D";
185 break;
186 }
187 res += " penalty_box";
188 sprintf(convBuff, "%d", uid);
189 res += " -m owner --uid-owner ";
190 res += convBuff;
191 res += " --jump REJECT --reject-with";
192 if (isIp6) {
193 res += " icmp6-adm-prohibited";
194 } else {
195 res += " icmp-net-prohibited";
196 }
197 return res;
198}
199
200int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) {
201 return maninpulateNaughtyApps(numUids, appUids, true);
202}
203
204int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) {
205 return maninpulateNaughtyApps(numUids, appUids, false);
206}
207
208int BandwidthController::maninpulateNaughtyApps(int numUids, char *appStrUids[], bool doAdd) {
209 char cmd[MAX_CMD_LEN];
210 int uidNum;
211 const char *addFailedTemplate = "Failed to add app uid %d to penalty box.";
212 const char *deleteFailedTemplate = "Failed to delete app uid %d from penalty box.";
213 IptOp op = doAdd ? IptOpInsert : IptOpDelete;
214
215 int appUids[numUids];
216 for (uidNum = 0; uidNum < numUids; uidNum++) {
217 appUids[uidNum] = atol(appStrUids[uidNum]);
218 if (appUids[uidNum] == 0) {
219 LOGE((doAdd ? addFailedTemplate : deleteFailedTemplate), appUids[uidNum]);
220 goto fail_parse;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700221 }
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700222 }
223
224 for (uidNum = 0; uidNum < numUids; uidNum++) {
225 std::string naughtyCmd = makeIptablesNaughtyCmd(op, appUids[uidNum], false);
226 if (runIptablesCmd(naughtyCmd.c_str())) {
227 LOGE((doAdd ? addFailedTemplate : deleteFailedTemplate), appUids[uidNum]);
228 goto fail_with_uidNum;
229 }
230 naughtyCmd = makeIptablesNaughtyCmd(op, appUids[uidNum], true);
231 if (runIptablesCmd(naughtyCmd.c_str(), true)) {
232 LOGE((doAdd ? addFailedTemplate : deleteFailedTemplate), appUids[uidNum]);
233 goto fail_with_uidNum;
234 }
235 }
236 return 0;
237
238 fail_with_uidNum:
239 /* Try to remove the uid that failed in any case*/
240 runIptablesCmd(makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum], false).c_str());
241 runIptablesCmd(makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum], true).c_str(), true);
242 fail_parse: return -1;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700243}
244
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700245std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, char *costName, int64_t quota, bool isIp6) {
246 std::string res;
247 char convBuff[21]; // log10(2^64) ~ 20
248 LOGD("makeIptablesQuotaCmd(%d, %llu, %d)", op, quota, isIp6);
249 switch (op) {
250 case IptOpInsert:
251 res = "-I";
252 break;
253 case IptOpReplace:
254 res = "-R";
255 break;
256 default:
257 case IptOpDelete:
258 res = "-D";
259 break;
260 }
261 res += " costly";
262 if (costName) {
263 res += costName;
264 }
265 sprintf(convBuff, "%lld", quota);
266 res += " -m quota ! --quota ";
267 res += convBuff;
268 ;
269 res += " --jump REJECT --reject-with";
270 if (isIp6) {
271 res += " icmp6-adm-prohibited";
272 } else {
273 res += " icmp-net-prohibited";
274 }
275 return res;
276}
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700277
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700278int BandwidthController::setInterfaceSharedQuota(int64_t maxBytes, const char *iface) {
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700279 char cmd[MAX_CMD_LEN];
280 char ifn[MAX_IFACENAME_LEN];
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700281 int res = 0;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700282
283 memset(ifn, 0, sizeof(ifn));
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700284 strncpy(ifn, iface, sizeof(ifn) - 1);
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700285
286 if (maxBytes == -1) {
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700287 return removeInterfaceSharedQuota(ifn);
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700288 }
289
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700290 char *costName = NULL; /* Shared quota */
291
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700292 /* Insert ingress quota. */
293 std::string ifaceName(ifn);
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700294 std::list<QuotaInfo>::iterator it;
295 for (it = ifaceRules.begin(); it != ifaceRules.end(); it++) {
296 if (it->first == ifaceName)
297 break;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700298 }
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700299
300 if (it == ifaceRules.end()) {
301 snprintf(cmd, sizeof(cmd), "-I INPUT -i %s --goto costly", ifn);
302 res |= runIptablesCmd(cmd);
303 res |= runIptablesCmd(cmd, true);
304 snprintf(cmd, sizeof(cmd), "-I OUTPUT -o %s --goto costly", ifn);
305 res |= runIptablesCmd(cmd);
306 res |= runIptablesCmd(cmd, true);
307
308 if (ifaceRules.empty()) {
309 std::string quotaCmd;
310 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes, false);
311 res |= runIptablesCmd(quotaCmd.c_str());
312 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes, true);
313 res |= runIptablesCmd(quotaCmd.c_str(), true);
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700314 if (res) {
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700315 LOGE("Failed set quota rule.");
316 goto fail;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700317 }
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700318 sharedQuotaBytes = maxBytes;
319 }
320
321 ifaceRules.push_front(QuotaInfo(ifaceName, maxBytes));
322
323 }
324
325 if (maxBytes != sharedQuotaBytes) {
326 /* Instead of replacing, which requires being aware of the rules in
327 * the kernel, we just add a new one, then delete the older one.
328 */
329 std::string quotaCmd;
330
331 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes, false);
332 res |= runIptablesCmd(quotaCmd.c_str());
333 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes, true);
334 res |= runIptablesCmd(quotaCmd.c_str(), true);
335
336 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes, false);
337 res |= runIptablesCmd(quotaCmd.c_str());
338 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes, true);
339 res |= runIptablesCmd(quotaCmd.c_str(), true);
340
341 if (res) {
342 LOGE("Failed replace quota rule.");
343 goto fail;
344 }
345 sharedQuotaBytes = maxBytes;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700346 }
347 return 0;
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700348
349 fail:
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700350 /*
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700351 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse
352 * rules in the kernel to see which ones need cleaning up.
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700353 * For now callers needs to choose if they want to "ndc bandwidth enable"
354 * which resets everything.
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700355 */
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700356 removeInterfaceSharedQuota(ifn);
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700357 return -1;
358}
359
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700360int BandwidthController::removeInterfaceSharedQuota(const char *iface) {
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700361 char cmd[MAX_CMD_LEN];
362 char ifn[MAX_IFACENAME_LEN];
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700363 int res = 0;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700364
365 memset(ifn, 0, sizeof(ifn));
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700366 strncpy(ifn, iface, sizeof(ifn) - 1);
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700367
368 std::string ifaceName(ifn);
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700369 std::list<QuotaInfo>::iterator it;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700370
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700371 for (it = ifaceRules.begin(); it != ifaceRules.end(); it++) {
372 if (it->first == ifaceName)
373 break;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700374 }
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700375 if (it == ifaceRules.end()) {
376 LOGE("No such iface %s to delete.", ifn);
377 return -1;
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700378 }
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700379
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700380 snprintf(cmd, sizeof(cmd), "--delete INPUT -i %s --goto costly", ifn);
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700381 res |= runIptablesCmd(cmd);
382 res |= runIptablesCmd(cmd, true);
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700383 snprintf(cmd, sizeof(cmd), "--delete OUTPUT -o %s --goto costly", ifn);
384 res |= runIptablesCmd(cmd);
JP Abgrallfa6f46d2011-06-17 23:17:28 -0700385 res |= runIptablesCmd(cmd, true);
386
387 ifaceRules.erase(it);
388 if (ifaceRules.empty()) {
389 std::string quotaCmd;
390 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, NULL, sharedQuotaBytes, false);
391 res |= runIptablesCmd(quotaCmd.c_str());
392
393 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, NULL, sharedQuotaBytes, true);
394 res |= runIptablesCmd(quotaCmd.c_str(), true);
395 sharedQuotaBytes = -1;
396 }
397
JP Abgrall4a5f5ca2011-06-15 18:37:39 -0700398 return res;
399}