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