Announce HTTPS via DNS (HTTPS + SVCB records)

Tutorial for enabling HTTPS record using bind9 and RFC3597

Originally posted and last modified at .

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:

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.