From a822654bbfa80bdcacced4d8cea67e2d3c554985 Mon Sep 17 00:00:00 2001 From: Vsevolod Strukchinsky Date: Mon, 29 Jun 2026 12:59:42 +0500 Subject: [PATCH 1/2] fix(relay): default interop image to WebTransport at root path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The moq-interop-runner's docker harness dials the relay at a path-less https://relay:4443 (WebTransport, ALPN "h3") and provides no env knob to select the relay's transport. The interop relay image defaulted to raw QUIC (MOQT_TRANSPORT=quic), so it advertised only the moqt-* ALPNs and rejected every h3 ClientHello with "tls: no application protocol" — every client connecting to a moq-go relay in docker failed, including moq-go -> moq-go (0/6). Default the interop entrypoint/Dockerfile to MOQT_TRANSPORT=webtransport and MOQT_WEBTRANSPORT_PATH=/ to match the harness, and skip the relay's "/" catch-all 404 handler when the WebTransport path is "/" (registering two handlers for the same pattern panics the ServeMux; a root-mounted upgrade already covers every request). The -webtransport and -webtransport-path binary flag defaults are unchanged, so local `go run ./cmd/relay` is unaffected. Verified through the runner's compose: moq-go -> moq-go docker goes from 0/6 to 6/6. Co-Authored-By: Claude Opus 4.8 --- cmd/relay/Dockerfile | 8 ++++++-- cmd/relay/entrypoint-relay.sh | 17 +++++++++++++---- cmd/relay/main.go | 25 ++++++++++++++++--------- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/cmd/relay/Dockerfile b/cmd/relay/Dockerfile index 5cd6f6a..7be4347 100644 --- a/cmd/relay/Dockerfile +++ b/cmd/relay/Dockerfile @@ -41,10 +41,14 @@ COPY cmd/relay/entrypoint-relay.sh /app/run_endpoint.sh RUN chmod +x /app/run_endpoint.sh && mkdir -p /certs /mlog # Defaults follow the runner conventions; override at `docker run`/compose time. +# MOQT_TRANSPORT defaults to webtransport: the runner's docker harness always +# dials the relay at https://relay:4443 (WebTransport / ALPN "h3") and exposes +# no knob to set MOQT_TRANSPORT, so a raw-QUIC default makes every client fail +# the TLS handshake with "no application protocol". ENV MOQT_ROLE=relay \ MOQT_PORT=4443 \ - MOQT_TRANSPORT=quic \ - MOQT_WEBTRANSPORT_PATH=/moq + MOQT_TRANSPORT=webtransport \ + MOQT_WEBTRANSPORT_PATH=/ EXPOSE 4443/udp diff --git a/cmd/relay/entrypoint-relay.sh b/cmd/relay/entrypoint-relay.sh index e65d292..93db255 100755 --- a/cmd/relay/entrypoint-relay.sh +++ b/cmd/relay/entrypoint-relay.sh @@ -7,8 +7,17 @@ # MOQT_PORT UDP port to listen on (default: 4443) # MOQT_CERT TLS certificate PEM (default: /certs/cert.pem) # MOQT_KEY TLS private key PEM (default: /certs/priv.key) -# MOQT_TRANSPORT "quic" (moqt://) or "webtransport" (https://) (default: quic) -# MOQT_WEBTRANSPORT_PATH HTTP/3 CONNECT path for WebTransport (default: /moq) +# MOQT_TRANSPORT "quic" (moqt://) or "webtransport" (https://) (default: webtransport) +# The runner's docker harness always points the client +# at https://relay:4443 (WebTransport / ALPN "h3") and +# provides no way to set MOQT_TRANSPORT, so the relay +# must speak WebTransport by default or every handshake +# fails with "tls: no application protocol". +# MOQT_WEBTRANSPORT_PATH HTTP/3 CONNECT path for WebTransport (default: /) +# The runner dials a path-less https://relay:4443, so +# the CONNECT targets "/"; serving WebTransport at the +# root is what lets the upgrade succeed (otherwise the +# request 404s against the /moq handler). # # Mounts: # /certs/cert.pem, /certs/priv.key @@ -21,8 +30,8 @@ ROLE="${MOQT_ROLE:-relay}" PORT="${MOQT_PORT:-4443}" CERT="${MOQT_CERT:-/certs/cert.pem}" KEY="${MOQT_KEY:-/certs/priv.key}" -TRANSPORT="${MOQT_TRANSPORT:-quic}" -WT_PATH="${MOQT_WEBTRANSPORT_PATH:-/moq}" +TRANSPORT="${MOQT_TRANSPORT:-webtransport}" +WT_PATH="${MOQT_WEBTRANSPORT_PATH:-/}" if [ "$ROLE" != "relay" ]; then echo "role '$ROLE' not supported (only 'relay')" >&2 diff --git a/cmd/relay/main.go b/cmd/relay/main.go index f691e9e..068636f 100644 --- a/cmd/relay/main.go +++ b/cmd/relay/main.go @@ -239,15 +239,22 @@ func webTransportListener(addr, path string, tlsCfg *tls.Config) (*wtconn.Listen mux := http.NewServeMux() // Catch-all so requests that don't hit `path` (wrong URL, missing // upgrade header, plain GET) get logged instead of silently 404'ing. - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - alpn := "" - if r.TLS != nil { - alpn = r.TLS.NegotiatedProtocol - } - log.Printf("http3: unmatched request %s %s%s proto=%s alpn=%q upgrade=%q", - r.Method, r.Host, r.URL.Path, r.Proto, alpn, r.Header.Get(":protocol")) - http.NotFound(w, r) - }) + // Skip it when the upgrade handler itself owns "/" (path == "/"): + // registering two handlers for the same pattern panics the ServeMux, + // and a root-mounted upgrade already covers every request anyway. The + // interop runner dials a path-less URL (https://relay:4443), so the + // CONNECT targets "/" — see MOQT_WEBTRANSPORT_PATH in entrypoint-relay.sh. + if path != "/" { + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + alpn := "" + if r.TLS != nil { + alpn = r.TLS.NegotiatedProtocol + } + log.Printf("http3: unmatched request %s %s%s proto=%s alpn=%q upgrade=%q", + r.Method, r.Host, r.URL.Path, r.Proto, alpn, r.Header.Get(":protocol")) + http.NotFound(w, r) + }) + } h3 := &http3.Server{ TLSConfig: tlsCfg, Handler: mux, From cafa15bf8bb562aac007dc62d3572a41fd757bdd Mon Sep 17 00:00:00 2001 From: Vsevolod Strukchinsky Date: Mon, 29 Jun 2026 13:05:39 +0500 Subject: [PATCH 2/2] fix(interop): pin client-direction loopback relay to raw QUIC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flipping the relay image default to WebTransport (so the external moq-interop-runner, which has no transport knob, can reach it) broke this repo's own "our client → our relay" CI job: `make interop-client` defaults to a raw-QUIC loopback (moqt://relay:4443), and its relay relied on the image's old quic default. With the relay now defaulting to WebTransport it advertised only h3, so the moqt:// client failed with "tls: no application protocol". docker-compose.client.yml now defaults the relay's MOQT_TRANSPORT to quic (matching the moqt:// loopback URL), and the Makefile derives it from CLIENT_RELAY_URL's scheme so an https:// override stays consistent. Third-party relay images ignore MOQT_TRANSPORT. interop/docker-compose.yml already forwarded MOQT_TRANSPORT, so interop-quic/interop-webtransport are unaffected. Verified: `make interop-client` loopback passes 6/6 with the relay in QUIC mode. Co-Authored-By: Claude Opus 4.8 --- Makefile | 9 ++++++++- interop/docker-compose.client.yml | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5d973c8..4d3c94a 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,12 @@ CLIENT_RELAY_IMAGE ?= moq-relay:latest CLIENT_RELAY_URL ?= moqt://relay:4443 CLIENT_COMPOSE := docker compose -f interop/docker-compose.client.yml +# Transport our loopback relay must speak, derived from the URL scheme so the +# default moqt:// loopback uses raw QUIC and an https:// override uses +# WebTransport. Only consumed by our own relay image (third-party relays ignore +# MOQT_TRANSPORT); see interop/docker-compose.client.yml. +CLIENT_RELAY_TRANSPORT ?= $(if $(filter https://%,$(CLIENT_RELAY_URL)),webtransport,quic) + # Build our test-client image from this repo's sources. interop-client-build: docker build -f cmd/interop-client/Dockerfile.client -t moq-interop-client:latest . @@ -91,7 +97,8 @@ interop-client-build: # loopback works out of the box; harmless when targeting a third-party relay. interop-client: interop-build interop-certs @echo ">> interop-client: moq-interop-client -> $(CLIENT_RELAY_IMAGE) ($(CLIENT_RELAY_URL))" - RELAY_IMAGE=$(CLIENT_RELAY_IMAGE) RELAY_URL=$(CLIENT_RELAY_URL) $(CLIENT_ENV) \ + RELAY_IMAGE=$(CLIENT_RELAY_IMAGE) RELAY_URL=$(CLIENT_RELAY_URL) \ + MOQT_TRANSPORT=$(CLIENT_RELAY_TRANSPORT) $(CLIENT_ENV) \ $(CLIENT_COMPOSE) up --build --abort-on-container-exit --exit-code-from test-client; \ status=$$?; $(CLIENT_COMPOSE) down -v >/dev/null 2>&1; exit $$status diff --git a/interop/docker-compose.client.yml b/interop/docker-compose.client.yml index 0bba6ee..2a7006d 100644 --- a/interop/docker-compose.client.yml +++ b/interop/docker-compose.client.yml @@ -19,6 +19,13 @@ services: environment: - MOQT_ROLE=relay - MOQT_PORT=4443 + # The default loopback (CLIENT_RELAY_IMAGE=moq-relay:latest, our own + # image) dials moqt://relay:4443, so the relay must speak raw QUIC — our + # image otherwise defaults to WebTransport (the external moq-interop-runner + # drives it that way). Defaults to quic to match the moqt:// URL; the + # Makefile derives it from CLIENT_RELAY_URL's scheme. Third-party relay + # images ignore this var and use their own transport conventions. + - MOQT_TRANSPORT=${MOQT_TRANSPORT:-quic} # No healthcheck override here: third-party relay images ship their own # (e.g. moq-rs uses `ss`, the moq-dev adapter greps /proc/net/udp6), and # some are shell-less so a `sh -c` override would never pass. We rely on the