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.