• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <ctype.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <string>
32 #include <vector>
33 
34 #define __STDC_FORMAT_MACROS 1
35 #include <inttypes.h>
36 
37 #include <sys/socket.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <sys/wait.h>
41 
42 #include <linux/netlink.h>
43 #include <linux/rtnetlink.h>
44 #include <linux/pkt_sched.h>
45 
46 #include "android-base/stringprintf.h"
47 #include "android-base/strings.h"
48 #define LOG_TAG "BandwidthController"
49 #include <cutils/properties.h>
50 #include <log/log.h>
51 
52 #include <netdutils/Syscalls.h>
53 #include "BandwidthController.h"
54 #include "Controllers.h"
55 #include "FirewallController.h" /* For makeCriticalCommands */
56 #include "Fwmark.h"
57 #include "NetdConstants.h"
58 #include "TrafficController.h"
59 #include "bpf/BpfUtils.h"
60 
61 /* Alphabetical */
62 #define ALERT_IPT_TEMPLATE "%s %s -m quota2 ! --quota %" PRId64" --name %s\n"
63 const char BandwidthController::LOCAL_INPUT[] = "bw_INPUT";
64 const char BandwidthController::LOCAL_FORWARD[] = "bw_FORWARD";
65 const char BandwidthController::LOCAL_OUTPUT[] = "bw_OUTPUT";
66 const char BandwidthController::LOCAL_RAW_PREROUTING[] = "bw_raw_PREROUTING";
67 const char BandwidthController::LOCAL_MANGLE_POSTROUTING[] = "bw_mangle_POSTROUTING";
68 const char BandwidthController::LOCAL_GLOBAL_ALERT[] = "bw_global_alert";
69 
70 auto BandwidthController::iptablesRestoreFunction = execIptablesRestoreWithOutput;
71 
72 using android::base::Join;
73 using android::base::StringAppendF;
74 using android::base::StringPrintf;
75 using android::net::FirewallController;
76 using android::net::gCtls;
77 using android::netdutils::Status;
78 using android::netdutils::StatusOr;
79 using android::netdutils::UniqueFile;
80 
81 namespace {
82 
83 const char ALERT_GLOBAL_NAME[] = "globalAlert";
84 const std::string NEW_CHAIN_COMMAND = "-N ";
85 
86 const char NAUGHTY_CHAIN[] = "bw_penalty_box";
87 const char NICE_CHAIN[] = "bw_happy_box";
88 
89 /**
90  * Some comments about the rules:
91  *  * Ordering
92  *    - when an interface is marked as costly it should be INSERTED into the INPUT/OUTPUT chains.
93  *      E.g. "-I bw_INPUT -i rmnet0 -j costly"
94  *    - quota'd rules in the costly chain should be before bw_penalty_box lookups.
95  *    - the qtaguid counting is done at the end of the bw_INPUT/bw_OUTPUT user chains.
96  *
97  * * global quota vs per interface quota
98  *   - global quota for all costly interfaces uses a single costly chain:
99  *    . initial rules
100  *      iptables -N bw_costly_shared
101  *      iptables -I bw_INPUT -i iface0 -j bw_costly_shared
102  *      iptables -I bw_OUTPUT -o iface0 -j bw_costly_shared
103  *      iptables -I bw_costly_shared -m quota \! --quota 500000 \
104  *          -j REJECT --reject-with icmp-net-prohibited
105  *      iptables -A bw_costly_shared -j bw_penalty_box
106  *      iptables -A bw_penalty_box -j bw_happy_box
107  *      iptables -A bw_happy_box -j bw_data_saver
108  *
109  *    . adding a new iface to this, E.g.:
110  *      iptables -I bw_INPUT -i iface1 -j bw_costly_shared
111  *      iptables -I bw_OUTPUT -o iface1 -j bw_costly_shared
112  *
113  *   - quota per interface. This is achieve by having "costly" chains per quota.
114  *     E.g. adding a new costly interface iface0 with its own quota:
115  *      iptables -N bw_costly_iface0
116  *      iptables -I bw_INPUT -i iface0 -j bw_costly_iface0
117  *      iptables -I bw_OUTPUT -o iface0 -j bw_costly_iface0
118  *      iptables -A bw_costly_iface0 -m quota \! --quota 500000 \
119  *          -j REJECT --reject-with icmp-port-unreachable
120  *      iptables -A bw_costly_iface0 -j bw_penalty_box
121  *
122  * * Penalty box, happy box and data saver.
123  *   - bw_penalty box is a blacklist of apps that are rejected.
124  *   - bw_happy_box is a whitelist of apps. It always includes all system apps
125  *   - bw_data_saver implements data usage restrictions.
126  *   - Via the UI the user can add and remove apps from the whitelist and
127  *     blacklist, and turn on/off data saver.
128  *   - The blacklist takes precedence over the whitelist and the whitelist
129  *     takes precedence over data saver.
130  *
131  * * bw_penalty_box handling:
132  *  - only one bw_penalty_box for all interfaces
133  *   E.g  Adding an app:
134  *    iptables -I bw_penalty_box -m owner --uid-owner app_3 \
135  *        -j REJECT --reject-with icmp-port-unreachable
136  *
137  * * bw_happy_box handling:
138  *  - The bw_happy_box comes after the penalty box.
139  *   E.g  Adding a happy app,
140  *    iptables -I bw_happy_box -m owner --uid-owner app_3 \
141  *        -j RETURN
142  *
143  * * bw_data_saver handling:
144  *  - The bw_data_saver comes after the happy box.
145  *    Enable data saver:
146  *      iptables -R 1 bw_data_saver -j REJECT --reject-with icmp-port-unreachable
147  *    Disable data saver:
148  *      iptables -R 1 bw_data_saver -j RETURN
149  */
150 
151 const std::string COMMIT_AND_CLOSE = "COMMIT\n";
152 const std::string HAPPY_BOX_MATCH_WHITELIST_COMMAND =
153         StringPrintf("-I bw_happy_box -m owner --uid-owner %d-%d -j RETURN", 0, MAX_SYSTEM_UID);
154 const std::string BPF_HAPPY_BOX_MATCH_WHITELIST_COMMAND = StringPrintf(
155         "-I bw_happy_box -m bpf --object-pinned %s -j RETURN", XT_BPF_WHITELIST_PROG_PATH);
156 const std::string BPF_PENALTY_BOX_MATCH_BLACKLIST_COMMAND = StringPrintf(
157         "-I bw_penalty_box -m bpf --object-pinned %s -j REJECT", XT_BPF_BLACKLIST_PROG_PATH);
158 
159 static const std::vector<std::string> IPT_FLUSH_COMMANDS = {
160         /*
161          * Cleanup rules.
162          * Should normally include bw_costly_<iface>, but we rely on the way they are setup
163          * to allow coexistance.
164          */
165         "*filter",
166         ":bw_INPUT -",
167         ":bw_OUTPUT -",
168         ":bw_FORWARD -",
169         ":bw_happy_box -",
170         ":bw_penalty_box -",
171         ":bw_data_saver -",
172         ":bw_costly_shared -",
173         ":bw_global_alert -",
174         "COMMIT",
175         "*raw",
176         ":bw_raw_PREROUTING -",
177         "COMMIT",
178         "*mangle",
179         ":bw_mangle_POSTROUTING -",
180         COMMIT_AND_CLOSE};
181 
182 static const uint32_t uidBillingMask = Fwmark::getUidBillingMask();
183 
184 /**
185  * Basic commands for creation of hooks into data accounting and data boxes.
186  *
187  * Included in these commands are rules to prevent the double-counting of IPsec
188  * packets. The general overview is as follows:
189  * > All interface counters (counted in PREROUTING, POSTROUTING) must be
190  *     completely accurate, and count only the outer packet. As such, the inner
191  *     packet must be ignored, which is done through the use of two rules: use
192  *     of the policy module (for tunnel mode), and VTI interface checks (for
193  *     tunnel or transport-in-tunnel mode). The VTI interfaces should be named
194  *     ipsec*
195  * > Outbound UID billing can always be done with the outer packets, due to the
196  *     ability to always find the correct UID (based on the skb->sk). As such,
197  *     the inner packets should be ignored based on the policy module, or the
198  *     output interface if a VTI (ipsec+)
199  * > Inbound UDP-encap-ESP packets can be correctly mapped to the UID that
200  *     opened the encap socket, and as such, should be billed as early as
201  *     possible (for transport mode; tunnel mode usage should be billed to
202  *     sending/receiving application). Due to the inner packet being
203  *     indistinguishable from the inner packet of ESP, a uidBillingDone mark
204  *     has to be applied to prevent counting a second time.
205  * > Inbound ESP has no socket, and as such must be accounted later. ESP
206  *     protocol packets are skipped via a blanket rule.
207  * > Note that this solution is asymmetrical. Adding the VTI or policy matcher
208  *     ignore rule in the input chain would actually break the INPUT chain;
209  *     Those rules are designed to ignore inner packets, and in the tunnel
210  *     mode UDP, or any ESP case, we would not have billed the outer packet.
211  *
212  * See go/ipsec-data-accounting for more information.
213  */
214 
getBasicAccountingCommands(const bool useBpf)215 std::vector<std::string> getBasicAccountingCommands(const bool useBpf) {
216     std::vector<std::string> ipt_basic_accounting_commands = {
217             "*filter",
218 
219             "-A bw_INPUT -j bw_global_alert",
220             // Prevents IPSec double counting (ESP and UDP-encap-ESP respectively)
221             "-A bw_INPUT -p esp -j RETURN",
222             StringPrintf("-A bw_INPUT -m mark --mark 0x%x/0x%x -j RETURN", uidBillingMask,
223                          uidBillingMask),
224             // This is ingress application UID xt_qtaguid (pre-ebpf) accounting,
225             // for bpf this is handled out of cgroup hooks instead.
226             useBpf ? "" : "-A bw_INPUT -m owner --socket-exists",
227             StringPrintf("-A bw_INPUT -j MARK --or-mark 0x%x", uidBillingMask),
228 
229             "-A bw_OUTPUT -j bw_global_alert",
230             // Prevents IPSec double counting (Tunnel mode and Transport mode,
231             // respectively)
232             useBpf ? "" : "-A bw_OUTPUT -o " IPSEC_IFACE_PREFIX "+ -j RETURN",
233             useBpf ? "" : "-A bw_OUTPUT -m policy --pol ipsec --dir out -j RETURN",
234             // Don't count clat traffic, as it has already been counted (and subject to
235             // costly / happy_box / data_saver / penalty_box etc. based on the real UID)
236             // on the stacked interface.
237             useBpf ? "" : "-A bw_OUTPUT -m owner --uid-owner clat -j RETURN",
238             // This is egress application UID xt_qtaguid (pre-ebpf) accounting,
239             // for bpf this is handled out of cgroup hooks instead.
240             useBpf ? "" : "-A bw_OUTPUT -m owner --socket-exists",
241 
242             "-A bw_costly_shared -j bw_penalty_box",
243             useBpf ? BPF_PENALTY_BOX_MATCH_BLACKLIST_COMMAND : "",
244             "-A bw_penalty_box -j bw_happy_box", "-A bw_happy_box -j bw_data_saver",
245             "-A bw_data_saver -j RETURN",
246             useBpf ? BPF_HAPPY_BOX_MATCH_WHITELIST_COMMAND : HAPPY_BOX_MATCH_WHITELIST_COMMAND,
247             "COMMIT",
248 
249             "*raw",
250             // Prevents IPSec double counting (Tunnel mode and Transport mode,
251             // respectively)
252             ("-A bw_raw_PREROUTING -i " IPSEC_IFACE_PREFIX "+ -j RETURN"),
253             "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN",
254             // This is ingress interface accounting. There is no need to do anything specific
255             // for 464xlat here, because we only ever account 464xlat traffic on the clat
256             // interface and later correct for overhead (+20 bytes/packet).
257             //
258             // Note: eBPF offloaded packets never hit base interface's ip6tables, and non
259             // offloaded packets (which when using xt_qtaguid means all packets, because
260             // clat eBPF offload does not work on xt_qtaguid devices) are dropped in
261             // clat_raw_PREROUTING.
262             //
263             // Hence we will never double count and additional corrections are not needed.
264             // We can simply take the sum of base and stacked (+20B/pkt) interface counts.
265             useBpf ? "-A bw_raw_PREROUTING -m bpf --object-pinned " XT_BPF_INGRESS_PROG_PATH
266                    : "-A bw_raw_PREROUTING -m owner --socket-exists",
267             "COMMIT",
268 
269             "*mangle",
270             // Prevents IPSec double counting (Tunnel mode and Transport mode,
271             // respectively)
272             ("-A bw_mangle_POSTROUTING -o " IPSEC_IFACE_PREFIX "+ -j RETURN"),
273             "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN",
274             // Clear the uid billing done (egress) mark before sending this packet
275             StringPrintf("-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x%x", uidBillingMask),
276             // Packets from the clat daemon have already been counted on egress through the
277             // stacked v4-* interface.
278             "-A bw_mangle_POSTROUTING -m owner --uid-owner clat -j RETURN",
279             // This is egress interface accounting: we account 464xlat traffic only on
280             // the clat interface (as offloaded packets never hit base interface's ip6tables)
281             // and later sum base and stacked with overhead (+20B/pkt) in higher layers
282             useBpf ? "-A bw_mangle_POSTROUTING -m bpf --object-pinned " XT_BPF_EGRESS_PROG_PATH
283                    : "-A bw_mangle_POSTROUTING -m owner --socket-exists",
284             COMMIT_AND_CLOSE};
285     return ipt_basic_accounting_commands;
286 }
287 
toStrVec(int num,const char * const strs[])288 std::vector<std::string> toStrVec(int num, const char* const strs[]) {
289     return std::vector<std::string>(strs, strs + num);
290 }
291 
292 }  // namespace
293 
setBpfEnabled(bool isEnabled)294 void BandwidthController::setBpfEnabled(bool isEnabled) {
295     mBpfSupported = isEnabled;
296 }
297 
BandwidthController()298 BandwidthController::BandwidthController() {
299 }
300 
flushCleanTables(bool doClean)301 void BandwidthController::flushCleanTables(bool doClean) {
302     /* Flush and remove the bw_costly_<iface> tables */
303     flushExistingCostlyTables(doClean);
304 
305     std::string commands = Join(IPT_FLUSH_COMMANDS, '\n');
306     iptablesRestoreFunction(V4V6, commands, nullptr);
307 }
308 
setupIptablesHooks()309 int BandwidthController::setupIptablesHooks() {
310     /* flush+clean is allowed to fail */
311     flushCleanTables(true);
312     return 0;
313 }
314 
enableBandwidthControl()315 int BandwidthController::enableBandwidthControl() {
316     /* Let's pretend we started from scratch ... */
317     mSharedQuotaIfaces.clear();
318     mQuotaIfaces.clear();
319     mGlobalAlertBytes = 0;
320     mSharedQuotaBytes = mSharedAlertBytes = 0;
321 
322     flushCleanTables(false);
323 
324     std::string commands = Join(getBasicAccountingCommands(mBpfSupported), '\n');
325     return iptablesRestoreFunction(V4V6, commands, nullptr);
326 }
327 
disableBandwidthControl()328 int BandwidthController::disableBandwidthControl() {
329 
330     flushCleanTables(false);
331     return 0;
332 }
333 
makeDataSaverCommand(IptablesTarget target,bool enable)334 std::string BandwidthController::makeDataSaverCommand(IptablesTarget target, bool enable) {
335     std::string cmd;
336     const char *chainName = "bw_data_saver";
337     const char *op = jumpToString(enable ? IptJumpReject : IptJumpReturn);
338     std::string criticalCommands = enable ?
339             FirewallController::makeCriticalCommands(target, chainName) : "";
340     StringAppendF(&cmd,
341         "*filter\n"
342         ":%s -\n"
343         "%s"
344         "-A %s%s\n"
345         "COMMIT\n", chainName, criticalCommands.c_str(), chainName, op);
346     return cmd;
347 }
348 
enableDataSaver(bool enable)349 int BandwidthController::enableDataSaver(bool enable) {
350     int ret = iptablesRestoreFunction(V4, makeDataSaverCommand(V4, enable), nullptr);
351     ret |= iptablesRestoreFunction(V6, makeDataSaverCommand(V6, enable), nullptr);
352     return ret;
353 }
354 
355 // TODO: Remove after removing these commands in CommandListener
addNaughtyApps(int numUids,const char * const appUids[])356 int BandwidthController::addNaughtyApps(int numUids, const char* const appUids[]) {
357     return manipulateSpecialApps(toStrVec(numUids, appUids), NAUGHTY_CHAIN,
358                                  IptJumpReject, IptOpInsert);
359 }
360 
361 // TODO: Remove after removing these commands in CommandListener
removeNaughtyApps(int numUids,const char * const appUids[])362 int BandwidthController::removeNaughtyApps(int numUids, const char* const appUids[]) {
363     return manipulateSpecialApps(toStrVec(numUids, appUids), NAUGHTY_CHAIN,
364                                  IptJumpReject, IptOpDelete);
365 }
366 
367 // TODO: Remove after removing these commands in CommandListener
addNiceApps(int numUids,const char * const appUids[])368 int BandwidthController::addNiceApps(int numUids, const char* const appUids[]) {
369     return manipulateSpecialApps(toStrVec(numUids, appUids), NICE_CHAIN,
370                                  IptJumpReturn, IptOpInsert);
371 }
372 
373 // TODO: Remove after removing these commands in CommandListener
removeNiceApps(int numUids,const char * const appUids[])374 int BandwidthController::removeNiceApps(int numUids, const char* const appUids[]) {
375     return manipulateSpecialApps(toStrVec(numUids, appUids), NICE_CHAIN,
376                                  IptJumpReturn, IptOpDelete);
377 }
378 
addNaughtyApps(const std::vector<std::string> & appStrUid)379 int BandwidthController::addNaughtyApps(const std::vector<std::string>& appStrUid) {
380     return manipulateSpecialApps(appStrUid, NAUGHTY_CHAIN, IptJumpReject, IptOpInsert);
381 }
382 
removeNaughtyApps(const std::vector<std::string> & appStrUid)383 int BandwidthController::removeNaughtyApps(const std::vector<std::string>& appStrUid) {
384     return manipulateSpecialApps(appStrUid, NAUGHTY_CHAIN, IptJumpReject, IptOpDelete);
385 }
386 
addNiceApps(const std::vector<std::string> & appStrUid)387 int BandwidthController::addNiceApps(const std::vector<std::string>& appStrUid) {
388     return manipulateSpecialApps(appStrUid, NICE_CHAIN, IptJumpReturn, IptOpInsert);
389 }
390 
removeNiceApps(const std::vector<std::string> & appStrUid)391 int BandwidthController::removeNiceApps(const std::vector<std::string>& appStrUid) {
392     return manipulateSpecialApps(appStrUid, NICE_CHAIN, IptJumpReturn, IptOpDelete);
393 }
394 
manipulateSpecialApps(const std::vector<std::string> & appStrUids,const std::string & chain,IptJumpOp jumpHandling,IptOp op)395 int BandwidthController::manipulateSpecialApps(const std::vector<std::string>& appStrUids,
396                                                const std::string& chain, IptJumpOp jumpHandling,
397                                                IptOp op) {
398     if (mBpfSupported) {
399         Status status = gCtls->trafficCtrl.updateUidOwnerMap(appStrUids, jumpHandling, op);
400         if (!isOk(status)) {
401             ALOGE("unable to update the Bandwidth Uid Map: %s", toString(status).c_str());
402       }
403       return status.code();
404     }
405     std::string cmd = "*filter\n";
406     for (const auto& appStrUid : appStrUids) {
407         StringAppendF(&cmd, "%s %s -m owner --uid-owner %s%s\n", opToString(op), chain.c_str(),
408                       appStrUid.c_str(), jumpToString(jumpHandling));
409     }
410     StringAppendF(&cmd, "COMMIT\n");
411     return iptablesRestoreFunction(V4V6, cmd, nullptr);
412 }
413 
setInterfaceSharedQuota(const std::string & iface,int64_t maxBytes)414 int BandwidthController::setInterfaceSharedQuota(const std::string& iface, int64_t maxBytes) {
415     int res = 0;
416     std::string quotaCmd;
417     constexpr char cost[] = "shared";
418     constexpr char chain[] = "bw_costly_shared";
419 
420     if (!maxBytes) {
421         /* Don't talk about -1, deprecate it. */
422         ALOGE("Invalid bytes value. 1..max_int64.");
423         return -1;
424     }
425     if (!isIfaceName(iface))
426         return -1;
427 
428     if (maxBytes == -1) {
429         return removeInterfaceSharedQuota(iface);
430     }
431 
432     auto it = mSharedQuotaIfaces.find(iface);
433 
434     if (it == mSharedQuotaIfaces.end()) {
435         const int ruleInsertPos = (mGlobalAlertBytes) ? 2 : 1;
436         std::vector<std::string> cmds = {
437                 "*filter",
438                 StringPrintf("-I bw_INPUT %d -i %s -j %s", ruleInsertPos, iface.c_str(), chain),
439                 StringPrintf("-I bw_OUTPUT %d -o %s -j %s", ruleInsertPos, iface.c_str(), chain),
440                 StringPrintf("-A bw_FORWARD -i %s -j %s", iface.c_str(), chain),
441                 StringPrintf("-A bw_FORWARD -o %s -j %s", iface.c_str(), chain),
442         };
443         if (mSharedQuotaIfaces.empty()) {
444             cmds.push_back(StringPrintf("-I %s -m quota2 ! --quota %" PRId64 " --name %s -j REJECT",
445                                         chain, maxBytes, cost));
446         }
447         cmds.push_back("COMMIT\n");
448 
449         res |= iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr);
450         if (res) {
451             ALOGE("Failed set quota rule");
452             removeInterfaceSharedQuota(iface);
453             return -1;
454         }
455         mSharedQuotaBytes = maxBytes;
456         mSharedQuotaIfaces.insert(iface);
457     }
458 
459     if (maxBytes != mSharedQuotaBytes) {
460         res |= updateQuota(cost, maxBytes);
461         if (res) {
462             ALOGE("Failed update quota for %s", cost);
463             removeInterfaceSharedQuota(iface);
464             return -1;
465         }
466         mSharedQuotaBytes = maxBytes;
467     }
468     return 0;
469 }
470 
471 /* It will also cleanup any shared alerts */
removeInterfaceSharedQuota(const std::string & iface)472 int BandwidthController::removeInterfaceSharedQuota(const std::string& iface) {
473     constexpr char cost[] = "shared";
474     constexpr char chain[] = "bw_costly_shared";
475 
476     if (!isIfaceName(iface))
477         return -1;
478 
479     auto it = mSharedQuotaIfaces.find(iface);
480 
481     if (it == mSharedQuotaIfaces.end()) {
482         ALOGE("No such iface %s to delete", iface.c_str());
483         return -1;
484     }
485 
486     std::vector<std::string> cmds = {
487             "*filter",
488             StringPrintf("-D bw_INPUT -i %s -j %s", iface.c_str(), chain),
489             StringPrintf("-D bw_OUTPUT -o %s -j %s", iface.c_str(), chain),
490             StringPrintf("-D bw_FORWARD -i %s -j %s", iface.c_str(), chain),
491             StringPrintf("-D bw_FORWARD -o %s -j %s", iface.c_str(), chain),
492     };
493     if (mSharedQuotaIfaces.size() == 1) {
494         cmds.push_back(StringPrintf("-D %s -m quota2 ! --quota %" PRIu64 " --name %s -j REJECT",
495                                     chain, mSharedQuotaBytes, cost));
496     }
497     cmds.push_back("COMMIT\n");
498 
499     if (iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr) != 0) {
500         ALOGE("Failed to remove shared quota on %s", iface.c_str());
501         return -1;
502     }
503 
504     int res = 0;
505     mSharedQuotaIfaces.erase(it);
506     if (mSharedQuotaIfaces.empty()) {
507         mSharedQuotaBytes = 0;
508         if (mSharedAlertBytes) {
509             res = removeSharedAlert();
510             if (res == 0) {
511                 mSharedAlertBytes = 0;
512             }
513         }
514     }
515 
516     return res;
517 
518 }
519 
setInterfaceQuota(const std::string & iface,int64_t maxBytes)520 int BandwidthController::setInterfaceQuota(const std::string& iface, int64_t maxBytes) {
521     const std::string& cost = iface;
522 
523     if (!isIfaceName(iface)) return -EINVAL;
524 
525     if (!maxBytes) {
526         ALOGE("Invalid bytes value. 1..max_int64.");
527         return -ERANGE;
528     }
529     if (maxBytes == -1) {
530         return removeInterfaceQuota(iface);
531     }
532 
533     /* Insert ingress quota. */
534     auto it = mQuotaIfaces.find(iface);
535 
536     if (it != mQuotaIfaces.end()) {
537         if (int res = updateQuota(cost, maxBytes)) {
538             ALOGE("Failed update quota for %s", iface.c_str());
539             removeInterfaceQuota(iface);
540             return res;
541         }
542         it->second.quota = maxBytes;
543         return 0;
544     }
545 
546     const std::string chain = "bw_costly_" + iface;
547     const int ruleInsertPos = (mGlobalAlertBytes) ? 2 : 1;
548     std::vector<std::string> cmds = {
549             "*filter",
550             StringPrintf(":%s -", chain.c_str()),
551             StringPrintf("-A %s -j bw_penalty_box", chain.c_str()),
552             StringPrintf("-I bw_INPUT %d -i %s -j %s", ruleInsertPos, iface.c_str(), chain.c_str()),
553             StringPrintf("-I bw_OUTPUT %d -o %s -j %s", ruleInsertPos, iface.c_str(),
554                          chain.c_str()),
555             StringPrintf("-A bw_FORWARD -i %s -j %s", iface.c_str(), chain.c_str()),
556             StringPrintf("-A bw_FORWARD -o %s -j %s", iface.c_str(), chain.c_str()),
557             StringPrintf("-A %s -m quota2 ! --quota %" PRId64 " --name %s -j REJECT", chain.c_str(),
558                          maxBytes, cost.c_str()),
559             "COMMIT\n",
560     };
561     if (iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr) != 0) {
562         ALOGE("Failed set quota rule");
563         removeInterfaceQuota(iface);
564         return -EREMOTEIO;
565     }
566 
567     mQuotaIfaces[iface] = QuotaInfo{maxBytes, 0};
568     return 0;
569 }
570 
getInterfaceSharedQuota(int64_t * bytes)571 int BandwidthController::getInterfaceSharedQuota(int64_t *bytes) {
572     return getInterfaceQuota("shared", bytes);
573 }
574 
getInterfaceQuota(const std::string & iface,int64_t * bytes)575 int BandwidthController::getInterfaceQuota(const std::string& iface, int64_t* bytes) {
576     const auto& sys = android::netdutils::sSyscalls.get();
577     const std::string fname = "/proc/net/xt_quota/" + iface;
578 
579     if (!isIfaceName(iface)) return -1;
580 
581     StatusOr<UniqueFile> file = sys.fopen(fname, "re");
582     if (!isOk(file)) {
583         ALOGE("Reading quota %s failed (%s)", iface.c_str(), toString(file).c_str());
584         return -1;
585     }
586     auto rv = sys.fscanf(file.value().get(), "%" SCNd64, bytes);
587     if (!isOk(rv)) {
588         ALOGE("Reading quota %s failed (%s)", iface.c_str(), toString(rv).c_str());
589         return -1;
590     }
591     ALOGV("Read quota res=%d bytes=%" PRId64, rv.value(), *bytes);
592     return rv.value() == 1 ? 0 : -1;
593 }
594 
removeInterfaceQuota(const std::string & iface)595 int BandwidthController::removeInterfaceQuota(const std::string& iface) {
596     if (!isIfaceName(iface)) return -EINVAL;
597 
598     auto it = mQuotaIfaces.find(iface);
599 
600     if (it == mQuotaIfaces.end()) {
601         ALOGE("No such iface %s to delete", iface.c_str());
602         return -ENODEV;
603     }
604 
605     const std::string chain = "bw_costly_" + iface;
606     std::vector<std::string> cmds = {
607             "*filter",
608             StringPrintf("-D bw_INPUT -i %s -j %s", iface.c_str(), chain.c_str()),
609             StringPrintf("-D bw_OUTPUT -o %s -j %s", iface.c_str(), chain.c_str()),
610             StringPrintf("-D bw_FORWARD -i %s -j %s", iface.c_str(), chain.c_str()),
611             StringPrintf("-D bw_FORWARD -o %s -j %s", iface.c_str(), chain.c_str()),
612             StringPrintf("-F %s", chain.c_str()),
613             StringPrintf("-X %s", chain.c_str()),
614             "COMMIT\n",
615     };
616 
617     const int res = iptablesRestoreFunction(V4V6, Join(cmds, "\n"), nullptr);
618 
619     if (res == 0) {
620         mQuotaIfaces.erase(it);
621     }
622 
623     return res ? -EREMOTEIO : 0;
624 }
625 
updateQuota(const std::string & quotaName,int64_t bytes)626 int BandwidthController::updateQuota(const std::string& quotaName, int64_t bytes) {
627     const auto& sys = android::netdutils::sSyscalls.get();
628     const std::string fname = "/proc/net/xt_quota/" + quotaName;
629 
630     if (!isIfaceName(quotaName)) {
631         ALOGE("updateQuota: Invalid quotaName \"%s\"", quotaName.c_str());
632         return -EINVAL;
633     }
634 
635     StatusOr<UniqueFile> file = sys.fopen(fname, "we");
636     if (!isOk(file)) {
637         int res = errno;
638         ALOGE("Updating quota %s failed (%s)", quotaName.c_str(), toString(file).c_str());
639         return -res;
640     }
641     // TODO: should we propagate this error?
642     sys.fprintf(file.value().get(), "%" PRId64 "\n", bytes).ignoreError();
643     return 0;
644 }
645 
runIptablesAlertCmd(IptOp op,const std::string & alertName,int64_t bytes)646 int BandwidthController::runIptablesAlertCmd(IptOp op, const std::string& alertName,
647                                              int64_t bytes) {
648     const char *opFlag = opToString(op);
649     std::string alertQuotaCmd = "*filter\n";
650 
651     // TODO: consider using an alternate template for the delete that does not include the --quota
652     // value. This code works because the --quota value is ignored by deletes
653 
654     /*
655      * Add alert rule in bw_global_alert chain, 3 chains might reference bw_global_alert.
656      * bw_INPUT, bw_OUTPUT (added by BandwidthController in enableBandwidthControl)
657      * bw_FORWARD (added by TetherController in setTetherGlobalAlertRule if nat enable/disable)
658      */
659     StringAppendF(&alertQuotaCmd, ALERT_IPT_TEMPLATE, opFlag, LOCAL_GLOBAL_ALERT, bytes,
660                   alertName.c_str());
661     StringAppendF(&alertQuotaCmd, "COMMIT\n");
662 
663     return iptablesRestoreFunction(V4V6, alertQuotaCmd, nullptr);
664 }
665 
setGlobalAlert(int64_t bytes)666 int BandwidthController::setGlobalAlert(int64_t bytes) {
667     const char *alertName = ALERT_GLOBAL_NAME;
668 
669     if (!bytes) {
670         ALOGE("Invalid bytes value. 1..max_int64.");
671         return -ERANGE;
672     }
673 
674     int res = 0;
675     if (mGlobalAlertBytes) {
676         res = updateQuota(alertName, bytes);
677     } else {
678         res = runIptablesAlertCmd(IptOpInsert, alertName, bytes);
679         if (res) {
680             res = -EREMOTEIO;
681         }
682     }
683     mGlobalAlertBytes = bytes;
684     return res;
685 }
686 
removeGlobalAlert()687 int BandwidthController::removeGlobalAlert() {
688 
689     const char *alertName = ALERT_GLOBAL_NAME;
690 
691     if (!mGlobalAlertBytes) {
692         ALOGE("No prior alert set");
693         return -1;
694     }
695 
696     int res = 0;
697     res = runIptablesAlertCmd(IptOpDelete, alertName, mGlobalAlertBytes);
698     mGlobalAlertBytes = 0;
699     return res;
700 }
701 
setSharedAlert(int64_t bytes)702 int BandwidthController::setSharedAlert(int64_t bytes) {
703     if (!mSharedQuotaBytes) {
704         ALOGE("Need to have a prior shared quota set to set an alert");
705         return -1;
706     }
707     if (!bytes) {
708         ALOGE("Invalid bytes value. 1..max_int64.");
709         return -1;
710     }
711     return setCostlyAlert("shared", bytes, &mSharedAlertBytes);
712 }
713 
removeSharedAlert()714 int BandwidthController::removeSharedAlert() {
715     return removeCostlyAlert("shared", &mSharedAlertBytes);
716 }
717 
setInterfaceAlert(const std::string & iface,int64_t bytes)718 int BandwidthController::setInterfaceAlert(const std::string& iface, int64_t bytes) {
719     if (!isIfaceName(iface)) {
720         ALOGE("setInterfaceAlert: Invalid iface \"%s\"", iface.c_str());
721         return -EINVAL;
722     }
723 
724     if (!bytes) {
725         ALOGE("Invalid bytes value. 1..max_int64.");
726         return -ERANGE;
727     }
728     auto it = mQuotaIfaces.find(iface);
729 
730     if (it == mQuotaIfaces.end()) {
731         ALOGE("Need to have a prior interface quota set to set an alert");
732         return -ENOENT;
733     }
734 
735     return setCostlyAlert(iface, bytes, &it->second.alert);
736 }
737 
removeInterfaceAlert(const std::string & iface)738 int BandwidthController::removeInterfaceAlert(const std::string& iface) {
739     if (!isIfaceName(iface)) {
740         ALOGE("removeInterfaceAlert: Invalid iface \"%s\"", iface.c_str());
741         return -EINVAL;
742     }
743 
744     auto it = mQuotaIfaces.find(iface);
745 
746     if (it == mQuotaIfaces.end()) {
747         ALOGE("No prior alert set for interface %s", iface.c_str());
748         return -ENOENT;
749     }
750 
751     return removeCostlyAlert(iface, &it->second.alert);
752 }
753 
setCostlyAlert(const std::string & costName,int64_t bytes,int64_t * alertBytes)754 int BandwidthController::setCostlyAlert(const std::string& costName, int64_t bytes,
755                                         int64_t* alertBytes) {
756     int res = 0;
757 
758     if (!isIfaceName(costName)) {
759         ALOGE("setCostlyAlert: Invalid costName \"%s\"", costName.c_str());
760         return -EINVAL;
761     }
762 
763     if (!bytes) {
764         ALOGE("Invalid bytes value. 1..max_int64.");
765         return -ERANGE;
766     }
767 
768     std::string alertName = costName + "Alert";
769     std::string chainName = "bw_costly_" + costName;
770     if (*alertBytes) {
771         res = updateQuota(alertName, *alertBytes);
772     } else {
773         std::vector<std::string> commands = {
774             "*filter\n",
775             StringPrintf(ALERT_IPT_TEMPLATE, "-A", chainName.c_str(), bytes, alertName.c_str()),
776             "COMMIT\n"
777         };
778         res = iptablesRestoreFunction(V4V6, Join(commands, ""), nullptr);
779         if (res) {
780             ALOGE("Failed to set costly alert for %s", costName.c_str());
781             res = -EREMOTEIO;
782         }
783     }
784     if (res == 0) {
785         *alertBytes = bytes;
786     }
787     return res;
788 }
789 
removeCostlyAlert(const std::string & costName,int64_t * alertBytes)790 int BandwidthController::removeCostlyAlert(const std::string& costName, int64_t* alertBytes) {
791     if (!isIfaceName(costName)) {
792         ALOGE("removeCostlyAlert: Invalid costName \"%s\"", costName.c_str());
793         return -EINVAL;
794     }
795 
796     if (!*alertBytes) {
797         ALOGE("No prior alert set for %s alert", costName.c_str());
798         return -ENOENT;
799     }
800 
801     std::string alertName = costName + "Alert";
802     std::string chainName = "bw_costly_" + costName;
803     std::vector<std::string> commands = {
804         "*filter\n",
805         StringPrintf(ALERT_IPT_TEMPLATE, "-D", chainName.c_str(), *alertBytes, alertName.c_str()),
806         "COMMIT\n"
807     };
808     if (iptablesRestoreFunction(V4V6, Join(commands, ""), nullptr) != 0) {
809         ALOGE("Failed to remove costly alert %s", costName.c_str());
810         return -EREMOTEIO;
811     }
812 
813     *alertBytes = 0;
814     return 0;
815 }
816 
flushExistingCostlyTables(bool doClean)817 void BandwidthController::flushExistingCostlyTables(bool doClean) {
818     std::string fullCmd = "*filter\n-S\nCOMMIT\n";
819     std::string ruleList;
820 
821     /* Only lookup ip4 table names as ip6 will have the same tables ... */
822     if (int ret = iptablesRestoreFunction(V4, fullCmd, &ruleList)) {
823         ALOGE("Failed to list existing costly tables ret=%d", ret);
824         return;
825     }
826     /* ... then flush/clean both ip4 and ip6 iptables. */
827     parseAndFlushCostlyTables(ruleList, doClean);
828 }
829 
parseAndFlushCostlyTables(const std::string & ruleList,bool doRemove)830 void BandwidthController::parseAndFlushCostlyTables(const std::string& ruleList, bool doRemove) {
831     std::stringstream stream(ruleList);
832     std::string rule;
833     std::vector<std::string> clearCommands = { "*filter" };
834     std::string chainName;
835 
836     // Find and flush all rules starting with "-N bw_costly_<iface>" except "-N bw_costly_shared".
837     while (std::getline(stream, rule, '\n')) {
838         if (rule.find(NEW_CHAIN_COMMAND) != 0) continue;
839         chainName = rule.substr(NEW_CHAIN_COMMAND.size());
840         ALOGV("parse chainName=<%s> orig line=<%s>", chainName.c_str(), rule.c_str());
841 
842         if (chainName.find("bw_costly_") != 0 || chainName == std::string("bw_costly_shared")) {
843             continue;
844         }
845 
846         clearCommands.push_back(StringPrintf(":%s -", chainName.c_str()));
847         if (doRemove) {
848             clearCommands.push_back(StringPrintf("-X %s", chainName.c_str()));
849         }
850     }
851 
852     if (clearCommands.size() == 1) {
853         // No rules found.
854         return;
855     }
856 
857     clearCommands.push_back("COMMIT\n");
858     iptablesRestoreFunction(V4V6, Join(clearCommands, '\n'), nullptr);
859 }
860 
opToString(IptOp op)861 inline const char *BandwidthController::opToString(IptOp op) {
862     switch (op) {
863     case IptOpInsert:
864         return "-I";
865     case IptOpDelete:
866         return "-D";
867     }
868 }
869 
jumpToString(IptJumpOp jumpHandling)870 inline const char *BandwidthController::jumpToString(IptJumpOp jumpHandling) {
871     /*
872      * Must be careful what one rejects with, as upper layer protocols will just
873      * keep on hammering the device until the number of retries are done.
874      * For port-unreachable (default), TCP should consider as an abort (RFC1122).
875      */
876     switch (jumpHandling) {
877     case IptJumpNoAdd:
878         return "";
879     case IptJumpReject:
880         return " -j REJECT";
881     case IptJumpReturn:
882         return " -j RETURN";
883     }
884 }
885