blob: c2e5a9e463b81e1d1414c3001db2d05e9f8272bb [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
Hugo Benichicd27f4e2020-11-19 18:32:23 +090016#include "patchpanel/routing_service.h"
17
Jie Jiang31a0b4e2020-07-09 15:06:16 +090018namespace patchpanel {
19
20namespace {
21
Jie Jianged0b1cc2020-07-10 15:55:33 +090022using Counter = CountersService::Counter;
23using SourceDevice = CountersService::SourceDevice;
24
Jie Jiang31a0b4e2020-07-09 15:06:16 +090025constexpr char kMangleTable[] = "mangle";
26
Jie Jianged0b1cc2020-07-10 15:55:33 +090027// The following regexs and code is written and tested for iptables v1.6.2.
28// Output code of iptables can be found at:
29// https://git.netfilter.org/iptables/tree/iptables/iptables.c?h=v1.6.2
30
31// The chain line looks like:
32// "Chain tx_fwd_eth0 (1 references)".
33// This regex extracts "tx" (direction), "eth0" (ifname) from this example, and
34// "fwd" (prebuilt chain) is matched but not captured because it is not required
35// for counters.
36constexpr LazyRE2 kChainLine = {
37 R"(Chain (rx|tx)_(?:input|fwd|postrt)_(\w+).*)"};
38
39// The counter line looks like (some spaces are deleted to make it fit in one
40// line):
41// " 6511 68041668 all -- any any anywhere anywhere"
42// The first two counters are captured for pkts and bytes.
43constexpr LazyRE2 kCounterLine = {R"( *(\d+) +(\d+).*)"};
44
45// Parses the output of `iptables -L -x -v` (or `ip6tables`) and adds the parsed
46// values into the corresponding counters in |counters|. An example of |output|
47// can be found in the test file. This function will try to find the pattern of:
48// <one chain line for an accounting chain>
49// <one header line>
50// <one counter line for an accounting rule>
51// The interface name and direction (rx or tx) will be extracted from the chain
52// line, and then the values extracted from the counter line will be added into
53// the counter for that interface. Note that this function will not fully
54// validate if |output| is an output from iptables.
55bool ParseOutput(const std::string& output,
56 const std::set<std::string>& devices,
57 std::map<SourceDevice, Counter>* counters) {
58 DCHECK(counters);
59 const std::vector<std::string> lines = base::SplitString(
60 output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
61
62 // Finds the chain line for an accounting chain first, and then parse the
63 // following line(s) to get the counters for this chain. Repeats this process
64 // until we reach the end of |output|.
65 for (auto it = lines.cbegin(); it != lines.cend(); it++) {
66 // Finds the chain name line.
67 std::string direction, ifname;
68 while (it != lines.cend() &&
69 !RE2::FullMatch(*it, *kChainLine, &direction, &ifname))
70 it++;
71
72 if (it == lines.cend())
73 break;
74
75 // Skips this group if this ifname is not requested.
76 if (!devices.empty() && devices.find(ifname) == devices.end())
77 continue;
78
79 // Skips the chain name line and the header line.
80 if (lines.cend() - it <= 2) {
81 LOG(ERROR) << "Invalid iptables output";
82 return false;
83 }
84 it += 2;
85
86 // The current line should be the accounting rule containing the counters.
87 // Currently we only have one accounting rule (UNKNOWN source) for each
88 // chain.
89 // TODO(jiejiang): The following part will be extended to a loop when we
90 // have more accounting rules.
91 uint64_t pkts, bytes;
92 if (!RE2::FullMatch(*it, *kCounterLine, &pkts, &bytes)) {
93 LOG(ERROR) << "Cannot parse \"" << *it << "\"";
94 return false;
95 }
96
97 TrafficCounter::Source source = TrafficCounter::UNKNOWN;
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 }
106 }
107 return true;
108}
109
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900110} // namespace
111
Jie Jianged0b1cc2020-07-10 15:55:33 +0900112Counter::Counter(uint64_t rx_bytes,
113 uint64_t rx_packets,
114 uint64_t tx_bytes,
115 uint64_t tx_packets)
116 : rx_bytes(rx_bytes),
117 rx_packets(rx_packets),
118 tx_bytes(tx_bytes),
119 tx_packets(tx_packets) {}
120
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900121CountersService::CountersService(ShillClient* shill_client,
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900122 Datapath* datapath,
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900123 MinijailedProcessRunner* runner)
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900124 : shill_client_(shill_client), datapath_(datapath), runner_(runner) {
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900125 // Triggers the callback manually to make sure no device is missed.
126 OnDeviceChanged(shill_client_->get_devices(), {});
127 shill_client_->RegisterDevicesChangedHandler(base::BindRepeating(
128 &CountersService::OnDeviceChanged, weak_factory_.GetWeakPtr()));
129}
130
Jie Jianged0b1cc2020-07-10 15:55:33 +0900131std::map<SourceDevice, Counter> CountersService::GetCounters(
132 const std::set<std::string>& devices) {
133 std::map<SourceDevice, Counter> counters;
134
135 // Handles counters for IPv4 and IPv6 separately and returns failure if either
136 // of the procession fails, since counters for only IPv4 or IPv6 are biased.
137 std::string iptables_result;
138 int ret = runner_->iptables(kMangleTable, {"-L", "-x", "-v", "-w"},
139 true /*log_failures*/, &iptables_result);
140 if (ret != 0 || iptables_result.empty()) {
141 LOG(ERROR) << "Failed to query IPv4 counters";
142 return {};
143 }
144 if (!ParseOutput(iptables_result, devices, &counters)) {
145 LOG(ERROR) << "Failed to parse IPv4 counters";
146 return {};
147 }
148
149 std::string ip6tables_result;
150 ret = runner_->ip6tables(kMangleTable, {"-L", "-x", "-v", "-w"},
151 true /*log_failures*/, &ip6tables_result);
152 if (ret != 0 || ip6tables_result.empty()) {
153 LOG(ERROR) << "Failed to query IPv6 counters";
154 return {};
155 }
156 if (!ParseOutput(ip6tables_result, devices, &counters)) {
157 LOG(ERROR) << "Failed to parse IPv6 counters";
158 return {};
159 }
160
161 return counters;
162}
163
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900164void CountersService::OnDeviceChanged(const std::set<std::string>& added,
Jie Jiangcf749152020-07-09 22:20:58 +0900165 const std::set<std::string>& removed) {
166 for (const auto& ifname : added)
167 SetupChainsAndRules(ifname);
168}
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900169
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900170bool CountersService::MakeAccountingChain(const std::string& chain_name) {
171 return datapath_->ModifyChain(IpFamily::Dual, kMangleTable, "-N", chain_name,
172 false /*log_failures*/);
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900173}
174
175void CountersService::IptablesNewRule(std::vector<std::string> params) {
176 DCHECK_GT(params.size(), 0);
177 const std::string action = params[0];
178 DCHECK(action == "-I" || action == "-A");
179 params.emplace_back("-w");
180
181 params[0] = "-C";
182 if (runner_->iptables(kMangleTable, params, false /*log_failures*/) != 0) {
183 params[0] = action;
184 runner_->iptables(kMangleTable, params);
185 }
186
187 params[0] = "-C";
188 if (runner_->ip6tables(kMangleTable, params, false /*log_failures*/) != 0) {
189 params[0] = action;
190 runner_->ip6tables(kMangleTable, params);
191 }
192}
193
Jie Jiangcf749152020-07-09 22:20:58 +0900194void CountersService::SetupChainsAndRules(const std::string& ifname) {
195 // For each group, we need to create 1) an accounting chain, 2) a jumping rule
196 // matching |ifname|, and 3) accounting rule(s) in the chain.
197 // Note that the length of a chain name must less than 29 chars and IFNAMSIZ
198 // is 16 so we can only use at most 12 chars for the prefix.
199
200 // Egress traffic in FORWARD chain. Only traffic for interface-type sources
201 // will be counted by these rules.
202 const std::string egress_forward_chain = "tx_fwd_" + ifname;
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900203 MakeAccountingChain(egress_forward_chain);
Jie Jiangcf749152020-07-09 22:20:58 +0900204 IptablesNewRule({"-A", "FORWARD", "-o", ifname, "-j", egress_forward_chain});
205 SetupAccountingRules(egress_forward_chain);
206
207 // Egress traffic in POSTROUTING chain. Only traffic for host-type sources
208 // will be counted by these rules, by having a "-m owner --socket-exists" in
209 // the jumping rule. Traffic via "FORWARD -> POSTROUTING" does not have a
210 // socket so will only be counted in FORWARD, while traffic from OUTPUT will
211 // always have an associated socket.
212 const std::string egress_postrouting_chain = "tx_postrt_" + ifname;
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900213 MakeAccountingChain(egress_postrouting_chain);
Jie Jiangcf749152020-07-09 22:20:58 +0900214 IptablesNewRule({"-A", "POSTROUTING", "-o", ifname, "-m", "owner",
215 "--socket-exists", "-j", egress_postrouting_chain});
216 SetupAccountingRules(egress_postrouting_chain);
217
218 // Ingress traffic in FORWARD chain. Only traffic for interface-type sources
219 // will be counted by these rules.
220 const std::string ingress_forward_chain = "rx_fwd_" + ifname;
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900221 MakeAccountingChain(ingress_forward_chain);
Jie Jiangcf749152020-07-09 22:20:58 +0900222 IptablesNewRule({"-A", "FORWARD", "-i", ifname, "-j", ingress_forward_chain});
223 SetupAccountingRules(ingress_forward_chain);
224
225 // Ingress traffic in INPUT chain. Only traffic for host-type sources will be
226 // counted by these rules.
227 const std::string ingress_input_chain = "rx_input_" + ifname;
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900228 MakeAccountingChain(ingress_input_chain);
Jie Jiangcf749152020-07-09 22:20:58 +0900229 IptablesNewRule({"-A", "INPUT", "-i", ifname, "-j", ingress_input_chain});
230 SetupAccountingRules(ingress_input_chain);
231}
232
233void CountersService::SetupAccountingRules(const std::string& chain_name) {
234 // TODO(jiejiang): This function will be extended to matching on fwmark for
235 // different sources.
236 IptablesNewRule({"-A", chain_name});
237}
238
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900239} // namespace patchpanel