blob: f859215f63fe108534f70234907fbe37d02e8007 [file] [log] [blame]
JP Abgrall4a5f5ca2011-06-15 18:37:39 -07001/*
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}