patchpanel: Introduce CountersService for traffic accounting
This patch adds the framework code for CountersService and two helper
functions in it. The actual implementation will comes with the following
patches.
BUG=b:160112868
TEST=build
Change-Id: I9a6f67cc5759969da3685541e0313dffd982769d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2289712
Reviewed-by: Garrick Evans <garrick@chromium.org>
Reviewed-by: Hugo Benichi <hugobenichi@google.com>
Tested-by: Jie Jiang <jiejiang@chromium.org>
Commit-Queue: Jie Jiang <jiejiang@chromium.org>
diff --git a/patchpanel/BUILD.gn b/patchpanel/BUILD.gn
index 7df328e..7b71e8a 100644
--- a/patchpanel/BUILD.gn
+++ b/patchpanel/BUILD.gn
@@ -64,6 +64,7 @@
"adb_proxy.cc",
"arc_service.cc",
"broadcast_forwarder.cc",
+ "counters_service.cc",
"crostini_service.cc",
"datapath.cc",
"device.cc",
diff --git a/patchpanel/counters_service.cc b/patchpanel/counters_service.cc
new file mode 100644
index 0000000..aeb1ff4
--- /dev/null
+++ b/patchpanel/counters_service.cc
@@ -0,0 +1,58 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "patchpanel/counters_service.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+namespace patchpanel {
+
+namespace {
+
+constexpr char kMangleTable[] = "mangle";
+
+} // namespace
+
+CountersService::CountersService(ShillClient* shill_client,
+ MinijailedProcessRunner* runner)
+ : shill_client_(shill_client), runner_(runner) {
+ // Triggers the callback manually to make sure no device is missed.
+ OnDeviceChanged(shill_client_->get_devices(), {});
+ shill_client_->RegisterDevicesChangedHandler(base::BindRepeating(
+ &CountersService::OnDeviceChanged, weak_factory_.GetWeakPtr()));
+}
+
+void CountersService::OnDeviceChanged(const std::set<std::string>& added,
+ const std::set<std::string>& removed) {}
+
+void CountersService::IptablesNewChain(const std::string& chain_name) {
+ // There is no straightforward way to check if a chain exists or not.
+ runner_->iptables(kMangleTable, {"-N", chain_name, "-w"},
+ false /*log_failures*/);
+ runner_->ip6tables(kMangleTable, {"-N", chain_name, "-w"},
+ false /*log_failures*/);
+}
+
+void CountersService::IptablesNewRule(std::vector<std::string> params) {
+ DCHECK_GT(params.size(), 0);
+ const std::string action = params[0];
+ DCHECK(action == "-I" || action == "-A");
+ params.emplace_back("-w");
+
+ params[0] = "-C";
+ if (runner_->iptables(kMangleTable, params, false /*log_failures*/) != 0) {
+ params[0] = action;
+ runner_->iptables(kMangleTable, params);
+ }
+
+ params[0] = "-C";
+ if (runner_->ip6tables(kMangleTable, params, false /*log_failures*/) != 0) {
+ params[0] = action;
+ runner_->ip6tables(kMangleTable, params);
+ }
+}
+
+} // namespace patchpanel
diff --git a/patchpanel/counters_service.h b/patchpanel/counters_service.h
new file mode 100644
index 0000000..d1ffdcd
--- /dev/null
+++ b/patchpanel/counters_service.h
@@ -0,0 +1,83 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PATCHPANEL_COUNTERS_SERVICE_H_
+#define PATCHPANEL_COUNTERS_SERVICE_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "patchpanel/minijailed_process_runner.h"
+#include "patchpanel/shill_client.h"
+
+namespace patchpanel {
+
+// This class manages the iptables rules for traffic counters, and queries
+// iptables to get the counters when a request comes. This class will set up
+// several iptable rules to track the counters for each possible combination of
+// {bytes, packets} x (Traffic source) x (shill device) x {rx, tx} x {IPv4,
+// IPv6}. These counters will never be removed after they are set up, and thus
+// they represent the traffic usage from boot time.
+//
+// TODO(jiejiang): The following will be implemented in the following patches.
+//
+// Implementation details
+//
+// Rules: All the rules/chains for accounting are in (INPUT, FORWARD or
+// POSTROUTING) chain in the mangle table. These rules take effect after routing
+// and will not change the fate of a packet. When a new interface comes up, we
+// will create the following new rules/chains (using both iptables and
+// ip6tables):
+// - Four accounting chains:
+// - For rx packets, `ingress_input_{ifname}` and `ingress_forward_{ifname}`
+// for INPUT and FORWARD chain, respectively;
+// - For tx packets, `egress_postrouting_{ifname}` and
+// `egress_forward_{ifname}` for POSTROUTING and FORWARD chain,
+// respectively. Note that we use `--socket-exists` in POSTROUTING chain to
+// avoid packets from FORWARD being matched again here.
+// - One accounting rule in each accounting chain, which provides the actual
+// counter for accounting. We will extend this to several rules when source
+// marking is ready.
+// - One jumping rule for each accounting chain in the corresponding prebuilt
+// chain, which matches packets with this new interface.
+// The above rules and chains will never be removed once created, so we will
+// check if one rule exists before creating it.
+//
+// Query: Two commands (iptables and ip6tables) will be executed in the mangle
+// table to get all the chains and rules. And then we perform a text parsing on
+// the output to get the counters. Counters for the same entry will be merged
+// before return.
+class CountersService {
+ public:
+ CountersService(ShillClient* shill_client, MinijailedProcessRunner* runner);
+ ~CountersService() = default;
+
+ private:
+ // TODO(b/161060333): Move the following two functions elsewhere.
+ // Creates a new chain using both iptables and ip6tables in the mangle table.
+ void IptablesNewChain(const std::string& chain_name);
+
+ // Creates a new rule using both iptables and ip6tables in the mangle table.
+ // The first element in |params| should be "-I" (insert) or "-A" (append), and
+ // this function will replace it with "-C" to do the check before executing
+ // the actual insert or append command. This function will also append "-w" to
+ // |params|. Note that |params| is passed by value because it will be modified
+ // inside the function, and the normal pattern to use this function is passing
+ // an rvalue (e.g., `IptablesNewRule({"-I", "INPUT", ...})`), so no extra copy
+ // should happen in such cases.
+ void IptablesNewRule(std::vector<std::string> params);
+
+ void OnDeviceChanged(const std::set<std::string>& added,
+ const std::set<std::string>& removed);
+
+ ShillClient* shill_client_;
+ MinijailedProcessRunner* runner_;
+
+ base::WeakPtrFactory<CountersService> weak_factory_{this};
+};
+
+} // namespace patchpanel
+
+#endif // PATCHPANEL_COUNTERS_SERVICE_H_