Shorenet for developers

Sea trials

Live test servers with known, documented behaviour. Point a browser, a command line client or your own parser at them and compare what you see with what each one is documented to send. We built them to test the capture engine inside Shorenet Trawler.

Servers

Each host is one handshake profile: a TLS version, a key-exchange group, an ALPN, resumption or ECH behaviour. The same paths are served on every host listed against them.

HostProfile
lab.shorenet.co.ukThe estate apex: the endpoint list, over HTTP/1.1 or HTTP/2
h1.lab.shorenet.co.ukHTTP/1.1 over TLS 1.3
h2.lab.shorenet.co.ukHTTP/2 over TLS 1.3
ws9573.lab.shorenet.co.uk:9573TLS 1.3 on a non-standard port
resume.lab.shorenet.co.ukTLS 1.3 resumption / 0-RTT
h1.lab.shorenet.co.uk:80Plain HTTP/1.1, no TLS
kex-x25519.lab.shorenet.co.ukgroup X25519
kex-p256.lab.shorenet.co.ukgroup secp256r1
kex-p384.lab.shorenet.co.ukgroup secp384r1
kex-mlkem768.lab.shorenet.co.ukgroup X25519MLKEM768 (post-quantum hybrid; what current Chrome/Edge/Firefox send by default)
kex-hrr.lab.shorenet.co.ukHelloRetryRequest: the server insists on a group the client did not key-share
resume-psk.lab.shorenet.co.ukPSK resumption, no early data
resume-ticket.lab.shorenet.co.ukSession tickets issued on one connection and used on the next
ticket-reject.lab.shorenet.co.ukissues session tickets and accepts none back: every connection is a full handshake
ticket-none.lab.shorenet.co.uksends no NewSessionTicket
ech-own.lab.shorenet.co.ukECH with keys this server holds; the ECHConfig is published in an HTTPS record
ech-reject.lab.shorenet.co.ukECH offered but rejected, with retry_configs returned
ech-grease.lab.shorenet.co.ukNo real ECH; the client sends GREASE ECH (what Chrome does by default)

Non-HTTP profiles

These complete a TLS handshake and then speak no HTTP. They are not web pages, so a browser has nothing to open: the handshake itself is the fixture, and what it shows you is the protocol the server selects. The command beside each one prints that and needs nothing installed beyond OpenSSL.

HostProfileTry it
noalpn.lab.shorenet.co.ukTLS 1.3, the server selects no ALPNopenssl s_client -connect noalpn.lab.shorenet.co.uk:443 -servername noalpn.lab.shorenet.co.uk -alpn h2,http/1.1 </dev/null
dot.lab.shorenet.co.ukTLS 1.3, ALPN dot (DNS over TLS, RFC 7858)openssl s_client -connect dot.lab.shorenet.co.uk:443 -servername dot.lab.shorenet.co.uk -alpn dot </dev/null
alpn.lab.shorenet.co.ukTLS 1.3, a custom non-HTTP ALPNopenssl s_client -connect alpn.lab.shorenet.co.uk:443 -servername alpn.lab.shorenet.co.uk -alpn shorenet-fixture/1 </dev/null

One server on Cloudflare

lab-cf.shorenet.co.uk is a Sea trials server that runs on Cloudflare rather than on the machine behind the hosts above, so what you meet there is Cloudflare's own edge. Its front page lists the paths it serves.

It is the only Sea trials server that offers HTTP/3. That is why /f/ech-h3 carries h3 in its name: on the hosts in the tables on this page it answers over HTTP/1.1 and HTTP/2 only.

Cloudflare sees your full IP address on requests to it and logs them under its own terms. Our own code there records the same shortened address and the same fixed list of fields as the other servers, but the record goes to Cloudflare's logging, so the seven days below do not apply to it.

Each Cloudflare instance serving it limits an address to 20 requests a second, with bursts of 50, counted in that instance's memory and never stored.

Paths

What each path sends on the wire, and a link to it on every host that serves it. The names in the last column are the profile names from the table above: apex is lab.shorenet.co.uk and plain80 is the same server on port 80, over plain HTTP with no TLS. Where a path takes a parameter, each documented value has its own link.

PathOn the wireOpen it on
/f/ce-identityHTTP 200, Content-Encoding: identity, 43-byte image/gifh1 h2 apex
/f/ce-noneHTTP 200, Content-Encoding: none, same 43-byte body, untransformedh1 h2 apex
/f/ce-utf8HTTP 200, Content-Encoding: utf-8, body untransformedh1 h2 apex
values on h1: v=626 v=7132
/f/ce-gzipHTTP 200, a correctly gzip-encoded bodyh1 h2 apex
/f/ce-gzip-mislabelledHTTP 200, gzip bytes labelled Content-Encoding: deflateh1 h2 apex
/f/status-{code}A bare response at that statush1 h2 apex
values on h1: 204 206 304 404 500
/f/bulk-bodyHTTP 200, a large deterministic bodyh1 h2 apex
values on h1: v=2 v=8 v=32
/f/sse-trickletext/event-stream, a fixed number of events at a fixed spacing, then the stream held open for 30 s and closed by the server. Set the count with ?n= (1, 5, 20 or 100) and the gap in milliseconds with ?ms= (10, 50, 250 or 1000); any other value is refused with 400h1 h2 apex
values on h1: n=1 n=5 n=20 n=100 ms=10 ms=50 ms=250 ms=1000
/f/body-slowHTTP 200, body written one byte at a time, 50 ms apart. Set the length with ?n= (8, 64 or 256 bytes); any other value is refused with 400h1 h2 apex
values on h1: n=8 n=64 n=256
/f/h1-keepalive-gzipIdentical gzip responses (Content-Length 1990, 5430 B decoded) on one keep-alive connection. Request it repeatedly on the same connectionh1 plain80 apex
/f/close-delimited302, Connection: close, no Content-Length and no Transfer-Encoding, empty body, then close_notify then FINh1 apex plain80
/f/ws-echoWebSocket upgrade (permessage-deflate when offered), then a byte-exact echonot a link: a WebSocket needs a client, so use /t/ws-echo on h1 /t/ws-echo on h2
/f/ws-ce-noneWebSocket upgrade whose 101 carries Content-Encoding: nonenot a link: a WebSocket needs a client, so use /t/ws-ce-none on h1
/f/h2-trailersResponse trailers after DATA (a trailer field carrying the body's SHA-256)h2 apex
/f/ech-h3HTTP 200, text/plainh1 h2 apex
/f/probeHTTP 200, a short deterministic bodyresume kex-x25519 kex-p256 kex-p384 kex-mlkem768 kex-hrr resume-psk resume-ticket-crossconn ech-own ech-reject ech-grease ticket-reject ticket-none
/f/early-hints103 Early Hints with two Link fields, then a 200 HTML pageh1 h2 apex
/f/interim-100An unsolicited 100 Continue, then 200h1 h2 apex
/f/chunked-trailersHTTP/1.1 chunked body followed by a trailer field carrying the body's SHA-256h1 plain80
/f/trailers-bodilessA response head, no content, then a trailer section (on HTTP/2: HEADERS, then HEADERS with END_STREAM and no DATA)h1 h2 apex
/f/data-framesA 32 KiB body written as 512 separately flushed 64-byte pieces (512 DATA frames on HTTP/2, 512 chunks on HTTP/1.1)h1 h2 apex
/f/abort-mid-body16 KiB of a body with no declared length, then the server stops: RST_STREAM(INTERNAL_ERROR) on HTTP/2, a chunked body with no last chunk and a closed connection on HTTP/1.1h1 h2 apex
/f/abort-mid-lengthContent-Length 65536, 16 KiB delivered, then the server stops (RST_STREAM on HTTP/2, connection close on HTTP/1.1)h1 h2 apex
/f/h2-early-responseA 200 sent before the request content has ended; the stream then ends with RST_STREAM(NO_ERROR)h2 apex
/f/hpack-churn24 distinct header fields of about 200 bytes each: more than a 4,096-byte HPACK dynamic table holdsh1 h2 apex
/f/huge-headersAbout 48 KiB of response header fields (on HTTP/2: HEADERS plus CONTINUATION frames)h1 h2 apex
/f/header-obs-textHeader field values containing bytes above 0x7F: a UTF-8 byte order mark and a Latin-1 byteh1 h2 apex
/f/set-cookie-multiThree Set-Cookie fields in one responseh1 h2 apex
/f/rangeA 19,883-byte body that honours Range: 206 for one range, multipart/byteranges for severalh1 h2 apex
/f/ce-brHTTP 200, a brotli-encoded bodyh1 h2 apex
/f/ce-zstdHTTP 200, a zstd-encoded bodyh1 h2 apex
/f/ce-zstd-skippableHTTP 200, a zstd body of two frames with skippable frames before, between and after themh1 h2 apex
/f/ce-gzip-brContent-Encoding: gzip, br (gzip applied first, then brotli)h1 h2 apex
/f/ce-deflateHTTP 200, a zlib-wrapped deflate bodyh1 h2 apex
/f/ce-gzip-headerHTTP 200, a gzip body whose header sets FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT and MTIMEh1 h2 apex
/f/dict/sourceA cacheable text body with Use-As-Dictionary: match="/f/dict/*"h1 h2 apex
/f/dict/dcbThe next version of that text, brotli-compressed against it (Content-Encoding: dcb) when the request names the dictionary, unencoded otherwiseh1 h2 apex
/f/dict/dczThe next version of that text, zstd-compressed against it (Content-Encoding: dcz) when the request names the dictionary, unencoded otherwiseh1 h2 apex
/f/http10An HTTP/1.0 status line, Content-Length, then the connection closesh1 plain80
/f/h1-no-reasonA status line with an empty reason phrase ("HTTP/1.1 200 ")h1 plain80
/f/chunked-extA chunked body whose chunk-size lines carry chunk extensionsh1 plain80
/f/te-gzip-chunkedTransfer-Encoding: gzip, chunkedh1 plain80
/f/conn-closeA 200 with Connection: close on a kept-alive connection, which then closesh1 plain80
/f/close-notify-rstA complete response, close_notify, then a TCP reset instead of a FINh1
/f/rst-after-responseA complete response, then a TCP reset with no close_notifyh1 plain80
/f/ws-plainWebSocket upgrade declining permessage-deflate, then a byte-exact echonot a link: a WebSocket needs a client, so use /t/ws-plain on h1 /t/ws-plain on h2
/f/ws-server-firstWebSocket upgrade whose first server message travels in the same write as the 101, then an echonot a link: a WebSocket needs a client, so use /t/ws-server-first on h1 /t/ws-server-first on h2
/f/ws-largeWebSocket upgrade, then one 256 KiB binary message (a 64-bit payload length), then an echonot a link: a WebSocket needs a client, so use /t/ws-large on h1 /t/ws-large on h2
/f/ws-fragmentedWebSocket upgrade, then one text message in 16 fragments with a PING between two of them, then an echonot a link: a WebSocket needs a client, so use /t/ws-fragmented on h1 /t/ws-fragmented on h2
/f/ws-server-closeWebSocket upgrade, one message, then the server's own Close 1000not a link: a WebSocket needs a client, so use /t/ws-server-close on h1 /t/ws-server-close on h2

Browser pages

Pages that make a browser produce a specific exchange when you open them. Follow one and watch it in your browser developer tools, or capture it and compare.

PageWhat it doesOpen it on
/t/early-hintsNavigates to a page whose 200 is preceded by a 103 Early Hints responseh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/interim-100Fetches a response preceded by an unsolicited 100 Continueh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/h2-trailersFetches a body followed by trailersh2.lab.shorenet.co.uk
/t/chunked-trailersFetches a chunked body followed by a trailer fieldh1.lab.shorenet.co.uk h1.lab.shorenet.co.uk:80
/t/trailers-bodilessFetches a response with trailers and no contenth2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/data-framesFetches a body delivered in 512 piecesh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/abort-mid-bodyFetches a response the server stops partway throughh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/abort-mid-lengthFetches a response that ends before its declared lengthh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/h2-early-responseStreams an upload and receives the response before the upload endsh2.lab.shorenet.co.uk
/t/hpack-churnFetches a response with many large header fields, three times on one connectionh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/hpack-shared-tableFetches four of those responses at once on one connection, twiceh2.lab.shorenet.co.uk
/t/huge-headersFetches a response with about 48 KiB of header fieldsh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/header-obs-textFetches a response whose header values contain bytes above 0x7Fh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/set-cookie-multiFetches a response with three Set-Cookie fieldsh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/rangeFetches the whole body, one range, then two rangesh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/head-requestSends HEAD requests: a response head with no contenth2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/content-codingsFetches bodies in brotli, zstd, zstd with skippable frames, gzip-then-brotli, deflate and a gzip with every header fieldh2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/dictionary-transportFetches a dictionary, then two resources a browser may request compressed against ith2.lab.shorenet.co.uk h1.lab.shorenet.co.uk
/t/http10Fetches a response with an HTTP/1.0 status lineh1.lab.shorenet.co.uk h1.lab.shorenet.co.uk:80
/t/h1-no-reasonFetches a response whose status line has no reason phraseh1.lab.shorenet.co.uk h1.lab.shorenet.co.uk:80
/t/chunked-extFetches a chunked body with chunk extensionsh1.lab.shorenet.co.uk h1.lab.shorenet.co.uk:80
/t/conn-closeFetches twice on a kept-alive connection, then a response that closes it, then once moreh1.lab.shorenet.co.uk h1.lab.shorenet.co.uk:80
/t/close-delimitedFetches a redirect whose body is delimited by the connection closingh1.lab.shorenet.co.uk h1.lab.shorenet.co.uk:80
/t/close-notify-rstFetches a response after which the connection ends with close_notify and a TCP reseth1.lab.shorenet.co.uk
/t/rst-after-responseFetches a response after which the connection is reseth1.lab.shorenet.co.uk h1.lab.shorenet.co.uk:80
/t/ws-echoOpens a WebSocket and has two messages echoedh1.lab.shorenet.co.uk h2.lab.shorenet.co.uk
/t/ws-plainThe same, with no compression extensionh1.lab.shorenet.co.uk h2.lab.shorenet.co.uk
/t/ws-ce-noneOpens a WebSocket whose 101 response carries Content-Encoding: noneh1.lab.shorenet.co.uk
/t/ws-server-firstOpens a WebSocket whose server speaks firsth1.lab.shorenet.co.uk h2.lab.shorenet.co.uk
/t/ws-largeOpens a WebSocket and receives one 256 KiB messageh1.lab.shorenet.co.uk h2.lab.shorenet.co.uk
/t/ws-fragmentedOpens a WebSocket and receives one message in 16 fragmentsh1.lab.shorenet.co.uk h2.lab.shorenet.co.uk
/t/ws-server-closeOpens a WebSocket that the server closesh1.lab.shorenet.co.uk h2.lab.shorenet.co.uk

Fair use

  • Up to 20 requests a second from one address, with bursts of 50; IPv6 is counted per /64. Above that a server answers 429 with Retry-After.
  • Up to 4 connections at once from one address, and 8 HTTP/2 streams per connection.
  • A handshake or request header must arrive within 10 seconds; idle connections close after 60 seconds.
  • These are test servers with no availability promise. They may be slow, rate limited or offline.

What the servers log

A fixed list of fields per request, with your address shortened to its /24 (IPv4) or /48 (IPv6). No request bodies, URLs, headers or cookies are logged. Every server in the tables on this page runs on one machine Vultr hosts for us in London, and its logs stay on that machine and are deleted after 7 days. The one exception is the server on Cloudflare, above. More on the privacy page.