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