Announce HTTPS via DNS (HTTPS + SVCB records)
Tutorial for enabling HTTPS record using bind9 and RFC3597
Update 07/2022: Bind9 support for HTTPS/SVBC records has been merged in 2021, so if you’re using version published after that, you might not need the TYPE65 hack described here.
If you’ve been following the development of HTTP/3 protocol, you might have also noticed that there is ongoing process on IETF for adding two new DNS resource record types: SVCB and HTTPS. These new record types make it possible to announce some details of your webserver configuration at DNS level to reduce the number of TCP round trips while negotiating the HTTP configuration. In other words, you can announce via DNS that clients should use HTTP/2 or HTTP/3 to connect your server, without clients needing to first create HTTP connection and then reconnect with newer protocol.
Another cool new feature that comes with these records, is that now you can announce that your HTTP service is actually running on some other host and maybe on non-standard port.
As I noticed CloudFlare announcing on Twitter today that they’re now pushing HTTPS DNS records to their customers, I decided that maybe I should too. CloudFlare has quite extensive blog post on this topic, I’ll just focus here on DIY part, ie. how to setup these records with bind9. You might want to read their blog first, as it gives much better insight.
So, with these new records your DNS server can announce extra details about your site to your visitors:
- you can tell the client that it should just connect directly using one of the newer protocols (HTTP/2 or HTTP/3) to speed up the connection
- you can tell the client to connect to other host than what your APEX points to (you may eg. easily use CDN as to serve APEX content, or eg. use different servers to server different protocols etc etc.)
Unfortunately, as far as I know, currently these new records are only supported by iOS 14… but that’s not reason to not try something new. :) (HTTPSVC Status tracker for Chrome)
As bind9 version I’m using (9.11) lacks support for these new RR types, I’m going to use the RFC3597: Handling of Unknown DNS Resource Record (RR) Types to set up the records for my zone. (For the record, I’m not aware if newer bind9 versions support HTTPS/SVCB records, there is one unmerged merge request at their GitLab repository.)
As I’m not going to write extensive tutorial, I’ll skip some parts, and borrow something from CloudFlare blog: let’s assume we’d like to set up this record, but our DNS server doesn’t support that yet:
example.com 3600 IN HTTPS 1 . alpn=”h3,h2”
; ^--------------------------- RRTYPE
; ^--------------------- priority
; ^------------------- FQDN, where service is hosted (. = "look A/AAAA" for this domain), you can define eg. CDN here
; ^----------------- options, here "h3 and h2 supported"
Using the method defined in RFC3597 you define new/unknown records in hexadecimals, which isn’t pretty, but works. In the RFC they tell how you should enter your data to zone:
5. Text Representation
In the "type" field of a master file line, an unknown RR type is
represented by the word "TYPE" immediately followed by the decimal RR
type number, with no intervening whitespace. In the "class" field,
an unknown class is similarly represented as the word "CLASS"
immediately followed by the decimal class number.
This convention allows types and classes to be distinguished from
each other and from TTL values, allowing the "[<TTL>] [<class>]
<type> <RDATA>" and "[<class>] [<TTL>] <type> <RDATA>" forms of
[RFC1035] to both be unambiguously parsed.
The RDATA section of an RR of unknown type is represented as a
sequence of white space separated words as follows:
The special token \# (a backslash immediately followed by a hash
sign), which identifies the RDATA as having the generic encoding
defined herein rather than a traditional type-specific encoding.
An unsigned decimal integer specifying the RDATA length in octets.
Zero or more words of hexadecimal data encoding the actual RDATA
field, each containing an even number of hexadecimal digits.
If the RDATA is of zero length, the text representation contains only
the \# token and the single zero representing the length.
An implementation MAY also choose to represent some RRs of known type
using the above generic representations for the type, class and/or
RDATA, which carries the benefit of making the resulting master file
portable to servers where these types are unknown. Using the generic
representation for the RDATA of an RR of known type can also be
useful in the case of an RR type where the text format varies
depending on a version, protocol, or similar field (or several)
embedded in the RDATA when such a field has a value for which no text
format is known, e.g., a LOC RR [RFC1876] with a VERSION other than
0.
Even though an RR of known type represented in the \# format is
effectively treated as an unknown type for the purpose of parsing the
RDATA text representation, all further processing by the server MUST
treat it as a known type and take into account any applicable type-
specific rules regarding compression, canonicalization, etc.
The following are examples of RRs represented in this manner,
illustrating various combinations of generic and type-specific
encodings for the different fields of the master file format:
a.example. CLASS32 TYPE731 \# 6 abcd (
ef 01 23 45 )
b.example. HS TYPE62347 \# 0
e.example. IN A \# 4 0A000001
e.example. CLASS1 TYPE1 10.0.0.2
Ie. the format for HTTPS, RR type value 65 (see spec), record would be
<selector> IN TYPE65 \# <length of the data> <hex-encoded data>
As we need to do some conversion for the RDATA part of HTTPS record, I wrote small script for generating these records…
#!/bin/bash
set -e
RDATA="$@"
HEX="$(echo -n "${RDATA}" | od -A n -t x1 |tr -d '[:space:]')"
LEN="$(echo "${HEX}" |wc -c)"
echo ";@ IN HTTPS ${RDATA}"
echo "@ IN TYPE65 \\# $((${LEN} / 2)) ${HEX}"
so… with the example record borrowed from CF blog,
example.com 3600 IN HTTPS 1 . alpn=”h3,h2”
we would run
$ sh tmp.sh 1 . alpn="h3,h2"
;@ IN HTTPS 1 . alpn="h3,h2"
@ IN TYPE65 \# 16 31202e20616c706e3d2268332c683222
Yay! This is something bind is willing to consume. Actual HTTPS record is also provided as comment - when your DNS server begins to support it, it’s already there.
My own setup is currently
;@ IN HTTPS 1 ypcs.github.io. alpn="h2"
@ IN TYPE65 \# 25 3120797063732E6769746875622E696F2E20616C706E3D6832
as my site is currently hosted at GitHub pages, so I now announce at APEX that “please, look at github.io., and you can use HTTP/2”.
To check record using dig
,
$ dig ypcs.fi TYPE65 +short
\# 25 3120797063732E6769746875622E696F2E20616C706E3D6832
Now we just need to wait for Firefox and Chrome to support these new records to see how much this really affects to site speed.
Feedback / comments?
Either, send e-mail of ping at Mastodon (mastodontti.fi/@ypcs.