1+ //go:build linux && !android
2+
13package netstate
24
35import (
@@ -7,74 +9,69 @@ import (
79 "syscall"
810)
911
10- // Manager is the single entry point to this package: it owns the socket
11- // marker (always-on, started once per process) and the runtime OS state of
12- // VPN gateway mode — the client routes and the server NAT. The struct is the
13- // same on every platform; the platform-specific behaviour lives in the marker
14- // and in the setup/teardown functions the methods call. Consumers declare
15- // their own narrow interfaces over the methods they use (see awl.NetManager,
16- // service.NetManager, service.SocketMarker).
12+ // awlMark is the numeric value shared by the SO_MARK fwmark applied to marked
13+ // sockets and the policy-routing table ID holding their exemption routes.
14+ // 0x61776C = "awl" in ASCII (lowercase). The two live in different kernel
15+ // namespaces and don't collide; using one value makes awl-owned state
16+ // trivially greppable in `ip rule` / `ip route show table`.
17+ const awlMark = 0x61776C
18+
19+ // Manager owns the Linux OS network state behind AWL's VPN gateway feature:
20+ // the always-on socket marking (SO_MARK, applied via ControlFunc) and the
21+ // runtime state of gateway mode — the client routes and the server NAT.
1722//
1823// Enable/Disable methods are idempotent and safe for concurrent use. The
1924// internal mutex only guards the Manager's own state; the orchestration
2025// above (service.VPNGateway) still serialises whole enable/disable
2126// transactions — config, tunnel binding, DNS and the calls here — under its
2227// own lock.
2328type Manager struct {
24- marker marker
25-
2629 mu sync.Mutex
2730 routeState * routeState
2831 natState * natState
2932}
3033
31- // NewManager returns the Manager for the current platform. On Android it
32- // carries a no-op socket protector — use NewAndroidManager to wire
33- // VpnService.protect .
34+ // NewManager returns the Manager for Linux. Setting SO_MARK requires
35+ // CAP_NET_ADMIN, which AWL already needs for TUN setup, so no extra
36+ // capability is required .
3437func NewManager () * Manager {
35- return & Manager {marker : newMarker () }
38+ return & Manager {}
3639}
3740
38- // Start performs the marker's initial setup and launches any background
39- // machinery it needs, living until ctx is cancelled. On Windows this is the
40- // uplink detection + network-change watcher; elsewhere it is a no-op. Must be
41- // called before the first libp2p socket is created (see marker.Start for the
42- // offline-start semantics).
43- func (m * Manager ) Start (ctx context.Context ) error {
44- return m .marker .Start (ctx )
45- }
41+ // Start is a no-op on Linux: SO_MARK is interpreted by the kernel per packet,
42+ // there is no per-socket state to keep in sync with the network.
43+ func (m * Manager ) Start (_ context.Context ) error { return nil }
4644
4745// ControlFunc returns a function compatible with net.Dialer.Control and the
48- // QUIC ListenUDP override, marking each new socket to bypass the VPN tunnel.
49- // It returns nil when the platform is not configured (e.g. Android before the
50- // host app supplies a protector).
46+ // QUIC ListenUDP override, marking each new socket with SO_MARK so its
47+ // traffic bypasses the VPN tunnel via the fwmark ip rule.
5148func (m * Manager ) ControlFunc () func (network , address string , c syscall.RawConn ) error {
52- return m .marker .ControlFunc ()
49+ return func (_ , _ string , c syscall.RawConn ) error {
50+ var sockErr error
51+ err := c .Control (func (fd uintptr ) {
52+ sockErr = syscall .SetsockoptInt (int (fd ), syscall .SOL_SOCKET , syscall .SO_MARK , awlMark )
53+ })
54+ if err != nil {
55+ return fmt .Errorf ("sockmark control: %w" , err )
56+ }
57+ if sockErr != nil {
58+ return fmt .Errorf ("sockmark SO_MARK: %w" , sockErr )
59+ }
60+ return nil
61+ }
5362}
5463
5564// EnableClientRoutes installs the gateway client routes on the TUN (the
5665// default-route capture plus the IPv6 fail-closed fence). Idempotent: a
57- // second call while routes are installed is a no-op. On Windows it refuses
58- // while no IPv4 uplink is known (marking could not exempt libp2p traffic —
59- // routing loop); the condition is self-healing, so that is "try again once
60- // online", not a permanent failure.
66+ // second call while routes are installed is a no-op.
6167func (m * Manager ) EnableClientRoutes (tunIfName string ) error {
6268 m .mu .Lock ()
6369 defer m .mu .Unlock ()
6470
6571 if m .routeState != nil {
6672 return nil
6773 }
68- // Markers that can be temporarily unable to guarantee loop-free marking
69- // (Windows: no uplink detected right now) expose Ready. Other platforms
70- // don't implement the interface and skip the check.
71- if readier , ok := m .marker .(interface { Ready () error }); ok {
72- if err := readier .Ready (); err != nil {
73- return fmt .Errorf ("cannot enable VPN gateway: %w" , err )
74- }
75- }
76-
77- state , err := setupGatewayRoutes (tunIfName , m .marker .FWMark ())
74+ state , err := m .setupGatewayRoutes (tunIfName )
7875 if err != nil {
7976 return fmt .Errorf ("setup gateway routes: %w" , err )
8077 }
@@ -95,7 +92,7 @@ func (m *Manager) DisableClientRoutes() error {
9592 }
9693 state := m .routeState
9794 m .routeState = nil
98- return teardownGatewayRoutes (state )
95+ return m . teardownGatewayRoutes (state )
9996}
10097
10198// ClientRoutesActive reports whether gateway client routes are currently
@@ -107,17 +104,16 @@ func (m *Manager) ClientRoutesActive() bool {
107104}
108105
109106// EnableServerNAT configures the exit-node data path for the awl subnet
110- // (Linux: ip_forward + iptables chain + MASQUERADE; Windows: WFP filter +
111- // per-interface forwarding + WinNAT). Idempotent: a second call while NAT is
112- // configured is a no-op.
107+ // (ip_forward + iptables chain + MASQUERADE). Idempotent: a second call while
108+ // NAT is configured is a no-op.
113109func (m * Manager ) EnableServerNAT (awlSubnet , tunIfName string ) error {
114110 m .mu .Lock ()
115111 defer m .mu .Unlock ()
116112
117113 if m .natState != nil {
118114 return nil
119115 }
120- state , err := setupNAT (awlSubnet , tunIfName )
116+ state , err := m . setupNAT (awlSubnet , tunIfName )
121117 if err != nil {
122118 return fmt .Errorf ("setup NAT: %w" , err )
123119 }
@@ -137,7 +133,7 @@ func (m *Manager) DisableServerNAT() error {
137133 }
138134 state := m .natState
139135 m .natState = nil
140- return teardownNAT (state )
136+ return m . teardownNAT (state )
141137}
142138
143139// ServerNATActive reports whether the exit-node NAT is currently configured.
0 commit comments