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