1 /*
2  * Copyright 2016 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  * BandwidthControllerTest.cpp - unit tests for BandwidthController.cpp
17  */
18 
19 #include <string>
20 #include <vector>
21 
22 #include <inttypes.h>
23 #include <fcntl.h>
24 #include <unistd.h>
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 
28 #include <gtest/gtest.h>
29 
30 #include <android-base/strings.h>
31 #include <android-base/stringprintf.h>
32 
33 #include <netdutils/MockSyscalls.h>
34 #include "BandwidthController.h"
35 #include "Fwmark.h"
36 #include "IptablesBaseTest.h"
37 #include "bpf/BpfUtils.h"
38 #include "netdbpf/bpf_shared.h"
39 #include "tun_interface.h"
40 
41 using ::testing::_;
42 using ::testing::ByMove;
43 using ::testing::Invoke;
44 using ::testing::Return;
45 using ::testing::StrictMock;
46 
47 using android::base::Join;
48 using android::base::StringPrintf;
49 using android::net::TunInterface;
50 using android::netdutils::UniqueFile;
51 using android::netdutils::status::ok;
52 
53 class BandwidthControllerTest : public IptablesBaseTest {
54 protected:
BandwidthControllerTest()55     BandwidthControllerTest() {
56         BandwidthController::iptablesRestoreFunction = fakeExecIptablesRestoreWithOutput;
57     }
58     BandwidthController mBw;
59     TunInterface mTun;
60 
SetUp()61     void SetUp() {
62         ASSERT_EQ(0, mTun.init());
63     }
64 
TearDown()65     void TearDown() {
66         mTun.destroy();
67     }
68 
expectSetupCommands(const std::string & expectedClean,const std::string & expectedAccounting)69     void expectSetupCommands(const std::string& expectedClean,
70                              const std::string& expectedAccounting) {
71         std::string expectedList =
72             "*filter\n"
73             "-S\n"
74             "COMMIT\n";
75 
76         std::string expectedFlush =
77                 "*filter\n"
78                 ":bw_INPUT -\n"
79                 ":bw_OUTPUT -\n"
80                 ":bw_FORWARD -\n"
81                 ":bw_happy_box -\n"
82                 ":bw_penalty_box -\n"
83                 ":bw_data_saver -\n"
84                 ":bw_costly_shared -\n"
85                 ":bw_global_alert -\n"
86                 "COMMIT\n"
87                 "*raw\n"
88                 ":bw_raw_PREROUTING -\n"
89                 "COMMIT\n"
90                 "*mangle\n"
91                 ":bw_mangle_POSTROUTING -\n"
92                 "COMMIT\n";
93 
94         ExpectedIptablesCommands expected = {{ V4, expectedList }};
95         if (expectedClean.size()) {
96             expected.push_back({ V4V6, expectedClean });
97         }
98         expected.push_back({ V4V6, expectedFlush });
99         if (expectedAccounting.size()) {
100             expected.push_back({ V4V6, expectedAccounting });
101         }
102 
103         expectIptablesRestoreCommands(expected);
104     }
105 
106     using IptOp = BandwidthController::IptOp;
107 
runIptablesAlertCmd(IptOp a,const char * b,int64_t c)108     int runIptablesAlertCmd(IptOp a, const char* b, int64_t c) {
109         return mBw.runIptablesAlertCmd(a, b, c);
110     }
111 
setCostlyAlert(const std::string & a,int64_t b,int64_t * c)112     int setCostlyAlert(const std::string& a, int64_t b, int64_t* c) {
113         return mBw.setCostlyAlert(a, b, c);
114     }
115 
removeCostlyAlert(const std::string & a,int64_t * b)116     int removeCostlyAlert(const std::string& a, int64_t* b) { return mBw.removeCostlyAlert(a, b); }
117 
expectUpdateQuota(uint64_t quota)118     void expectUpdateQuota(uint64_t quota) {
119         uintptr_t dummy;
120         FILE* dummyFile = reinterpret_cast<FILE*>(&dummy);
121 
122         EXPECT_CALL(mSyscalls, fopen(_, _)).WillOnce(Return(ByMove(UniqueFile(dummyFile))));
123         EXPECT_CALL(mSyscalls, vfprintf(dummyFile, _, _))
124             .WillOnce(Invoke([quota](FILE*, const std::string&, va_list ap) {
125                 EXPECT_EQ(quota, va_arg(ap, uint64_t));
126                 return 0;
127             }));
128         EXPECT_CALL(mSyscalls, fclose(dummyFile)).WillOnce(Return(ok));
129     }
130 
131     StrictMock<android::netdutils::ScopedMockSyscalls> mSyscalls;
132 };
133 
TEST_F(BandwidthControllerTest,TestSetupIptablesHooks)134 TEST_F(BandwidthControllerTest, TestSetupIptablesHooks) {
135     // Pretend some bw_costly_shared_<iface> rules already exist...
136     addIptablesRestoreOutput(
137         "-P OUTPUT ACCEPT\n"
138         "-N bw_costly_rmnet_data0\n"
139         "-N bw_costly_shared\n"
140         "-N unrelated\n"
141         "-N bw_costly_rmnet_data7\n");
142 
143     // ... and expect that they be flushed and deleted.
144     std::string expectedCleanCmds =
145         "*filter\n"
146         ":bw_costly_rmnet_data0 -\n"
147         "-X bw_costly_rmnet_data0\n"
148         ":bw_costly_rmnet_data7 -\n"
149         "-X bw_costly_rmnet_data7\n"
150         "COMMIT\n";
151 
152     mBw.setupIptablesHooks();
153     expectSetupCommands(expectedCleanCmds, "");
154 }
155 
TEST_F(BandwidthControllerTest,TestCheckUidBillingMask)156 TEST_F(BandwidthControllerTest, TestCheckUidBillingMask) {
157     uint32_t uidBillingMask = Fwmark::getUidBillingMask();
158 
159     // If mask is non-zero, and mask & mask-1 is equal to 0, then the mask is a power of two.
160     bool isPowerOfTwo = uidBillingMask && (uidBillingMask & (uidBillingMask - 1)) == 0;
161 
162     // Must be exactly a power of two
163     EXPECT_TRUE(isPowerOfTwo);
164 }
165 
TEST_F(BandwidthControllerTest,TestEnableBandwidthControl)166 TEST_F(BandwidthControllerTest, TestEnableBandwidthControl) {
167     // Pretend no bw_costly_shared_<iface> rules already exist...
168     addIptablesRestoreOutput(
169             "-P OUTPUT ACCEPT\n"
170             "-N bw_costly_shared\n"
171             "-N unrelated\n");
172 
173     // ... so none are flushed or deleted.
174     // clang-format off
175     static const std::string expectedClean = "";
176     static const std::string expectedAccounting =
177             "*filter\n"
178             "-A bw_INPUT -j bw_global_alert\n"
179             "-A bw_INPUT -p esp -j RETURN\n"
180             "-A bw_INPUT -m mark --mark 0x100000/0x100000 -j RETURN\n"
181             "-A bw_INPUT -j MARK --or-mark 0x100000\n"
182             "-A bw_OUTPUT -j bw_global_alert\n"
183             "-A bw_costly_shared -j bw_penalty_box\n"
184             "-I bw_penalty_box -m bpf --object-pinned " XT_BPF_DENYLIST_PROG_PATH " -j REJECT\n"
185             "-A bw_penalty_box -j bw_happy_box\n"
186             "-A bw_happy_box -j bw_data_saver\n"
187             "-A bw_data_saver -j RETURN\n"
188             "-I bw_happy_box -m bpf --object-pinned " XT_BPF_ALLOWLIST_PROG_PATH " -j RETURN\n"
189             "COMMIT\n"
190             "*raw\n"
191             "-A bw_raw_PREROUTING -i ipsec+ -j RETURN\n"
192             "-A bw_raw_PREROUTING -m policy --pol ipsec --dir in -j RETURN\n"
193             "-A bw_raw_PREROUTING -m bpf --object-pinned " XT_BPF_INGRESS_PROG_PATH "\n"
194             "COMMIT\n"
195             "*mangle\n"
196             "-A bw_mangle_POSTROUTING -o ipsec+ -j RETURN\n"
197             "-A bw_mangle_POSTROUTING -m policy --pol ipsec --dir out -j RETURN\n"
198             "-A bw_mangle_POSTROUTING -j MARK --set-mark 0x0/0x100000\n"
199             "-A bw_mangle_POSTROUTING -m owner --uid-owner clat -j RETURN\n"
200             "-A bw_mangle_POSTROUTING -m bpf --object-pinned " XT_BPF_EGRESS_PROG_PATH "\n"
201             "COMMIT\n";
202     // clang-format on
203 
204     mBw.enableBandwidthControl();
205     expectSetupCommands(expectedClean, expectedAccounting);
206 }
207 
TEST_F(BandwidthControllerTest,TestDisableBandwidthControl)208 TEST_F(BandwidthControllerTest, TestDisableBandwidthControl) {
209     // Pretend some bw_costly_shared_<iface> rules already exist...
210     addIptablesRestoreOutput(
211         "-P OUTPUT ACCEPT\n"
212         "-N bw_costly_rmnet_data0\n"
213         "-N bw_costly_shared\n"
214         "-N unrelated\n"
215         "-N bw_costly_rmnet_data7\n");
216 
217     // ... and expect that they be flushed.
218     std::string expectedCleanCmds =
219         "*filter\n"
220         ":bw_costly_rmnet_data0 -\n"
221         ":bw_costly_rmnet_data7 -\n"
222         "COMMIT\n";
223 
224     mBw.disableBandwidthControl();
225     expectSetupCommands(expectedCleanCmds, "");
226 }
227 
TEST_F(BandwidthControllerTest,TestEnableDataSaver)228 TEST_F(BandwidthControllerTest, TestEnableDataSaver) {
229     mBw.enableDataSaver(true);
230     std::string expected4 =
231             "*filter\n"
232             ":bw_data_saver -\n"
233             "-A bw_data_saver -j REJECT\n"
234             "COMMIT\n";
235     std::string expected6 =
236             "*filter\n"
237             ":bw_data_saver -\n"
238             "-A bw_data_saver -p icmpv6 --icmpv6-type packet-too-big -j RETURN\n"
239             "-A bw_data_saver -p icmpv6 --icmpv6-type router-solicitation -j RETURN\n"
240             "-A bw_data_saver -p icmpv6 --icmpv6-type router-advertisement -j RETURN\n"
241             "-A bw_data_saver -p icmpv6 --icmpv6-type neighbour-solicitation -j RETURN\n"
242             "-A bw_data_saver -p icmpv6 --icmpv6-type neighbour-advertisement -j RETURN\n"
243             "-A bw_data_saver -p icmpv6 --icmpv6-type redirect -j RETURN\n"
244             "-A bw_data_saver -j REJECT\n"
245             "COMMIT\n";
246     expectIptablesRestoreCommands({
247         {V4, expected4},
248         {V6, expected6},
249     });
250 
251     mBw.enableDataSaver(false);
252     std::string expected = {
253             "*filter\n"
254             ":bw_data_saver -\n"
255             "-A bw_data_saver -j RETURN\n"
256             "COMMIT\n"};
257     expectIptablesRestoreCommands({
258         {V4, expected},
259         {V6, expected},
260     });
261 }
262 
makeInterfaceQuotaCommands(const std::string & iface,int ruleIndex,int64_t quota)263 const std::vector<std::string> makeInterfaceQuotaCommands(const std::string& iface, int ruleIndex,
264                                                           int64_t quota) {
265     const std::string chain = "bw_costly_" + iface;
266     const char* c_chain = chain.c_str();
267     const char* c_iface = iface.c_str();
268     std::vector<std::string> cmds = {
269             "*filter",
270             StringPrintf(":%s -", c_chain),
271             StringPrintf("-A %s -j bw_penalty_box", c_chain),
272             StringPrintf("-I bw_INPUT %d -i %s -j %s", ruleIndex, c_iface, c_chain),
273             StringPrintf("-I bw_OUTPUT %d -o %s -j %s", ruleIndex, c_iface, c_chain),
274             StringPrintf("-A bw_FORWARD -i %s -j %s", c_iface, c_chain),
275             StringPrintf("-A bw_FORWARD -o %s -j %s", c_iface, c_chain),
276             StringPrintf("-A %s -m quota2 ! --quota %" PRIu64 " --name %s -j REJECT", c_chain,
277                          quota, c_iface),
278             "COMMIT\n",
279     };
280     return {Join(cmds, "\n")};
281 }
282 
removeInterfaceQuotaCommands(const std::string & iface)283 const std::vector<std::string> removeInterfaceQuotaCommands(const std::string& iface) {
284     const std::string chain = "bw_costly_" + iface;
285     const char* c_chain = chain.c_str();
286     const char* c_iface = iface.c_str();
287     std::vector<std::string> cmds = {
288             "*filter",
289             StringPrintf("-D bw_INPUT -i %s -j %s", c_iface, c_chain),
290             StringPrintf("-D bw_OUTPUT -o %s -j %s", c_iface, c_chain),
291             StringPrintf("-D bw_FORWARD -i %s -j %s", c_iface, c_chain),
292             StringPrintf("-D bw_FORWARD -o %s -j %s", c_iface, c_chain),
293             StringPrintf("-F %s", c_chain),
294             StringPrintf("-X %s", c_chain),
295             "COMMIT\n",
296     };
297     return {Join(cmds, "\n")};
298 }
299 
TEST_F(BandwidthControllerTest,TestSetInterfaceQuota)300 TEST_F(BandwidthControllerTest, TestSetInterfaceQuota) {
301     constexpr uint64_t kOldQuota = 123456;
302     const std::string iface = mTun.name();
303     std::vector<std::string> expected = makeInterfaceQuotaCommands(iface, 1, kOldQuota);
304 
305     EXPECT_EQ(0, mBw.setInterfaceQuota(iface, kOldQuota));
306     expectIptablesRestoreCommands(expected);
307 
308     constexpr uint64_t kNewQuota = kOldQuota + 1;
309     expected = {};
310     expectUpdateQuota(kNewQuota);
311     EXPECT_EQ(0, mBw.setInterfaceQuota(iface, kNewQuota));
312     expectIptablesRestoreCommands(expected);
313 
314     expected = removeInterfaceQuotaCommands(iface);
315     EXPECT_EQ(0, mBw.removeInterfaceQuota(iface));
316     expectIptablesRestoreCommands(expected);
317 }
318 
makeInterfaceSharedQuotaCommands(const std::string & iface,int ruleIndex,int64_t quota,bool insertQuota)319 const std::vector<std::string> makeInterfaceSharedQuotaCommands(const std::string& iface,
320                                                                 int ruleIndex, int64_t quota,
321                                                                 bool insertQuota) {
322     const std::string chain = "bw_costly_shared";
323     const char* c_chain = chain.c_str();
324     const char* c_iface = iface.c_str();
325     std::vector<std::string> cmds = {
326             "*filter",
327             StringPrintf("-I bw_INPUT %d -i %s -j %s", ruleIndex, c_iface, c_chain),
328             StringPrintf("-I bw_OUTPUT %d -o %s -j %s", ruleIndex, c_iface, c_chain),
329             StringPrintf("-A bw_FORWARD -i %s -j %s", c_iface, c_chain),
330             StringPrintf("-A bw_FORWARD -o %s -j %s", c_iface, c_chain),
331     };
332     if (insertQuota) {
333         cmds.push_back(StringPrintf("-I %s -m quota2 ! --quota %" PRIu64 " --name shared -j REJECT",
334                                     c_chain, quota));
335     }
336     cmds.push_back("COMMIT\n");
337     return {Join(cmds, "\n")};
338 }
339 
removeInterfaceSharedQuotaCommands(const std::string & iface,int64_t quota,bool deleteQuota)340 const std::vector<std::string> removeInterfaceSharedQuotaCommands(const std::string& iface,
341                                                                   int64_t quota, bool deleteQuota) {
342     const std::string chain = "bw_costly_shared";
343     const char* c_chain = chain.c_str();
344     const char* c_iface = iface.c_str();
345     std::vector<std::string> cmds = {
346             "*filter",
347             StringPrintf("-D bw_INPUT -i %s -j %s", c_iface, c_chain),
348             StringPrintf("-D bw_OUTPUT -o %s -j %s", c_iface, c_chain),
349             StringPrintf("-D bw_FORWARD -i %s -j %s", c_iface, c_chain),
350             StringPrintf("-D bw_FORWARD -o %s -j %s", c_iface, c_chain),
351     };
352     if (deleteQuota) {
353         cmds.push_back(StringPrintf("-D %s -m quota2 ! --quota %" PRIu64 " --name shared -j REJECT",
354                                     c_chain, quota));
355     }
356     cmds.push_back("COMMIT\n");
357     return {Join(cmds, "\n")};
358 }
359 
TEST_F(BandwidthControllerTest,TestSetInterfaceSharedQuotaDuplicate)360 TEST_F(BandwidthControllerTest, TestSetInterfaceSharedQuotaDuplicate) {
361     constexpr uint64_t kQuota = 123456;
362     const std::string iface = mTun.name();
363     std::vector<std::string> expected = makeInterfaceSharedQuotaCommands(iface, 1, 123456, true);
364     EXPECT_EQ(0, mBw.setInterfaceSharedQuota(iface, kQuota));
365     expectIptablesRestoreCommands(expected);
366 
367     expected = {};
368     EXPECT_EQ(0, mBw.setInterfaceSharedQuota(iface, kQuota));
369     expectIptablesRestoreCommands(expected);
370 
371     expected = removeInterfaceSharedQuotaCommands(iface, kQuota, true);
372     EXPECT_EQ(0, mBw.removeInterfaceSharedQuota(iface));
373     expectIptablesRestoreCommands(expected);
374 }
375 
TEST_F(BandwidthControllerTest,TestSetInterfaceSharedQuotaUpdate)376 TEST_F(BandwidthControllerTest, TestSetInterfaceSharedQuotaUpdate) {
377     constexpr uint64_t kOldQuota = 123456;
378     const std::string iface = mTun.name();
379     std::vector<std::string> expected = makeInterfaceSharedQuotaCommands(iface, 1, kOldQuota, true);
380     EXPECT_EQ(0, mBw.setInterfaceSharedQuota(iface, kOldQuota));
381     expectIptablesRestoreCommands(expected);
382 
383     constexpr uint64_t kNewQuota = kOldQuota + 1;
384     expected = {};
385     expectUpdateQuota(kNewQuota);
386     EXPECT_EQ(0, mBw.setInterfaceSharedQuota(iface, kNewQuota));
387     expectIptablesRestoreCommands(expected);
388 
389     expected = removeInterfaceSharedQuotaCommands(iface, kNewQuota, true);
390     EXPECT_EQ(0, mBw.removeInterfaceSharedQuota(iface));
391     expectIptablesRestoreCommands(expected);
392 }
393 
TEST_F(BandwidthControllerTest,TestSetInterfaceSharedQuotaTwoInterfaces)394 TEST_F(BandwidthControllerTest, TestSetInterfaceSharedQuotaTwoInterfaces) {
395     constexpr uint64_t kQuota = 123456;
396     const std::vector<std::string> ifaces{
397         {"a" + mTun.name()},
398         {"b" + mTun.name()},
399     };
400 
401     for (const auto& iface : ifaces) {
402         // Quota rule is only added when the total number of
403         // interfaces transitions from 0 -> 1.
404         bool first = (iface == ifaces[0]);
405         auto expected = makeInterfaceSharedQuotaCommands(iface, 1, kQuota, first);
406         EXPECT_EQ(0, mBw.setInterfaceSharedQuota(iface, kQuota));
407         expectIptablesRestoreCommands(expected);
408     }
409 
410     for (const auto& iface : ifaces) {
411         // Quota rule is only removed when the total number of
412         // interfaces transitions from 1 -> 0.
413         bool last = (iface == ifaces[1]);
414         auto expected = removeInterfaceSharedQuotaCommands(iface, kQuota, last);
415         EXPECT_EQ(0, mBw.removeInterfaceSharedQuota(iface));
416         expectIptablesRestoreCommands(expected);
417     }
418 }
419 
TEST_F(BandwidthControllerTest,IptablesAlertCmd)420 TEST_F(BandwidthControllerTest, IptablesAlertCmd) {
421     std::vector<std::string> expected = {
422             "*filter\n"
423             "-I bw_global_alert -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
424             "COMMIT\n"};
425     EXPECT_EQ(0, runIptablesAlertCmd(IptOp::IptOpInsert, "MyWonderfulAlert", 123456));
426     expectIptablesRestoreCommands(expected);
427 
428     expected = {
429             "*filter\n"
430             "-D bw_global_alert -m quota2 ! --quota 123456 --name MyWonderfulAlert\n"
431             "COMMIT\n"};
432     EXPECT_EQ(0, runIptablesAlertCmd(IptOp::IptOpDelete, "MyWonderfulAlert", 123456));
433     expectIptablesRestoreCommands(expected);
434 }
435 
TEST_F(BandwidthControllerTest,CostlyAlert)436 TEST_F(BandwidthControllerTest, CostlyAlert) {
437     const int64_t kQuota = 123456;
438     int64_t alertBytes = 0;
439 
440     std::vector<std::string> expected = {
441         "*filter\n"
442         "-A bw_costly_shared -m quota2 ! --quota 123456 --name sharedAlert\n"
443         "COMMIT\n"
444     };
445     EXPECT_EQ(0, setCostlyAlert("shared", kQuota, &alertBytes));
446     EXPECT_EQ(kQuota, alertBytes);
447     expectIptablesRestoreCommands(expected);
448 
449     expected = {};
450     expectUpdateQuota(kQuota);
451     EXPECT_EQ(0, setCostlyAlert("shared", kQuota + 1, &alertBytes));
452     EXPECT_EQ(kQuota + 1, alertBytes);
453     expectIptablesRestoreCommands(expected);
454 
455     expected = {
456         "*filter\n"
457         "-D bw_costly_shared -m quota2 ! --quota 123457 --name sharedAlert\n"
458         "COMMIT\n"
459     };
460     EXPECT_EQ(0, removeCostlyAlert("shared", &alertBytes));
461     EXPECT_EQ(0, alertBytes);
462     expectIptablesRestoreCommands(expected);
463 }
464 
465