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 // #define LOG_NDEBUG 0
18
19 /*
20 * The CommandListener, FrameworkListener don't allow for
21 * multiple calls in parallel to reach the BandwidthController.
22 * If they ever were to allow it, then netd/ would need some tweaking.
23 */
24
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include <sys/socket.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #include <sys/wait.h>
35
36 #include <linux/netlink.h>
37 #include <linux/rtnetlink.h>
38 #include <linux/pkt_sched.h>
39
40 #define LOG_TAG "BandwidthController"
41 #include <cutils/log.h>
42 #include <cutils/properties.h>
43
44 extern "C" int logwrap(int argc, const char **argv);
45 extern "C" int system_nosh(const char *command);
46
47 #include "NetdConstants.h"
48 #include "BandwidthController.h"
49
50 /* Alphabetical */
51 #define ALERT_IPT_TEMPLATE "%s %s %s -m quota2 ! --quota %lld --name %s"
52 const int BandwidthController::ALERT_RULE_POS_IN_COSTLY_CHAIN = 4;
53 const char BandwidthController::ALERT_GLOBAL_NAME[] = "globalAlert";
54 const char* BandwidthController::LOCAL_INPUT = "bw_INPUT";
55 const char* BandwidthController::LOCAL_FORWARD = "bw_FORWARD";
56 const char* BandwidthController::LOCAL_OUTPUT = "bw_OUTPUT";
57 const char* BandwidthController::LOCAL_RAW_PREROUTING = "bw_raw_PREROUTING";
58 const char* BandwidthController::LOCAL_MANGLE_POSTROUTING = "bw_mangle_POSTROUTING";
59 const int BandwidthController::MAX_CMD_ARGS = 32;
60 const int BandwidthController::MAX_CMD_LEN = 1024;
61 const int BandwidthController::MAX_IFACENAME_LEN = 64;
62 const int BandwidthController::MAX_IPT_OUTPUT_LINE_LEN = 256;
63
64 bool BandwidthController::useLogwrapCall = false;
65
66 /**
67 * Some comments about the rules:
68 * * Ordering
69 * - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains.
70 * E.g. "-I bw_INPUT -i rmnet0 --jump costly"
71 * - quota'd rules in the costly chain should be before penalty_box lookups.
72 * - the qtaguid counting is done at the end of the bw_INPUT/bw_OUTPUT user chains.
73 *
74 * * global quota vs per interface quota
75 * - global quota for all costly interfaces uses a single costly chain:
76 * . initial rules
77 * iptables -N costly_shared
78 * iptables -I bw_INPUT -i iface0 --jump costly_shared
79 * iptables -I bw_OUTPUT -o iface0 --jump costly_shared
80 * iptables -I costly_shared -m quota \! --quota 500000 \
81 * --jump REJECT --reject-with icmp-net-prohibited
82 * iptables -A costly_shared --jump penalty_box
83 *
84 * . adding a new iface to this, E.g.:
85 * iptables -I bw_INPUT -i iface1 --jump costly_shared
86 * iptables -I bw_OUTPUT -o iface1 --jump costly_shared
87 *
88 * - quota per interface. This is achieve by having "costly" chains per quota.
89 * E.g. adding a new costly interface iface0 with its own quota:
90 * iptables -N costly_iface0
91 * iptables -I bw_INPUT -i iface0 --jump costly_iface0
92 * iptables -I bw_OUTPUT -o iface0 --jump costly_iface0
93 * iptables -A costly_iface0 -m quota \! --quota 500000 \
94 * --jump REJECT --reject-with icmp-net-prohibited
95 * iptables -A costly_iface0 --jump penalty_box
96 *
97 * * penalty_box handling:
98 * - only one penalty_box for all interfaces
99 * E.g Adding an app:
100 * iptables -A penalty_box -m owner --uid-owner app_3 \
101 * --jump REJECT --reject-with icmp-net-prohibited
102 */
103 const char *BandwidthController::IPT_FLUSH_COMMANDS[] = {
104 /*
105 * Cleanup rules.
106 * Should normally include costly_<iface>, but we rely on the way they are setup
107 * to allow coexistance.
108 */
109 "-F bw_INPUT",
110 "-F bw_OUTPUT",
111 "-F bw_FORWARD",
112 "-F penalty_box",
113 "-F costly_shared",
114
115 "-t raw -F bw_raw_PREROUTING",
116 "-t mangle -F bw_mangle_POSTROUTING",
117 };
118
119 /* The cleanup commands assume flushing has been done. */
120 const char *BandwidthController::IPT_CLEANUP_COMMANDS[] = {
121 "-X penalty_box",
122 "-X costly_shared",
123 };
124
125 const char *BandwidthController::IPT_SETUP_COMMANDS[] = {
126 "-N costly_shared",
127 "-N penalty_box",
128 };
129
130 const char *BandwidthController::IPT_BASIC_ACCOUNTING_COMMANDS[] = {
131 "-A bw_INPUT -i lo --jump RETURN",
132 "-A bw_INPUT -m owner --socket-exists", /* This is a tracking rule. */
133
134 "-A bw_OUTPUT -o lo --jump RETURN",
135 "-A bw_OUTPUT -m owner --socket-exists", /* This is a tracking rule. */
136
137 "-A costly_shared --jump penalty_box",
138
139 "-t raw -A bw_raw_PREROUTING ! -i lo+ -m owner --socket-exists", /* This is a tracking rule. */
140 "-t mangle -A bw_mangle_POSTROUTING ! -o lo+ -m owner --socket-exists", /* This is a tracking rule. */
141 };
142
BandwidthController(void)143 BandwidthController::BandwidthController(void) {
144 char value[PROPERTY_VALUE_MAX];
145
146 property_get("persist.bandwidth.uselogwrap", value, "0");
147 useLogwrapCall = !strcmp(value, "1");
148 }
149
runIpxtablesCmd(const char * cmd,IptRejectOp rejectHandling,IptFailureLog failureHandling)150 int BandwidthController::runIpxtablesCmd(const char *cmd, IptRejectOp rejectHandling,
151 IptFailureLog failureHandling) {
152 int res = 0;
153
154 ALOGV("runIpxtablesCmd(cmd=%s)", cmd);
155 res |= runIptablesCmd(cmd, rejectHandling, IptIpV4, failureHandling);
156 res |= runIptablesCmd(cmd, rejectHandling, IptIpV6, failureHandling);
157 return res;
158 }
159
StrncpyAndCheck(char * buffer,const char * src,size_t buffSize)160 int BandwidthController::StrncpyAndCheck(char *buffer, const char *src, size_t buffSize) {
161
162 memset(buffer, '\0', buffSize); // strncpy() is not filling leftover with '\0'
163 strncpy(buffer, src, buffSize);
164 return buffer[buffSize - 1];
165 }
166
runIptablesCmd(const char * cmd,IptRejectOp rejectHandling,IptIpVer iptVer,IptFailureLog failureHandling)167 int BandwidthController::runIptablesCmd(const char *cmd, IptRejectOp rejectHandling,
168 IptIpVer iptVer, IptFailureLog failureHandling) {
169 char buffer[MAX_CMD_LEN];
170 const char *argv[MAX_CMD_ARGS];
171 int argc = 0;
172 char *next = buffer;
173 char *tmp;
174 int res;
175
176 std::string fullCmd = cmd;
177
178 if (rejectHandling == IptRejectAdd) {
179 fullCmd += " --jump REJECT --reject-with";
180 switch (iptVer) {
181 case IptIpV4:
182 fullCmd += " icmp-net-prohibited";
183 break;
184 case IptIpV6:
185 fullCmd += " icmp6-adm-prohibited";
186 break;
187 }
188 }
189
190 fullCmd.insert(0, " ");
191 fullCmd.insert(0, iptVer == IptIpV4 ? IPTABLES_PATH : IP6TABLES_PATH);
192
193 if (!useLogwrapCall) {
194 res = system_nosh(fullCmd.c_str());
195 } else {
196 if (StrncpyAndCheck(buffer, fullCmd.c_str(), sizeof(buffer))) {
197 ALOGE("iptables command too long");
198 return -1;
199 }
200
201 argc = 0;
202 while ((tmp = strsep(&next, " "))) {
203 argv[argc++] = tmp;
204 if (argc >= MAX_CMD_ARGS) {
205 ALOGE("iptables argument overflow");
206 return -1;
207 }
208 }
209
210 argv[argc] = NULL;
211 res = logwrap(argc, argv);
212 }
213 if (res && failureHandling == IptFailShow) {
214 ALOGE("runIptablesCmd(): failed %s res=%d", fullCmd.c_str(), res);
215 }
216 return res;
217 }
218
setupIptablesHooks(void)219 int BandwidthController::setupIptablesHooks(void) {
220
221 /* Some of the initialCommands are allowed to fail */
222 runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*),
223 IPT_FLUSH_COMMANDS, RunCmdFailureOk);
224
225 runCommands(sizeof(IPT_CLEANUP_COMMANDS) / sizeof(char*),
226 IPT_CLEANUP_COMMANDS, RunCmdFailureOk);
227
228 runCommands(sizeof(IPT_SETUP_COMMANDS) / sizeof(char*),
229 IPT_SETUP_COMMANDS, RunCmdFailureBad);
230
231 return 0;
232 }
233
enableBandwidthControl(bool force)234 int BandwidthController::enableBandwidthControl(bool force) {
235 int res;
236 char value[PROPERTY_VALUE_MAX];
237
238 if (!force) {
239 property_get("persist.bandwidth.enable", value, "1");
240 if (!strcmp(value, "0"))
241 return 0;
242 }
243
244 /* Let's pretend we started from scratch ... */
245 sharedQuotaIfaces.clear();
246 quotaIfaces.clear();
247 naughtyAppUids.clear();
248 globalAlertBytes = 0;
249 globalAlertTetherCount = 0;
250 sharedQuotaBytes = sharedAlertBytes = 0;
251
252 res = runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*),
253 IPT_FLUSH_COMMANDS, RunCmdFailureOk);
254
255 res |= runCommands(sizeof(IPT_BASIC_ACCOUNTING_COMMANDS) / sizeof(char*),
256 IPT_BASIC_ACCOUNTING_COMMANDS, RunCmdFailureBad);
257
258 return res;
259
260 }
261
disableBandwidthControl(void)262 int BandwidthController::disableBandwidthControl(void) {
263 runCommands(sizeof(IPT_FLUSH_COMMANDS) / sizeof(char*),
264 IPT_FLUSH_COMMANDS, RunCmdFailureOk);
265 return 0;
266 }
267
runCommands(int numCommands,const char * commands[],RunCmdErrHandling cmdErrHandling)268 int BandwidthController::runCommands(int numCommands, const char *commands[],
269 RunCmdErrHandling cmdErrHandling) {
270 int res = 0;
271 IptFailureLog failureLogging = IptFailShow;
272 if (cmdErrHandling == RunCmdFailureOk) {
273 failureLogging = IptFailHide;
274 }
275 ALOGV("runCommands(): %d commands", numCommands);
276 for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) {
277 res = runIpxtablesCmd(commands[cmdNum], IptRejectNoAdd, failureLogging);
278 if (res && cmdErrHandling != RunCmdFailureOk)
279 return res;
280 }
281 return 0;
282 }
283
makeIptablesNaughtyCmd(IptOp op,int uid)284 std::string BandwidthController::makeIptablesNaughtyCmd(IptOp op, int uid) {
285 std::string res;
286 char *buff;
287 const char *opFlag;
288
289 switch (op) {
290 case IptOpInsert:
291 opFlag = "-I";
292 break;
293 case IptOpReplace:
294 opFlag = "-R";
295 break;
296 default:
297 case IptOpDelete:
298 opFlag = "-D";
299 break;
300 }
301 asprintf(&buff, "%s penalty_box -m owner --uid-owner %d", opFlag, uid);
302 res = buff;
303 free(buff);
304 return res;
305 }
306
addNaughtyApps(int numUids,char * appUids[])307 int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) {
308 return maninpulateNaughtyApps(numUids, appUids, NaughtyAppOpAdd);
309 }
310
removeNaughtyApps(int numUids,char * appUids[])311 int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) {
312 return maninpulateNaughtyApps(numUids, appUids, NaughtyAppOpRemove);
313 }
314
maninpulateNaughtyApps(int numUids,char * appStrUids[],NaughtyAppOp appOp)315 int BandwidthController::maninpulateNaughtyApps(int numUids, char *appStrUids[], NaughtyAppOp appOp) {
316 char cmd[MAX_CMD_LEN];
317 int uidNum;
318 const char *failLogTemplate;
319 IptOp op;
320 int appUids[numUids];
321 std::string naughtyCmd;
322 std::list<int /*uid*/>::iterator it;
323
324 switch (appOp) {
325 case NaughtyAppOpAdd:
326 op = IptOpInsert;
327 failLogTemplate = "Failed to add app uid %d to penalty box.";
328 break;
329 case NaughtyAppOpRemove:
330 op = IptOpDelete;
331 failLogTemplate = "Failed to delete app uid %d from penalty box.";
332 break;
333 default:
334 ALOGE("Unexpected app Op %d", appOp);
335 return -1;
336 }
337
338 for (uidNum = 0; uidNum < numUids; uidNum++) {
339 appUids[uidNum] = atol(appStrUids[uidNum]);
340 if (appUids[uidNum] == 0) {
341 ALOGE(failLogTemplate, appUids[uidNum]);
342 goto fail_parse;
343 }
344 }
345
346 for (uidNum = 0; uidNum < numUids; uidNum++) {
347 int uid = appUids[uidNum];
348 for (it = naughtyAppUids.begin(); it != naughtyAppUids.end(); it++) {
349 if (*it == uid)
350 break;
351 }
352 bool found = (it != naughtyAppUids.end());
353
354 if (appOp == NaughtyAppOpRemove) {
355 if (!found) {
356 ALOGE("No such appUid %d to remove", uid);
357 return -1;
358 }
359 naughtyAppUids.erase(it);
360 } else {
361 if (found) {
362 ALOGE("appUid %d exists already", uid);
363 return -1;
364 }
365 naughtyAppUids.push_front(uid);
366 }
367
368 naughtyCmd = makeIptablesNaughtyCmd(op, uid);
369 if (runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd)) {
370 ALOGE(failLogTemplate, uid);
371 goto fail_with_uidNum;
372 }
373 }
374 return 0;
375
376 fail_with_uidNum:
377 /* Try to remove the uid that failed in any case*/
378 naughtyCmd = makeIptablesNaughtyCmd(IptOpDelete, appUids[uidNum]);
379 runIpxtablesCmd(naughtyCmd.c_str(), IptRejectAdd);
380 fail_parse:
381 return -1;
382 }
383
makeIptablesQuotaCmd(IptOp op,const char * costName,int64_t quota)384 std::string BandwidthController::makeIptablesQuotaCmd(IptOp op, const char *costName, int64_t quota) {
385 std::string res;
386 char *buff;
387 const char *opFlag;
388
389 ALOGV("makeIptablesQuotaCmd(%d, %lld)", op, quota);
390
391 switch (op) {
392 case IptOpInsert:
393 opFlag = "-I";
394 break;
395 case IptOpReplace:
396 opFlag = "-R";
397 break;
398 default:
399 case IptOpDelete:
400 opFlag = "-D";
401 break;
402 }
403
404 // The requried IP version specific --jump REJECT ... will be added later.
405 asprintf(&buff, "%s costly_%s -m quota2 ! --quota %lld --name %s", opFlag, costName, quota,
406 costName);
407 res = buff;
408 free(buff);
409 return res;
410 }
411
prepCostlyIface(const char * ifn,QuotaType quotaType)412 int BandwidthController::prepCostlyIface(const char *ifn, QuotaType quotaType) {
413 char cmd[MAX_CMD_LEN];
414 int res = 0, res1, res2;
415 int ruleInsertPos = 1;
416 std::string costString;
417 const char *costCString;
418
419 /* The "-N costly" is created upfront, no need to handle it here. */
420 switch (quotaType) {
421 case QuotaUnique:
422 costString = "costly_";
423 costString += ifn;
424 costCString = costString.c_str();
425 /*
426 * Flush the costly_<iface> is allowed to fail in case it didn't exist.
427 * Creating a new one is allowed to fail in case it existed.
428 * This helps with netd restarts.
429 */
430 snprintf(cmd, sizeof(cmd), "-F %s", costCString);
431 res1 = runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide);
432 snprintf(cmd, sizeof(cmd), "-N %s", costCString);
433 res2 = runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide);
434 res = (res1 && res2) || (!res1 && !res2);
435
436 snprintf(cmd, sizeof(cmd), "-A %s -j penalty_box", costCString);
437 res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
438 break;
439 case QuotaShared:
440 costCString = "costly_shared";
441 break;
442 default:
443 ALOGE("Unexpected quotatype %d", quotaType);
444 return -1;
445 }
446
447 if (globalAlertBytes) {
448 /* The alert rule comes 1st */
449 ruleInsertPos = 2;
450 }
451
452 snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString);
453 runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide);
454
455 snprintf(cmd, sizeof(cmd), "-I bw_INPUT %d -i %s --jump %s", ruleInsertPos, ifn, costCString);
456 res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
457
458 snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString);
459 runIpxtablesCmd(cmd, IptRejectNoAdd, IptFailHide);
460
461 snprintf(cmd, sizeof(cmd), "-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, ifn, costCString);
462 res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
463 return res;
464 }
465
cleanupCostlyIface(const char * ifn,QuotaType quotaType)466 int BandwidthController::cleanupCostlyIface(const char *ifn, QuotaType quotaType) {
467 char cmd[MAX_CMD_LEN];
468 int res = 0;
469 std::string costString;
470 const char *costCString;
471
472 switch (quotaType) {
473 case QuotaUnique:
474 costString = "costly_";
475 costString += ifn;
476 costCString = costString.c_str();
477 break;
478 case QuotaShared:
479 costCString = "costly_shared";
480 break;
481 default:
482 ALOGE("Unexpected quotatype %d", quotaType);
483 return -1;
484 }
485
486 snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString);
487 res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
488 snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString);
489 res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
490
491 /* The "-N costly_shared" is created upfront, no need to handle it here. */
492 if (quotaType == QuotaUnique) {
493 snprintf(cmd, sizeof(cmd), "-F %s", costCString);
494 res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
495 snprintf(cmd, sizeof(cmd), "-X %s", costCString);
496 res |= runIpxtablesCmd(cmd, IptRejectNoAdd);
497 }
498 return res;
499 }
500
setInterfaceSharedQuota(const char * iface,int64_t maxBytes)501 int BandwidthController::setInterfaceSharedQuota(const char *iface, int64_t maxBytes) {
502 char cmd[MAX_CMD_LEN];
503 char ifn[MAX_IFACENAME_LEN];
504 int res = 0;
505 std::string quotaCmd;
506 std::string ifaceName;
507 ;
508 const char *costName = "shared";
509 std::list<std::string>::iterator it;
510
511 if (!maxBytes) {
512 /* Don't talk about -1, deprecate it. */
513 ALOGE("Invalid bytes value. 1..max_int64.");
514 return -1;
515 }
516 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
517 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
518 return -1;
519 }
520 ifaceName = ifn;
521
522 if (maxBytes == -1) {
523 return removeInterfaceSharedQuota(ifn);
524 }
525
526 /* Insert ingress quota. */
527 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) {
528 if (*it == ifaceName)
529 break;
530 }
531
532 if (it == sharedQuotaIfaces.end()) {
533 res |= prepCostlyIface(ifn, QuotaShared);
534 if (sharedQuotaIfaces.empty()) {
535 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes);
536 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd);
537 if (res) {
538 ALOGE("Failed set quota rule");
539 goto fail;
540 }
541 sharedQuotaBytes = maxBytes;
542 }
543 sharedQuotaIfaces.push_front(ifaceName);
544
545 }
546
547 if (maxBytes != sharedQuotaBytes) {
548 res |= updateQuota(costName, maxBytes);
549 if (res) {
550 ALOGE("Failed update quota for %s", costName);
551 goto fail;
552 }
553 sharedQuotaBytes = maxBytes;
554 }
555 return 0;
556
557 fail:
558 /*
559 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse
560 * rules in the kernel to see which ones need cleaning up.
561 * For now callers needs to choose if they want to "ndc bandwidth enable"
562 * which resets everything.
563 */
564 removeInterfaceSharedQuota(ifn);
565 return -1;
566 }
567
568 /* It will also cleanup any shared alerts */
removeInterfaceSharedQuota(const char * iface)569 int BandwidthController::removeInterfaceSharedQuota(const char *iface) {
570 char ifn[MAX_IFACENAME_LEN];
571 int res = 0;
572 std::string ifaceName;
573 std::list<std::string>::iterator it;
574 const char *costName = "shared";
575
576 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
577 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
578 return -1;
579 }
580 ifaceName = ifn;
581
582 for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) {
583 if (*it == ifaceName)
584 break;
585 }
586 if (it == sharedQuotaIfaces.end()) {
587 ALOGE("No such iface %s to delete", ifn);
588 return -1;
589 }
590
591 res |= cleanupCostlyIface(ifn, QuotaShared);
592 sharedQuotaIfaces.erase(it);
593
594 if (sharedQuotaIfaces.empty()) {
595 std::string quotaCmd;
596 quotaCmd = makeIptablesQuotaCmd(IptOpDelete, costName, sharedQuotaBytes);
597 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd);
598 sharedQuotaBytes = 0;
599 if (sharedAlertBytes) {
600 removeSharedAlert();
601 sharedAlertBytes = 0;
602 }
603 }
604 return res;
605 }
606
setInterfaceQuota(const char * iface,int64_t maxBytes)607 int BandwidthController::setInterfaceQuota(const char *iface, int64_t maxBytes) {
608 char ifn[MAX_IFACENAME_LEN];
609 int res = 0;
610 std::string ifaceName;
611 const char *costName;
612 std::list<QuotaInfo>::iterator it;
613 std::string quotaCmd;
614
615 if (!maxBytes) {
616 /* Don't talk about -1, deprecate it. */
617 ALOGE("Invalid bytes value. 1..max_int64.");
618 return -1;
619 }
620 if (maxBytes == -1) {
621 return removeInterfaceQuota(iface);
622 }
623
624 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
625 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
626 return -1;
627 }
628 ifaceName = ifn;
629 costName = iface;
630
631 /* Insert ingress quota. */
632 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
633 if (it->ifaceName == ifaceName)
634 break;
635 }
636
637 if (it == quotaIfaces.end()) {
638 res |= prepCostlyIface(ifn, QuotaUnique);
639 quotaCmd = makeIptablesQuotaCmd(IptOpInsert, costName, maxBytes);
640 res |= runIpxtablesCmd(quotaCmd.c_str(), IptRejectAdd);
641 if (res) {
642 ALOGE("Failed set quota rule");
643 goto fail;
644 }
645
646 quotaIfaces.push_front(QuotaInfo(ifaceName, maxBytes, 0));
647
648 } else {
649 res |= updateQuota(costName, maxBytes);
650 if (res) {
651 ALOGE("Failed update quota for %s", iface);
652 goto fail;
653 }
654 it->quota = maxBytes;
655 }
656 return 0;
657
658 fail:
659 /*
660 * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse
661 * rules in the kernel to see which ones need cleaning up.
662 * For now callers needs to choose if they want to "ndc bandwidth enable"
663 * which resets everything.
664 */
665 removeInterfaceSharedQuota(ifn);
666 return -1;
667 }
668
getInterfaceSharedQuota(int64_t * bytes)669 int BandwidthController::getInterfaceSharedQuota(int64_t *bytes) {
670 return getInterfaceQuota("shared", bytes);
671 }
672
getInterfaceQuota(const char * costName,int64_t * bytes)673 int BandwidthController::getInterfaceQuota(const char *costName, int64_t *bytes) {
674 FILE *fp;
675 char *fname;
676 int scanRes;
677
678 asprintf(&fname, "/proc/net/xt_quota/%s", costName);
679 fp = fopen(fname, "r");
680 free(fname);
681 if (!fp) {
682 ALOGE("Reading quota %s failed (%s)", costName, strerror(errno));
683 return -1;
684 }
685 scanRes = fscanf(fp, "%lld", bytes);
686 ALOGV("Read quota res=%d bytes=%lld", scanRes, *bytes);
687 fclose(fp);
688 return scanRes == 1 ? 0 : -1;
689 }
690
removeInterfaceQuota(const char * iface)691 int BandwidthController::removeInterfaceQuota(const char *iface) {
692
693 char ifn[MAX_IFACENAME_LEN];
694 int res = 0;
695 std::string ifaceName;
696 const char *costName;
697 std::list<QuotaInfo>::iterator it;
698
699 if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
700 ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
701 return -1;
702 }
703 ifaceName = ifn;
704 costName = iface;
705
706 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
707 if (it->ifaceName == ifaceName)
708 break;
709 }
710
711 if (it == quotaIfaces.end()) {
712 ALOGE("No such iface %s to delete", ifn);
713 return -1;
714 }
715
716 /* This also removes the quota command of CostlyIface chain. */
717 res |= cleanupCostlyIface(ifn, QuotaUnique);
718
719 quotaIfaces.erase(it);
720
721 return res;
722 }
723
updateQuota(const char * quotaName,int64_t bytes)724 int BandwidthController::updateQuota(const char *quotaName, int64_t bytes) {
725 FILE *fp;
726 char *fname;
727
728 asprintf(&fname, "/proc/net/xt_quota/%s", quotaName);
729 fp = fopen(fname, "w");
730 free(fname);
731 if (!fp) {
732 ALOGE("Updating quota %s failed (%s)", quotaName, strerror(errno));
733 return -1;
734 }
735 fprintf(fp, "%lld\n", bytes);
736 fclose(fp);
737 return 0;
738 }
739
runIptablesAlertCmd(IptOp op,const char * alertName,int64_t bytes)740 int BandwidthController::runIptablesAlertCmd(IptOp op, const char *alertName, int64_t bytes) {
741 int res = 0;
742 const char *opFlag;
743 const char *ifaceLimiting;
744 char *alertQuotaCmd;
745
746 switch (op) {
747 case IptOpInsert:
748 opFlag = "-I";
749 break;
750 case IptOpReplace:
751 opFlag = "-R";
752 break;
753 default:
754 case IptOpDelete:
755 opFlag = "-D";
756 break;
757 }
758
759 ifaceLimiting = "! -i lo+";
760 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "bw_INPUT",
761 bytes, alertName);
762 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd);
763 free(alertQuotaCmd);
764 ifaceLimiting = "! -o lo+";
765 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "bw_OUTPUT",
766 bytes, alertName);
767 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd);
768 free(alertQuotaCmd);
769 return res;
770 }
771
runIptablesAlertFwdCmd(IptOp op,const char * alertName,int64_t bytes)772 int BandwidthController::runIptablesAlertFwdCmd(IptOp op, const char *alertName, int64_t bytes) {
773 int res = 0;
774 const char *opFlag;
775 const char *ifaceLimiting;
776 char *alertQuotaCmd;
777
778 switch (op) {
779 case IptOpInsert:
780 opFlag = "-I";
781 break;
782 case IptOpReplace:
783 opFlag = "-R";
784 break;
785 default:
786 case IptOpDelete:
787 opFlag = "-D";
788 break;
789 }
790
791 ifaceLimiting = "! -i lo+";
792 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, ifaceLimiting, opFlag, "bw_FORWARD",
793 bytes, alertName);
794 res = runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd);
795 free(alertQuotaCmd);
796 return res;
797 }
798
setGlobalAlert(int64_t bytes)799 int BandwidthController::setGlobalAlert(int64_t bytes) {
800 const char *alertName = ALERT_GLOBAL_NAME;
801 int res = 0;
802
803 if (!bytes) {
804 ALOGE("Invalid bytes value. 1..max_int64.");
805 return -1;
806 }
807 if (globalAlertBytes) {
808 res = updateQuota(alertName, bytes);
809 } else {
810 res = runIptablesAlertCmd(IptOpInsert, alertName, bytes);
811 if (globalAlertTetherCount) {
812 ALOGV("setGlobalAlert for %d tether", globalAlertTetherCount);
813 res |= runIptablesAlertFwdCmd(IptOpInsert, alertName, bytes);
814 }
815 }
816 globalAlertBytes = bytes;
817 return res;
818 }
819
setGlobalAlertInForwardChain(void)820 int BandwidthController::setGlobalAlertInForwardChain(void) {
821 const char *alertName = ALERT_GLOBAL_NAME;
822 int res = 0;
823
824 globalAlertTetherCount++;
825 ALOGV("setGlobalAlertInForwardChain(): %d tether", globalAlertTetherCount);
826
827 /*
828 * If there is no globalAlert active we are done.
829 * If there is an active globalAlert but this is not the 1st
830 * tether, we are also done.
831 */
832 if (!globalAlertBytes || globalAlertTetherCount != 1) {
833 return 0;
834 }
835
836 /* We only add the rule if this was the 1st tether added. */
837 res = runIptablesAlertFwdCmd(IptOpInsert, alertName, globalAlertBytes);
838 return res;
839 }
840
removeGlobalAlert(void)841 int BandwidthController::removeGlobalAlert(void) {
842
843 const char *alertName = ALERT_GLOBAL_NAME;
844 int res = 0;
845
846 if (!globalAlertBytes) {
847 ALOGE("No prior alert set");
848 return -1;
849 }
850 res = runIptablesAlertCmd(IptOpDelete, alertName, globalAlertBytes);
851 if (globalAlertTetherCount) {
852 res |= runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes);
853 }
854 globalAlertBytes = 0;
855 return res;
856 }
857
removeGlobalAlertInForwardChain(void)858 int BandwidthController::removeGlobalAlertInForwardChain(void) {
859 int res = 0;
860 const char *alertName = ALERT_GLOBAL_NAME;
861
862 if (!globalAlertTetherCount) {
863 ALOGE("No prior alert set");
864 return -1;
865 }
866
867 globalAlertTetherCount--;
868 /*
869 * If there is no globalAlert active we are done.
870 * If there is an active globalAlert but there are more
871 * tethers, we are also done.
872 */
873 if (!globalAlertBytes || globalAlertTetherCount >= 1) {
874 return 0;
875 }
876
877 /* We only detete the rule if this was the last tether removed. */
878 res = runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes);
879 return res;
880 }
881
setSharedAlert(int64_t bytes)882 int BandwidthController::setSharedAlert(int64_t bytes) {
883 if (!sharedQuotaBytes) {
884 ALOGE("Need to have a prior shared quota set to set an alert");
885 return -1;
886 }
887 if (!bytes) {
888 ALOGE("Invalid bytes value. 1..max_int64.");
889 return -1;
890 }
891 return setCostlyAlert("shared", bytes, &sharedAlertBytes);
892 }
893
removeSharedAlert(void)894 int BandwidthController::removeSharedAlert(void) {
895 return removeCostlyAlert("shared", &sharedAlertBytes);
896 }
897
setInterfaceAlert(const char * iface,int64_t bytes)898 int BandwidthController::setInterfaceAlert(const char *iface, int64_t bytes) {
899 std::list<QuotaInfo>::iterator it;
900
901 if (!bytes) {
902 ALOGE("Invalid bytes value. 1..max_int64.");
903 return -1;
904 }
905 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
906 if (it->ifaceName == iface)
907 break;
908 }
909
910 if (it == quotaIfaces.end()) {
911 ALOGE("Need to have a prior interface quota set to set an alert");
912 return -1;
913 }
914
915 return setCostlyAlert(iface, bytes, &it->alert);
916 }
917
removeInterfaceAlert(const char * iface)918 int BandwidthController::removeInterfaceAlert(const char *iface) {
919 std::list<QuotaInfo>::iterator it;
920
921 for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
922 if (it->ifaceName == iface)
923 break;
924 }
925
926 if (it == quotaIfaces.end()) {
927 ALOGE("No prior alert set for interface %s", iface);
928 return -1;
929 }
930
931 return removeCostlyAlert(iface, &it->alert);
932 }
933
setCostlyAlert(const char * costName,int64_t bytes,int64_t * alertBytes)934 int BandwidthController::setCostlyAlert(const char *costName, int64_t bytes, int64_t *alertBytes) {
935 char *alertQuotaCmd;
936 char *chainNameAndPos;
937 int res = 0;
938 char *alertName;
939
940 if (!bytes) {
941 ALOGE("Invalid bytes value. 1..max_int64.");
942 return -1;
943 }
944 asprintf(&alertName, "%sAlert", costName);
945 if (*alertBytes) {
946 res = updateQuota(alertName, *alertBytes);
947 } else {
948 asprintf(&chainNameAndPos, "costly_%s %d", costName, ALERT_RULE_POS_IN_COSTLY_CHAIN);
949 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "", "-I", chainNameAndPos, bytes, alertName);
950 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd);
951 free(alertQuotaCmd);
952 free(chainNameAndPos);
953 }
954 *alertBytes = bytes;
955 free(alertName);
956 return res;
957 }
958
removeCostlyAlert(const char * costName,int64_t * alertBytes)959 int BandwidthController::removeCostlyAlert(const char *costName, int64_t *alertBytes) {
960 char *alertQuotaCmd;
961 char *chainName;
962 char *alertName;
963 int res = 0;
964
965 asprintf(&alertName, "%sAlert", costName);
966 if (!*alertBytes) {
967 ALOGE("No prior alert set for %s alert", costName);
968 return -1;
969 }
970
971 asprintf(&chainName, "costly_%s", costName);
972 asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "", "-D", chainName, *alertBytes, alertName);
973 res |= runIpxtablesCmd(alertQuotaCmd, IptRejectNoAdd);
974 free(alertQuotaCmd);
975 free(chainName);
976
977 *alertBytes = 0;
978 free(alertName);
979 return res;
980 }
981
982 /*
983 * Parse the ptks and bytes out of:
984 * Chain FORWARD (policy RETURN 0 packets, 0 bytes)
985 * pkts bytes target prot opt in out source destination
986 * 0 0 RETURN all -- rmnet0 wlan0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
987 * 0 0 DROP all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0 state INVALID
988 * 0 0 RETURN all -- wlan0 rmnet0 0.0.0.0/0 0.0.0.0/0
989 *
990 */
parseForwardChainStats(TetherStats & stats,FILE * fp,std::string & extraProcessingInfo)991 int BandwidthController::parseForwardChainStats(TetherStats &stats, FILE *fp,
992 std::string &extraProcessingInfo) {
993 int res;
994 char lineBuffer[MAX_IPT_OUTPUT_LINE_LEN];
995 char iface0[MAX_IPT_OUTPUT_LINE_LEN];
996 char iface1[MAX_IPT_OUTPUT_LINE_LEN];
997 char rest[MAX_IPT_OUTPUT_LINE_LEN];
998
999 char *buffPtr;
1000 int64_t packets, bytes;
1001
1002 while (NULL != (buffPtr = fgets(lineBuffer, MAX_IPT_OUTPUT_LINE_LEN, fp))) {
1003 /* Clean up, so a failed parse can still print info */
1004 iface0[0] = iface1[0] = rest[0] = packets = bytes = 0;
1005 res = sscanf(buffPtr, "%lld %lld RETURN all -- %s %s 0.%s",
1006 &packets, &bytes, iface0, iface1, rest);
1007 ALOGV("parse res=%d iface0=<%s> iface1=<%s> pkts=%lld bytes=%lld rest=<%s> orig line=<%s>", res,
1008 iface0, iface1, packets, bytes, rest, buffPtr);
1009 extraProcessingInfo += buffPtr;
1010
1011 if (res != 5) {
1012 continue;
1013 }
1014 if ((stats.ifaceIn == iface0) && (stats.ifaceOut == iface1)) {
1015 ALOGV("iface_in=%s iface_out=%s rx_bytes=%lld rx_packets=%lld ", iface0, iface1, bytes, packets);
1016 stats.rxPackets = packets;
1017 stats.rxBytes = bytes;
1018 } else if ((stats.ifaceOut == iface0) && (stats.ifaceIn == iface1)) {
1019 ALOGV("iface_in=%s iface_out=%s tx_bytes=%lld tx_packets=%lld ", iface1, iface0, bytes, packets);
1020 stats.txPackets = packets;
1021 stats.txBytes = bytes;
1022 }
1023 }
1024 /* Failure if rx or tx was not found */
1025 return (stats.rxBytes == -1 || stats.txBytes == -1) ? -1 : 0;
1026 }
1027
1028
getStatsLine(void)1029 char *BandwidthController::TetherStats::getStatsLine(void) {
1030 char *msg;
1031 asprintf(&msg, "%s %s %lld %lld %lld %lld", ifaceIn.c_str(), ifaceOut.c_str(),
1032 rxBytes, rxPackets, txBytes, txPackets);
1033 return msg;
1034 }
1035
getTetherStats(TetherStats & stats,std::string & extraProcessingInfo)1036 int BandwidthController::getTetherStats(TetherStats &stats, std::string &extraProcessingInfo) {
1037 int res;
1038 std::string fullCmd;
1039 FILE *iptOutput;
1040 const char *cmd;
1041
1042 if (stats.rxBytes != -1 || stats.txBytes != -1) {
1043 ALOGE("Unexpected input stats. Byte counts should be -1.");
1044 return -1;
1045 }
1046
1047 /*
1048 * Why not use some kind of lib to talk to iptables?
1049 * Because the only libs are libiptc and libip6tc in iptables, and they are
1050 * not easy to use. They require the known iptables match modules to be
1051 * preloaded/linked, and require apparently a lot of wrapper code to get
1052 * the wanted info.
1053 */
1054 fullCmd = IPTABLES_PATH;
1055 fullCmd += " -nvx -L natctrl_FORWARD";
1056 iptOutput = popen(fullCmd.c_str(), "r");
1057 if (!iptOutput) {
1058 ALOGE("Failed to run %s err=%s", fullCmd.c_str(), strerror(errno));
1059 extraProcessingInfo += "Failed to run iptables.";
1060 return -1;
1061 }
1062 res = parseForwardChainStats(stats, iptOutput, extraProcessingInfo);
1063 pclose(iptOutput);
1064
1065 /* Currently NatController doesn't do ipv6 tethering, so we are done. */
1066 return res;
1067 }
1068