Below is a cover letter for the "oob-server-probe" patch series.

https://gerrit.openvpn.net/q/topic:%22oob-server-probe%22

Motivation
----------

A client with several remotes today tries them in configuration order
(or randomized with --remote-random), with no regard to which server is
actually closest or least loaded. Picking a good server currently
requires external tooling. On top of that, every UDP connection pays
the full three-way handshake before the TLS exchange even starts.

This series lets the client ask the servers themselves. Before
connecting, the client sends a small out-of-band probe to every
configured UDP remote, and each willing server answers with a stateless
reply carrying its DNS-SRV-style priority and weight. From the replies
the client learns three things at once: which servers are reachable,
how far away they are (RTT), and how the operator wants load spread
across them. The client then reorders its remote list accordingly, and
can additionally reuse the probe reply to skip one round trip of the
handshake when connecting to the winner.

Community benefit:

  - Faster, smarter connects out of the box: reachable and nearby
    servers are tried first, dead ones last, with no external tooling.
  - Server-side traffic steering: operators of multi-server deployments
    get DNS-SRV-like priority/weight control (failover tiers, weighted
    load distribution) without touching DNS.
  - One RTT less to connect when the shortcut applies.
  - Probing is opt-in on the client (--server-probe). On the server,
    answering probes is stateless, replay-checked and shares the rate
    limiter of the stateless HMAC-cookie reset, so probes cannot be
    used as a reflection vector; --server-probe-reply tunes the
    advertised values. Works with tls-auth, tls-crypt and
    tls-crypt-v2.

The design follows the OpenVPN wire protocol specification update
proposed in openvpn-rfc PR #30 ("Add control channel support for server
latency checks and mtu discovery"). The specification states the goal
of these messages directly: "ping all available remotes to get a quick
response to select the best server", with the probe exchange optionally
replacing the three-way handshake for servers that use an HMAC/SYN-
cookie style stateless handshake, as OpenVPN 2.x does. This series
implements the out-of-band SERVER_PROBE / PROBE_REPLY part of that
specification (opcodes 12 and 13); the CONTROL_DATA_V1 / ECHO messages
for MTU discovery, also proposed in the same PR, are not part of this
series.

Implementation overview
-----------------------

Wire format. Two new control opcodes are reserved: P_CONTROL_OOB_V1
(12) for out-of-band control messages and P_CONTROL_OOB_WKC_V1 (13) for
the same with a tls-crypt-v2 wrapped client key (WKc) appended,
mirroring the P_CONTROL_HARD_RESET_CLIENT_V3 / P_CONTROL_WKC_V1 pair.
An OOB packet carries no reliability/ACK fields, just opcode + session
id + a TLV payload. The TLV encoding (15-bit type, 16-bit length) is
forward compatible: readers skip unknown types and tolerate trailing
bytes, so the messages can grow later without a version bump. Two
messages are defined: SERVER_PROBE (client timestamp + capability
flags) and PROBE_REPLY (echoed client session id, priority, weight,
max_latency_diff, connect_lifetime, behaviour flags).

Server side. A --mode server UDP listener answers probes from the same
stateless pre-decrypt path that handles HARD_RESET cookies:
tls_pre_decrypt_lite() accepts the OOB opcodes, the probe timestamp is
checked against a replay/skew window, and the reply is sent
synchronously without allocating any session state - the same DoS
posture as the existing HMAC-cookie reset. Crucially, the session id
the server puts in its reply IS its stateless SYN cookie, computed
exactly as for a real handshake; this is what later makes the handshake
shortcut possible. The new --server-probe-reply [max-latency-diff]
[weight] [prio] option sets the advertised values (defaults: 0 ms =
defer to the client's margin, weight 50, priority 100).

Client side. With --server-probe, the client runs a short self-contained
probe phase before the first connection attempt (a single call from
init.c, the logic lives in the new oob_client.c). It opens one
AF-native UDP socket per address family (the IPv6 socket with
IPV6_V6ONLY set, so no IPv4-mapped traffic crosses it - every remote is
probed on a socket that can later serve as its connection socket),
resolves all remotes and fans a SERVER_PROBE out to every resolved
A/AAAA address of every UDP remote in parallel, resending while
unanswered. Replies are matched to remotes by source
address, spoof-checked against the echoed session id, and RTT is
measured per target. Remotes are then ranked with DNS-SRV (RFC 2782)
semantics: responders before non-responders, grouped by priority
(lowest first); within a priority group, servers whose RTT is within a
margin of the fastest form the candidate band and are ordered by
weighted-random selection (load distribution), the rest follow by RTT.
The margin is the client's own setting when configured (the optional
max-latency-diff argument of --server-probe), else the server-advertised
max_latency_diff from the reply, else a built-in 10 ms default.
The connection list is reordered to match.

Control-channel wrapping. Probes and replies are not a side protocol:
they go through the same wrapping path as every other control packet.
The client builds a standalone wrap context (the tls-auth/tls-crypt
setup was factored out of do_init_crypto_tls() for this), so the probe
carries the tls-auth HMAC or tls-crypt encryption whenever configured -
an unwrapped probe against a wrapped server is simply dropped, as
today. tls-crypt-v2 needs one extra step because the server only learns
the per-client key from the WKc: the client sends the probe as
P_CONTROL_OOB_WKC_V1 with the WKc appended, and the server extracts the
key before unwrapping, exactly as it does for the v3 handshake reset.
The server's reply is a plain P_CONTROL_OOB_V1 (it holds the key by
then). With no wrapping configured the probe is sent in plaintext.

Handshake shortcut. Since the probe reply already contains a valid SYN
cookie, a probing client has in effect completed the first two packets
of the three-way handshake. The server advertises this in the reply as
connect_lifetime: the number of seconds the cookie is guaranteed valid
(inferred from the handshake window, not configurable). When the
winning remote advertised a lifetime, the client adopts the probe
socket as the connection socket (a new LS_MODE_UDP_ADOPT link-socket
mode - the cookie is bound to the probe's 5-tuple, so source and
destination must not change), seeds its session state from the probe
(own session id from the probe, remote session id from the cookie,
phantom server reset acked), and starts directly at the third packet -
saving one RTT. For tls-crypt-v2 the reply carries a RESEND_WKC flag
telling the client to attach the WKc to that third packet, since the
server kept no state. If the server ignores the shortcut (middlebox,
version mismatch), a short first-response deadline (min(handshake
window, 5s)) makes the client fall back to a normal handshake within a
few seconds instead of stalling for the full ~60s window.

Scope and limitations
---------------------

Only UDP remotes are probed; TCP remotes keep their configured
position. Probing runs once, before the first connection. The handshake
shortcut is skipped (probing still works) on dco-win, which has no
user-to-kernel socket handoff. Client-side probing and the shortcut
are off by default. A UDP server answers probes by default; replies
are stateless, replay-checked and rate-limited. Deployments where no
client probes are completely unaffected.

The TLV codec, probe/reply parsing, ranking (including the
weighted-random selection, via an injected RNG) and the tls-crypt-v2
wrap/extract/unwrap round trip are covered by unit tests. Both new
options are documented in the man page, and Changes.rst carries
entries for the feature.

-- 
-Lev
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to