BandwidthController.cpp revision d9db08c4a12d6a2953b597d39bb3ac37c43d3658
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 <string>
26#include <vector>
27
28#include <errno.h>
29#include <fcntl.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <ctype.h>
34
35#define __STDC_FORMAT_MACROS 1
36#include <inttypes.h>
37
38#include <sys/socket.h>
39#include <sys/stat.h>
40#include <sys/types.h>
41#include <sys/wait.h>
42
43#include <linux/netlink.h>
44#include <linux/rtnetlink.h>
45#include <linux/pkt_sched.h>
46
47#include "android-base/stringprintf.h"
48#include "android-base/strings.h"
49#define LOG_TAG "BandwidthController"
50#include <cutils/log.h>
51#include <cutils/properties.h>
52#include <logwrap/logwrap.h>
53
54#include "NetdConstants.h"
55#include "BandwidthController.h"
56#include "NatController.h"  /* For LOCAL_TETHER_COUNTERS_CHAIN */
57#include "ResponseCode.h"
58
59/* Alphabetical */
60#define ALERT_IPT_TEMPLATE "%s %s -m quota2 ! --quota %" PRId64" --name %s\n"
61const char* BandwidthController::LOCAL_INPUT = "bw_INPUT";
62const char* BandwidthController::LOCAL_FORWARD = "bw_FORWARD";
63const char* BandwidthController::LOCAL_OUTPUT = "bw_OUTPUT";
64const char* BandwidthController::LOCAL_RAW_PREROUTING = "bw_raw_PREROUTING";
65const char* BandwidthController::LOCAL_MANGLE_POSTROUTING = "bw_mangle_POSTROUTING";
66
67auto BandwidthController::execFunction = android_fork_execvp;
68auto BandwidthController::popenFunction = popen;
69auto BandwidthController::iptablesRestoreFunction = execIptablesRestoreWithOutput;
70
71using android::base::StringAppendF;
72using android::base::StringPrintf;
73
74namespace {
75
76const char ALERT_GLOBAL_NAME[] = "globalAlert";
77const int  MAX_CMD_ARGS = 32;
78const int  MAX_CMD_LEN = 1024;
79const int  MAX_IFACENAME_LEN = 64;
80const int  MAX_IPT_OUTPUT_LINE_LEN = 256;
81const std::string NEW_CHAIN_COMMAND = "-N ";
82const std::string GET_TETHER_STATS_COMMAND = StringPrintf(
83    "*filter\n"
84    "-nvx -L %s\n"
85    "COMMIT\n", NatController::LOCAL_TETHER_COUNTERS_CHAIN);
86
87
88/**
89 * Some comments about the rules:
90 *  * Ordering
91 *    - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains.
92 *      E.g. "-I bw_INPUT -i rmnet0 --jump costly"
93 *    - quota'd rules in the costly chain should be before bw_penalty_box lookups.
94 *    - the qtaguid counting is done at the end of the bw_INPUT/bw_OUTPUT user chains.
95 *
96 * * global quota vs per interface quota
97 *   - global quota for all costly interfaces uses a single costly chain:
98 *    . initial rules
99 *      iptables -N bw_costly_shared
100 *      iptables -I bw_INPUT -i iface0 --jump bw_costly_shared
101 *      iptables -I bw_OUTPUT -o iface0 --jump bw_costly_shared
102 *      iptables -I bw_costly_shared -m quota \! --quota 500000 \
103 *          --jump REJECT --reject-with icmp-net-prohibited
104 *      iptables -A bw_costly_shared --jump bw_penalty_box
105 *      iptables -A bw_penalty_box --jump bw_happy_box
106 *      iptables -A bw_happy_box --jump bw_data_saver
107 *
108 *    . adding a new iface to this, E.g.:
109 *      iptables -I bw_INPUT -i iface1 --jump bw_costly_shared
110 *      iptables -I bw_OUTPUT -o iface1 --jump bw_costly_shared
111 *
112 *   - quota per interface. This is achieve by having "costly" chains per quota.
113 *     E.g. adding a new costly interface iface0 with its own quota:
114 *      iptables -N bw_costly_iface0
115 *      iptables -I bw_INPUT -i iface0 --jump bw_costly_iface0
116 *      iptables -I bw_OUTPUT -o iface0 --jump bw_costly_iface0
117 *      iptables -A bw_costly_iface0 -m quota \! --quota 500000 \
118 *          --jump REJECT --reject-with icmp-port-unreachable
119 *      iptables -A bw_costly_iface0 --jump bw_penalty_box
120 *
121 * * Penalty box, happy box and data saver.
122 *   - bw_penalty box is a blacklist of apps that are rejected.
123 *   - bw_happy_box is a whitelist of apps. It always includes all system apps
124 *   - bw_data_saver implements data usage restrictions.
125 *   - Via the UI the user can add and remove apps from the whitelist and
126 *     blacklist, and turn on/off data saver.
127 *   - The blacklist takes precedence over the whitelist and the whitelist
128 *     takes precedence over data saver.
129 *
130 * * bw_penalty_box handling:
131 *  - only one bw_penalty_box for all interfaces
132 *   E.g  Adding an app:
133 *    iptables -I bw_penalty_box -m owner --uid-owner app_3 \
134 *        --jump REJECT --reject-with icmp-port-unreachable
135 *
136 * * bw_happy_box handling:
137 *  - The bw_happy_box comes after the penalty box.
138 *   E.g  Adding a happy app,
139 *    iptables -I bw_happy_box -m owner --uid-owner app_3 \
140 *        --jump RETURN
141 *
142 * * bw_data_saver handling:
143 *  - The bw_data_saver comes after the happy box.
144 *    Enable data saver:
145 *      iptables -R 1 bw_data_saver --jump REJECT --reject-with icmp-port-unreachable
146 *    Disable data saver:
147 *      iptables -R 1 bw_data_saver --jump RETURN
148 */
149
150const std::string COMMIT_AND_CLOSE = "COMMIT\n";
151const std::string DATA_SAVER_ENABLE_COMMAND = "-R bw_data_saver 1";
152const std::string HAPPY_BOX_WHITELIST_COMMAND = StringPrintf(
153    "-I bw_happy_box -m owner --uid-owner %d-%d --jump RETURN", 0, MAX_SYSTEM_UID);
154
155static const std::vector<std::string> IPT_FLUSH_COMMANDS = {
156    /*
157     * Cleanup rules.
158     * Should normally include bw_costly_<iface>, but we rely on the way they are setup
159     * to allow coexistance.
160     */
161    "*filter",
162    ":bw_INPUT -",
163    ":bw_OUTPUT -",
164    ":bw_FORWARD -",
165    ":bw_happy_box -",
166    ":bw_penalty_box -",
167    ":bw_data_saver -",
168    ":bw_costly_shared -",
169    "COMMIT",
170    "*raw",
171    ":bw_raw_PREROUTING -",
172    "COMMIT",
173    "*mangle",
174    ":bw_mangle_POSTROUTING -",
175    COMMIT_AND_CLOSE
176};
177
178static const std::vector<std::string> IPT_BASIC_ACCOUNTING_COMMANDS = {
179    "*filter",
180    "-A bw_INPUT -m owner --socket-exists", /* This is a tracking rule. */
181    "-A bw_OUTPUT -m owner --socket-exists", /* This is a tracking rule. */
182    "-A bw_costly_shared --jump bw_penalty_box",
183    "-A bw_penalty_box --jump bw_happy_box",
184    "-A bw_happy_box --jump bw_data_saver",
185    "-A bw_data_saver -j RETURN",
186    HAPPY_BOX_WHITELIST_COMMAND,
187    "COMMIT",
188
189    "*raw",
190    "-A bw_raw_PREROUTING -m owner --socket-exists", /* This is a tracking rule. */
191    "COMMIT",
192
193    "*mangle",
194    "-A bw_mangle_POSTROUTING -m owner --socket-exists", /* This is a tracking rule. */
195    COMMIT_AND_CLOSE
196};
197
198
199}  // namespace
200
201BandwidthController::BandwidthController(void) {
202}
203
204int BandwidthController::runIpxtablesCmd(const char *cmd, IptJumpOp jumpHandling,
205                                         IptFailureLog failureHandling) {
206    int res = 0;
207
208    ALOGV("runIpxtablesCmd(cmd=%s)", cmd);
209    res |= runIptablesCmd(cmd, jumpHandling, IptIpV4, failureHandling);
210    res |= runIptablesCmd(cmd, jumpHandling, IptIpV6, failureHandling);
211    return res;
212}
213
214int BandwidthController::StrncpyAndCheck(char *buffer, const char *src, size_t buffSize) {
215
216    memset(buffer, '\0', buffSize);  // strncpy() is not filling leftover with '\0'
217    strncpy(buffer, src, buffSize);
218    return buffer[buffSize - 1];
219}
220
221int BandwidthController::runIptablesCmd(const char *cmd, IptJumpOp jumpHandling,
222                                        IptIpVer iptVer, IptFailureLog failureHandling) {
223    char buffer[MAX_CMD_LEN];
224    const char *argv[MAX_CMD_ARGS];
225    int argc = 0;
226    char *next = buffer;
227    char *tmp;
228    int res;
229    int status = 0;
230
231    std::string fullCmd = cmd;
232    fullCmd += jumpToString(jumpHandling);
233
234    fullCmd.insert(0, " -w ");
235    fullCmd.insert(0, iptVer == IptIpV4 ? IPTABLES_PATH : IP6TABLES_PATH);
236
237    if (StrncpyAndCheck(buffer, fullCmd.c_str(), sizeof(buffer))) {
238        ALOGE("iptables command too long");
239        return -1;
240    }
241
242    argc = 0;
243    while ((tmp = strsep(&next, " "))) {
244        argv[argc++] = tmp;
245        if (argc >= MAX_CMD_ARGS) {
246            ALOGE("iptables argument overflow");
247            return -1;
248        }
249    }
250
251    argv[argc] = NULL;
252    res = execFunction(argc, (char **)argv, &status, false,
253            failureHandling == IptFailShow);
254    res = res || !WIFEXITED(status) || WEXITSTATUS(status);
255    if (res && failureHandling == IptFailShow) {
256      ALOGE("runIptablesCmd(): res=%d status=%d failed %s", res, status,
257            fullCmd.c_str());
258    }
259    return res;
260}
261
262void BandwidthController::flushCleanTables(bool doClean) {
263    /* Flush and remove the bw_costly_<iface> tables */
264    flushExistingCostlyTables(doClean);
265
266    std::string commands = android::base::Join(IPT_FLUSH_COMMANDS, '\n');
267    iptablesRestoreFunction(V4V6, commands, nullptr);
268}
269
270int BandwidthController::setupIptablesHooks(void) {
271    /* flush+clean is allowed to fail */
272    flushCleanTables(true);
273    return 0;
274}
275
276int BandwidthController::enableBandwidthControl(bool force) {
277    char value[PROPERTY_VALUE_MAX];
278
279    if (!force) {
280            property_get("persist.bandwidth.enable", value, "1");
281            if (!strcmp(value, "0"))
282                    return 0;
283    }
284
285    /* Let's pretend we started from scratch ... */
286    sharedQuotaIfaces.clear();
287    quotaIfaces.clear();
288    globalAlertBytes = 0;
289    globalAlertTetherCount = 0;
290    sharedQuotaBytes = sharedAlertBytes = 0;
291
292    flushCleanTables(false);
293    std::string commands = android::base::Join(IPT_BASIC_ACCOUNTING_COMMANDS, '\n');
294    return iptablesRestoreFunction(V4V6, commands, nullptr);
295}
296
297int BandwidthController::disableBandwidthControl(void) {
298
299    flushCleanTables(false);
300    return 0;
301}
302
303int BandwidthController::enableDataSaver(bool enable) {
304    return runIpxtablesCmd(DATA_SAVER_ENABLE_COMMAND.c_str(),
305                           enable ? IptJumpReject : IptJumpReturn, IptFailShow);
306}
307
308int BandwidthController::runCommands(int numCommands, const char *commands[],
309                                     RunCmdErrHandling cmdErrHandling) {
310    int res = 0;
311    IptFailureLog failureLogging = IptFailShow;
312    if (cmdErrHandling == RunCmdFailureOk) {
313        failureLogging = IptFailHide;
314    }
315    ALOGV("runCommands(): %d commands", numCommands);
316    for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) {
317        res = runIpxtablesCmd(commands[cmdNum], IptJumpNoAdd, failureLogging);
318        if (res && cmdErrHandling != RunCmdFailureOk)
319            return res;
320    }
321    return 0;
322}
323
324std::string BandwidthController::makeIptablesSpecialAppCmd(IptOp op, int uid, const char *chain) {
325    std::string res;
326    char *buff;
327    const char *opFlag;
328
329    switch (op) {
330    case IptOpInsert:
331        opFlag = "-I";
332        break;
333    case IptOpDelete:
334        opFlag = "-D";
335        break;
336    }
337    asprintf(&buff, "%s %s -m owner --uid-owner %d", opFlag, chain, uid);
338    res = buff;
339    free(buff);
340    return res;
341}
342
343int BandwidthController::addNaughtyApps(int numUids, char *appUids[]) {
344    return manipulateNaughtyApps(numUids, appUids, IptOpInsert);
345}
346
347int BandwidthController::removeNaughtyApps(int numUids, char *appUids[]) {
348    return manipulateNaughtyApps(numUids, appUids, IptOpDelete);
349}
350
351int BandwidthController::addNiceApps(int numUids, char *appUids[]) {
352    return manipulateNiceApps(numUids, appUids, IptOpInsert);
353}
354
355int BandwidthController::removeNiceApps(int numUids, char *appUids[]) {
356    return manipulateNiceApps(numUids, appUids, IptOpDelete);
357}
358
359int BandwidthController::manipulateNaughtyApps(int numUids, char *appStrUids[], IptOp op) {
360    return manipulateSpecialApps(numUids, appStrUids, "bw_penalty_box", IptJumpReject, op);
361}
362
363int BandwidthController::manipulateNiceApps(int numUids, char *appStrUids[], IptOp op) {
364    return manipulateSpecialApps(numUids, appStrUids, "bw_happy_box", IptJumpReturn, op);
365}
366
367
368int BandwidthController::manipulateSpecialApps(int numUids, char *appStrUids[],
369                                               const char *chain,
370                                               IptJumpOp jumpHandling, IptOp op) {
371
372    int uidNum;
373    const char *failLogTemplate;
374    int appUids[numUids];
375    std::string iptCmd;
376
377    switch (op) {
378    case IptOpInsert:
379        failLogTemplate = "Failed to add app uid %s(%d) to %s.";
380        break;
381    case IptOpDelete:
382        failLogTemplate = "Failed to delete app uid %s(%d) from %s box.";
383        break;
384    }
385
386    for (uidNum = 0; uidNum < numUids; uidNum++) {
387        char *end;
388        appUids[uidNum] = strtoul(appStrUids[uidNum], &end, 0);
389        if (*end || !*appStrUids[uidNum]) {
390            ALOGE(failLogTemplate, appStrUids[uidNum], appUids[uidNum], chain);
391            goto fail_parse;
392        }
393    }
394
395    for (uidNum = 0; uidNum < numUids; uidNum++) {
396        int uid = appUids[uidNum];
397
398        iptCmd = makeIptablesSpecialAppCmd(op, uid, chain);
399        if (runIpxtablesCmd(iptCmd.c_str(), jumpHandling)) {
400            ALOGE(failLogTemplate, appStrUids[uidNum], uid, chain);
401            goto fail_with_uidNum;
402        }
403    }
404    return 0;
405
406fail_with_uidNum:
407    /* Try to remove the uid that failed in any case*/
408    iptCmd = makeIptablesSpecialAppCmd(IptOpDelete, appUids[uidNum], chain);
409    runIpxtablesCmd(iptCmd.c_str(), jumpHandling);
410fail_parse:
411    return -1;
412}
413
414std::string BandwidthController::makeIptablesQuotaCmd(IptFullOp op, const char *costName, int64_t quota) {
415    std::string res;
416    char *buff;
417    const char *opFlag;
418
419    ALOGV("makeIptablesQuotaCmd(%d, %" PRId64")", op, quota);
420
421    switch (op) {
422    case IptFullOpInsert:
423        opFlag = "-I";
424        break;
425    case IptFullOpAppend:
426        opFlag = "-A";
427        break;
428    case IptFullOpDelete:
429        opFlag = "-D";
430        break;
431    }
432
433    // The requried IP version specific --jump REJECT ... will be added later.
434    asprintf(&buff, "%s bw_costly_%s -m quota2 ! --quota %" PRId64" --name %s", opFlag, costName, quota,
435             costName);
436    res = buff;
437    free(buff);
438    return res;
439}
440
441int BandwidthController::prepCostlyIface(const char *ifn, QuotaType quotaType) {
442    char cmd[MAX_CMD_LEN];
443    int res = 0, res1, res2;
444    int ruleInsertPos = 1;
445    std::string costString;
446    const char *costCString;
447
448    /* The "-N costly" is created upfront, no need to handle it here. */
449    switch (quotaType) {
450    case QuotaUnique:
451        costString = "bw_costly_";
452        costString += ifn;
453        costCString = costString.c_str();
454        /*
455         * Flush the bw_costly_<iface> is allowed to fail in case it didn't exist.
456         * Creating a new one is allowed to fail in case it existed.
457         * This helps with netd restarts.
458         */
459        snprintf(cmd, sizeof(cmd), "-F %s", costCString);
460        res1 = runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
461        snprintf(cmd, sizeof(cmd), "-N %s", costCString);
462        res2 = runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
463        res = (res1 && res2) || (!res1 && !res2);
464
465        snprintf(cmd, sizeof(cmd), "-A %s -j bw_penalty_box", costCString);
466        res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
467        break;
468    case QuotaShared:
469        costCString = "bw_costly_shared";
470        break;
471    }
472
473    if (globalAlertBytes) {
474        /* The alert rule comes 1st */
475        ruleInsertPos = 2;
476    }
477
478    snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString);
479    runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
480
481    snprintf(cmd, sizeof(cmd), "-I bw_INPUT %d -i %s --jump %s", ruleInsertPos, ifn, costCString);
482    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
483
484    snprintf(cmd, sizeof(cmd), "-D bw_OUTPUT -o %s --jump %s", ifn, costCString);
485    runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
486
487    snprintf(cmd, sizeof(cmd), "-I bw_OUTPUT %d -o %s --jump %s", ruleInsertPos, ifn, costCString);
488    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
489
490    snprintf(cmd, sizeof(cmd), "-D bw_FORWARD -o %s --jump %s", ifn, costCString);
491    runIpxtablesCmd(cmd, IptJumpNoAdd, IptFailHide);
492    snprintf(cmd, sizeof(cmd), "-A bw_FORWARD -o %s --jump %s", ifn, costCString);
493    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
494
495    return res;
496}
497
498int BandwidthController::cleanupCostlyIface(const char *ifn, QuotaType quotaType) {
499    char cmd[MAX_CMD_LEN];
500    int res = 0;
501    std::string costString;
502    const char *costCString;
503
504    switch (quotaType) {
505    case QuotaUnique:
506        costString = "bw_costly_";
507        costString += ifn;
508        costCString = costString.c_str();
509        break;
510    case QuotaShared:
511        costCString = "bw_costly_shared";
512        break;
513    }
514
515    snprintf(cmd, sizeof(cmd), "-D bw_INPUT -i %s --jump %s", ifn, costCString);
516    res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
517    for (const auto tableName : {LOCAL_OUTPUT, LOCAL_FORWARD}) {
518        snprintf(cmd, sizeof(cmd), "-D %s -o %s --jump %s", tableName, ifn, costCString);
519        res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
520    }
521
522    /* The "-N bw_costly_shared" is created upfront, no need to handle it here. */
523    if (quotaType == QuotaUnique) {
524        snprintf(cmd, sizeof(cmd), "-F %s", costCString);
525        res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
526        snprintf(cmd, sizeof(cmd), "-X %s", costCString);
527        res |= runIpxtablesCmd(cmd, IptJumpNoAdd);
528    }
529    return res;
530}
531
532int BandwidthController::setInterfaceSharedQuota(const char *iface, int64_t maxBytes) {
533    char ifn[MAX_IFACENAME_LEN];
534    int res = 0;
535    std::string quotaCmd;
536    std::string ifaceName;
537    ;
538    const char *costName = "shared";
539    std::list<std::string>::iterator it;
540
541    if (!maxBytes) {
542        /* Don't talk about -1, deprecate it. */
543        ALOGE("Invalid bytes value. 1..max_int64.");
544        return -1;
545    }
546    if (!isIfaceName(iface))
547        return -1;
548    if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
549        ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
550        return -1;
551    }
552    ifaceName = ifn;
553
554    if (maxBytes == -1) {
555        return removeInterfaceSharedQuota(ifn);
556    }
557
558    /* Insert ingress quota. */
559    for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) {
560        if (*it == ifaceName)
561            break;
562    }
563
564    if (it == sharedQuotaIfaces.end()) {
565        res |= prepCostlyIface(ifn, QuotaShared);
566        if (sharedQuotaIfaces.empty()) {
567            quotaCmd = makeIptablesQuotaCmd(IptFullOpInsert, costName, maxBytes);
568            res |= runIpxtablesCmd(quotaCmd.c_str(), IptJumpReject);
569            if (res) {
570                ALOGE("Failed set quota rule");
571                goto fail;
572            }
573            sharedQuotaBytes = maxBytes;
574        }
575        sharedQuotaIfaces.push_front(ifaceName);
576
577    }
578
579    if (maxBytes != sharedQuotaBytes) {
580        res |= updateQuota(costName, maxBytes);
581        if (res) {
582            ALOGE("Failed update quota for %s", costName);
583            goto fail;
584        }
585        sharedQuotaBytes = maxBytes;
586    }
587    return 0;
588
589    fail:
590    /*
591     * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse
592     * rules in the kernel to see which ones need cleaning up.
593     * For now callers needs to choose if they want to "ndc bandwidth enable"
594     * which resets everything.
595     */
596    removeInterfaceSharedQuota(ifn);
597    return -1;
598}
599
600/* It will also cleanup any shared alerts */
601int BandwidthController::removeInterfaceSharedQuota(const char *iface) {
602    char ifn[MAX_IFACENAME_LEN];
603    int res = 0;
604    std::string ifaceName;
605    std::list<std::string>::iterator it;
606    const char *costName = "shared";
607
608    if (!isIfaceName(iface))
609        return -1;
610    if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
611        ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
612        return -1;
613    }
614    ifaceName = ifn;
615
616    for (it = sharedQuotaIfaces.begin(); it != sharedQuotaIfaces.end(); it++) {
617        if (*it == ifaceName)
618            break;
619    }
620    if (it == sharedQuotaIfaces.end()) {
621        ALOGE("No such iface %s to delete", ifn);
622        return -1;
623    }
624
625    res |= cleanupCostlyIface(ifn, QuotaShared);
626    sharedQuotaIfaces.erase(it);
627
628    if (sharedQuotaIfaces.empty()) {
629        std::string quotaCmd;
630        quotaCmd = makeIptablesQuotaCmd(IptFullOpDelete, costName, sharedQuotaBytes);
631        res |= runIpxtablesCmd(quotaCmd.c_str(), IptJumpReject);
632        sharedQuotaBytes = 0;
633        if (sharedAlertBytes) {
634            removeSharedAlert();
635            sharedAlertBytes = 0;
636        }
637    }
638    return res;
639}
640
641int BandwidthController::setInterfaceQuota(const char *iface, int64_t maxBytes) {
642    char ifn[MAX_IFACENAME_LEN];
643    int res = 0;
644    std::string ifaceName;
645    const char *costName;
646    std::list<QuotaInfo>::iterator it;
647    std::string quotaCmd;
648
649    if (!isIfaceName(iface))
650        return -1;
651
652    if (!maxBytes) {
653        /* Don't talk about -1, deprecate it. */
654        ALOGE("Invalid bytes value. 1..max_int64.");
655        return -1;
656    }
657    if (maxBytes == -1) {
658        return removeInterfaceQuota(iface);
659    }
660
661    if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
662        ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
663        return -1;
664    }
665    ifaceName = ifn;
666    costName = iface;
667
668    /* Insert ingress quota. */
669    for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
670        if (it->ifaceName == ifaceName)
671            break;
672    }
673
674    if (it == quotaIfaces.end()) {
675        /* Preparing the iface adds a penalty/happy box check */
676        res |= prepCostlyIface(ifn, QuotaUnique);
677        /*
678         * The rejecting quota limit should go after the penalty/happy box checks
679         * or else a naughty app could just eat up the quota.
680         * So we append here.
681         */
682        quotaCmd = makeIptablesQuotaCmd(IptFullOpAppend, costName, maxBytes);
683        res |= runIpxtablesCmd(quotaCmd.c_str(), IptJumpReject);
684        if (res) {
685            ALOGE("Failed set quota rule");
686            goto fail;
687        }
688
689        quotaIfaces.push_front(QuotaInfo(ifaceName, maxBytes, 0));
690
691    } else {
692        res |= updateQuota(costName, maxBytes);
693        if (res) {
694            ALOGE("Failed update quota for %s", iface);
695            goto fail;
696        }
697        it->quota = maxBytes;
698    }
699    return 0;
700
701    fail:
702    /*
703     * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse
704     * rules in the kernel to see which ones need cleaning up.
705     * For now callers needs to choose if they want to "ndc bandwidth enable"
706     * which resets everything.
707     */
708    removeInterfaceSharedQuota(ifn);
709    return -1;
710}
711
712int BandwidthController::getInterfaceSharedQuota(int64_t *bytes) {
713    return getInterfaceQuota("shared", bytes);
714}
715
716int BandwidthController::getInterfaceQuota(const char *costName, int64_t *bytes) {
717    FILE *fp;
718    char *fname;
719    int scanRes;
720
721    if (!isIfaceName(costName))
722        return -1;
723
724    asprintf(&fname, "/proc/net/xt_quota/%s", costName);
725    fp = fopen(fname, "re");
726    free(fname);
727    if (!fp) {
728        ALOGE("Reading quota %s failed (%s)", costName, strerror(errno));
729        return -1;
730    }
731    scanRes = fscanf(fp, "%" SCNd64, bytes);
732    ALOGV("Read quota res=%d bytes=%" PRId64, scanRes, *bytes);
733    fclose(fp);
734    return scanRes == 1 ? 0 : -1;
735}
736
737int BandwidthController::removeInterfaceQuota(const char *iface) {
738
739    char ifn[MAX_IFACENAME_LEN];
740    int res = 0;
741    std::string ifaceName;
742    std::list<QuotaInfo>::iterator it;
743
744    if (!isIfaceName(iface))
745        return -1;
746    if (StrncpyAndCheck(ifn, iface, sizeof(ifn))) {
747        ALOGE("Interface name longer than %d", MAX_IFACENAME_LEN);
748        return -1;
749    }
750    ifaceName = ifn;
751
752    for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
753        if (it->ifaceName == ifaceName)
754            break;
755    }
756
757    if (it == quotaIfaces.end()) {
758        ALOGE("No such iface %s to delete", ifn);
759        return -1;
760    }
761
762    /* This also removes the quota command of CostlyIface chain. */
763    res |= cleanupCostlyIface(ifn, QuotaUnique);
764
765    quotaIfaces.erase(it);
766
767    return res;
768}
769
770int BandwidthController::updateQuota(const char *quotaName, int64_t bytes) {
771    FILE *fp;
772    char *fname;
773
774    if (!isIfaceName(quotaName)) {
775        ALOGE("updateQuota: Invalid quotaName \"%s\"", quotaName);
776        return -1;
777    }
778
779    asprintf(&fname, "/proc/net/xt_quota/%s", quotaName);
780    fp = fopen(fname, "we");
781    free(fname);
782    if (!fp) {
783        ALOGE("Updating quota %s failed (%s)", quotaName, strerror(errno));
784        return -1;
785    }
786    fprintf(fp, "%" PRId64"\n", bytes);
787    fclose(fp);
788    return 0;
789}
790
791int BandwidthController::runIptablesAlertCmd(IptOp op, const char *alertName, int64_t bytes) {
792    const char *opFlag = opToString(op);
793    std::string alertQuotaCmd = "*filter\n";
794
795    // TODO: consider using an alternate template for the delete that does not include the --quota
796    // value. This code works because the --quota value is ignored by deletes
797    StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_INPUT", bytes, alertName);
798    StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_OUTPUT", bytes, alertName);
799    StringAppendF(&alertQuotaCmd, "COMMIT\n");
800
801    return iptablesRestoreFunction(V4V6, alertQuotaCmd, nullptr);
802}
803
804int BandwidthController::runIptablesAlertFwdCmd(IptOp op, const char *alertName, int64_t bytes) {
805    const char *opFlag = opToString(op);
806    std::string alertQuotaCmd = "*filter\n";
807    StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, "bw_FORWARD", bytes, alertName);
808    StringAppendF(&alertQuotaCmd, "COMMIT\n");
809
810    return iptablesRestoreFunction(V4V6, alertQuotaCmd, nullptr);
811}
812
813int BandwidthController::setGlobalAlert(int64_t bytes) {
814    const char *alertName = ALERT_GLOBAL_NAME;
815    int res = 0;
816
817    if (!bytes) {
818        ALOGE("Invalid bytes value. 1..max_int64.");
819        return -1;
820    }
821    if (globalAlertBytes) {
822        res = updateQuota(alertName, bytes);
823    } else {
824        res = runIptablesAlertCmd(IptOpInsert, alertName, bytes);
825        if (globalAlertTetherCount) {
826            ALOGV("setGlobalAlert for %d tether", globalAlertTetherCount);
827            res |= runIptablesAlertFwdCmd(IptOpInsert, alertName, bytes);
828        }
829    }
830    globalAlertBytes = bytes;
831    return res;
832}
833
834int BandwidthController::setGlobalAlertInForwardChain(void) {
835    const char *alertName = ALERT_GLOBAL_NAME;
836    int res = 0;
837
838    globalAlertTetherCount++;
839    ALOGV("setGlobalAlertInForwardChain(): %d tether", globalAlertTetherCount);
840
841    /*
842     * If there is no globalAlert active we are done.
843     * If there is an active globalAlert but this is not the 1st
844     * tether, we are also done.
845     */
846    if (!globalAlertBytes || globalAlertTetherCount != 1) {
847        return 0;
848    }
849
850    /* We only add the rule if this was the 1st tether added. */
851    res = runIptablesAlertFwdCmd(IptOpInsert, alertName, globalAlertBytes);
852    return res;
853}
854
855int BandwidthController::removeGlobalAlert(void) {
856
857    const char *alertName = ALERT_GLOBAL_NAME;
858    int res = 0;
859
860    if (!globalAlertBytes) {
861        ALOGE("No prior alert set");
862        return -1;
863    }
864    res = runIptablesAlertCmd(IptOpDelete, alertName, globalAlertBytes);
865    if (globalAlertTetherCount) {
866        res |= runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes);
867    }
868    globalAlertBytes = 0;
869    return res;
870}
871
872int BandwidthController::removeGlobalAlertInForwardChain(void) {
873    int res = 0;
874    const char *alertName = ALERT_GLOBAL_NAME;
875
876    if (!globalAlertTetherCount) {
877        ALOGE("No prior alert set");
878        return -1;
879    }
880
881    globalAlertTetherCount--;
882    /*
883     * If there is no globalAlert active we are done.
884     * If there is an active globalAlert but there are more
885     * tethers, we are also done.
886     */
887    if (!globalAlertBytes || globalAlertTetherCount >= 1) {
888        return 0;
889    }
890
891    /* We only detete the rule if this was the last tether removed. */
892    res = runIptablesAlertFwdCmd(IptOpDelete, alertName, globalAlertBytes);
893    return res;
894}
895
896int BandwidthController::setSharedAlert(int64_t bytes) {
897    if (!sharedQuotaBytes) {
898        ALOGE("Need to have a prior shared quota set to set an alert");
899        return -1;
900    }
901    if (!bytes) {
902        ALOGE("Invalid bytes value. 1..max_int64.");
903        return -1;
904    }
905    return setCostlyAlert("shared", bytes, &sharedAlertBytes);
906}
907
908int BandwidthController::removeSharedAlert(void) {
909    return removeCostlyAlert("shared", &sharedAlertBytes);
910}
911
912int BandwidthController::setInterfaceAlert(const char *iface, int64_t bytes) {
913    std::list<QuotaInfo>::iterator it;
914
915    if (!isIfaceName(iface)) {
916        ALOGE("setInterfaceAlert: Invalid iface \"%s\"", iface);
917        return -1;
918    }
919
920    if (!bytes) {
921        ALOGE("Invalid bytes value. 1..max_int64.");
922        return -1;
923    }
924    for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
925        if (it->ifaceName == iface)
926            break;
927    }
928
929    if (it == quotaIfaces.end()) {
930        ALOGE("Need to have a prior interface quota set to set an alert");
931        return -1;
932    }
933
934    return setCostlyAlert(iface, bytes, &it->alert);
935}
936
937int BandwidthController::removeInterfaceAlert(const char *iface) {
938    std::list<QuotaInfo>::iterator it;
939
940    if (!isIfaceName(iface)) {
941        ALOGE("removeInterfaceAlert: Invalid iface \"%s\"", iface);
942        return -1;
943    }
944
945    for (it = quotaIfaces.begin(); it != quotaIfaces.end(); it++) {
946        if (it->ifaceName == iface)
947            break;
948    }
949
950    if (it == quotaIfaces.end()) {
951        ALOGE("No prior alert set for interface %s", iface);
952        return -1;
953    }
954
955    return removeCostlyAlert(iface, &it->alert);
956}
957
958int BandwidthController::setCostlyAlert(const char *costName, int64_t bytes, int64_t *alertBytes) {
959    char *alertQuotaCmd;
960    char *chainName;
961    int res = 0;
962    char *alertName;
963
964    if (!isIfaceName(costName)) {
965        ALOGE("setCostlyAlert: Invalid costName \"%s\"", costName);
966        return -1;
967    }
968
969    if (!bytes) {
970        ALOGE("Invalid bytes value. 1..max_int64.");
971        return -1;
972    }
973    asprintf(&alertName, "%sAlert", costName);
974    if (*alertBytes) {
975        res = updateQuota(alertName, *alertBytes);
976    } else {
977        asprintf(&chainName, "bw_costly_%s", costName);
978        asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "-A", chainName, bytes, alertName);
979        res |= runIpxtablesCmd(alertQuotaCmd, IptJumpNoAdd);
980        free(alertQuotaCmd);
981        free(chainName);
982    }
983    *alertBytes = bytes;
984    free(alertName);
985    return res;
986}
987
988int BandwidthController::removeCostlyAlert(const char *costName, int64_t *alertBytes) {
989    char *alertQuotaCmd;
990    char *chainName;
991    char *alertName;
992    int res = 0;
993
994    if (!isIfaceName(costName)) {
995        ALOGE("removeCostlyAlert: Invalid costName \"%s\"", costName);
996        return -1;
997    }
998
999    if (!*alertBytes) {
1000        ALOGE("No prior alert set for %s alert", costName);
1001        return -1;
1002    }
1003
1004    asprintf(&alertName, "%sAlert", costName);
1005    asprintf(&chainName, "bw_costly_%s", costName);
1006    asprintf(&alertQuotaCmd, ALERT_IPT_TEMPLATE, "-D", chainName, *alertBytes, alertName);
1007    res |= runIpxtablesCmd(alertQuotaCmd, IptJumpNoAdd);
1008    free(alertQuotaCmd);
1009    free(chainName);
1010
1011    *alertBytes = 0;
1012    free(alertName);
1013    return res;
1014}
1015
1016void BandwidthController::addStats(TetherStatsList& statsList, const TetherStats& stats) {
1017    for (TetherStats& existing : statsList) {
1018        if (existing.addStatsIfMatch(stats)) {
1019            return;
1020        }
1021    }
1022    // No match. Insert a new interface pair.
1023    statsList.push_back(stats);
1024}
1025
1026/*
1027 * Parse the ptks and bytes out of:
1028 *   Chain natctrl_tether_counters (4 references)
1029 *       pkts      bytes target     prot opt in     out     source               destination
1030 *         26     2373 RETURN     all  --  wlan0  rmnet0  0.0.0.0/0            0.0.0.0/0
1031 *         27     2002 RETURN     all  --  rmnet0 wlan0   0.0.0.0/0            0.0.0.0/0
1032 *       1040   107471 RETURN     all  --  bt-pan rmnet0  0.0.0.0/0            0.0.0.0/0
1033 *       1450  1708806 RETURN     all  --  rmnet0 bt-pan  0.0.0.0/0            0.0.0.0/0
1034 * or:
1035 *   Chain natctrl_tether_counters (0 references)
1036 *       pkts      bytes target     prot opt in     out     source               destination
1037 *          0        0 RETURN     all      wlan0  rmnet_data0  ::/0                 ::/0
1038 *          0        0 RETURN     all      rmnet_data0 wlan0   ::/0                 ::/0
1039 *
1040 * It results in an error if invoked and no tethering counter rules exist. The constraint
1041 * helps detect complete parsing failure.
1042 */
1043int BandwidthController::addForwardChainStats(const TetherStats& filter,
1044                                              TetherStatsList& statsList,
1045                                              const std::string& statsOutput,
1046                                              std::string &extraProcessingInfo) {
1047    int res;
1048    std::string statsLine;
1049    char iface0[MAX_IPT_OUTPUT_LINE_LEN];
1050    char iface1[MAX_IPT_OUTPUT_LINE_LEN];
1051    char rest[MAX_IPT_OUTPUT_LINE_LEN];
1052
1053    TetherStats stats;
1054    const char *buffPtr;
1055    int64_t packets, bytes;
1056    int statsFound = 0;
1057
1058    bool filterPair = filter.intIface[0] && filter.extIface[0];
1059
1060    char *filterMsg = filter.getStatsLine();
1061    ALOGV("filter: %s",  filterMsg);
1062    free(filterMsg);
1063
1064    stats = filter;
1065
1066    std::stringstream stream(statsOutput);
1067    while (std::getline(stream, statsLine, '\n')) {
1068        buffPtr = statsLine.c_str();
1069
1070        /* Clean up, so a failed parse can still print info */
1071        iface0[0] = iface1[0] = rest[0] = packets = bytes = 0;
1072        if (strstr(buffPtr, "0.0.0.0")) {
1073            // IPv4 has -- indicating what to do with fragments...
1074            //       26     2373 RETURN     all  --  wlan0  rmnet0  0.0.0.0/0            0.0.0.0/0
1075            res = sscanf(buffPtr, "%" SCNd64" %" SCNd64" RETURN all -- %s %s 0.%s",
1076                    &packets, &bytes, iface0, iface1, rest);
1077        } else {
1078            // ... but IPv6 does not.
1079            //       26     2373 RETURN     all      wlan0  rmnet0  ::/0                 ::/0
1080            res = sscanf(buffPtr, "%" SCNd64" %" SCNd64" RETURN all %s %s ::/%s",
1081                    &packets, &bytes, iface0, iface1, rest);
1082        }
1083        ALOGV("parse res=%d iface0=<%s> iface1=<%s> pkts=%" PRId64" bytes=%" PRId64" rest=<%s> orig line=<%s>", res,
1084             iface0, iface1, packets, bytes, rest, buffPtr);
1085        extraProcessingInfo += buffPtr;
1086        extraProcessingInfo += "\n";
1087
1088        if (res != 5) {
1089            continue;
1090        }
1091        /*
1092         * The following assumes that the 1st rule has in:extIface out:intIface,
1093         * which is what NatController sets up.
1094         * If not filtering, the 1st match rx, and sets up the pair for the tx side.
1095         */
1096        if (filter.intIface[0] && filter.extIface[0]) {
1097            if (filter.intIface == iface0 && filter.extIface == iface1) {
1098                ALOGV("2Filter RX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
1099                stats.rxPackets = packets;
1100                stats.rxBytes = bytes;
1101            } else if (filter.intIface == iface1 && filter.extIface == iface0) {
1102                ALOGV("2Filter TX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
1103                stats.txPackets = packets;
1104                stats.txBytes = bytes;
1105            }
1106        } else if (filter.intIface[0] || filter.extIface[0]) {
1107            if (filter.intIface == iface0 || filter.extIface == iface1) {
1108                ALOGV("1Filter RX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
1109                stats.intIface = iface0;
1110                stats.extIface = iface1;
1111                stats.rxPackets = packets;
1112                stats.rxBytes = bytes;
1113            } else if (filter.intIface == iface1 || filter.extIface == iface0) {
1114                ALOGV("1Filter TX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
1115                stats.intIface = iface1;
1116                stats.extIface = iface0;
1117                stats.txPackets = packets;
1118                stats.txBytes = bytes;
1119            }
1120        } else /* if (!filter.intFace[0] && !filter.extIface[0]) */ {
1121            if (!stats.intIface[0]) {
1122                ALOGV("0Filter RX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
1123                stats.intIface = iface0;
1124                stats.extIface = iface1;
1125                stats.rxPackets = packets;
1126                stats.rxBytes = bytes;
1127            } else if (stats.intIface == iface1 && stats.extIface == iface0) {
1128                ALOGV("0Filter TX iface_in=%s iface_out=%s rx_bytes=%" PRId64" rx_packets=%" PRId64" ", iface0, iface1, bytes, packets);
1129                stats.txPackets = packets;
1130                stats.txBytes = bytes;
1131            }
1132        }
1133        if (stats.rxBytes != -1 && stats.txBytes != -1) {
1134            ALOGV("rx_bytes=%" PRId64" tx_bytes=%" PRId64" filterPair=%d", stats.rxBytes, stats.txBytes, filterPair);
1135            addStats(statsList, stats);
1136            if (filterPair) {
1137                return 0;
1138            } else {
1139                statsFound++;
1140                stats = filter;
1141            }
1142        }
1143    }
1144
1145    /* It is always an error to find only one side of the stats. */
1146    /* It is an error to find nothing when not filtering. */
1147    if (((stats.rxBytes == -1) != (stats.txBytes == -1)) ||
1148        (!statsFound && !filterPair)) {
1149        return -1;
1150    }
1151    return 0;
1152}
1153
1154char *BandwidthController::TetherStats::getStatsLine(void) const {
1155    char *msg;
1156    asprintf(&msg, "%s %s %" PRId64" %" PRId64" %" PRId64" %" PRId64, intIface.c_str(), extIface.c_str(),
1157            rxBytes, rxPackets, txBytes, txPackets);
1158    return msg;
1159}
1160
1161int BandwidthController::getTetherStats(SocketClient *cli, TetherStats& filter,
1162                                        std::string &extraProcessingInfo) {
1163    int res = 0;
1164
1165    TetherStatsList statsList;
1166
1167    for (const IptablesTarget target : {V4, V6}) {
1168        std::string statsString;
1169        res = iptablesRestoreFunction(target, GET_TETHER_STATS_COMMAND, &statsString);
1170        if (res != 0) {
1171            ALOGE("Failed to run %s err=%d", GET_TETHER_STATS_COMMAND.c_str(), res);
1172            return -1;
1173        }
1174
1175        res = addForwardChainStats(filter, statsList, statsString, extraProcessingInfo);
1176        if (res != 0) {
1177            return res;
1178        }
1179    }
1180
1181    if (filter.intIface[0] && filter.extIface[0] && statsList.size() == 1) {
1182        cli->sendMsg(ResponseCode::TetheringStatsResult, statsList[0].getStatsLine(), false);
1183    } else {
1184        for (const auto& stats: statsList) {
1185            cli->sendMsg(ResponseCode::TetheringStatsListResult, stats.getStatsLine(), false);
1186        }
1187        if (res == 0) {
1188            cli->sendMsg(ResponseCode::CommandOkay, "Tethering stats list completed", false);
1189        }
1190    }
1191
1192    return res;
1193}
1194
1195void BandwidthController::flushExistingCostlyTables(bool doClean) {
1196    std::string fullCmd = "*filter\n-S\nCOMMIT\n";
1197    std::string ruleList;
1198
1199    /* Only lookup ip4 table names as ip6 will have the same tables ... */
1200    if (int ret = iptablesRestoreFunction(V4, fullCmd, &ruleList)) {
1201        ALOGE("Failed to list existing costly tables ret=%d", ret);
1202        return;
1203    }
1204    /* ... then flush/clean both ip4 and ip6 iptables. */
1205    parseAndFlushCostlyTables(ruleList, doClean);
1206}
1207
1208void BandwidthController::parseAndFlushCostlyTables(const std::string& ruleList, bool doRemove) {
1209    std::stringstream stream(ruleList);
1210    std::string rule;
1211    std::vector<std::string> clearCommands = { "*filter" };
1212    std::string chainName;
1213
1214    // Find and flush all rules starting with "-N bw_costly_<iface>" except "-N bw_costly_shared".
1215    while (std::getline(stream, rule, '\n')) {
1216        if (rule.find(NEW_CHAIN_COMMAND) != 0) continue;
1217        chainName = rule.substr(NEW_CHAIN_COMMAND.size());
1218        ALOGV("parse chainName=<%s> orig line=<%s>", chainName.c_str(), rule.c_str());
1219
1220        if (chainName.find("bw_costly_") != 0 || chainName == std::string("bw_costly_shared")) {
1221            continue;
1222        }
1223
1224        clearCommands.push_back(StringPrintf(":%s -", chainName.c_str()));
1225        if (doRemove) {
1226            clearCommands.push_back(StringPrintf("-X %s", chainName.c_str()));
1227        }
1228    }
1229
1230    if (clearCommands.size() == 1) {
1231        // No rules found.
1232        return;
1233    }
1234
1235    clearCommands.push_back("COMMIT\n");
1236    iptablesRestoreFunction(V4V6, android::base::Join(clearCommands, '\n'), nullptr);
1237}
1238
1239inline const char *BandwidthController::opToString(IptOp op) {
1240    switch (op) {
1241    case IptOpInsert:
1242        return "-I";
1243    case IptOpDelete:
1244        return "-D";
1245    }
1246}
1247
1248inline const char *BandwidthController::jumpToString(IptJumpOp jumpHandling) {
1249    /*
1250     * Must be careful what one rejects with, as upper layer protocols will just
1251     * keep on hammering the device until the number of retries are done.
1252     * For port-unreachable (default), TCP should consider as an abort (RFC1122).
1253     */
1254    switch (jumpHandling) {
1255    case IptJumpNoAdd:
1256        return "";
1257    case IptJumpReject:
1258        return " --jump REJECT";
1259    case IptJumpReturn:
1260        return " --jump RETURN";
1261    }
1262}
1263