Skip to content

Add support for SVCB and HTTPS resource records (RFC 9460) - #222

Open
mrideout wants to merge 4 commits into
alexdalitz:masterfrom
mrideout:svcb-https-rr
Open

Add support for SVCB and HTTPS resource records (RFC 9460)#222
mrideout wants to merge 4 commits into
alexdalitz:masterfrom
mrideout:svcb-https-rr

Conversation

@mrideout

@mrideout mrideout commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Implements SVCB (type 64) and HTTPS (type 65), plus the two fixes they turned out to need: a ZoneReader fix for quoted values that open mid-token, and RFC 5952 canonical output from IPv6#to_s. Each has its own "Prerequisite" section below and can be broken out into separate pull requests if you'd like.

lib/dnsruby/resource/SVCB.rb (+ HTTPS.rb)

SVCB follows the usual RR subclass shape (from_hash / from_data / from_string, rdata_to_string, encode_rdata / decode_rdata); HTTPS subclasses it, since RFC 9460 Section 9 gives the two the same wire and presentation formats. Registered in code_mappers.rb (Types), resource/IN.rb (ClassInsensitiveTypes) and resource/generic.rb, and added to the RR list in README.md.

  • SvcParams. Recognizes the registered mnemonics: mandatory, alpn, no-default-alpn, port, ipv4hint, ipv6hint (RFC 9460), ech (RFC 9848), dohpath (RFC 9461), ohttp (RFC 9540) and docpath (RFC 9953). Any other key reads and writes losslessly as keyNNNNN with an opaque value.
  • Presentation format. Full RFC 9460 Appendix A escaping: quoted and unquoted values, \X and \DDD escapes resolved before value-list splitting, and value-list \, / \\. Output escapes anything that would not survive a re-parse.
  • Parentheses. Grouping parentheses work as for every other type: ZoneReader and RR.new_from_string strip them, so a record may be wrapped across lines. The cost is that a parenthesis inside a SvcParamValue is lost, so dohpath="/a(b)c" loads as /abc, though RFC 1035 Section 5.1 treats an unescaped parenthesis inside a quoted string as data. Two independent layers strip it, and both predate this PR. RR.new_from_string strips parentheses from the rdata of every type outside the %w(NAPTR TXT) allowlist in resource/RR.rb, which accounts for the quoted spelling; ZoneReader removes its own grouping parentheses before the rdata is typed at all, which accounts for the unquoted one. Adding SVCB and HTTPS to that allowlist would therefore fix only half of it, and the other half needs the reader to track quote and escape state, too broad a change to fold in here. Dnsruby's own output round-trips regardless: it writes a parenthesis as \040 / \041, which survives both.
  • Wire format. SvcParams are emitted in increasing key order; the TargetName is never compressed (Section 2.2) and never downcased for DNSSEC (RFC 6840 Section 5.1).
  • Validation. Rejects duplicate keys, keys with leading zeros, key 65535, values whose length does not fit their key's shape or runs past the end of the RDATA, non-self-consistent ServiceMode records (Sections 2.4.3, 7.1.1 and 8), and out-of-order or duplicate SvcParams on the wire. All as DecodeError rather than ArgumentError or RangeError, so a zone loader rescuing one line at a time survives a bad record.
  • Zone files. ZoneReader qualifies a relative TargetName against the origin. The TargetName sits mid-RDATA, before the SvcParams, out of reach of the existing trailing-name logic.

test/tc_svcb.rb adds 37 tests / 293 assertions, including every RFC 9460 Appendix D wire vector (Figures 2-10) and the Appendix D.3 records a parser must reject. test/tc_zone_reader.rb gains relative and absolute TargetNames, multi-line records, and quoted SvcParamValues both bare and inside grouping parentheses.

Prerequisite: ZoneReader reattaches a quoted tail as it was written

ZoneReader splits a line at its first ", parses the front half and glues the tail back on with a space. That works for TXT, whose quotes wrap a whole token, but not for SVCB, where they open mid-token: alpn="h2,h3" became alpn= "h2,h3" and failed to parse. The space is now added only where there was one to begin with.

That also fixes an existing bug: an unquoted character string containing \" was split in two, so TXT \"escaped read as "", "\"escaped" rather than as one string, though RFC 1035 Section 5.1 makes \" a literal quote. ZoneReader and RR.create now agree on these inputs.

Prerequisite: IPv6#to_s emits RFC 5952 canonical form

A behavior change to an existing public API, so worth a look on its own. Isolated in commit 70230fd if you would rather take it separately.

IPv6#to_s emitted uppercase hex and could shorten a shorter run of zero fields than RFC 5952 prescribes. It now emits lowercase hex, no leading zeros, and :: for the longest run of all-zero fields (never a single field), with mixed notation for ::ffff:0:0/96. It is in this PR because ipv6hint prints IPv6 addresses into zone files, where people read and diff them. Settling the form before the record type ships avoids changing its output again in a later release.

  • Affects the text output of AAAA, IPSECKEY and APL records, and log output. Code that string-compares the old uppercase form needs updating. Parsing and wire format are unchanged, and the old output always re-parsed correctly, so this is presentation only.
  • One deliberate departure from IPAddr#to_s and inet_ntop: the deprecated IPv4-compatible ::/96 range (RFC 4291 Section 2.5.5.1) keeps the hexadecimal form, because a dotted quad there would imply a semantic that no longer exists.
  • test/tc_ipv6.rb covers each RFC 5952 rule; one test/tc_ipseckey.rb expectation is updated.

Testing

bundle exec rake test_offline passes: 186 runs, 1754 assertions, 0 failures, 0 errors, and again under RUBYOPT="--enable-frozen-string-literal --debug-frozen-string-literal" to match the CI job. tc_svcb.rb and tc_ipv6.rb are added to test/ts_offline.rb. Release notes are filed under ## Unreleased.

@keithrbennett

Copy link
Copy Markdown
Contributor

I did not review the code myself, but offer these AI reviews (recommending accepting the PR):

https://chat.deepseek.com/share/gf010o0oduhpe6eqke
https://claude.ai/share/36e85291-5216-40a3-8907-0de9840be94b

@mrideout

Copy link
Copy Markdown
Contributor Author

@keithrbennett, thanks for sharing those reviews! It's great to see that my changes please our new AI overlords. :) I'm happy to make any adjustments if @alexdalitz (or other maintainers) have additional feedback once they have a chance to take a look.

@alexdalitz

alexdalitz commented Aug 13, 2026 via email

Copy link
Copy Markdown
Owner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants