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