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