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