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