@@ -303,6 +303,67 @@ To test local changes in the library when used as a Conan package dependency, fo
303303 Note:
304304 - If you have already exported the library in this way, the cached version must be purged: `conan remove -c vpn-libs/<commit_hash>`.
305305
306+ ## Integrating Tunnel Activity Tracking
307+
308+ The library tracks whether a VPN TUN interface is active in the process to fail
309+ closed when an outgoing socket cannot be protected from being routed into the
310+ tunnel. Applications that create a TUN interface themselves (as opposed to
311+ using the built-in tunnels from `net/os_tunnel.h`) **must** register it with
312+ this API, otherwise the socket protection handlers have no way to distinguish
313+ "no tunnel" from "tunnel active but unprotected".
314+
315+ ### API overview
316+
317+ - `vpn_network_manager_acquire_tunnel_activity()` — registers an active TUN
318+ interface and returns a non-zero ownership token.
319+ - `vpn_network_manager_release_tunnel_activity(token)` — unregisters a TUN
320+ interface previously registered with `acquire`. Invalid or already released
321+ tokens are ignored, so duplicate releases are safe.
322+ - `vpn_network_manager_get_tunnel_active()` — returns `true` while at least one
323+ TUN interface is registered.
324+ - `vpn_network_manager_set_outbound_interface(idx)` — sets the index of the
325+ physical interface that outgoing sockets should be bound to. See below.
326+
327+ ### Integration checklist
328+
329+ 1. **Acquire when the TUN interface is created.** Call
330+ `vpn_network_manager_acquire_tunnel_activity()` only after the TUN interface
331+ has been successfully established (e.g. after the TUN fd has been opened),
332+ and store the returned token. Do not acquire before the interface exists:
333+ on failure there is nothing to release, and the token must not outlive the interface.
334+
335+ 2. **Release when the TUN interface is closed.** Call
336+ `vpn_network_manager_release_tunnel_activity(token)` with the stored token
337+ after the TUN interface (and all its duplicated descriptors) are closed.
338+ Releasing earlier would allow sockets to fall back to the system default
339+ route while the tunnel is still capturing it. Keep the token for the whole
340+ lifetime of the interface.
341+
342+ 3. **Set the outbound interface before notifying about network changes.**
343+ Call `vpn_network_manager_set_outbound_interface()` with the index of the
344+ physical interface **before** notifying the running VPN instance with
345+ `vpn_notify_network_change()`. The DNS handler restarts the system DNS proxy
346+ on network changes and reads the current outbound interface at that moment.
347+
348+ 4. **Multiple TUN interfaces are supported.** Each interface acquires its own
349+ token; the state stays active until the last token is released. Never reuse
350+ a token for two interfaces, and never release a token owned by another
351+ interface.
352+
353+ 5. **Platform equivalents of a bound interface.** On Android, sockets are
354+ protected with `VpnService.protect(fd)` instead of an interface binding; the
355+ application is responsible for routing its outgoing sockets through that
356+ mechanism while the tunnel is active. On Apple, the built-in `AGTunnel`
357+ registers the activity automatically.
358+
359+ ### Behavior
360+
361+ While `vpn_network_manager_get_tunnel_active()` returns `true` and the outbound
362+ interface is zero (or stale), platform socket protection handlers reject
363+ outgoing sockets instead of letting them use the system default route. This
364+ prevents the VPN' s own traffic from looping back into the TUN interface. The
365+ DNS proxy startup is also gated on the same condition on non-Android platforms.
366+
306367# # Companion Endpoint Repository
307368
308369Complementary endpoint implementation for the TrustTunnel VPN can be found in
0 commit comments