relay: add WebTransport listener + WASM client, share UDP/443 via ALPN mux

The relay now accepts WebTransport sessions on the same UDP socket that
serves raw QUIC. The ALPN-multiplexing QUIC listener owns the socket and
dispatches incoming connections: "nb-quic" continues to the existing
relay handler, "h3" is handed to webtransport-go via http3.Server.
Browsers reach the relay over 443/udp without a second port.

Client side:
- Native builds keep using raw QUIC (no WT dialer registered).
- WASM/browser builds gain a WebTransport dialer that bridges syscall/js
  to the browser's WebTransport API and uses datagrams (matching the
  native QUIC dialer's semantics — no head-of-line blocking).
- The race dialer learned a transport hint so clients skip dialers a
  given relay has not advertised.

Management protocol carries the hint as a new RelayEndpoint{url,
transports[]} list on RelayConfig, mirroring how peers and proxies
announce capabilities. Older management servers that only send urls keep
working unchanged.

devcert build: relay generates an ECDSA P-256 cert with 13-day validity
(within the WebTransport serverCertificateHashes 14-day cap) and exposes
its SHA-256 so the WASM dialer can pin it.

Bumps quic-go v0.55.0 -> v0.59.0 (no API breaks for relay's importers)
and adds github.com/quic-go/webtransport-go v0.10.0.
This commit is contained in:
Claude
2026-05-17 11:08:30 +00:00
parent 3f91f49277
commit 078c323ef3
21 changed files with 1693 additions and 784 deletions

View File

@@ -952,8 +952,20 @@ func (e *Engine) handleRelayUpdate(update *mgmProto.RelayConfig) error {
if override, ok := peer.OverrideRelayURLs(); ok {
log.Infof("overriding relay URLs from %s: %v", peer.EnvKeyNBHomeRelayServers, override)
urls = override
e.relayManager.UpdateServerURLs(urls)
} else if eps := update.GetEndpoints(); len(eps) > 0 {
// Management announced per-relay transport hints; use the rich form.
converted := make([]relayClient.ServerEndpoint, len(eps))
for i, ep := range eps {
converted[i] = relayClient.ServerEndpoint{
URL: ep.GetUrl(),
Transports: ep.GetTransports(),
}
}
e.relayManager.UpdateServerEndpoints(converted)
} else {
e.relayManager.UpdateServerURLs(urls)
}
e.relayManager.UpdateServerURLs(urls)
// Just in case the agent started with an MGM server where the relay was disabled but was later enabled.
// We can ignore all errors because the guard will manage the reconnection retries.