blob: 8e30f868029cdb07fc0753a6014f015f7421c71c [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:
Hugo Benichi33af8f72020-11-20 10:08:47 +090032// "Chain tx_eth0 (2 references)".
33// This regex extracts "tx" (direction), "eth0" (ifname) from this example.
34constexpr LazyRE2 kChainLine = {R"(Chain (rx|tx)_(\w+).*)"};
Jie Jianged0b1cc2020-07-10 15:55:33 +090035
36// The counter line looks like (some spaces are deleted to make it fit in one
37// line):
38// " 6511 68041668 all -- any any anywhere anywhere"
39// The first two counters are captured for pkts and bytes.
40constexpr LazyRE2 kCounterLine = {R"( *(\d+) +(\d+).*)"};
41
42// Parses the output of `iptables -L -x -v` (or `ip6tables`) and adds the parsed
43// values into the corresponding counters in |counters|. An example of |output|
44// can be found in the test file. This function will try to find the pattern of:
45// <one chain line for an accounting chain>
46// <one header line>
47// <one counter line for an accounting rule>
48// The interface name and direction (rx or tx) will be extracted from the chain
49// line, and then the values extracted from the counter line will be added into
50// the counter for that interface. Note that this function will not fully
51// validate if |output| is an output from iptables.
52bool ParseOutput(const std::string& output,
53 const std::set<std::string>& devices,
54 std::map<SourceDevice, Counter>* counters) {
55 DCHECK(counters);
56 const std::vector<std::string> lines = base::SplitString(
57 output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
58
59 // Finds the chain line for an accounting chain first, and then parse the
60 // following line(s) to get the counters for this chain. Repeats this process
61 // until we reach the end of |output|.
62 for (auto it = lines.cbegin(); it != lines.cend(); it++) {
63 // Finds the chain name line.
64 std::string direction, ifname;
65 while (it != lines.cend() &&
66 !RE2::FullMatch(*it, *kChainLine, &direction, &ifname))
67 it++;
68
69 if (it == lines.cend())
70 break;
71
72 // Skips this group if this ifname is not requested.
73 if (!devices.empty() && devices.find(ifname) == devices.end())
74 continue;
75
76 // Skips the chain name line and the header line.
77 if (lines.cend() - it <= 2) {
78 LOG(ERROR) << "Invalid iptables output";
79 return false;
80 }
81 it += 2;
82
83 // The current line should be the accounting rule containing the counters.
84 // Currently we only have one accounting rule (UNKNOWN source) for each
85 // chain.
86 // TODO(jiejiang): The following part will be extended to a loop when we
87 // have more accounting rules.
88 uint64_t pkts, bytes;
89 if (!RE2::FullMatch(*it, *kCounterLine, &pkts, &bytes)) {
90 LOG(ERROR) << "Cannot parse \"" << *it << "\"";
91 return false;
92 }
93
94 TrafficCounter::Source source = TrafficCounter::UNKNOWN;
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 }
103 }
104 return true;
105}
106
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900107} // namespace
108
Jie Jianged0b1cc2020-07-10 15:55:33 +0900109Counter::Counter(uint64_t rx_bytes,
110 uint64_t rx_packets,
111 uint64_t tx_bytes,
112 uint64_t tx_packets)
113 : rx_bytes(rx_bytes),
114 rx_packets(rx_packets),
115 tx_bytes(tx_bytes),
116 tx_packets(tx_packets) {}
117
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900118CountersService::CountersService(ShillClient* shill_client,
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900119 Datapath* datapath,
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900120 MinijailedProcessRunner* runner)
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900121 : shill_client_(shill_client), datapath_(datapath), runner_(runner) {
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900122 // Triggers the callback manually to make sure no device is missed.
123 OnDeviceChanged(shill_client_->get_devices(), {});
124 shill_client_->RegisterDevicesChangedHandler(base::BindRepeating(
125 &CountersService::OnDeviceChanged, weak_factory_.GetWeakPtr()));
126}
127
Jie Jianged0b1cc2020-07-10 15:55:33 +0900128std::map<SourceDevice, Counter> CountersService::GetCounters(
129 const std::set<std::string>& devices) {
130 std::map<SourceDevice, Counter> counters;
131
132 // Handles counters for IPv4 and IPv6 separately and returns failure if either
133 // of the procession fails, since counters for only IPv4 or IPv6 are biased.
134 std::string iptables_result;
135 int ret = runner_->iptables(kMangleTable, {"-L", "-x", "-v", "-w"},
136 true /*log_failures*/, &iptables_result);
137 if (ret != 0 || iptables_result.empty()) {
138 LOG(ERROR) << "Failed to query IPv4 counters";
139 return {};
140 }
141 if (!ParseOutput(iptables_result, devices, &counters)) {
142 LOG(ERROR) << "Failed to parse IPv4 counters";
143 return {};
144 }
145
146 std::string ip6tables_result;
147 ret = runner_->ip6tables(kMangleTable, {"-L", "-x", "-v", "-w"},
148 true /*log_failures*/, &ip6tables_result);
149 if (ret != 0 || ip6tables_result.empty()) {
150 LOG(ERROR) << "Failed to query IPv6 counters";
151 return {};
152 }
153 if (!ParseOutput(ip6tables_result, devices, &counters)) {
154 LOG(ERROR) << "Failed to parse IPv6 counters";
155 return {};
156 }
157
158 return counters;
159}
160
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900161void CountersService::OnDeviceChanged(const std::set<std::string>& added,
Jie Jiangcf749152020-07-09 22:20:58 +0900162 const std::set<std::string>& removed) {
163 for (const auto& ifname : added)
164 SetupChainsAndRules(ifname);
165}
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900166
Hugo Benichicd27f4e2020-11-19 18:32:23 +0900167bool CountersService::MakeAccountingChain(const std::string& chain_name) {
168 return datapath_->ModifyChain(IpFamily::Dual, kMangleTable, "-N", chain_name,
169 false /*log_failures*/);
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900170}
171
172void CountersService::IptablesNewRule(std::vector<std::string> params) {
173 DCHECK_GT(params.size(), 0);
174 const std::string action = params[0];
175 DCHECK(action == "-I" || action == "-A");
176 params.emplace_back("-w");
177
178 params[0] = "-C";
179 if (runner_->iptables(kMangleTable, params, false /*log_failures*/) != 0) {
180 params[0] = action;
181 runner_->iptables(kMangleTable, params);
182 }
183
184 params[0] = "-C";
185 if (runner_->ip6tables(kMangleTable, params, false /*log_failures*/) != 0) {
186 params[0] = action;
187 runner_->ip6tables(kMangleTable, params);
188 }
189}
190
Jie Jiangcf749152020-07-09 22:20:58 +0900191void CountersService::SetupChainsAndRules(const std::string& ifname) {
Hugo Benichi33af8f72020-11-20 10:08:47 +0900192 // For each device and traffic direction, we need to create:
193 // 1) an accounting chain to jump to,
194 // 2) jumping rules in mangle POSTROUTING for egress traffic, and in mangle
195 // INPUT and FORWARD for ingress traffic.
196 // 3) source accounting rules in the chain.
Jie Jiangcf749152020-07-09 22:20:58 +0900197 // 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
Hugo Benichi33af8f72020-11-20 10:08:47 +0900200 // Ingress traffic chain.
201 const std::string ingress_chain = "rx_" + ifname;
202 MakeAccountingChain(ingress_chain);
203 IptablesNewRule({"-A", "FORWARD", "-i", ifname, "-j", ingress_chain});
204 IptablesNewRule({"-A", "INPUT", "-i", ifname, "-j", ingress_chain});
205 SetupAccountingRules(ingress_chain);
Jie Jiangcf749152020-07-09 22:20:58 +0900206
Hugo Benichi33af8f72020-11-20 10:08:47 +0900207 // Egress traffic chain.
208 const std::string egress_chain = "tx_" + ifname;
209 MakeAccountingChain(egress_chain);
210 IptablesNewRule({"-A", "POSTROUTING", "-o", ifname, "-j", egress_chain});
211 SetupAccountingRules(egress_chain);
Jie Jiangcf749152020-07-09 22:20:58 +0900212}
213
214void CountersService::SetupAccountingRules(const std::string& chain_name) {
215 // TODO(jiejiang): This function will be extended to matching on fwmark for
216 // different sources.
217 IptablesNewRule({"-A", chain_name});
218}
219
Jie Jiang31a0b4e2020-07-09 15:06:16 +0900220} // namespace patchpanel