blob: bae044e84ec2584f01bf0780846df2c653784fc1 [file] [log] [blame]
Jie Jiangcf749152020-07-09 22:20:58 +09001// Copyright 2020 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "patchpanel/counters_service.h"
6
7#include <memory>
Jie Jiang8fd0ace2020-09-01 14:51:57 +09008#include <net/if.h>
Jie Jiangcf749152020-07-09 22:20:58 +09009#include <string>
10#include <vector>
11
12#include <chromeos/dbus/service_constants.h>
13#include <gmock/gmock.h>
14#include <gtest/gtest.h>
15
16#include "patchpanel/fake_shill_client.h"
Hugo Benichicd27f4e2020-11-19 18:32:23 +090017#include "patchpanel/mock_firewall.h"
Jie Jiangcf749152020-07-09 22:20:58 +090018
19namespace patchpanel {
Jie Jiangcf749152020-07-09 22:20:58 +090020
Jie Jianged0b1cc2020-07-10 15:55:33 +090021using ::testing::ContainerEq;
Jie Jiangcf749152020-07-09 22:20:58 +090022using ::testing::Contains;
Jie Jianged0b1cc2020-07-10 15:55:33 +090023using ::testing::DoAll;
Jie Jiang8fd0ace2020-09-01 14:51:57 +090024using ::testing::Each;
Jie Jiangcf749152020-07-09 22:20:58 +090025using ::testing::ElementsAreArray;
Jie Jiang8fd0ace2020-09-01 14:51:57 +090026using ::testing::Lt;
Jie Jianged0b1cc2020-07-10 15:55:33 +090027using ::testing::Return;
28using ::testing::SetArgPointee;
Jie Jiang8fd0ace2020-09-01 14:51:57 +090029using ::testing::SizeIs;
Jie Jianged0b1cc2020-07-10 15:55:33 +090030
31using Counter = CountersService::Counter;
32using SourceDevice = CountersService::SourceDevice;
33
34// The following two functions should be put outside the anounymous namespace
35// otherwise they could not be found in the tests.
36std::ostream& operator<<(std::ostream& os, const Counter& counter) {
37 os << "rx_bytes:" << counter.rx_bytes << ", rx_packets:" << counter.rx_packets
38 << ", tx_bytes:" << counter.tx_bytes
39 << ", tx_packets:" << counter.tx_packets;
40 return os;
41}
42
43bool operator==(const CountersService::Counter lhs,
44 const CountersService::Counter rhs) {
45 return lhs.rx_bytes == rhs.rx_bytes && lhs.rx_packets == rhs.rx_packets &&
46 lhs.tx_bytes == rhs.tx_bytes && lhs.tx_packets == rhs.tx_packets;
47}
48
49namespace {
50// The following string is copied from the real output of iptables v1.6.2 by
51// `iptables -t mangle -L -x -v`. This output contains all the accounting
52// chains/rules for eth0 and wlan0.
53// TODO(jiejiang): presubmit checker is complaining about the line length for
54// this (and the other raw strings in this file). Find a way to make it happy.
55const char kIptablesOutput[] = R"(
56Chain PREROUTING (policy ACCEPT 22785 packets, 136093545 bytes)
57 pkts bytes target prot opt in out source destination
58 18 2196 MARK all -- arcbr0 any anywhere anywhere MARK set 0x1
59 0 0 MARK all -- vmtap+ any anywhere anywhere MARK set 0x1
60 6526 68051766 MARK all -- arc_eth0 any anywhere anywhere MARK set 0x1
61 9 1104 MARK all -- arc_wlan0 any anywhere anywhere MARK set 0x1
62
63Chain INPUT (policy ACCEPT 4421 packets, 2461233 bytes)
64 pkts bytes target prot opt in out source destination
65 312491 1767147156 rx_input_eth0 all -- eth0 any anywhere anywhere
66 0 0 rx_input_wlan0 all -- wlan0 any anywhere anywhere
67
68Chain FORWARD (policy ACCEPT 18194 packets, 133612816 bytes)
69 pkts bytes target prot opt in out source destination
70 6511 68041668 tx_fwd_eth0 all -- any eth0 anywhere anywhere
71 11683 65571148 rx_fwd_eth0 all -- eth0 any anywhere anywhere
72 0 0 tx_fwd_wlan0 all -- any wlan0 anywhere anywhere
73 0 0 rx_fwd_wlan0 all -- wlan0 any anywhere anywhere
74
75Chain OUTPUT (policy ACCEPT 4574 packets, 2900995 bytes)
76 pkts bytes target prot opt in out source destination
77
78Chain POSTROUTING (policy ACCEPT 22811 packets, 136518827 bytes)
79 pkts bytes target prot opt in out source destination
80 202160 1807550291 tx_postrt_eth0 all -- any eth0 anywhere anywhere owner socket exists
81 2 96 tx_postrt_wlan0 all -- any wlan0 anywhere anywhere owner socket exists
82
83Chain tx_fwd_eth0 (1 references)
84 pkts bytes target prot opt in out source destination
85 6511 68041668 all -- any any anywhere anywhere
86
87Chain tx_fwd_wlan0 (1 references)
88 pkts bytes target prot opt in out source destination
89 0 0 all -- any any anywhere anywhere
90
91Chain tx_postrt_eth0 (1 references)
92 pkts bytes target prot opt in out source destination
93 202160 1807550291 all -- any any anywhere anywhere
94
95Chain tx_postrt_wlan0 (1 references)
96 pkts bytes target prot opt in out source destination
97 2 96 all -- any any anywhere anywhere
98
99Chain rx_fwd_eth0 (1 references)
100 pkts bytes target prot opt in out source destination
101 11683 65571148 all -- any any anywhere anywhere
102
103Chain rx_fwd_wlan0 (1 references)
104 pkts bytes target prot opt in out source destination
105 0 0 all -- any any anywhere anywhere
106
107Chain rx_input_eth0 (1 references)
108 pkts bytes target prot opt in out source destination
109 312491 1767147156 all -- any any anywhere anywhere
110
111Chain rx_input_wlan0 (1 references)
112 pkts bytes target prot opt in out source destination
113 0 0 all -- any any anywhere anywhere
114)";
115
116// The expected counters for the above output. "* 2" because the same string
117// will be returned for both iptables and ip6tables in the tests.
118const Counter kCounter_eth0{(65571148 + 1767147156ULL) * 2 /*rx_bytes*/,
119 (11683 + 312491) * 2 /*rx_packets*/,
120 (68041668 + 1807550291ULL) * 2 /*tx_bytes*/,
121 (6511 + 202160) * 2 /*tx_packets*/};
122const Counter kCounter_wlan0{0, 0, 96 * 2, 2 * 2};
Jie Jiangcf749152020-07-09 22:20:58 +0900123
124class MockProcessRunner : public MinijailedProcessRunner {
125 public:
126 MockProcessRunner() = default;
127 ~MockProcessRunner() = default;
128
129 MOCK_METHOD(int,
130 iptables,
131 (const std::string& table,
132 const std::vector<std::string>& argv,
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900133 bool log_failures,
134 std::string* output),
Jie Jiangcf749152020-07-09 22:20:58 +0900135 (override));
136 MOCK_METHOD(int,
137 ip6tables,
138 (const std::string& table,
139 const std::vector<std::string>& argv,
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900140 bool log_failures,
141 std::string* output),
Jie Jiangcf749152020-07-09 22:20:58 +0900142 (override));
143};
144
145class CountersServiceTest : public testing::Test {
146 protected:
147 void SetUp() override {
148 fake_shill_client_ = shill_helper_.FakeClient();
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900149 datapath_ = std::make_unique<Datapath>(&runner_, &firewall_);
150 counters_svc_ = std::make_unique<CountersService>(
151 fake_shill_client_.get(), datapath_.get(), &runner_);
Jie Jiangcf749152020-07-09 22:20:58 +0900152 }
153
Jie Jianged0b1cc2020-07-10 15:55:33 +0900154 // Makes `iptables` returning a bad |output|. Expects an empty map from
155 // GetCounters().
156 void TestBadIptablesOutput(const std::string& output) {
157 EXPECT_CALL(runner_, iptables(_, _, _, _))
158 .WillRepeatedly(DoAll(SetArgPointee<3>(output), Return(0)));
159 EXPECT_CALL(runner_, ip6tables(_, _, _, _))
160 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
161
162 auto actual = counters_svc_->GetCounters({});
163 std::map<SourceDevice, Counter> expected;
164
165 EXPECT_THAT(actual, ContainerEq(expected));
166 }
167
168 // Makes `ip6tables` returning a bad |output|. Expects an empty map from
169 // GetCounters().
170 void TestBadIp6tablesOutput(const std::string& output) {
171 EXPECT_CALL(runner_, iptables(_, _, _, _))
172 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
173 EXPECT_CALL(runner_, ip6tables(_, _, _, _))
174 .WillRepeatedly(DoAll(SetArgPointee<3>(output), Return(0)));
175
176 auto actual = counters_svc_->GetCounters({});
177 std::map<SourceDevice, Counter> expected;
178
179 EXPECT_THAT(actual, ContainerEq(expected));
180 }
181
Jie Jiangcf749152020-07-09 22:20:58 +0900182 FakeShillClientHelper shill_helper_;
183 MockProcessRunner runner_;
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900184 MockFirewall firewall_;
Jie Jiangcf749152020-07-09 22:20:58 +0900185 std::unique_ptr<FakeShillClient> fake_shill_client_;
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900186 std::unique_ptr<Datapath> datapath_;
Jie Jiangcf749152020-07-09 22:20:58 +0900187 std::unique_ptr<CountersService> counters_svc_;
188};
189
190TEST_F(CountersServiceTest, OnNewDevice) {
191 // Makes the check commands return 1 (not found).
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900192 EXPECT_CALL(runner_, iptables(_, Contains("-C"), _, _))
Jie Jiangcf749152020-07-09 22:20:58 +0900193 .WillRepeatedly(Return(1));
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900194 EXPECT_CALL(runner_, ip6tables(_, Contains("-C"), _, _))
Jie Jiangcf749152020-07-09 22:20:58 +0900195 .WillRepeatedly(Return(1));
196
197 // The following commands are expected when eth0 comes up.
198 const std::vector<std::vector<std::string>> expected_calls{
199 {"-N", "rx_input_eth0", "-w"},
200 {"-A", "INPUT", "-i", "eth0", "-j", "rx_input_eth0", "-w"},
201 {"-A", "rx_input_eth0", "-w"},
202
203 {"-N", "rx_fwd_eth0", "-w"},
204 {"-A", "FORWARD", "-i", "eth0", "-j", "rx_fwd_eth0", "-w"},
205 {"-A", "rx_fwd_eth0", "-w"},
206
207 {"-N", "tx_postrt_eth0", "-w"},
208 {"-A", "POSTROUTING", "-o", "eth0", "-m", "owner", "--socket-exists",
209 "-j", "tx_postrt_eth0", "-w"},
210 {"-A", "tx_postrt_eth0", "-w"},
211
212 {"-N", "tx_fwd_eth0", "-w"},
213 {"-A", "FORWARD", "-o", "eth0", "-j", "tx_fwd_eth0", "-w"},
214 {"-A", "tx_fwd_eth0", "-w"},
215 };
216
217 for (const auto& rule : expected_calls) {
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900218 EXPECT_CALL(runner_, iptables("mangle", ElementsAreArray(rule), _, _));
219 EXPECT_CALL(runner_, ip6tables("mangle", ElementsAreArray(rule), _, _));
Jie Jiangcf749152020-07-09 22:20:58 +0900220 }
221
222 std::vector<dbus::ObjectPath> devices = {dbus::ObjectPath("/device/eth0")};
223 fake_shill_client_->NotifyManagerPropertyChange(shill::kDevicesProperty,
224 brillo::Any(devices));
225}
226
227TEST_F(CountersServiceTest, OnSameDeviceAppearAgain) {
228 // Makes the check commands return 0 (we already have these rules).
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900229 EXPECT_CALL(runner_, iptables(_, Contains("-C"), _, _))
Jie Jiangcf749152020-07-09 22:20:58 +0900230 .WillRepeatedly(Return(0));
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900231 EXPECT_CALL(runner_, ip6tables(_, Contains("-C"), _, _))
Jie Jiangcf749152020-07-09 22:20:58 +0900232 .WillRepeatedly(Return(0));
233
234 // Creating chains commands are expected but no more creating rules command
235 // (with "-I" or "-A") should come.
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900236 EXPECT_CALL(runner_, iptables(_, Contains("-N"), _, _)).Times(AnyNumber());
237 EXPECT_CALL(runner_, ip6tables(_, Contains("-N"), _, _)).Times(AnyNumber());
Jie Jiangcf749152020-07-09 22:20:58 +0900238
239 std::vector<dbus::ObjectPath> devices = {dbus::ObjectPath("/device/eth0")};
240 fake_shill_client_->NotifyManagerPropertyChange(shill::kDevicesProperty,
241 brillo::Any(devices));
242}
243
Jie Jiang8fd0ace2020-09-01 14:51:57 +0900244TEST_F(CountersServiceTest, ChainNameLength) {
245 // Makes the check commands return 1 (not found).
246 EXPECT_CALL(runner_, iptables(_, Contains("-C"), _, _))
247 .WillRepeatedly(Return(1));
248 EXPECT_CALL(runner_, ip6tables(_, Contains("-C"), _, _))
249 .WillRepeatedly(Return(1));
250
251 // The name of a new chain must be shorter than 29 characters, otherwise
252 // iptables will reject the request. Uses Each() here for simplicity since no
253 // other params could be longer than 29 for now.
254 static constexpr int kMaxChainNameLength = 29;
255 EXPECT_CALL(runner_, iptables(_, Each(SizeIs(Lt(kMaxChainNameLength))), _, _))
256 .Times(AnyNumber());
257 EXPECT_CALL(runner_,
258 ip6tables(_, Each(SizeIs(Lt(kMaxChainNameLength))), _, _))
259 .Times(AnyNumber());
260
261 static const std::string kLongInterfaceName(IFNAMSIZ, 'a');
262 std::vector<dbus::ObjectPath> devices = {
263 dbus::ObjectPath("/device/" + kLongInterfaceName)};
264 fake_shill_client_->NotifyManagerPropertyChange(shill::kDevicesProperty,
265 brillo::Any(devices));
266}
267
Jie Jianged0b1cc2020-07-10 15:55:33 +0900268TEST_F(CountersServiceTest, QueryTrafficCounters) {
269 EXPECT_CALL(runner_, iptables(_, _, _, _))
270 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
271 EXPECT_CALL(runner_, ip6tables(_, _, _, _))
272 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
273
274 auto actual = counters_svc_->GetCounters({});
275
276 std::map<SourceDevice, Counter> expected{
277 {{TrafficCounter::UNKNOWN, "eth0"}, kCounter_eth0},
278 {{TrafficCounter::UNKNOWN, "wlan0"}, kCounter_wlan0},
279 };
280
281 EXPECT_THAT(actual, ContainerEq(expected));
282}
283
284TEST_F(CountersServiceTest, QueryTrafficCountersWithFilter) {
285 EXPECT_CALL(runner_, iptables(_, _, _, _))
286 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
287 EXPECT_CALL(runner_, ip6tables(_, _, _, _))
288 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
289
290 // Only counters for eth0 should be returned. eth1 should be ignored.
291 auto actual = counters_svc_->GetCounters({"eth0", "eth1"});
292
293 std::map<SourceDevice, Counter> expected{
294 {{TrafficCounter::UNKNOWN, "eth0"}, kCounter_eth0},
295 };
296
297 EXPECT_THAT(actual, ContainerEq(expected));
298}
299
300TEST_F(CountersServiceTest, QueryTrafficCountersWithEmptyIPv4Output) {
301 const std::string kEmptyOutput = "";
302 TestBadIptablesOutput(kEmptyOutput);
303}
304
305TEST_F(CountersServiceTest, QueryTrafficCountersWithEmptyIPv6Output) {
306 const std::string kEmptyOutput = "";
307 TestBadIp6tablesOutput(kEmptyOutput);
308}
309
310TEST_F(CountersServiceTest, QueryTrafficCountersWithOnlyChainName) {
311 const std::string kBadOutput = R"(
312Chain tx_fwd_eth0 (1 references)
313 pkts bytes target prot opt in out source destination
314 6511 68041668 all -- any any anywhere anywhere
315
316Chain tx_fwd_wlan0 (1 references)
317)";
318 TestBadIptablesOutput(kBadOutput);
319}
320
321TEST_F(CountersServiceTest, QueryTrafficCountersWithOnlyChainNameAndHeader) {
322 const std::string kBadOutput = R"(
323Chain tx_fwd_eth0 (1 references)
324 pkts bytes target prot opt in out source destination
325 6511 68041668 all -- any any anywhere anywhere
326
327Chain tx_fwd_wlan0 (1 references)
328 pkts bytes target prot opt in out source destination
329)";
330 TestBadIptablesOutput(kBadOutput);
331}
332
333TEST_F(CountersServiceTest, QueryTrafficCountersWithNotFinishedCountersLine) {
334 const std::string kBadOutput = R"(
335Chain tx_fwd_eth0 (1 references)
336 pkts bytes target prot opt in out source destination
337 6511 68041668 all -- any any anywhere anywhere
338
339Chain tx_fwd_wlan0 (1 references)
340 pkts bytes target prot opt in out source destination pkts bytes target prot opt in out source destination
341 0 )";
342 TestBadIptablesOutput(kBadOutput);
343}
344
Jie Jiangcf749152020-07-09 22:20:58 +0900345} // namespace
346} // namespace patchpanel