Pdns: Add dnsdist rules for DoT and DoH connections to detect the DNS server name

Created on 19 Nov 2018  路  4Comments  路  Source: PowerDNS/pdns



  • Program: dnsdist
  • Issue type: Feature request

Short description


For DoT and DoH it would be very useful to differenciate between different services based on the DNS servers domain name. With TLS based connections a DNS client can add the Server Name Indication (SNI) in the Client Hello message. The idea is to store the SNI for the whole session context and make it available via a dnsdist rule. For DoH the server name can be extracted from the request URI.

Other information

For DoH it can be extracted from the request URI:
https://service1.example.com/dns-query

For DoT and DoH it can be extracted from the SNI in the Client Hello:
Extension: server_name (len=25)
Type: server_name (0)
Length: 25
Server Name Indication extension
Server Name list length: 23
Server Name Type: host_name (0)
Server Name length: 20
Server Name: service1.example.com

Usecase

Example rules in dnsdist:

  • TLSServerNameRule(servername)
    Matches queries with SNI value specified in servername.
  • HTTPServerNameRule(servername)
    Matches queries with DNS server domain name specified in servername.

If there would be a possibility to detect the DNS server name with dnsdist rules, there are countless possibilities. Here two examples:

Enable or disable options based on the DNS server name:

addAction(TLSServerNameRule("no-ecs.example.com"), DisableECSAction())
addAction(HTTPServerNameRule("no-ecs.example.com"), DisableECSAction())
addAction(AllRule(), PoolAction("DEFAULT"))

Send query to special downstream servers that offer different services (e.g. adblock, family-shield, safesearch for popular search engines, DNSSEC, etc.)

addAction(TLSServerNameRule("adblock.example.com"), PoolAction("BLOCK-ADS"))
addAction(HTTPServerNameRule("adblock.example.com"), PoolAction("BLOCK-ADS"))
addAction(AllRule(), PoolAction("DEFAULT"))

Description

It seems that DoT and DoH are becoming more and more popular and the number of (native) client implemenations is increasing. For those two DNS transports it is normally possible to set a domain name or a URL (just DoH) in the DNS resolver setting. Also https://dnscrypt.info/stamps-specifications is saying the hostname will "_be used as a SNI name_" for DoT and DoH.

Here some interesting DoT/DoH implementations:

In the past the only possibility to differenciate between different services was to have multiple different IPs on the same DNS Resolver or different ports (might be very hard to configure for most DNS clients). Another options is to make use of the source IP address. But this is sometimes very hard because you need to know the current IP address in realtime and especially for mobile devices the IP changes all the time because of different networks (RAN, WiFi, etc.). The DNS server domain name will not change and is independent of IP addresses, NAT scenarios and transport networks. Making use it to offer different services for DoT and DoH connections can be very useful for a lot of applications.

dnsdist feature request

Most helpful comment

An experiment, just for testing SNI extraction with OpenSSL (Debian Buster, version 1.1.1b-2) and (Debian Buster, libgnutls30 3.6.6-2): https://gist.github.com/Habbie/31a5a6a6dc15d1ba727fe3f1d23a968c

Observations:

  • both APIs support having more than one SNI; however, the TLS spec, as of RFC6066 and RFC8446, says there can only be one SNI, and there is only one defined type, that of a DNS hostname
  • those DNS hostnames are encoded in ASCII, with IDN names encoded as A-labels, i.e. xn--.... Names have no trailing dot, so for example, a legal SNI would be www.powerdns.com.) Nothing in the libraries promises that what we will receive is a valid DNS name. My proposal is to try to turn the SNI into a DNSName, and if that throws an exception, pretend there was no SNI.
  • RFC8446 (TLS1.3) (page 57) mentions that changing the SNI on session resumption is allowed, but discouraged. I suspect that libssl will do the right thing for us on SSL_handshake, but I did not test.
  • I tested both implementations with openssl s_client in TLS1.2 and TLS1.3 mode

'Now we just have to type it in'.

All 4 comments

I have just tested DoT ("Private DNS") with an Android 9 Pie emulator. It is always sending the SNI in Client Hello messages. And btw, it looks like it creates three connections to the DoT server in parallel.

An experiment, just for testing SNI extraction with OpenSSL (Debian Buster, version 1.1.1b-2) and (Debian Buster, libgnutls30 3.6.6-2): https://gist.github.com/Habbie/31a5a6a6dc15d1ba727fe3f1d23a968c

Observations:

  • both APIs support having more than one SNI; however, the TLS spec, as of RFC6066 and RFC8446, says there can only be one SNI, and there is only one defined type, that of a DNS hostname
  • those DNS hostnames are encoded in ASCII, with IDN names encoded as A-labels, i.e. xn--.... Names have no trailing dot, so for example, a legal SNI would be www.powerdns.com.) Nothing in the libraries promises that what we will receive is a valid DNS name. My proposal is to try to turn the SNI into a DNSName, and if that throws an exception, pretend there was no SNI.
  • RFC8446 (TLS1.3) (page 57) mentions that changing the SNI on session resumption is allowed, but discouraged. I suspect that libssl will do the right thing for us on SSL_handshake, but I did not test.
  • I tested both implementations with openssl s_client in TLS1.2 and TLS1.3 mode

'Now we just have to type it in'.

Similar 'just get the SNI out' hack for DoH. https://gist.github.com/Habbie/0ab650579120ac01dd3578b9a1a4215f

Note that this one wouldn't even know where to store the name.

After discussion with @pieterlexis and @rgacogne we realise that addXLocal is not the best interface to begin with, and that the list of paths in addDOHLocal really overstretches the concept. Ideally we'd rearchitect some of this. When we do that, we can also support separate certs for separate SNIs.

After discussion with @pieterlexis and @rgacogne we realise that addXLocal is not the best interface to begin with, and that the list of paths in addDOHLocal really overstretches the concept.

This would also be difficult especially with wildcard certificates or similar... My idea was to use a multidomain or a wildcard TLS certificate (e.g. *.example.com) and the different SNI names can be dynamically used with the dnsdist rule functions:

  • addAction(TLSServerNameRule("no-ecs.example.com"), DisableECSAction())
  • addAction(TLSServerNameRule("feature-a.example.com"), PoolAction("Feature-A"))
  • addAction(TLSServerNameRule("feature-b.example.com"), PoolAction("Feature-B"))
  • etc.

And if possible, make the SNI available in the DNSQuestion object (e.g. dq.sniName) for dynamic and custom Lua actions, debugging, etc.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

enilfodne picture enilfodne  路  6Comments

maikzumstrull picture maikzumstrull  路  6Comments

wiesl picture wiesl  路  8Comments

hxzhaoyf picture hxzhaoyf  路  6Comments

wojas picture wojas  路  8Comments