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