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