dns: DNS over HTTPS (DNS slice 6) #70

Closed
opened 2026-10-05 21:07:16 +00:00 by orion · 4 comments
Collaborator

Context

DNS over HTTPS (RFC 8484) for std/http, DNS.md slice 6. Slices 1 to 5 (std/dns, UDP, TCP, DoT) and NETWORK.md slice 7 (Client) are built, so the pieces exist: query(name, type, id) and readResponse(bytes, query) of std/dns, and TlsResolver in std/tls as the shape to follow ("the same two calls around another pipe", DNS.md section 1).

This issue is the first step: the resolver type. Making a Client use it to connect (the "option of the client" wording of the slice table) is the next step and is filed when this one has landed, because it needs a decision on a resolver abstraction that Resolver, TlsResolver and the new type share.

Source

  • DNS.md sections 2 (transport table: "POST of application/dns-message, identifier 0"), 7 (slice 6, "Lookups, as built") and 8
  • std/tls/src/lib.trb TlsResolver (shape, error mapping), std/http/src/pool.trb Client

Scope

A new DohResolver in std/http (new file std/http/src/doh.trb, exported from lib.trb), the shape of TlsResolver:

  • Fields: url: Uri (the server's https endpoint, e.g. https://dns.example.test/dns-query), tls: TlsSettings = TlsSettings(), attemptMilliseconds: Int = 5000.
  • fn lookup(name: DomainName, recordType: RecordType): Task<Result<List<Record>, NetworkError>> and fn exchange(name, recordType): Task<Result<Message, NetworkError>>, same contract as TlsResolver (NXDOMAIN is isHostNotFound(), SERVFAIL/REFUSED/NOTIMP and a non-DNS body are isNameServerFailure(), the attempt's time passing is isTimedOut()).
  • exchange sends query(name, type, 0) (identifier 0, RFC 8484 section 4.1) as a POST with Content-Type and Accept of application/dns-message through the existing client code, and reads the body (bounded) with readResponse.
  • A scheme other than https and a non-2xx status are failures (isNameServerFailure() with the status in the message); an HttpError of the exchange maps to the matching NetworkError.
  • DNS.md: slice 6 marked done, the "stays std/http's" bullet in section 7 replaced by the as-built description, section 2's tree and table updated.

Acceptance criteria

  • DohResolver with lookup and exchange as above, public and documented (torb doc --check clean), imported from std/http.
  • A conformance program tests/conformance/dns-over-https.trb runs natively and in the VM against a DoH server written in the test itself (loopback, TLS with a test root as dns-over-tls.trb does), with recorded response bytes: an address answer, a CNAME chain, NXDOMAIN, SERVFAIL, a non-DNS body, a non-2xx status, and a silent server timing out. It asserts the request: method POST, both media types, identifier 0 in the body.
  • A server that cannot prove the URL's host is refused (certificate rejected), like the DoT test.
  • A http:// URL is refused without a connection being opened.
  • DNS.md updated as listed; skills/ regenerated if docs change it.

Seed

No seed impact expected: TorbScript only in std/http (importing std/dns, which has no natives), no syntax, no new native, no name the compiler looks up by string.

Out of scope

  • The Client option that resolves hosts through a resolver (next step, after this one).
  • GET with the ?dns= base64url form, HTTP/2 (NETWORK.md slice 12), keeping connections alive across lookups, caching.
  • HTTPS/SVCB records for ALPN (DNS.md slice 7), DNSSEC validation.

Dependencies

None open: DNS.md slices 1 to 5 and NETWORK.md slice 7 are done.

Size: one PR, one or two Conventional Commits (feat(http): ..., docs(dns): ...).

Moved from the planning lists of the docs; the design record stays the specification.

## Context DNS over HTTPS (RFC 8484) for `std/http`, [DNS.md slice 6](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/DNS.md#L274). Slices 1 to 5 (`std/dns`, UDP, TCP, DoT) and NETWORK.md slice 7 (`Client`) are built, so the pieces exist: `query(name, type, id)` and `readResponse(bytes, query)` of `std/dns`, and `TlsResolver` in `std/tls` as the shape to follow ("the same two calls around another pipe", DNS.md section 1). This issue is the first step: the resolver type. Making a `Client` use it to connect (the "option of the client" wording of the slice table) is the next step and is filed when this one has landed, because it needs a decision on a resolver abstraction that `Resolver`, `TlsResolver` and the new type share. ## Source - DNS.md sections 2 (transport table: "`POST` of `application/dns-message`, identifier 0"), 7 (slice 6, "Lookups, as built") and 8 - `std/tls/src/lib.trb` `TlsResolver` (shape, error mapping), `std/http/src/pool.trb` `Client` ## Scope A new `DohResolver` in `std/http` (new file `std/http/src/doh.trb`, exported from `lib.trb`), the shape of `TlsResolver`: - Fields: `url: Uri` (the server's `https` endpoint, e.g. `https://dns.example.test/dns-query`), `tls: TlsSettings = TlsSettings()`, `attemptMilliseconds: Int = 5000`. - `fn lookup(name: DomainName, recordType: RecordType): Task<Result<List<Record>, NetworkError>>` and `fn exchange(name, recordType): Task<Result<Message, NetworkError>>`, same contract as `TlsResolver` (NXDOMAIN is `isHostNotFound()`, `SERVFAIL`/`REFUSED`/`NOTIMP` and a non-DNS body are `isNameServerFailure()`, the attempt's time passing is `isTimedOut()`). - `exchange` sends `query(name, type, 0)` (identifier 0, RFC 8484 section 4.1) as a `POST` with `Content-Type` and `Accept` of `application/dns-message` through the existing client code, and reads the body (bounded) with `readResponse`. - A scheme other than `https` and a non-2xx status are failures (`isNameServerFailure()` with the status in the message); an `HttpError` of the exchange maps to the matching `NetworkError`. - DNS.md: slice 6 marked done, the "stays `std/http`'s" bullet in section 7 replaced by the as-built description, section 2's tree and table updated. ## Acceptance criteria - [ ] `DohResolver` with `lookup` and `exchange` as above, public and documented (`torb doc --check` clean), imported from `std/http`. - [ ] A conformance program `tests/conformance/dns-over-https.trb` runs natively and in the VM against a DoH server written in the test itself (loopback, TLS with a test root as `dns-over-tls.trb` does), with recorded response bytes: an address answer, a CNAME chain, NXDOMAIN, `SERVFAIL`, a non-DNS body, a non-2xx status, and a silent server timing out. It asserts the request: method `POST`, both media types, identifier 0 in the body. - [ ] A server that cannot prove the URL's host is refused (certificate rejected), like the DoT test. - [ ] A `http://` URL is refused without a connection being opened. - [ ] DNS.md updated as listed; `skills/` regenerated if docs change it. ## Seed No seed impact expected: TorbScript only in `std/http` (importing `std/dns`, which has no natives), no syntax, no new native, no name the compiler looks up by string. ## Out of scope - The `Client` option that resolves hosts through a resolver (next step, after this one). - GET with the `?dns=` base64url form, HTTP/2 (NETWORK.md slice 12), keeping connections alive across lookups, caching. - HTTPS/SVCB records for ALPN (DNS.md slice 7), DNSSEC validation. ## Dependencies None open: DNS.md slices 1 to 5 and NETWORK.md slice 7 are done. Size: one PR, one or two Conventional Commits (`feat(http): ...`, `docs(dns): ...`). _Moved from the planning lists of the docs; the design record stays the specification._
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop), source DNS.md slice 6 (RFC 8484), not a duplicate, no collision with a design record. TorbScript in std/http, tests with recorded responses, no seed impact expected. next: refine, waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z

[loop:torbscript] intake: in scope (author orion, label loop), source DNS.md slice 6 (RFC 8484), not a duplicate, no collision with a design record. TorbScript in `std/http`, tests with recorded responses, no seed impact expected. next: refine, waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261023 until 2026-10-23T10:04:00Z

[loop:torbscript] claim refine by session:loop-torbscript-20261023 until 2026-10-23T10:04:00Z
Author
Collaborator

[loop:torbscript] refine: dependencies checked (DNS.md slices 1 to 5 and NETWORK.md slice 7 are built; std/dns, TlsResolver and Client are on main), keyword search over open and closed issues (DoH, DNS over HTTPS, 8484, dns-message, resolver) found no duplicate. decided by the loop: slice 6 as written is "a resolver option of the client", which is two changes (a DoH resolver type, then the Client using a resolver to connect, which needs a shared resolver abstraction and its own design choice). This issue is narrowed to the first one, DohResolver in std/http in the shape of TlsResolver (one new file, one conformance program, DNS.md update, one or two commits, no seed impact); the Client option is filed as the next step once this has landed (just in time, not up front). Scope, acceptance criteria and out-of-scope are rewritten in the issue body. next: ready (label set), implement.

[loop:torbscript] refine: dependencies checked (DNS.md slices 1 to 5 and NETWORK.md slice 7 are built; `std/dns`, `TlsResolver` and `Client` are on `main`), keyword search over open and closed issues (DoH, DNS over HTTPS, 8484, dns-message, resolver) found no duplicate. decided by the loop: slice 6 as written is "a resolver option of the client", which is two changes (a DoH resolver type, then the `Client` using a resolver to connect, which needs a shared resolver abstraction and its own design choice). This issue is narrowed to the first one, `DohResolver` in `std/http` in the shape of `TlsResolver` (one new file, one conformance program, DNS.md update, one or two commits, no seed impact); the `Client` option is filed as the next step once this has landed (just in time, not up front). Scope, acceptance criteria and out-of-scope are rewritten in the issue body. next: ready (label set), implement.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T14:16:00Z

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T14:16:00Z
argus closed this issue 2026-10-23 11:04:51 +00:00
Sign in to join this conversation.
No labels
loop
ready
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#70
No description provided.