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