patchpanel: define a fwmark representation

This patch defines a Fwmark struct representation for defining how
patchpanel splits and uses the 32 bits of a fwmark for routing policies
and traffic accounting.

BUG=b:161508179
TEST=new unit tests

Change-Id: I78e8f2e9ea947115ee4e9f9932964a7d83b32389
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2303158
Tested-by: Hugo Benichi <hugobenichi@google.com>
Commit-Queue: Hugo Benichi <hugobenichi@google.com>
Reviewed-by: Taoyu Li <taoyl@chromium.org>
diff --git a/patchpanel/routing_service.h b/patchpanel/routing_service.h
index cda7b66..e80fcde 100644
--- a/patchpanel/routing_service.h
+++ b/patchpanel/routing_service.h
@@ -5,13 +5,151 @@
 #ifndef PATCHPANEL_ROUTING_SERVICE_H_
 #define PATCHPANEL_ROUTING_SERVICE_H_
 
+#include <arpa/inet.h>
 #include <stdint.h>
 #include <sys/socket.h>
 
+#include <array>
+#include <string>
+
+#include <base/strings/stringprintf.h>
+
 #include <patchpanel/proto_bindings/patchpanel_service.pb.h>
 
 namespace patchpanel {
 
+// The list of all sources of traffic that need to be distinguished
+// for routing or traffic accounting. Currently 6 bits are used for encoding
+// the TrafficSource enum in a fwmark. The enum is split into two groups:local
+// sources and forwarded sources. The enum values of forwarded sources are
+// offset by 0x20 so that their most significant bit is always set and can be
+// easily matched separately from local sources.
+enum TrafficSource {
+  UNKNOWN = 0,
+
+  // Local sources:
+  // Traffic corresponding to uid "chronos".
+  CHROME = 1,
+  // Other uids classified as "user" for traffic purposes: debugd, cups,
+  // tlsdate, pluginvm, etc.
+  USER = 2,
+  // Traffic from Update engine.
+  UPDATE_ENGINE = 3,
+  // Other system traffic.
+  SYSTEM = 4,
+  // Traffic emitted on an underlying physical network by the built-in OpenVPN
+  // and L2TP clients, or Chrome 3rd party VPN Apps. This traffic constitutes
+  // the VPN tunnel.
+  HOST_VPN = 5,
+
+  // Forwarded sources:
+  // ARC++ and ARCVM.
+  ARC = 0x20,
+  // Crostini VMs and lxc containers.
+  CROSVM = 0x21,
+  // Other plugin VMs.
+  PLUGINVM = 0x22,
+  // A tethered downstream network. Currently reserved for future use.
+  TETHER_DOWNSTREAM = 0x23,
+  // Traffic emitted by Android VPNs for their tunnelled connections.
+  ARC_VPN = 0x24,
+};
+
+// A representation of how fwmark bits are split and used for tagging and
+// routing traffic. The 32 bits of the fwmark are currently organized as such:
+//    0                   1                   2                   3
+//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |        routing table id       |VPN|source enum|   reserved  |*|
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+// routing table id (16bits): the routing table id of a physical device managed
+//                            by shill or of a virtual private network.
+// VPN (2bits): policy bits controlled by host application to force VPN routing
+//              or bypass VPN routing.
+// source enum(6bits): policy bits controlled by patchpanel for grouping
+//                     originated traffic by domain.
+// reserved(7bits): no usage at the moment.
+// legacy SNAT(1bit): legacy bit used for setting up SNAT for ARC, Crostini, and
+//                    PluginVMs with iptables MASQUERADE.
+//
+// Note that bitfields are not a portable way to define a
+// stable Fwmark. Also note that the in-memory representation of values of
+// this union changes depending on endianness, so care must be taken when
+// serializing or deserializing Fwmark values, or when aliasing with raw bytes
+// through pointers. In practice client code should not rely on a specific
+// memory representation and should instead use ToString() and Value().
+union Fwmark {
+  struct {
+    // The LSB is currently only used for applying IPv4 SNAT to egress traffic
+    // from ARC and other VMs; indicated by a value of 1.
+    uint8_t legacy;
+    // The 3rd byte is used to store the intent and policy to be applied to the
+    // traffic. The first 2 bits are used for host processes to select a VPN
+    // routing intent via patchpanel SetVpnIntent API. The next 6 bits of are
+    // used for tagging the traffic with a source.
+    uint8_t policy;
+    // The 2 upper bytes corresponds to the routing table id associated with
+    // a shill device or a VPN.
+    uint16_t rt_table_id;
+  };
+  // The raw memory representation of this fwmark as a uint32_t.
+  uint32_t fwmark;
+
+  // Returns a String representation of this Fwmark. This should
+  std::string ToString() const {
+    return base::StringPrintf("0x%04x%02x%02x", rt_table_id, policy, legacy);
+  }
+
+  // Returns the logical uint32_t value of this Fwmark.
+  uint32_t Value() const { return rt_table_id << 16 | policy << 8 | legacy; }
+
+  constexpr TrafficSource Source() {
+    return static_cast<TrafficSource>(policy & 0x3f);
+  }
+
+  constexpr bool operator==(Fwmark that) const { return fwmark == that.fwmark; }
+
+  constexpr Fwmark operator|(Fwmark that) const {
+    return {.fwmark = fwmark | that.fwmark};
+  }
+
+  constexpr Fwmark operator&(Fwmark that) const {
+    return {.fwmark = fwmark & that.fwmark};
+  }
+
+  constexpr Fwmark operator~() const { return {.fwmark = ~fwmark}; }
+
+  static Fwmark FromSource(TrafficSource source) {
+    return {
+        .policy = static_cast<uint8_t>(source), .legacy = 0, .rt_table_id = 0};
+  }
+};
+
+// All local sources
+constexpr std::array<TrafficSource, 5> kLocalSources{
+    {CHROME, USER, UPDATE_ENGINE, SYSTEM, HOST_VPN}};
+
+// All forwarded sources
+constexpr std::array<TrafficSource, 5> kForwardedSources{
+    {ARC, CROSVM, PLUGINVM, TETHER_DOWNSTREAM, ARC_VPN}};
+
+// Constant fwmark value for tagging traffic with the "route-on-vpn" intent.
+constexpr const Fwmark kFwmarkRouteOnVpn = {.policy = 0x80};
+// Constant fwmark value for tagging traffic with the "bypass-vpn" intent.
+constexpr const Fwmark kFwmarkBypassVpn = {.policy = 0x40};
+// constexpr const Fwmark kFwmarkVpnMask = kFwmarkRouteOnVpn | kFwmarkBypassVpn;
+constexpr const Fwmark kFwmarkVpnMask = {.policy = 0xc0};
+// A mask for matching fwmarks on the routing table id.
+constexpr const Fwmark kFwmarkRoutingMask = {.rt_table_id = 0xffff};
+// A mask for matching fwmarks on the source.
+constexpr const Fwmark kFwmarkAllSourcesMask = {.policy = 0x3f};
+// A mast for matching fwmarks of forwarded sources.
+constexpr const Fwmark kFwmarkForwardedSourcesMask = {.policy = 0x20};
+// Both the mask and fwmark values for legacy SNAT rules used for ARC and other
+// containers.
+constexpr const Fwmark kFwmarkLegacySNAT = {.legacy = 0x1};
+
 // Service implementing routing features of patchpanel.
 // TODO(hugobenichi) Explain how this coordinates with shill's RoutingTable.
 class RoutingService {
@@ -28,7 +166,7 @@
 
   // Sets the fwmark on the given socket with the given mask.
   // Preserves any other bits of the fwmark already set.
-  bool SetFwmark(int sockfd, uint32_t mark, uint32_t mask);
+  bool SetFwmark(int sockfd, Fwmark mark, Fwmark mask);
 
  protected:
   // Can be overridden in tests.