patchpanel: mark connections with fwmark routing tags

This patch enables per-network tracking of connections using
CONNMARK conntrack for storing the fwmark routing tag associated with
the output interface selected by routing. This is achieved with a mangle
POSTROUTING rule using the CONNMARK target.

A follow-up patch in shill will add the final piece and add ip rules
using these fwmark routing tags to complete the per-network connection
routing and fwmark routing system.

BUG=b:161507671
BUG=b:161508179
TEST=Unit tests. Manual tests: verified with /proc/net/ip_conntrack that
conntrack entries get marked with the fwmark routing tag of the network
through which they are routed.

Change-Id: I9f147179283246c1aafda87ad716e209ce387736
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2377337
Tested-by: Hugo Benichi <hugobenichi@google.com>
Tested-by: Mitu Aktar <mitu64649@gmail.com>
Commit-Queue: Hugo Benichi <hugobenichi@google.com>
Reviewed-by: Taoyu Li <taoyl@chromium.org>
diff --git a/patchpanel/datapath.cc b/patchpanel/datapath.cc
index 73075bd..6155749 100644
--- a/patchpanel/datapath.cc
+++ b/patchpanel/datapath.cc
@@ -571,6 +571,56 @@
   process_runner_->ip6("addr", "del", {ipv6_addr, "dev", ifname});
 }
 
+void Datapath::StartConnectionPinning(const std::string& ext_ifname) {
+  if (!ModifyConnmarkSetPostrouting(IpFamily::Dual, "-A", ext_ifname))
+    LOG(ERROR) << "Could not start connection pinning on " << ext_ifname;
+}
+
+void Datapath::StopConnectionPinning(const std::string& ext_ifname) {
+  if (!ModifyConnmarkSetPostrouting(IpFamily::Dual, "-D", ext_ifname))
+    LOG(ERROR) << "Could not stop connection pinning on " << ext_ifname;
+}
+
+bool Datapath::ModifyConnmarkSetPostrouting(IpFamily family,
+                                            const std::string& op,
+                                            const std::string& oif) {
+  if (oif.empty()) {
+    LOG(ERROR) << "Cannot change POSTROUTING CONNMARK set-mark with no "
+                  "interface specified";
+    return false;
+  }
+
+  if (!IsValidIpFamily(family)) {
+    LOG(ERROR) << "Cannot change POSTROUTING CONNMARK set-mark for " << oif
+               << ": incorrect IP family " << family;
+    return false;
+  }
+
+  int ifindex = FindIfIndex(oif);
+  if (ifindex == 0) {
+    PLOG(ERROR) << "if_nametoindex(" << oif << ") failed";
+    return false;
+  }
+
+  std::vector<std::string> args = {op,
+                                   "POSTROUTING",
+                                   "-o",
+                                   oif,
+                                   "-j",
+                                   "CONNMARK",
+                                   "--set-mark",
+                                   Fwmark::FromIfIndex(ifindex).ToString() +
+                                       "/" + kFwmarkRoutingMask.ToString(),
+                                   "-w"};
+
+  bool success = true;
+  if (family & IPv4)
+    success &= process_runner_->iptables("mangle", args) == 0;
+  if (family & IPv6)
+    success &= process_runner_->ip6tables("mangle", args) == 0;
+  return false;
+}
+
 bool Datapath::ModifyConnmarkRestore(IpFamily family,
                                      const std::string& chain,
                                      const std::string& op,