Session roaming: how Qeli moves a session
Changing the physical path without a new lease: TCP/UDP handover, usable Wi-Fi/cellular gating, fail-closed TUN, stable multipath slots and troubleshooting.
The session is separate from its physical connection
Since Qeli 0.8.0, session roaming preserves one logical VPN session when the physical path changes: Wi-Fi gives way to cellular, the outer address changes, NAT rebinding occurs, or another A/AAAA endpoint becomes usable. The new carrier is validated inside the existing authenticated contract, so the server does not allocate a second lease or create another user session.
reality-tls profile to udp-quic or select another server. It moves the same profile's session between physical paths.Prepare → validate → commit
- The platform adapter observes a network change and prepares an exact candidate path: temporary host route, kill-switch rules and a separate socket bound to the intended interface.
- The shared core binds the candidate to the current generation/epoch and validates it with authenticated control. Wrong CID, token, direction or stale epoch is rejected.
- The old path stays active until validation completes. After commit, the new carrier becomes current and temporary candidate state moves to the normal session owner.
- An error before commit rolls back only the candidate. An ambiguous result past the commit point terminates the generation and performs a clean reconnect instead of leaving a live-looking black hole.
| Carrier | Path migration | Protection |
|---|---|---|
| TCP | Make-before-break: a new socket completes JOIN/validation before the old one closes. Commit moves the complete bonded carrier set. | Two-phase commit and reconnect on ambiguous post-commit failure. |
| UDP | The new peer/CID is a candidate. Authenticated candidate-path data stays in a bounded queue until commit. | 128-bit challenge, anti-amplification, stale-epoch rejection and candidate limits. |
The same mechanism can move an outer IPv4 path to IPv6 and back when endpoint, listener and platform support both. The inner TUN family is independent from the outer carrier family.
roaming = off | auto | required
| Value | Behavior | Use |
|---|---|---|
off | Masks the capability and uses a normal reconnect on path change. | Troubleshooting or deliberate disablement. |
auto | Uses negotiated TCP/UDP roaming and safely falls back to reconnect with an old server, core or unsupported platform. | Recommended and the shipped-client default. |
required | Fails before credentials/full AUTH when the complete transport-specific contract is unavailable. | Only a fully upgraded, verified fleet. |
local or non-zero lport pins the carrier socket. auto uses reconnect fallback; required is rejected by validation.Enablement and resource bounds
roaming.enabled = true
roaming.grace_secs = 30
roaming.max_orphaned = 256
roaming.max_orphan_bytes = 67108864
# client.conf · [qeli]
roaming = auto
| Key | New-template default | Range | Purpose |
|---|---|---|---|
roaming.enabled | true | bool | Profile feature gate. Missing from an old sparse config means false. |
roaming.grace_secs | 30 | 1…3600 | How long an unexpectedly detached TCP session waits for authenticated resume. |
roaming.max_orphaned | 256 | 1…65536 | Profile limit for detached TCP sessions retained for resume. |
roaming.max_orphan_bytes | 67108864 | 4194304…1073741824 | Total profile memory limit for orphaned sessions. |
New installer profiles enable bounded roaming. Replacing the binary does not rewrite an existing server.conf: add the keys explicitly, run qeli check-config, then restart the profile.
Transports, platforms and routes
- Ordinary TCP:
plain,fake-tls,obfs,realityandreality-tls. - UDP camouflage:
udp-fake-tls,udp-quic,udp-obfsandobfs-awgthrough one state machine. - Android and iOS report physical network changes; Windows, macOS and Linux use the same fail-closed candidate/commit result. An old native core means reconnect in
auto. - Explicit
excluderules and carrier host routes retain priority, preventing the endpoint from being routed into its own tunnel. - Startup and live PMTU use an authenticated 128-bit challenge; a new network can establish a different safe packet size.
Mobile recovery follows actual network availability
- Android and iOS do not spend reconnect backoff while no usable Wi-Fi or cellular carrier exists. They resume immediately when connectivity returns.
- Android preserves the fail-closed TUN across native-core restarts when NetworkPlan is unchanged. Manual Disconnect remains authoritative.
- iOS avoids reapplying identical NetworkExtension settings and transfers only the bounded queue of uplink packets not yet accepted by the previous core generation.
- TCP multipath uses stable logical slot IDs: losing or restoring one bonded carrier does not remap healthy flows.
- Android retains up to 1,000 recent events, capped at 512 KiB, in private no-backup storage. Clear removes the history; secrets and the complete profile are never stored there.
1.15 appears separately from the compatibility floor.Confirming that roaming actually committed
$ sudo qeli check-config --config /etc/qeli/server.conf
$ sudo journalctl -u qeli -f
- Connect with
roaming=auto, start continuous ping and a long transfer. - Switch Wi-Fi to cellular or change the usable uplink without disconnecting the profile.
- Verify that the inner lease/TUN was not recreated. Panel/statistics attempts and commits should rise, and candidate should return to zero.
- If failures or reconnect fallbacks rise, check the server feature gate, native-core version, explicit
local/lport, both carrier firewalls and PMTU.