blob: 6b7a131306480d9c6a65a9fedb59914b178ab487 [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";
Hugo Benichi058a26a2020-11-26 10:10:39 +090024constexpr char kVpnChainTag[] = "vpn";
25constexpr char kRxTag[] = "rx_";
26constexpr char kTxTag[] = "tx_";
Jie Jiang31a0b4e2020-07-09 15:06:16 +090027
Jie Jianged0b1cc2020-07-10 15:55:33 +090028// The following regexs and code is written and tested for iptables v1.6.2.
29// Output code of iptables can be found at:
30// https://git.netfilter.org/iptables/tree/iptables/iptables.c?h=v1.6.2
31
32// The chain line looks like:
Hugo Benichi33af8f72020-11-20 10:08:47 +090033// "Chain tx_eth0 (2 references)".
34// This regex extracts "tx" (direction), "eth0" (ifname) from this example.
35constexpr LazyRE2 kChainLine = {R"(Chain (rx|tx)_(\w+).*)"};
Jie Jianged0b1cc2020-07-10 15:55:33 +090036
Hugo Benichiae5803d2020-12-08 10:49:48 +090037// The counter line for a defined source looks like (some spaces are deleted to
38// make it fit in one line):
39// " 5374 6172 RETURN all -- any any anywhere anywhere mark match 0x2000/0x3f00"
40// The final counter line for catching untagged traffic looks like:
41// " 5374 6172 all -- any any anywhere anywhere"
42// The first two counters are captured for pkts and bytes. For lines with a mark
43// matcher, the source is also captured.
Hugo Benichi92fa2032020-11-20 17:47:32 +090044constexpr LazyRE2 kCounterLine = {R"( *(\d+) +(\d+).*mark match (.*)/0x3f00)"};
Hugo Benichiae5803d2020-12-08 10:49:48 +090045constexpr LazyRE2 kFinalCounterLine = {R"( *(\d+) +(\d+).*anywhere\s*)"};
46
47bool MatchCounterLine(const std::string& line,
48 uint64_t* pkts,
49 uint64_t* bytes,
50 TrafficSource* source) {
51 Fwmark mark;
52 if (RE2::FullMatch(line, *kCounterLine, pkts, bytes,
53 RE2::Hex(&mark.fwmark))) {
54 *source = mark.Source();
55 return true;
56 }
57
58 if (RE2::FullMatch(line, *kFinalCounterLine, pkts, bytes)) {
59 *source = TrafficSource::UNKNOWN;
60 return true;
61 }
62
63 return false;
64}
Jie Jianged0b1cc2020-07-10 15:55:33 +090065
66// Parses the output of `iptables -L -x -v` (or `ip6tables`) and adds the parsed
67// values into the corresponding counters in |counters|. An example of |output|
68// can be found in the test file. This function will try to find the pattern of:
69// <one chain line for an accounting chain>
70// <one header line>
71// <one counter line for an accounting rule>
72// The interface name and direction (rx or tx) will be extracted from the chain
73// line, and then the values extracted from the counter line will be added into
74// the counter for that interface. Note that this function will not fully
75// validate if |output| is an output from iptables.
76bool ParseOutput(const std::string& output,
77 const std::set<std::string>& devices,
78 std::map<SourceDevice, Counter>* counters) {
79 DCHECK(counters);
80 const std::vector<std::string> lines = base::SplitString(
81 output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
82
83 // Finds the chain line for an accounting chain first, and then parse the
84 // following line(s) to get the counters for this chain. Repeats this process
85 // until we reach the end of |output|.
86 for (auto it = lines.cbegin(); it != lines.cend(); it++) {
87 // Finds the chain name line.
88 std::string direction, ifname;
89 while (it != lines.cend() &&
90 !RE2::FullMatch(*it, *kChainLine, &direction, &ifname))
91 it++;
92
93 if (it == lines.cend())
94 break;
95
96 // Skips this group if this ifname is not requested.
97 if (!devices.empty() && devices.find(ifname) == devices.end())
98 continue;
99
100 // Skips the chain name line and the header line.
101 if (lines.cend() - it <= 2) {
Hugo Benichiae5803d2020-12-08 10:49:48 +0900102 LOG(ERROR) << "Invalid iptables output for " << direction << "_"
103 << ifname;
Jie Jianged0b1cc2020-07-10 15:55:33 +0900104 return false;
105 }
106 it += 2;
107
Hugo Benichiae5803d2020-12-08 10:49:48 +0900108 // Checks that there are some counter rules defined.
109 if (it == lines.cend() || it->empty()) {
110 LOG(ERROR) << "No counter rule defined for " << direction << "_"
111 << ifname;
112 return false;
113 }
114
Hugo Benichi92fa2032020-11-20 17:47:32 +0900115 // The next block of lines are the counters lines for individual sources.
116 for (; it != lines.cend() && !it->empty(); it++) {
117 uint64_t pkts, bytes;
Hugo Benichiae5803d2020-12-08 10:49:48 +0900118 TrafficSource source;
119 if (!MatchCounterLine(*it, &pkts, &bytes, &source)) {
120 LOG(ERROR) << "Cannot parse counter line \"" << *it << "\" for "
121 << direction << "_" << ifname;
Hugo Benichi92fa2032020-11-20 17:47:32 +0900122 return false;
123 }
Jie Jianged0b1cc2020-07-10 15:55:33 +0900124
Hugo Benichi92fa2032020-11-20 17:47:32 +0900125 if (pkts == 0 && bytes == 0)
126 continue;
127
Hugo Benichiae5803d2020-12-08 10:49:48 +0900128 auto& counter =
129 (*counters)[std::make_pair(TrafficSourceToProto(source), ifname)];
Hugo Benichi92fa2032020-11-20 17:47:32 +0900130 if (direction == "rx") {
Hugo Benichi92fa2032020-11-20 17:47:32 +0900131 counter.rx_bytes += bytes;
Hugo Benichiae5803d2020-12-08 10:49:48 +0900132 counter.rx_packets += pkts;
Hugo Benichi92fa2032020-11-20 17:47:32 +0900133 } else {
Hugo Benichi92fa2032020-11-20 17:47:32 +0900134 counter.tx_bytes += bytes;
Hugo Benichiae5803d2020-12-08 10:49:48 +0900135 counter.tx_packets += pkts;
Hugo Benichi92fa2032020-11-20 17:47:32 +0900136 }
Jie Jianged0b1cc2020-07-10 15:55:33 +0900137 }
138 }
139 return true;
140}
141
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900142} // namespace
143
Jie Jianged0b1cc2020-07-10 15:55:33 +0900144Counter::Counter(uint64_t rx_bytes,
145 uint64_t rx_packets,
146 uint64_t tx_bytes,
147 uint64_t tx_packets)
148 : rx_bytes(rx_bytes),
149 rx_packets(rx_packets),
150 tx_bytes(tx_bytes),
151 tx_packets(tx_packets) {}
152
Hugo Benichi058a26a2020-11-26 10:10:39 +0900153CountersService::CountersService(Datapath* datapath,
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900154 MinijailedProcessRunner* runner)
Hugo Benichi058a26a2020-11-26 10:10:39 +0900155 : datapath_(datapath), runner_(runner) {}
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900156
Jie Jianged0b1cc2020-07-10 15:55:33 +0900157std::map<SourceDevice, Counter> CountersService::GetCounters(
158 const std::set<std::string>& devices) {
159 std::map<SourceDevice, Counter> counters;
160
161 // Handles counters for IPv4 and IPv6 separately and returns failure if either
162 // of the procession fails, since counters for only IPv4 or IPv6 are biased.
163 std::string iptables_result;
164 int ret = runner_->iptables(kMangleTable, {"-L", "-x", "-v", "-w"},
165 true /*log_failures*/, &iptables_result);
166 if (ret != 0 || iptables_result.empty()) {
167 LOG(ERROR) << "Failed to query IPv4 counters";
168 return {};
169 }
170 if (!ParseOutput(iptables_result, devices, &counters)) {
171 LOG(ERROR) << "Failed to parse IPv4 counters";
172 return {};
173 }
174
175 std::string ip6tables_result;
176 ret = runner_->ip6tables(kMangleTable, {"-L", "-x", "-v", "-w"},
177 true /*log_failures*/, &ip6tables_result);
178 if (ret != 0 || ip6tables_result.empty()) {
179 LOG(ERROR) << "Failed to query IPv6 counters";
180 return {};
181 }
182 if (!ParseOutput(ip6tables_result, devices, &counters)) {
183 LOG(ERROR) << "Failed to parse IPv6 counters";
184 return {};
185 }
186
187 return counters;
188}
189
Hugo Benichi058a26a2020-11-26 10:10:39 +0900190void CountersService::Init(const std::set<std::string>& devices) {
191 SetupAccountingRules(kVpnChainTag);
192 for (const auto& device : devices) {
193 OnPhysicalDeviceAdded(device);
194 }
195}
196
197void CountersService::OnPhysicalDeviceAdded(const std::string& ifname) {
198 if (SetupAccountingRules(ifname))
199 SetupJumpRules("-A", ifname, ifname);
200}
201
202void CountersService::OnPhysicalDeviceRemoved(const std::string& ifname) {
203 SetupJumpRules("-D", ifname, ifname);
204}
205
206void CountersService::OnVpnDeviceAdded(const std::string& ifname) {
207 SetupJumpRules("-A", ifname, kVpnChainTag);
208}
209
210void CountersService::OnVpnDeviceRemoved(const std::string& ifname) {
211 SetupJumpRules("-D", ifname, kVpnChainTag);
Jie Jiangcf749152020-07-09 22:20:58 +0900212}
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900213
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900214bool CountersService::MakeAccountingChain(const std::string& chain_name) {
215 return datapath_->ModifyChain(IpFamily::Dual, kMangleTable, "-N", chain_name,
216 false /*log_failures*/);
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900217}
218
Hugo Benichiaf2021b2020-11-20 14:07:16 +0900219bool CountersService::AddAccountingRule(const std::string& chain_name,
220 TrafficSource source) {
221 std::vector<std::string> args = {"-A",
222 chain_name,
223 "-m",
224 "mark",
225 "--mark",
226 Fwmark::FromSource(source).ToString() + "/" +
227 kFwmarkAllSourcesMask.ToString(),
228 "-j",
229 "RETURN",
230 "-w"};
231 return datapath_->ModifyIptables(IpFamily::Dual, kMangleTable, args);
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900232}
233
Hugo Benichi058a26a2020-11-26 10:10:39 +0900234bool CountersService::SetupAccountingRules(const std::string& chain_tag) {
235 // For a new target accounting chain, create
Hugo Benichi33af8f72020-11-20 10:08:47 +0900236 // 1) an accounting chain to jump to,
Hugo Benichi058a26a2020-11-26 10:10:39 +0900237 // 2) source accounting rules in the chain.
Jie Jiangcf749152020-07-09 22:20:58 +0900238 // Note that the length of a chain name must less than 29 chars and IFNAMSIZ
239 // is 16 so we can only use at most 12 chars for the prefix.
Hugo Benichi058a26a2020-11-26 10:10:39 +0900240 const std::string ingress_chain = kRxTag + chain_tag;
241 const std::string egress_chain = kTxTag + chain_tag;
Hugo Benichiddf00842020-11-20 10:24:08 +0900242
243 // Creates egress and ingress traffic chains, or stops if they already exist.
Hugo Benichi058a26a2020-11-26 10:10:39 +0900244 if (!MakeAccountingChain(egress_chain) ||
245 !MakeAccountingChain(ingress_chain)) {
246 LOG(INFO) << "Traffic accounting chains already exist for " << chain_tag;
247 return false;
Hugo Benichiddf00842020-11-20 10:24:08 +0900248 }
249
Hugo Benichiaf2021b2020-11-20 14:07:16 +0900250 // Add source accounting rules.
251 for (TrafficSource source : kAllSources) {
252 AddAccountingRule(ingress_chain, source);
253 AddAccountingRule(egress_chain, source);
254 }
Hugo Benichiae5803d2020-12-08 10:49:48 +0900255 // Add catch-all accounting rule for any remaining and untagged traffic.
256 datapath_->ModifyIptables(IpFamily::Dual, kMangleTable,
257 {"-A", ingress_chain, "-w"});
258 datapath_->ModifyIptables(IpFamily::Dual, kMangleTable,
259 {"-A", egress_chain, "-w"});
Hugo Benichi058a26a2020-11-26 10:10:39 +0900260
261 return true;
262}
263
264void CountersService::SetupJumpRules(const std::string& op,
265 const std::string& ifname,
266 const std::string& chain_tag) {
267 // For each device create a jumping rule in mangle POSTROUTING for egress
268 // traffic, and two jumping rules in mangle INPUT and FORWARD for ingress
269 // traffic.
270 datapath_->ModifyIptables(
271 IpFamily::Dual, kMangleTable,
272 {"-A", "FORWARD", "-i", ifname, "-j", kRxTag + chain_tag, "-w"});
273 datapath_->ModifyIptables(
274 IpFamily::Dual, kMangleTable,
275 {"-A", "INPUT", "-i", ifname, "-j", kRxTag + chain_tag, "-w"});
276 datapath_->ModifyIptables(
277 IpFamily::Dual, kMangleTable,
278 {"-A", "POSTROUTING", "-o", ifname, "-j", kTxTag + chain_tag, "-w"});
Jie Jiangcf749152020-07-09 22:20:58 +0900279}
280
Hugo Benichi92fa2032020-11-20 17:47:32 +0900281TrafficCounter::Source TrafficSourceToProto(TrafficSource source) {
282 switch (source) {
283 case CHROME:
284 return TrafficCounter::CHROME;
285 case USER:
286 return TrafficCounter::USER;
287 case UPDATE_ENGINE:
288 return TrafficCounter::UPDATE_ENGINE;
289 case SYSTEM:
290 return TrafficCounter::SYSTEM;
291 case HOST_VPN:
292 return TrafficCounter::VPN;
293 case ARC:
294 return TrafficCounter::ARC;
295 case CROSVM:
296 return TrafficCounter::CROSVM;
297 case PLUGINVM:
298 return TrafficCounter::PLUGINVM;
299 case TETHER_DOWNSTREAM:
300 return TrafficCounter::SYSTEM;
301 case ARC_VPN:
302 return TrafficCounter::VPN;
303 case UNKNOWN:
304 default:
305 return TrafficCounter::UNKNOWN;
306 }
307}
308
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900309} // namespace patchpanel