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_