BandwidthController.cpp revision 4a5f5ca3c9e07fc3e6feca2afde07f41a8a64f11
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 <stdlib.h>
18#include <errno.h>
19#include <fcntl.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
39
40const int BandwidthController::MAX_CMD_LEN = 255;
41const int BandwidthController::MAX_IFACENAME_LEN = 64;
42const int BandwidthController::MAX_CMD_ARGS = 32;
43const char BandwidthController::IPTABLES_PATH[] = "/system/bin/iptables";
44
45
46/**
47 * Some comments about the rules:
48 *  * Ordering
49 *    - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains.
50 *      E.g. "-I INPUT -i rmnet0 --goto costly"
51 *    - quota'd rules in the costly chain should be before penalty_box lookups.
52 *
53 * * global quota vs per interface quota
54 *   - global quota for all costly interfaces uses a single costly chain:
55 *    . initial rules
56 *      iptables -N costly
57 *      iptables -I INPUT -i iface0 --goto costly
58 *      iptables -I OUTPUT -o iface0 --goto costly
59 *      iptables -I costly -m quota \! --quota 500000 --jump REJECT --reject-with icmp-net-prohibited
60 *      iptables -A costly                            --jump penalty_box
61 *      iptables -A costly -m owner --socket-exists
62 *    . adding a new iface to this, E.g.:
63 *      iptables -I INPUT -i iface1 --goto costly
64 *      iptables -I OUTPUT -o iface1 --goto costly
65 *
66 *   - quota per interface. This is achieve by having "costly" chains per quota.
67 *     E.g. adding a new costly interface iface0 with its own quota:
68 *      iptables -N costly_iface0
69 *      iptables -I INPUT -i iface0 --goto costly_iface0
70 *      iptables -I OUTPUT -o iface0 --goto costly_iface0
71 *      iptables -A costly_iface0 -m quota \! --quota 500000 --jump REJECT --reject-with icmp-net-prohibited
72 *      iptables -A costly_iface0                            --jump penalty_box
73 *      iptables -A costly_iface0 -m owner --socket-exists
74 *
75 * * penalty_box handling:
76 *  - only one penalty_box for all interfaces
77 *   E.g  Adding an app:
78 *    iptables -A penalty_box -m owner --uid-owner app_3 --jump REJECT --reject-with icmp-net-prohibited
79 */
80const char *BandwidthController::cleanupCommands[] = {
81    /* Cleanup rules. */
82    "-F",
83    "-t raw -F",
84    "-X costly",
85    "-X penalty_box",
86};
87
88const char *BandwidthController::setupCommands[] = {
89    /* Created needed chains. */
90    "-N costly",
91    "-N penalty_box",
92};
93
94const char *BandwidthController::basicAccountingCommands[] = {
95    "-F INPUT",
96    "-A INPUT -i lo --jump ACCEPT",
97    "-A INPUT -m owner --socket-exists",  /* This is a tracking rule. */
98
99    "-F OUTPUT",
100    "-A OUTPUT -o lo --jump ACCEPT",
101    "-A OUTPUT -m owner --socket-exists",  /* This is a tracking rule. */
102
103    "-F costly",
104    "-A costly --jump penalty_box",
105    "-A costly -m owner --socket-exists",    /* This is a tracking rule. */
106    /* TODO(jpa): Figure out why iptables doesn't correctly return from this
107     * chain. For now, hack the chain exit with an ACCEPT.
108     */
109    "-A costly --jump ACCEPT",
110};
111
112
113BandwidthController::BandwidthController(void) {
114
115    char value[PROPERTY_VALUE_MAX];
116
117    property_get("persist.bandwidth.enable", value, "0");
118    if (!strcmp(value, "1")) {
119        enableBandwidthControl();
120    }
121
122}
123
124int BandwidthController::runIptablesCmd(const char *cmd) {
125    char buffer[MAX_CMD_LEN];
126
127    LOGD("About to run: iptables %s", cmd);
128
129    strncpy(buffer, cmd, sizeof(buffer)-1);
130
131    const char *argv[MAX_CMD_ARGS];
132    char *next = buffer;
133    char *tmp;
134
135    argv[0] = IPTABLES_PATH;
136    int argc = 1;
137
138    while ((tmp = strsep(&next, " "))) {
139        argv[argc++] = tmp;
140        if (argc == MAX_CMD_ARGS) {
141            LOGE("iptables argument overflow");
142            errno = E2BIG;
143            return -1;
144        }
145    }
146    argv[argc] = NULL;
147    /* TODO(jpa): Once this stabilizes, remove logwrap() as it tends to wedge netd
148     * Then just talk directly to the kernel via rtnetlink.
149     */
150    return logwrap(argc, argv, 0);
151}
152
153
154int BandwidthController::enableBandwidthControl(void) {
155        /* Some of the initialCommands are allowed to fail */
156        runCommands(cleanupCommands, sizeof(cleanupCommands)/sizeof(char*), true);
157        runCommands(setupCommands, sizeof(setupCommands)/sizeof(char*), true);
158        return runCommands(basicAccountingCommands,
159                           sizeof(basicAccountingCommands)/sizeof(char*));
160
161}
162
163int BandwidthController::disableBandwidthControl(void) {
164        /* The cleanupCommands are allowed to fail */
165        runCommands(cleanupCommands, sizeof(cleanupCommands)/sizeof(char*), true);
166        return 0;
167}
168
169int BandwidthController::runCommands(const char *commands[], int numCommands, bool allowFailure) {
170        int res = 0;
171        LOGD("runCommands(): %d commands", numCommands);
172        for (int cmdNum = 0; cmdNum < numCommands; cmdNum++) {
173                res = runIptablesCmd(commands[cmdNum]);
174                if(res && !allowFailure) return res;
175        }
176        return allowFailure?res:0;
177}
178
179
180int BandwidthController::setInterfaceQuota(const char *iface,
181                                           int64_t maxBytes) {
182    char cmd[MAX_CMD_LEN];
183    char ifn[MAX_IFACENAME_LEN];
184    int res;
185
186    memset(ifn, 0, sizeof(ifn));
187    strncpy(ifn, iface, sizeof(ifn)-1);
188
189    if (maxBytes == -1) {
190        return removeQuota(ifn);
191    }
192
193    /* Insert ingress quota. */
194    std::string ifaceName(ifn);
195    std::list<std::string>::iterator it;
196    int pos;
197    for (pos=1, it = ifaceRules.begin(); it != ifaceRules.end(); it++, pos++) {
198            if (*it == ifaceName)
199                    break;
200    }
201    if (it != ifaceRules.end()) {
202            snprintf(cmd, sizeof(cmd), "-R INPUT %d -i %s --goto costly", pos, ifn);
203            res = runIptablesCmd(cmd);
204            snprintf(cmd, sizeof(cmd), "-R OUTPUT %d -o %s --goto costly", pos, ifn);
205            res |= runIptablesCmd(cmd);
206            snprintf(cmd, sizeof(cmd), "-R costly %d -m quota ! --quota %lld"
207                    " --jump REJECT --reject-with icmp-net-prohibited",
208                    pos, maxBytes);
209            res |= runIptablesCmd(cmd);
210            if (res) {
211                    LOGE("Failed set quota rule.");
212                    goto fail;
213            }
214    } else {
215            pos = 1;
216            snprintf(cmd, sizeof(cmd), "-I INPUT -i %s --goto costly", ifn);
217            res = runIptablesCmd(cmd);
218            snprintf(cmd, sizeof(cmd), "-I OUTPUT -o %s --goto costly", ifn);
219            res |= runIptablesCmd(cmd);
220            snprintf(cmd, sizeof(cmd), "-I costly -m quota ! --quota %lld"
221                    " --jump REJECT --reject-with icmp-net-prohibited",
222                    maxBytes);
223            res |= runIptablesCmd(cmd);
224            if (res) {
225                    LOGE("Failed set quota rule.");
226                    goto fail;
227            }
228            ifaceRules.push_front(ifaceName);
229    }
230    return 0;
231fail:
232    /*
233     * Failure tends to be that the rules have been messed up.
234     * For now cleanup all the rules.
235     * TODO(jpa): once we get rid of iptables in favor of rtnetlink, reparse
236     * rules in the kernel to see which ones need cleaning up.
237     */
238    runCommands(basicAccountingCommands,
239                sizeof(basicAccountingCommands)/sizeof(char*), true);
240    removeQuota(ifn);
241    return -1;
242}
243
244int BandwidthController::removeQuota(const char *iface) {
245    char cmd[MAX_CMD_LEN];
246    char ifn[MAX_IFACENAME_LEN];
247    int res;
248
249    memset(ifn, 0, sizeof(ifn));
250    strncpy(ifn, iface, sizeof(ifn)-1);
251
252    std::string ifaceName(ifn);
253    std::list<std::string>::iterator it;
254
255    int pos;
256    for (pos=1, it = ifaceRules.begin(); it != ifaceRules.end(); it++, pos++) {
257            if (*it == ifaceName)
258                    break;
259    }
260    if(it == ifaceRules.end()) {
261            LOGE("No such iface %s to delete.", ifn);
262            return -1;
263    }
264    ifaceRules.erase(it);
265    snprintf(cmd, sizeof(cmd), "--delete INPUT -i %s --goto costly", ifn);
266    res = runIptablesCmd(cmd);
267    snprintf(cmd, sizeof(cmd), "--delete OUTPUT -o %s --goto costly", ifn);
268    res |= runIptablesCmd(cmd);
269    // Don't use rule-matching for this one. Quota is the remaining one.
270    snprintf(cmd, sizeof(cmd), "--delete costly %d", pos);
271    res |= runIptablesCmd(cmd);
272    return res;
273}
274