http: the client reads the HTTPS record of an origin (step 2 of #71) #558

Closed
opened 2026-10-28 23:14:27 +00:00 by orion · 6 comments
Collaborator

Goal

Step 2 of #71: a Client with a resolver reads the HTTPS record (RFC 9460) of an https origin and connects the way the record says. Step 1 (#525, PR #527) landed NameResolver and the Client.resolver option; this step uses it.

Rules

For an https origin and a client whose resolver is Some:

  • Query. Ask for the HTTPS record of the host: owner name <host> where the port is 443, _<port>._https.<host> otherwise (RFC 9460 section 9.5). An IP literal asks nobody. A client without a resolver behaves as before.
  • AliasMode (priority 0): follow the target, at most 8 hops; a longer chain, a loop or an empty target (.) means "no usable record". ServiceMode records are tried by ascending priority (stable among equal priorities); the first usable one decides.
  • No record, a failed lookup, or an unusable record is today's behaviour (connect to the origin as if nothing was asked), never a failure of the request.
  • port replaces the origin's port for the connection only; the Host header and the TLS server name keep the origin's.
  • alpn (plus the default http/1.1 unless no-default-alpn) is intersected with what the client supports and offered through TlsSettings.protocols. Until h2 exists (#72) the client supports http/1.1 only; a record that offers nothing the client supports is unusable.
  • ipv4hint / ipv6hint are used only when A / AAAA of the connection target answer nothing (RFC 9460 section 7.3).
  • mandatory lists a key the client does not understand, or any key the client does not implement: the record is unusable. ech and the other keys are ignored here (own issues).
  • A ServiceMode record whose target is . means the origin's own host (RFC 9460 section 2.5.2).

Acceptance criteria

A new conformance test, tests/conformance/http-client-https-record.trb, runs a name server and an HTTPS server in one program (as tests/conformance/dns-over-https.trb does) and, in the VM and with --native, proves each rule above with at least:

  1. port redirects the connection while Host stays the origin's;
  2. an alpn list reaches the server in the handshake (and no-default-alpn removes http/1.1);
  3. an AliasMode chain is followed, and a chain over 8 hops is ignored;
  4. priorities are tried in ascending order (the first record's target refuses, the second works);
  5. hints are used only when A / AAAA are empty;
  6. a missing record, SERVFAIL, and a mandatory key the client does not know each give today's behaviour, and the request succeeds;
  7. a non-443 port asks _<port>._https.<host>.

tools/gates.sh a and b green in forge CI. DNS.md section 7 and the slice 7 row are updated to "built" for the client part (h2 stays "after #72"); skills/ regenerated.

Design record / seed

Implements DNS.md slice 7 and section 7. Seed: no (std only, in std/http; no syntax, no native, no string lookup by the compiler).

Size

One PR, one or two Conventional Commits (for example feat(http): the client reads the HTTPS record of an origin, docs(dns): ...).

Dependencies

Step 1 #525 (landed). Not blocked by #72: until then only http/1.1 is offered.

## Goal Step 2 of #71: a `Client` with a `resolver` reads the `HTTPS` record (RFC 9460) of an `https` origin and connects the way the record says. Step 1 (#525, PR #527) landed `NameResolver` and the `Client.resolver` option; this step uses it. ## Rules For an `https` origin and a client whose `resolver` is `Some`: - **Query.** Ask for the `HTTPS` record of the host: owner name `<host>` where the port is 443, `_<port>._https.<host>` otherwise (RFC 9460 section 9.5). An IP literal asks nobody. A client without a resolver behaves as before. - **AliasMode** (priority 0): follow the target, at most 8 hops; a longer chain, a loop or an empty target (`.`) means "no usable record". **ServiceMode** records are tried by ascending priority (stable among equal priorities); the first usable one decides. - **No record, a failed lookup, or an unusable record is today's behaviour** (connect to the origin as if nothing was asked), never a failure of the request. - **`port`** replaces the origin's port for the connection only; the `Host` header and the TLS server name keep the origin's. - **`alpn`** (plus the default `http/1.1` unless `no-default-alpn`) is intersected with what the client supports and offered through `TlsSettings.protocols`. Until `h2` exists (#72) the client supports `http/1.1` only; a record that offers nothing the client supports is unusable. - **`ipv4hint` / `ipv6hint`** are used only when `A` / `AAAA` of the connection target answer nothing (RFC 9460 section 7.3). - **`mandatory`** lists a key the client does not understand, or any key the client does not implement: the record is unusable. `ech` and the other keys are ignored here (own issues). - A ServiceMode record whose target is `.` means the origin's own host (RFC 9460 section 2.5.2). ## Acceptance criteria A new conformance test, `tests/conformance/http-client-https-record.trb`, runs a name server and an HTTPS server in one program (as `tests/conformance/dns-over-https.trb` does) and, in the VM and with `--native`, proves each rule above with at least: 1. `port` redirects the connection while `Host` stays the origin's; 2. an `alpn` list reaches the server in the handshake (and `no-default-alpn` removes `http/1.1`); 3. an AliasMode chain is followed, and a chain over 8 hops is ignored; 4. priorities are tried in ascending order (the first record's target refuses, the second works); 5. hints are used only when `A` / `AAAA` are empty; 6. a missing record, `SERVFAIL`, and a `mandatory` key the client does not know each give today's behaviour, and the request succeeds; 7. a non-443 port asks `_<port>._https.<host>`. `tools/gates.sh a` and `b` green in forge CI. DNS.md section 7 and the slice 7 row are updated to "built" for the client part (`h2` stays "after #72"); `skills/` regenerated. ## Design record / seed Implements DNS.md slice 7 and section 7. Seed: no (std only, in `std/http`; no syntax, no native, no string lookup by the compiler). ## Size One PR, one or two Conventional Commits (for example `feat(http): the client reads the HTTPS record of an origin`, `docs(dns): ...`). ## Dependencies Step 1 #525 (landed). Not blocked by #72: until then only `http/1.1` is offered.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261029 until 2026-10-29T05:22:42Z

[loop:torbscript] claim implement by session:loop-torbscript-20261029 until 2026-10-29T05:22:42Z
Author
Collaborator

[loop:torbscript] release implement: the worker was lost with its execution context (gateway event, not a usage limit). State is on Forgejo: PR #561 (loop/558-http-client-https-record at 01ec6268). Respawning on the same profile to read forge CI and fix red lanes caused by this change, next: implement

[loop:torbscript] release implement: the worker was lost with its execution context (gateway event, not a usage limit). State is on Forgejo: PR #561 (loop/558-http-client-https-record at 01ec6268). Respawning on the same profile to read forge CI and fix red lanes caused by this change, next: implement
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261029 until 2026-10-29T06:24:25Z

[loop:torbscript] claim implement by session:loop-torbscript-20261029 until 2026-10-29T06:24:25Z
Author
Collaborator

[loop:torbscript] release implement: the worker was lost with its execution context again (gateway event, not a usage limit). PR #561 is open at 01ec6268 and its forge CI is running; nothing left to do until it reports. next: waiting (forge CI on PR #561); a red lane caused by this change gets a fix worker

[loop:torbscript] release implement: the worker was lost with its execution context again (gateway event, not a usage limit). PR #561 is open at 01ec6268 and its forge CI is running; nothing left to do until it reports. next: waiting (forge CI on PR #561); a red lane caused by this change gets a fix worker
Author
Collaborator

[loop:torbscript] claim fix by session:loop-torbscript-20261029 until 2026-10-29T04:14:34Z

[loop:torbscript] claim fix by session:loop-torbscript-20261029 until 2026-10-29T04:14:34Z
Author
Collaborator

[loop:torbscript] release fix: the fix worker was lost with its execution context (gateway event, not a usage limit) after pushing 6a2218b4 (fix(http): the binding module doc names Client without a link) to PR #561; forge CI on it is running. next: waiting (forge CI on PR #561); a red lane caused by this change gets a fix worker with a fresh claim.

[loop:torbscript] release fix: the fix worker was lost with its execution context (gateway event, not a usage limit) after pushing 6a2218b4 (fix(http): the binding module doc names Client without a link) to PR #561; forge CI on it is running. next: waiting (forge CI on PR #561); a red lane caused by this change gets a fix worker with a fresh claim.
argus closed this issue 2026-10-29 03:18:24 +00:00
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#558
No description provided.