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/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, 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