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