patchpanel: directly write to /proc/sys/net/

This patch changes patchpanel to not interact with /proc/sys/net runtime
configuration through /usr/sbin/sysctl but instead directly writing to a
fixed set of files in /proc/sys/net.

After the device has booted, reading the following /proc/sys path
returns the expected content:

localhost ~ # cat /proc/sys/net/ipv4/ip_forward
1
localhost ~ # cat /proc/sys/net/ipv4/ip_local_port_range
32768   47103
localhost ~ # cat /proc/sys/net/ipv6/conf/all/forwarding
1

BUG=b:178980566
TEST=Unit tests. Flashed rammus, checked that /proc/sys/net
configuration on the host happens as expected, checked that ndproxyd
starts correctly.

Change-Id: I1cfa71c3ccd88bdfd89c81b5a75b0c0912b39c72
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2738499
Tested-by: Hugo Benichi <hugobenichi@google.com>
Commit-Queue: Hugo Benichi <hugobenichi@google.com>
Reviewed-by: Garrick Evans <garrick@chromium.org>
diff --git a/patchpanel/system.cc b/patchpanel/system.cc
index f802b82..b971206 100644
--- a/patchpanel/system.cc
+++ b/patchpanel/system.cc
@@ -4,8 +4,43 @@
 
 #include "patchpanel/system.h"
 
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/files/scoped_file.h>
+
 namespace patchpanel {
 
+namespace {
+
+// /proc/sys/ paths and fragments used for System::SysNetSet
+// Defines the local port range that is used by TCP and UDP traffic to choose
+// the local port (IPv4 and IPv6).
+constexpr const char kSysNetIPLocalPortRangePath[] =
+    "/proc/sys/net/ipv4/ip_local_port_range";
+// Enables/Disables IPv4 forwarding between interfaces.
+constexpr const char kSysNetIPv4ForwardingPath[] =
+    "/proc/sys/net/ipv4/ip_forward";
+// /proc/sys path for controlling connection tracking helper modules
+constexpr const char kSysNetConntrackHelperPath[] =
+    "/proc/sys/net/netfilter/nf_conntrack_helper";
+// Prefix for IPv4 interface configuration.
+constexpr const char kSysNetIPv4ConfPrefix[] = "/proc/sys/net/ipv4/conf/";
+// Suffix for allowing localhost as a source or destination when routing IPv4.
+constexpr const char kSysNetIPv4RouteLocalnetSuffix[] = "/route_localnet";
+// Enables/Disables IPv6 forwarding between interfaces.
+constexpr const char kSysNetIPv6ForwardingPath[] =
+    "/proc/sys/net/ipv6/conf/all/forwarding";
+// Prefix for IPv6 interface configuration.
+constexpr const char kSysNetIPv6ConfPrefix[] = "/proc/sys/net/ipv6/conf/";
+// Suffix for accepting Router Advertisements on an interface and
+// autoconfiguring it with IPv6 parameters.
+constexpr const char kSysNetIPv6AcceptRaSuffix[] = "/accept_ra";
+
+}  // namespace
+
 int System::Ioctl(int fd, ioctl_req_t request, const char* argp) {
   return ioctl(fd, request, argp);
 }
@@ -22,4 +57,53 @@
   return Ioctl(fd, request, reinterpret_cast<const char*>(route));
 }
 
+bool System::SysNetSet(SysNet target,
+                       const std::string& content,
+                       const std::string& iface) {
+  std::string path;
+  switch (target) {
+    case SysNet::IPv4Forward:
+      return Write(kSysNetIPv4ForwardingPath, content);
+    case SysNet::IPLocalPortRange:
+      return Write(kSysNetIPLocalPortRangePath, content);
+    case SysNet::IPv4RouteLocalnet:
+      if (iface.empty()) {
+        LOG(ERROR) << "IPv4LocalPortRange requires a valid interface";
+        return false;
+      }
+      return Write(
+          kSysNetIPv4ConfPrefix + iface + kSysNetIPv4RouteLocalnetSuffix,
+          content);
+    case SysNet::IPv6Forward:
+      return Write(kSysNetIPv6ForwardingPath, content);
+    case SysNet::IPv6AcceptRA:
+      if (iface.empty()) {
+        LOG(ERROR) << "IPv6AcceptRA requires a valid interface";
+        return false;
+      }
+      return Write(kSysNetIPv6ConfPrefix + iface + kSysNetIPv6AcceptRaSuffix,
+                   content);
+    case ConntrackHelper:
+      return Write(kSysNetConntrackHelperPath, content);
+    default:
+      LOG(ERROR) << "Unknown SysNet value " << target;
+      return false;
+  }
+}
+
+bool System::Write(const std::string& path, const std::string& content) {
+  base::ScopedFD fd(open(path.c_str(), O_WRONLY | O_TRUNC | O_CLOEXEC));
+  if (!fd.is_valid()) {
+    PLOG(ERROR) << "Failed to open " << path;
+    return false;
+  }
+
+  if (write(fd.get(), content.c_str(), content.size()) != content.size()) {
+    PLOG(ERROR) << "Failed to write \"" << content << "\" to " << path;
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace patchpanel