blob: 66765618a6ce676f09bbbdc6461f62b53c588072 [file] [log] [blame]
Jie Jiang31a0b4e2020-07-09 15:06:16 +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 <set>
8#include <string>
Jie Jianged0b1cc2020-07-10 15:55:33 +09009#include <utility>
Jie Jiang31a0b4e2020-07-09 15:06:16 +090010#include <vector>
11
Jie Jianged0b1cc2020-07-10 15:55:33 +090012#include <base/strings/strcat.h>
13#include <base/strings/string_split.h>
14#include <re2/re2.h>
15
Jie Jiang31a0b4e2020-07-09 15:06:16 +090016namespace patchpanel {
17
18namespace {
19
Jie Jianged0b1cc2020-07-10 15:55:33 +090020using Counter = CountersService::Counter;
21using SourceDevice = CountersService::SourceDevice;
22
Jie Jiang31a0b4e2020-07-09 15:06:16 +090023constexpr char kMangleTable[] = "mangle";
24
Jie Jianged0b1cc2020-07-10 15:55:33 +090025// The following regexs and code is written and tested for iptables v1.6.2.
26// Output code of iptables can be found at:
27// https://git.netfilter.org/iptables/tree/iptables/iptables.c?h=v1.6.2
28
29// The chain line looks like:
Hugo Benichi33af8f72020-11-20 10:08:47 +090030// "Chain tx_eth0 (2 references)".
31// This regex extracts "tx" (direction), "eth0" (ifname) from this example.
32constexpr LazyRE2 kChainLine = {R"(Chain (rx|tx)_(\w+).*)"};
Jie Jianged0b1cc2020-07-10 15:55:33 +090033
34// The counter line looks like (some spaces are deleted to make it fit in one
35// line):
Hugo Benichi92fa2032020-11-20 17:47:32 +090036// " 5374 876172 all -- any any anywhere anywhere mark match 0x2000/0x3f00"
Jie Jianged0b1cc2020-07-10 15:55:33 +090037// The first two counters are captured for pkts and bytes.
Hugo Benichi92fa2032020-11-20 17:47:32 +090038constexpr LazyRE2 kCounterLine = {R"( *(\d+) +(\d+).*mark match (.*)/0x3f00)"};
Jie Jianged0b1cc2020-07-10 15:55:33 +090039
40// Parses the output of `iptables -L -x -v` (or `ip6tables`) and adds the parsed
41// values into the corresponding counters in |counters|. An example of |output|
42// can be found in the test file. This function will try to find the pattern of:
43// <one chain line for an accounting chain>
44// <one header line>
45// <one counter line for an accounting rule>
46// The interface name and direction (rx or tx) will be extracted from the chain
47// line, and then the values extracted from the counter line will be added into
48// the counter for that interface. Note that this function will not fully
49// validate if |output| is an output from iptables.
50bool ParseOutput(const std::string& output,
51 const std::set<std::string>& devices,
52 std::map<SourceDevice, Counter>* counters) {
53 DCHECK(counters);
54 const std::vector<std::string> lines = base::SplitString(
55 output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
56
57 // Finds the chain line for an accounting chain first, and then parse the
58 // following line(s) to get the counters for this chain. Repeats this process
59 // until we reach the end of |output|.
60 for (auto it = lines.cbegin(); it != lines.cend(); it++) {
61 // Finds the chain name line.
62 std::string direction, ifname;
63 while (it != lines.cend() &&
64 !RE2::FullMatch(*it, *kChainLine, &direction, &ifname))
65 it++;
66
67 if (it == lines.cend())
68 break;
69
70 // Skips this group if this ifname is not requested.
71 if (!devices.empty() && devices.find(ifname) == devices.end())
72 continue;
73
74 // Skips the chain name line and the header line.
75 if (lines.cend() - it <= 2) {
76 LOG(ERROR) << "Invalid iptables output";
77 return false;
78 }
79 it += 2;
80
Hugo Benichi92fa2032020-11-20 17:47:32 +090081 // The next block of lines are the counters lines for individual sources.
82 for (; it != lines.cend() && !it->empty(); it++) {
83 uint64_t pkts, bytes;
84 Fwmark mark;
85 if (!RE2::FullMatch(*it, *kCounterLine, &pkts, &bytes,
86 RE2::Hex(&mark.fwmark))) {
87 LOG(ERROR) << "Cannot parse counter line \"" << *it << "\"";
88 return false;
89 }
Jie Jianged0b1cc2020-07-10 15:55:33 +090090
Hugo Benichi92fa2032020-11-20 17:47:32 +090091 if (pkts == 0 && bytes == 0)
92 continue;
93
94 TrafficCounter::Source source = TrafficSourceToProto(mark.Source());
95 auto& counter = (*counters)[std::make_pair(source, ifname)];
96 if (direction == "rx") {
97 counter.rx_packets += pkts;
98 counter.rx_bytes += bytes;
99 } else {
100 counter.tx_packets += pkts;
101 counter.tx_bytes += bytes;
102 }
Jie Jianged0b1cc2020-07-10 15:55:33 +0900103 }
104 }
105 return true;
106}
107
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900108} // namespace
109
Jie Jianged0b1cc2020-07-10 15:55:33 +0900110Counter::Counter(uint64_t rx_bytes,
111 uint64_t rx_packets,
112 uint64_t tx_bytes,
113 uint64_t tx_packets)
114 : rx_bytes(rx_bytes),
115 rx_packets(rx_packets),
116 tx_bytes(tx_bytes),
117 tx_packets(tx_packets) {}
118
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900119CountersService::CountersService(ShillClient* shill_client,
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900120 Datapath* datapath,
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900121 MinijailedProcessRunner* runner)
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900122 : shill_client_(shill_client), datapath_(datapath), runner_(runner) {
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900123 // Triggers the callback manually to make sure no device is missed.
124 OnDeviceChanged(shill_client_->get_devices(), {});
125 shill_client_->RegisterDevicesChangedHandler(base::BindRepeating(
126 &CountersService::OnDeviceChanged, weak_factory_.GetWeakPtr()));
127}
128
Jie Jianged0b1cc2020-07-10 15:55:33 +0900129std::map<SourceDevice, Counter> CountersService::GetCounters(
130 const std::set<std::string>& devices) {
131 std::map<SourceDevice, Counter> counters;
132
133 // Handles counters for IPv4 and IPv6 separately and returns failure if either
134 // of the procession fails, since counters for only IPv4 or IPv6 are biased.
135 std::string iptables_result;
136 int ret = runner_->iptables(kMangleTable, {"-L", "-x", "-v", "-w"},
137 true /*log_failures*/, &iptables_result);
138 if (ret != 0 || iptables_result.empty()) {
139 LOG(ERROR) << "Failed to query IPv4 counters";
140 return {};
141 }
142 if (!ParseOutput(iptables_result, devices, &counters)) {
143 LOG(ERROR) << "Failed to parse IPv4 counters";
144 return {};
145 }
146
147 std::string ip6tables_result;
148 ret = runner_->ip6tables(kMangleTable, {"-L", "-x", "-v", "-w"},
149 true /*log_failures*/, &ip6tables_result);
150 if (ret != 0 || ip6tables_result.empty()) {
151 LOG(ERROR) << "Failed to query IPv6 counters";
152 return {};
153 }
154 if (!ParseOutput(ip6tables_result, devices, &counters)) {
155 LOG(ERROR) << "Failed to parse IPv6 counters";
156 return {};
157 }
158
159 return counters;
160}
161
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900162void CountersService::OnDeviceChanged(const std::set<std::string>& added,
Jie Jiangcf749152020-07-09 22:20:58 +0900163 const std::set<std::string>& removed) {
164 for (const auto& ifname : added)
165 SetupChainsAndRules(ifname);
166}
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900167
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900168bool CountersService::MakeAccountingChain(const std::string& chain_name) {
169 return datapath_->ModifyChain(IpFamily::Dual, kMangleTable, "-N", chain_name,
170 false /*log_failures*/);
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900171}
172
Hugo Benichiaf2021b2020-11-20 14:07:16 +0900173bool CountersService::AddAccountingRule(const std::string& chain_name,
174 TrafficSource source) {
175 std::vector<std::string> args = {"-A",
176 chain_name,
177 "-m",
178 "mark",
179 "--mark",
180 Fwmark::FromSource(source).ToString() + "/" +
181 kFwmarkAllSourcesMask.ToString(),
182 "-j",
183 "RETURN",
184 "-w"};
185 return datapath_->ModifyIptables(IpFamily::Dual, kMangleTable, args);
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900186}
187
Jie Jiangcf749152020-07-09 22:20:58 +0900188void CountersService::SetupChainsAndRules(const std::string& ifname) {
Hugo Benichi33af8f72020-11-20 10:08:47 +0900189 // For each device and traffic direction, we need to create:
190 // 1) an accounting chain to jump to,
191 // 2) jumping rules in mangle POSTROUTING for egress traffic, and in mangle
192 // INPUT and FORWARD for ingress traffic.
193 // 3) source accounting rules in the chain.
Jie Jiangcf749152020-07-09 22:20:58 +0900194 // Note that the length of a chain name must less than 29 chars and IFNAMSIZ
195 // is 16 so we can only use at most 12 chars for the prefix.
196
Hugo Benichi33af8f72020-11-20 10:08:47 +0900197 const std::string ingress_chain = "rx_" + ifname;
Hugo Benichi33af8f72020-11-20 10:08:47 +0900198 const std::string egress_chain = "tx_" + ifname;
Hugo Benichiddf00842020-11-20 10:24:08 +0900199
200 // Creates egress and ingress traffic chains, or stops if they already exist.
201 if (!MakeAccountingChain(ingress_chain) ||
202 !MakeAccountingChain(egress_chain)) {
203 LOG(INFO) << "Traffic accounting chains already exist for " << ifname;
204 return;
205 }
206
207 // Add jump rules
208 datapath_->ModifyIptables(
209 IpFamily::Dual, kMangleTable,
210 {"-A", "FORWARD", "-i", ifname, "-j", ingress_chain, "-w"});
211 datapath_->ModifyIptables(
212 IpFamily::Dual, kMangleTable,
213 {"-A", "INPUT", "-i", ifname, "-j", ingress_chain, "-w"});
214 datapath_->ModifyIptables(
215 IpFamily::Dual, kMangleTable,
216 {"-A", "POSTROUTING", "-o", ifname, "-j", egress_chain, "-w"});
217
Hugo Benichiaf2021b2020-11-20 14:07:16 +0900218 // Add source accounting rules.
219 for (TrafficSource source : kAllSources) {
220 AddAccountingRule(ingress_chain, source);
221 AddAccountingRule(egress_chain, source);
222 }
223 // TODO(b/160112868): add default rules for counting any traffic left as
224 // UNKNOWN.
Jie Jiangcf749152020-07-09 22:20:58 +0900225}
226
Hugo Benichi92fa2032020-11-20 17:47:32 +0900227TrafficCounter::Source TrafficSourceToProto(TrafficSource source) {
228 switch (source) {
229 case CHROME:
230 return TrafficCounter::CHROME;
231 case USER:
232 return TrafficCounter::USER;
233 case UPDATE_ENGINE:
234 return TrafficCounter::UPDATE_ENGINE;
235 case SYSTEM:
236 return TrafficCounter::SYSTEM;
237 case HOST_VPN:
238 return TrafficCounter::VPN;
239 case ARC:
240 return TrafficCounter::ARC;
241 case CROSVM:
242 return TrafficCounter::CROSVM;
243 case PLUGINVM:
244 return TrafficCounter::PLUGINVM;
245 case TETHER_DOWNSTREAM:
246 return TrafficCounter::SYSTEM;
247 case ARC_VPN:
248 return TrafficCounter::VPN;
249 case UNKNOWN:
250 default:
251 return TrafficCounter::UNKNOWN;
252 }
253}
254
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900255} // namespace patchpanel