patchpanel: Implement accounting rules in CountersService

This patch adds the logic for setting up accounting chains and rules
when an interface comes up. Traffics from (to) different sources to
(from) the same devices are not distinguished now and contributes to the
same counter.

BUG=b:160112868
TEST=P2_TEST_FILTER="CountersServiceTest.*" cros_workon_make
  --board=$BOARD --test patchpanel
TEST=Manually tested by running speed test in Chrome and ARC, the diff
  of byte counters is roughly the same as the diff in ifconfig.

Change-Id: I5cacb6290b5eff4f6c4578d89205865fe9b012cf
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2291414
Tested-by: Jie Jiang <jiejiang@chromium.org>
Reviewed-by: Hugo Benichi <hugobenichi@google.com>
Reviewed-by: Garrick Evans <garrick@chromium.org>
Commit-Queue: Jie Jiang <jiejiang@chromium.org>
diff --git a/patchpanel/counters_service.cc b/patchpanel/counters_service.cc
index aeb1ff4..091b3bd 100644
--- a/patchpanel/counters_service.cc
+++ b/patchpanel/counters_service.cc
@@ -26,7 +26,10 @@
 }
 
 void CountersService::OnDeviceChanged(const std::set<std::string>& added,
-                                      const std::set<std::string>& removed) {}
+                                      const std::set<std::string>& removed) {
+  for (const auto& ifname : added)
+    SetupChainsAndRules(ifname);
+}
 
 void CountersService::IptablesNewChain(const std::string& chain_name) {
   // There is no straightforward way to check if a chain exists or not.
@@ -55,4 +58,49 @@
   }
 }
 
+void CountersService::SetupChainsAndRules(const std::string& ifname) {
+  // For each group, we need to create 1) an accounting chain, 2) a jumping rule
+  // matching |ifname|, and 3) accounting rule(s) in the chain.
+  // Note that the length of a chain name must less than 29 chars and IFNAMSIZ
+  // is 16 so we can only use at most 12 chars for the prefix.
+
+  // Egress traffic in FORWARD chain. Only traffic for interface-type sources
+  // will be counted by these rules.
+  const std::string egress_forward_chain = "tx_fwd_" + ifname;
+  IptablesNewChain(egress_forward_chain);
+  IptablesNewRule({"-A", "FORWARD", "-o", ifname, "-j", egress_forward_chain});
+  SetupAccountingRules(egress_forward_chain);
+
+  // Egress traffic in POSTROUTING chain. Only traffic for host-type sources
+  // will be counted by these rules, by having a "-m owner --socket-exists" in
+  // the jumping rule. Traffic via "FORWARD -> POSTROUTING" does not have a
+  // socket so will only be counted in FORWARD, while traffic from OUTPUT will
+  // always have an associated socket.
+  const std::string egress_postrouting_chain = "tx_postrt_" + ifname;
+  IptablesNewChain(egress_postrouting_chain);
+  IptablesNewRule({"-A", "POSTROUTING", "-o", ifname, "-m", "owner",
+                   "--socket-exists", "-j", egress_postrouting_chain});
+  SetupAccountingRules(egress_postrouting_chain);
+
+  // Ingress traffic in FORWARD chain. Only traffic for interface-type sources
+  // will be counted by these rules.
+  const std::string ingress_forward_chain = "rx_fwd_" + ifname;
+  IptablesNewChain(ingress_forward_chain);
+  IptablesNewRule({"-A", "FORWARD", "-i", ifname, "-j", ingress_forward_chain});
+  SetupAccountingRules(ingress_forward_chain);
+
+  // Ingress traffic in INPUT chain. Only traffic for host-type sources will be
+  // counted by these rules.
+  const std::string ingress_input_chain = "rx_input_" + ifname;
+  IptablesNewChain(ingress_input_chain);
+  IptablesNewRule({"-A", "INPUT", "-i", ifname, "-j", ingress_input_chain});
+  SetupAccountingRules(ingress_input_chain);
+}
+
+void CountersService::SetupAccountingRules(const std::string& chain_name) {
+  // TODO(jiejiang): This function will be extended to matching on fwmark for
+  // different sources.
+  IptablesNewRule({"-A", chain_name});
+}
+
 }  // namespace patchpanel