blob: 2b78b0a15c40255eee6cc4bf0ef87130e4150f4e [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
37// The counter line looks like (some spaces are deleted to make it fit in one
38// line):
Hugo Benichi92fa2032020-11-20 17:47:32 +090039// " 5374 876172 all -- any any anywhere anywhere mark match 0x2000/0x3f00"
Jie Jianged0b1cc2020-07-10 15:55:33 +090040// The first two counters are captured for pkts and bytes.
Hugo Benichi92fa2032020-11-20 17:47:32 +090041constexpr LazyRE2 kCounterLine = {R"( *(\d+) +(\d+).*mark match (.*)/0x3f00)"};
Jie Jianged0b1cc2020-07-10 15:55:33 +090042
43// Parses the output of `iptables -L -x -v` (or `ip6tables`) and adds the parsed
44// values into the corresponding counters in |counters|. An example of |output|
45// can be found in the test file. This function will try to find the pattern of:
46// <one chain line for an accounting chain>
47// <one header line>
48// <one counter line for an accounting rule>
49// The interface name and direction (rx or tx) will be extracted from the chain
50// line, and then the values extracted from the counter line will be added into
51// the counter for that interface. Note that this function will not fully
52// validate if |output| is an output from iptables.
53bool ParseOutput(const std::string& output,
54 const std::set<std::string>& devices,
55 std::map<SourceDevice, Counter>* counters) {
56 DCHECK(counters);
57 const std::vector<std::string> lines = base::SplitString(
58 output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
59
60 // Finds the chain line for an accounting chain first, and then parse the
61 // following line(s) to get the counters for this chain. Repeats this process
62 // until we reach the end of |output|.
63 for (auto it = lines.cbegin(); it != lines.cend(); it++) {
64 // Finds the chain name line.
65 std::string direction, ifname;
66 while (it != lines.cend() &&
67 !RE2::FullMatch(*it, *kChainLine, &direction, &ifname))
68 it++;
69
70 if (it == lines.cend())
71 break;
72
73 // Skips this group if this ifname is not requested.
74 if (!devices.empty() && devices.find(ifname) == devices.end())
75 continue;
76
77 // Skips the chain name line and the header line.
78 if (lines.cend() - it <= 2) {
79 LOG(ERROR) << "Invalid iptables output";
80 return false;
81 }
82 it += 2;
83
Hugo Benichi92fa2032020-11-20 17:47:32 +090084 // The next block of lines are the counters lines for individual sources.
85 for (; it != lines.cend() && !it->empty(); it++) {
86 uint64_t pkts, bytes;
87 Fwmark mark;
88 if (!RE2::FullMatch(*it, *kCounterLine, &pkts, &bytes,
89 RE2::Hex(&mark.fwmark))) {
90 LOG(ERROR) << "Cannot parse counter line \"" << *it << "\"";
91 return false;
92 }
Jie Jianged0b1cc2020-07-10 15:55:33 +090093
Hugo Benichi92fa2032020-11-20 17:47:32 +090094 if (pkts == 0 && bytes == 0)
95 continue;
96
97 TrafficCounter::Source source = TrafficSourceToProto(mark.Source());
98 auto& counter = (*counters)[std::make_pair(source, ifname)];
99 if (direction == "rx") {
100 counter.rx_packets += pkts;
101 counter.rx_bytes += bytes;
102 } else {
103 counter.tx_packets += pkts;
104 counter.tx_bytes += bytes;
105 }
Jie Jianged0b1cc2020-07-10 15:55:33 +0900106 }
107 }
108 return true;
109}
110
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900111} // namespace
112
Jie Jianged0b1cc2020-07-10 15:55:33 +0900113Counter::Counter(uint64_t rx_bytes,
114 uint64_t rx_packets,
115 uint64_t tx_bytes,
116 uint64_t tx_packets)
117 : rx_bytes(rx_bytes),
118 rx_packets(rx_packets),
119 tx_bytes(tx_bytes),
120 tx_packets(tx_packets) {}
121
Hugo Benichi058a26a2020-11-26 10:10:39 +0900122CountersService::CountersService(Datapath* datapath,
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900123 MinijailedProcessRunner* runner)
Hugo Benichi058a26a2020-11-26 10:10:39 +0900124 : datapath_(datapath), runner_(runner) {}
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900125
Jie Jianged0b1cc2020-07-10 15:55:33 +0900126std::map<SourceDevice, Counter> CountersService::GetCounters(
127 const std::set<std::string>& devices) {
128 std::map<SourceDevice, Counter> counters;
129
130 // Handles counters for IPv4 and IPv6 separately and returns failure if either
131 // of the procession fails, since counters for only IPv4 or IPv6 are biased.
132 std::string iptables_result;
133 int ret = runner_->iptables(kMangleTable, {"-L", "-x", "-v", "-w"},
134 true /*log_failures*/, &iptables_result);
135 if (ret != 0 || iptables_result.empty()) {
136 LOG(ERROR) << "Failed to query IPv4 counters";
137 return {};
138 }
139 if (!ParseOutput(iptables_result, devices, &counters)) {
140 LOG(ERROR) << "Failed to parse IPv4 counters";
141 return {};
142 }
143
144 std::string ip6tables_result;
145 ret = runner_->ip6tables(kMangleTable, {"-L", "-x", "-v", "-w"},
146 true /*log_failures*/, &ip6tables_result);
147 if (ret != 0 || ip6tables_result.empty()) {
148 LOG(ERROR) << "Failed to query IPv6 counters";
149 return {};
150 }
151 if (!ParseOutput(ip6tables_result, devices, &counters)) {
152 LOG(ERROR) << "Failed to parse IPv6 counters";
153 return {};
154 }
155
156 return counters;
157}
158
Hugo Benichi058a26a2020-11-26 10:10:39 +0900159void CountersService::Init(const std::set<std::string>& devices) {
160 SetupAccountingRules(kVpnChainTag);
161 for (const auto& device : devices) {
162 OnPhysicalDeviceAdded(device);
163 }
164}
165
166void CountersService::OnPhysicalDeviceAdded(const std::string& ifname) {
167 if (SetupAccountingRules(ifname))
168 SetupJumpRules("-A", ifname, ifname);
169}
170
171void CountersService::OnPhysicalDeviceRemoved(const std::string& ifname) {
172 SetupJumpRules("-D", ifname, ifname);
173}
174
175void CountersService::OnVpnDeviceAdded(const std::string& ifname) {
176 SetupJumpRules("-A", ifname, kVpnChainTag);
177}
178
179void CountersService::OnVpnDeviceRemoved(const std::string& ifname) {
180 SetupJumpRules("-D", ifname, kVpnChainTag);
Jie Jiangcf749152020-07-09 22:20:58 +0900181}
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900182
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900183bool CountersService::MakeAccountingChain(const std::string& chain_name) {
184 return datapath_->ModifyChain(IpFamily::Dual, kMangleTable, "-N", chain_name,
185 false /*log_failures*/);
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900186}
187
Hugo Benichiaf2021b2020-11-20 14:07:16 +0900188bool CountersService::AddAccountingRule(const std::string& chain_name,
189 TrafficSource source) {
190 std::vector<std::string> args = {"-A",
191 chain_name,
192 "-m",
193 "mark",
194 "--mark",
195 Fwmark::FromSource(source).ToString() + "/" +
196 kFwmarkAllSourcesMask.ToString(),
197 "-j",
198 "RETURN",
199 "-w"};
200 return datapath_->ModifyIptables(IpFamily::Dual, kMangleTable, args);
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900201}
202
Hugo Benichi058a26a2020-11-26 10:10:39 +0900203bool CountersService::SetupAccountingRules(const std::string& chain_tag) {
204 // For a new target accounting chain, create
Hugo Benichi33af8f72020-11-20 10:08:47 +0900205 // 1) an accounting chain to jump to,
Hugo Benichi058a26a2020-11-26 10:10:39 +0900206 // 2) source accounting rules in the chain.
Jie Jiangcf749152020-07-09 22:20:58 +0900207 // Note that the length of a chain name must less than 29 chars and IFNAMSIZ
208 // is 16 so we can only use at most 12 chars for the prefix.
Hugo Benichi058a26a2020-11-26 10:10:39 +0900209 const std::string ingress_chain = kRxTag + chain_tag;
210 const std::string egress_chain = kTxTag + chain_tag;
Hugo Benichiddf00842020-11-20 10:24:08 +0900211
212 // Creates egress and ingress traffic chains, or stops if they already exist.
Hugo Benichi058a26a2020-11-26 10:10:39 +0900213 if (!MakeAccountingChain(egress_chain) ||
214 !MakeAccountingChain(ingress_chain)) {
215 LOG(INFO) << "Traffic accounting chains already exist for " << chain_tag;
216 return false;
Hugo Benichiddf00842020-11-20 10:24:08 +0900217 }
218
Hugo Benichiaf2021b2020-11-20 14:07:16 +0900219 // Add source accounting rules.
220 for (TrafficSource source : kAllSources) {
221 AddAccountingRule(ingress_chain, source);
222 AddAccountingRule(egress_chain, source);
223 }
224 // TODO(b/160112868): add default rules for counting any traffic left as
225 // UNKNOWN.
Hugo Benichi058a26a2020-11-26 10:10:39 +0900226
227 return true;
228}
229
230void CountersService::SetupJumpRules(const std::string& op,
231 const std::string& ifname,
232 const std::string& chain_tag) {
233 // For each device create a jumping rule in mangle POSTROUTING for egress
234 // traffic, and two jumping rules in mangle INPUT and FORWARD for ingress
235 // traffic.
236 datapath_->ModifyIptables(
237 IpFamily::Dual, kMangleTable,
238 {"-A", "FORWARD", "-i", ifname, "-j", kRxTag + chain_tag, "-w"});
239 datapath_->ModifyIptables(
240 IpFamily::Dual, kMangleTable,
241 {"-A", "INPUT", "-i", ifname, "-j", kRxTag + chain_tag, "-w"});
242 datapath_->ModifyIptables(
243 IpFamily::Dual, kMangleTable,
244 {"-A", "POSTROUTING", "-o", ifname, "-j", kTxTag + chain_tag, "-w"});
Jie Jiangcf749152020-07-09 22:20:58 +0900245}
246
Hugo Benichi92fa2032020-11-20 17:47:32 +0900247TrafficCounter::Source TrafficSourceToProto(TrafficSource source) {
248 switch (source) {
249 case CHROME:
250 return TrafficCounter::CHROME;
251 case USER:
252 return TrafficCounter::USER;
253 case UPDATE_ENGINE:
254 return TrafficCounter::UPDATE_ENGINE;
255 case SYSTEM:
256 return TrafficCounter::SYSTEM;
257 case HOST_VPN:
258 return TrafficCounter::VPN;
259 case ARC:
260 return TrafficCounter::ARC;
261 case CROSVM:
262 return TrafficCounter::CROSVM;
263 case PLUGINVM:
264 return TrafficCounter::PLUGINVM;
265 case TETHER_DOWNSTREAM:
266 return TrafficCounter::SYSTEM;
267 case ARC_VPN:
268 return TrafficCounter::VPN;
269 case UNKNOWN:
270 default:
271 return TrafficCounter::UNKNOWN;
272 }
273}
274
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900275} // namespace patchpanel