blob: b810256c746714ed735e1f2838e2bcdb1d39d5ea [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
Hugo Benichi33af8f72020-11-20 10:08:47 +090065 312491 1767147156 rx_eth0 all -- eth0 any anywhere anywhere
66 0 0 rx_wlan0 all -- wlan0 any anywhere anywhere
Jie Jianged0b1cc2020-07-10 15:55:33 +090067
68Chain FORWARD (policy ACCEPT 18194 packets, 133612816 bytes)
69 pkts bytes target prot opt in out source destination
Hugo Benichi33af8f72020-11-20 10:08:47 +090070 6511 68041668 tx_eth0 all -- any eth0 anywhere anywhere
71 11683 65571148 rx_eth0 all -- eth0 any anywhere anywhere
Jie Jianged0b1cc2020-07-10 15:55:33 +090072
73Chain OUTPUT (policy ACCEPT 4574 packets, 2900995 bytes)
74 pkts bytes target prot opt in out source destination
75
76Chain POSTROUTING (policy ACCEPT 22811 packets, 136518827 bytes)
77 pkts bytes target prot opt in out source destination
Hugo Benichi33af8f72020-11-20 10:08:47 +090078 202160 1807550291 tx_eth0 all -- any eth0 anywhere anywhere owner socket exists
79 2 96 tx_wlan0 all -- any wlan0 anywhere anywhere owner socket exists
Jie Jianged0b1cc2020-07-10 15:55:33 +090080
Hugo Benichi33af8f72020-11-20 10:08:47 +090081Chain tx_eth0 (1 references)
Jie Jianged0b1cc2020-07-10 15:55:33 +090082 pkts bytes target prot opt in out source destination
Hugo Benichi33af8f72020-11-20 10:08:47 +090083 208671 1875591959 all -- any any anywhere anywhere
Jie Jianged0b1cc2020-07-10 15:55:33 +090084
Hugo Benichi33af8f72020-11-20 10:08:47 +090085Chain tx_wlan0 (1 references)
Jie Jianged0b1cc2020-07-10 15:55:33 +090086 pkts bytes target prot opt in out source destination
87 2 96 all -- any any anywhere anywhere
88
Hugo Benichi33af8f72020-11-20 10:08:47 +090089Chain rx_eth0 (2 references)
Jie Jianged0b1cc2020-07-10 15:55:33 +090090 pkts bytes target prot opt in out source destination
Hugo Benichi33af8f72020-11-20 10:08:47 +090091 324174 1832718304 all -- any any anywhere anywhere
Jie Jianged0b1cc2020-07-10 15:55:33 +090092
Hugo Benichi33af8f72020-11-20 10:08:47 +090093Chain rx_wlan0 (2 references)
Jie Jianged0b1cc2020-07-10 15:55:33 +090094 pkts bytes target prot opt in out source destination
95 0 0 all -- any any anywhere anywhere
96)";
97
98// The expected counters for the above output. "* 2" because the same string
99// will be returned for both iptables and ip6tables in the tests.
100const Counter kCounter_eth0{(65571148 + 1767147156ULL) * 2 /*rx_bytes*/,
101 (11683 + 312491) * 2 /*rx_packets*/,
102 (68041668 + 1807550291ULL) * 2 /*tx_bytes*/,
103 (6511 + 202160) * 2 /*tx_packets*/};
104const Counter kCounter_wlan0{0, 0, 96 * 2, 2 * 2};
Jie Jiangcf749152020-07-09 22:20:58 +0900105
106class MockProcessRunner : public MinijailedProcessRunner {
107 public:
108 MockProcessRunner() = default;
109 ~MockProcessRunner() = default;
110
111 MOCK_METHOD(int,
112 iptables,
113 (const std::string& table,
114 const std::vector<std::string>& argv,
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900115 bool log_failures,
116 std::string* output),
Jie Jiangcf749152020-07-09 22:20:58 +0900117 (override));
118 MOCK_METHOD(int,
119 ip6tables,
120 (const std::string& table,
121 const std::vector<std::string>& argv,
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900122 bool log_failures,
123 std::string* output),
Jie Jiangcf749152020-07-09 22:20:58 +0900124 (override));
125};
126
127class CountersServiceTest : public testing::Test {
128 protected:
129 void SetUp() override {
130 fake_shill_client_ = shill_helper_.FakeClient();
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900131 datapath_ = std::make_unique<Datapath>(&runner_, &firewall_);
132 counters_svc_ = std::make_unique<CountersService>(
133 fake_shill_client_.get(), datapath_.get(), &runner_);
Jie Jiangcf749152020-07-09 22:20:58 +0900134 }
135
Jie Jianged0b1cc2020-07-10 15:55:33 +0900136 // Makes `iptables` returning a bad |output|. Expects an empty map from
137 // GetCounters().
138 void TestBadIptablesOutput(const std::string& output) {
139 EXPECT_CALL(runner_, iptables(_, _, _, _))
140 .WillRepeatedly(DoAll(SetArgPointee<3>(output), Return(0)));
141 EXPECT_CALL(runner_, ip6tables(_, _, _, _))
142 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
143
144 auto actual = counters_svc_->GetCounters({});
145 std::map<SourceDevice, Counter> expected;
146
147 EXPECT_THAT(actual, ContainerEq(expected));
148 }
149
150 // Makes `ip6tables` returning a bad |output|. Expects an empty map from
151 // GetCounters().
152 void TestBadIp6tablesOutput(const std::string& output) {
153 EXPECT_CALL(runner_, iptables(_, _, _, _))
154 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
155 EXPECT_CALL(runner_, ip6tables(_, _, _, _))
156 .WillRepeatedly(DoAll(SetArgPointee<3>(output), Return(0)));
157
158 auto actual = counters_svc_->GetCounters({});
159 std::map<SourceDevice, Counter> expected;
160
161 EXPECT_THAT(actual, ContainerEq(expected));
162 }
163
Jie Jiangcf749152020-07-09 22:20:58 +0900164 FakeShillClientHelper shill_helper_;
165 MockProcessRunner runner_;
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900166 MockFirewall firewall_;
Jie Jiangcf749152020-07-09 22:20:58 +0900167 std::unique_ptr<FakeShillClient> fake_shill_client_;
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900168 std::unique_ptr<Datapath> datapath_;
Jie Jiangcf749152020-07-09 22:20:58 +0900169 std::unique_ptr<CountersService> counters_svc_;
170};
171
172TEST_F(CountersServiceTest, OnNewDevice) {
173 // Makes the check commands return 1 (not found).
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900174 EXPECT_CALL(runner_, iptables(_, Contains("-C"), _, _))
Jie Jiangcf749152020-07-09 22:20:58 +0900175 .WillRepeatedly(Return(1));
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900176 EXPECT_CALL(runner_, ip6tables(_, Contains("-C"), _, _))
Jie Jiangcf749152020-07-09 22:20:58 +0900177 .WillRepeatedly(Return(1));
178
179 // The following commands are expected when eth0 comes up.
180 const std::vector<std::vector<std::string>> expected_calls{
Hugo Benichi33af8f72020-11-20 10:08:47 +0900181 {"-N", "rx_eth0", "-w"},
182 {"-A", "INPUT", "-i", "eth0", "-j", "rx_eth0", "-w"},
183 {"-A", "FORWARD", "-i", "eth0", "-j", "rx_eth0", "-w"},
184 {"-A", "rx_eth0", "-w"},
Jie Jiangcf749152020-07-09 22:20:58 +0900185
Hugo Benichi33af8f72020-11-20 10:08:47 +0900186 {"-N", "tx_eth0", "-w"},
187 {"-A", "POSTROUTING", "-o", "eth0", "-j", "tx_eth0", "-w"},
188 {"-A", "tx_eth0", "-w"},
Jie Jiangcf749152020-07-09 22:20:58 +0900189 };
190
191 for (const auto& rule : expected_calls) {
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900192 EXPECT_CALL(runner_, iptables("mangle", ElementsAreArray(rule), _, _));
193 EXPECT_CALL(runner_, ip6tables("mangle", ElementsAreArray(rule), _, _));
Jie Jiangcf749152020-07-09 22:20:58 +0900194 }
195
196 std::vector<dbus::ObjectPath> devices = {dbus::ObjectPath("/device/eth0")};
197 fake_shill_client_->NotifyManagerPropertyChange(shill::kDevicesProperty,
198 brillo::Any(devices));
199}
200
201TEST_F(CountersServiceTest, OnSameDeviceAppearAgain) {
202 // Makes the check commands return 0 (we already have these rules).
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900203 EXPECT_CALL(runner_, iptables(_, Contains("-C"), _, _))
Jie Jiangcf749152020-07-09 22:20:58 +0900204 .WillRepeatedly(Return(0));
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900205 EXPECT_CALL(runner_, ip6tables(_, Contains("-C"), _, _))
Jie Jiangcf749152020-07-09 22:20:58 +0900206 .WillRepeatedly(Return(0));
207
208 // Creating chains commands are expected but no more creating rules command
209 // (with "-I" or "-A") should come.
Jie Jiangcf5ce9c2020-07-14 17:22:03 +0900210 EXPECT_CALL(runner_, iptables(_, Contains("-N"), _, _)).Times(AnyNumber());
211 EXPECT_CALL(runner_, ip6tables(_, Contains("-N"), _, _)).Times(AnyNumber());
Jie Jiangcf749152020-07-09 22:20:58 +0900212
213 std::vector<dbus::ObjectPath> devices = {dbus::ObjectPath("/device/eth0")};
214 fake_shill_client_->NotifyManagerPropertyChange(shill::kDevicesProperty,
215 brillo::Any(devices));
216}
217
Jie Jiang8fd0ace2020-09-01 14:51:57 +0900218TEST_F(CountersServiceTest, ChainNameLength) {
219 // Makes the check commands return 1 (not found).
220 EXPECT_CALL(runner_, iptables(_, Contains("-C"), _, _))
221 .WillRepeatedly(Return(1));
222 EXPECT_CALL(runner_, ip6tables(_, Contains("-C"), _, _))
223 .WillRepeatedly(Return(1));
224
225 // The name of a new chain must be shorter than 29 characters, otherwise
226 // iptables will reject the request. Uses Each() here for simplicity since no
227 // other params could be longer than 29 for now.
228 static constexpr int kMaxChainNameLength = 29;
229 EXPECT_CALL(runner_, iptables(_, Each(SizeIs(Lt(kMaxChainNameLength))), _, _))
230 .Times(AnyNumber());
231 EXPECT_CALL(runner_,
232 ip6tables(_, Each(SizeIs(Lt(kMaxChainNameLength))), _, _))
233 .Times(AnyNumber());
234
235 static const std::string kLongInterfaceName(IFNAMSIZ, 'a');
236 std::vector<dbus::ObjectPath> devices = {
237 dbus::ObjectPath("/device/" + kLongInterfaceName)};
238 fake_shill_client_->NotifyManagerPropertyChange(shill::kDevicesProperty,
239 brillo::Any(devices));
240}
241
Jie Jianged0b1cc2020-07-10 15:55:33 +0900242TEST_F(CountersServiceTest, QueryTrafficCounters) {
243 EXPECT_CALL(runner_, iptables(_, _, _, _))
244 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
245 EXPECT_CALL(runner_, ip6tables(_, _, _, _))
246 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
247
248 auto actual = counters_svc_->GetCounters({});
249
250 std::map<SourceDevice, Counter> expected{
251 {{TrafficCounter::UNKNOWN, "eth0"}, kCounter_eth0},
252 {{TrafficCounter::UNKNOWN, "wlan0"}, kCounter_wlan0},
253 };
254
255 EXPECT_THAT(actual, ContainerEq(expected));
256}
257
258TEST_F(CountersServiceTest, QueryTrafficCountersWithFilter) {
259 EXPECT_CALL(runner_, iptables(_, _, _, _))
260 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
261 EXPECT_CALL(runner_, ip6tables(_, _, _, _))
262 .WillRepeatedly(DoAll(SetArgPointee<3>(kIptablesOutput), Return(0)));
263
264 // Only counters for eth0 should be returned. eth1 should be ignored.
265 auto actual = counters_svc_->GetCounters({"eth0", "eth1"});
266
267 std::map<SourceDevice, Counter> expected{
268 {{TrafficCounter::UNKNOWN, "eth0"}, kCounter_eth0},
269 };
270
271 EXPECT_THAT(actual, ContainerEq(expected));
272}
273
274TEST_F(CountersServiceTest, QueryTrafficCountersWithEmptyIPv4Output) {
275 const std::string kEmptyOutput = "";
276 TestBadIptablesOutput(kEmptyOutput);
277}
278
279TEST_F(CountersServiceTest, QueryTrafficCountersWithEmptyIPv6Output) {
280 const std::string kEmptyOutput = "";
281 TestBadIp6tablesOutput(kEmptyOutput);
282}
283
284TEST_F(CountersServiceTest, QueryTrafficCountersWithOnlyChainName) {
285 const std::string kBadOutput = R"(
286Chain tx_fwd_eth0 (1 references)
287 pkts bytes target prot opt in out source destination
288 6511 68041668 all -- any any anywhere anywhere
289
290Chain tx_fwd_wlan0 (1 references)
291)";
292 TestBadIptablesOutput(kBadOutput);
293}
294
295TEST_F(CountersServiceTest, QueryTrafficCountersWithOnlyChainNameAndHeader) {
296 const std::string kBadOutput = R"(
297Chain tx_fwd_eth0 (1 references)
298 pkts bytes target prot opt in out source destination
299 6511 68041668 all -- any any anywhere anywhere
300
301Chain tx_fwd_wlan0 (1 references)
302 pkts bytes target prot opt in out source destination
303)";
304 TestBadIptablesOutput(kBadOutput);
305}
306
307TEST_F(CountersServiceTest, QueryTrafficCountersWithNotFinishedCountersLine) {
308 const std::string kBadOutput = R"(
309Chain tx_fwd_eth0 (1 references)
310 pkts bytes target prot opt in out source destination
311 6511 68041668 all -- any any anywhere anywhere
312
313Chain tx_fwd_wlan0 (1 references)
314 pkts bytes target prot opt in out source destination pkts bytes target prot opt in out source destination
315 0 )";
316 TestBadIptablesOutput(kBadOutput);
317}
318
Jie Jiangcf749152020-07-09 22:20:58 +0900319} // namespace
320} // namespace patchpanel