First publication of this article on 31 May 2022
Last update on of 25 February 2024
Let me introduce you to a new program, an experimental
authoritative DNS
server intended for dynamic answers (answers depending, for
instance, on the client). It is just for fun and it does not pretend
to replace existing programs. But you may want to read its source
code, or use its online demo, at
dyn.bortzmeyer.fr
.
My main goal was to have fun. A secondary goal was to have a service to get the IP address used by resolvers to query authoritative name servers. So, as said above, this program is not intended for mission-critical uses.
The program is named Drink, for no special reason. Its source code is available online, under a free-software licence. It uses the Elixir programming language and I'll talk about it later.
Let's first use the program. An instance is installed on the
domain dyn.bortzmeyer.fr
. The tests are done
with dig,
the links in this article are through the DNS looking glass. You can send TXT queries
to get help:
% dig dyn.bortzmeyer.fr TXT ... ;; ANSWER SECTION: dyn.bortzmeyer.fr. 0 IN TXT "Possible queries: " "hello/TXT to have a greeting. " "ip/TXT,A,AAAA to have the IP address of the client. ...
A query for the subdomain hello
will produce
a
greeting and the version numbers of the programs used:
% dig hello.dyn.bortzmeyer.fr TXT ... ;; ANSWER SECTION: hello.dyn.bortzmeyer.fr. 0 IN TXT "Hello, this is the Drink DNS server, version 0.1.0 and I use the dns library version 2.3.0, running on Elixir 1.13.2"
The most useful service today, in my opinion, is at the subdomain
ip
, returning the IP address of the DNS client:
% dig ip.dyn.bortzmeyer.fr AAAA ... ;; ANSWER SECTION: ip.dyn.bortzmeyer.fr. 0 IN AAAA 2001:860:de02:102::d2
Two important things about this service:
Here, we query through Quad9:
% dig @2620:fe::9 ip.dyn.bortzmeyer.fr A ... ;; ANSWER SECTION: ip.dyn.bortzmeyer.fr. 0 IN A 66.185.123.250
As you can see, we queried Quad9 over IPv6 but Quad9 queried our dynamic server over IPv4, using one of PCH addresses.
If you want to know the ECS option sent by your resolver, you can query
the ecs
service (TXT query).
You can get the date and time of the DNS server, in RFC 3339 syntax:
% dig TXT date.dyn.bortzmeyer.fr ... ;; ANSWER SECTION: date.dyn.bortzmeyer.fr. 0 IN TXT "2022-05-31T11:05:36.507343Z"
(Obviously, it is less efficient to synchronize clocks than NTP.) You can also get a random number, or IP address:
% dig A random.dyn.bortzmeyer.fr ... ;; ANSWER SECTION: random.dyn.bortzmeyer.fr. 0 IN A 149.154.12.82
It's probably a useless service but you can use it to explore
randomly the Internet, with commands such as whois $(dig
+short random.dyn.bortzmeyer.fr
A)
. Beware, there is a security
weakness in this command. Can you spot it? If you are the sort of person
who does curl http://random-site.example/something.sh |
sudo bash
, don't worry, this is not worse. Also, this
command is less fun with IPv6, since a great part of the IPv6 address
space is unallocated.
Drink has EDNS (RFC 6891) and recognizes a few options, such as the maximum size of the answer:
% dig @ns1-dyn.bortzmeyer.fr +bufsize=50 date.dyn.bortzmeyer.fr TXT ;; Truncated, retrying in TCP mode. ... ;; ANSWER SECTION: date.dyn.bortzmeyer.fr. 0 IN TXT "2022-05-31T11:24:09.318345Z" ... ;; MSG SIZE rcvd: 91
As you can see from the "Truncated, retrying in TCP mode.", Drink returned a response with the truncation flag, and dig retried with TCP (which is of course supported by Drink). Another thing you can do with EDNS, is to query NSID (Name Server Identifier, RFC 5001):
% dig @ns1-dyn.bortzmeyer.fr +nsid date.dyn.bortzmeyer.fr TXT ... ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1200 ; NSID: 6e 73 31 2d 64 79 6e 2e 62 6f 72 74 7a 6d 65 79 65 72 2e 66 72 ("ns1-dyn.bortzmeyer.fr") ... ;; ANSWER SECTION: date.dyn.bortzmeyer.fr. 0 IN TXT "2022-05-31T11:31:43.240583Z"
As you can see, the name server identifier is returned (not terribly useful in that case, but much more interesting if you use anycast). Drink also properly manage cookies (RFC 7873) and EDE (Extended DNS Errors, RFC 8914).
If you want not only the IP address of the machine querying
Drink, but also the port and the
transport protocol used (currently almot
always UDP), you can query the
subdomain connection
:
% dig connection.dyn.bortzmeyer.fr TXT ... ;; ANSWER SECTION: connection.dyn.bortzmeyer.fr. 0 IN TXT "80.77.95.49:58931 over UDP"
Another service is at the subdomain
bgp
, returning not only the IP address of the DNS client, but also the IP
prefix announced in the DFZ, and the
AS number which originates this prefix:
% dig bgp.dyn.bortzmeyer.fr TXT ... ;; ANSWER SECTION: bgp.dyn.bortzmeyer.fr. 3597 IN TXT "2605:4500:2:245b::bad:dcaf" "2605:4500::/32" "46636"
(It uses the bgp.bortzmeyer.org
HTTPS service.)
Here, the resolver 2605:4500:2:245b::bad:dcaf
is part of the prefix 2605:4500::/32
, announced
by AS
46636. This is specially interesting when you ask many
separate machines to query this name. For instance, with RIPE Atlas probes and the Blaeu
program, we can ask 20 probes to do a DNS resolution:
% blaeu-resolve --requested 20 --type TXT bgp.dyn.bortzmeyer.fr ["195.211.77.68" "195.211.76.0/23" "49825"] : 1 occurrences ["162.158.85.93" "162.158.84.0/22" "13335"] : 1 occurrences ["173.194.169.12" "173.194.0.0/16" "15169"] : 1 occurrences ["2001:610:1:40ba:145:100:185:17" "2001:610::/29" "1103"] : 1 occurrences ["192.87.106.106" "192.87.0.0/16" "1103"] : 1 occurrences ["45.32.149.90" "45.32.144.0/21" "20473"] : 1 occurrences ["185.73.24.170" "185.73.24.0/22" "201454"] : 1 occurrences ["66.96.115.242" "66.96.112.0/20" "715"] : 1 occurrences ["2a02:908:2:110b::24" "2a02:908::/33" "3209"] : 1 occurrences ["2404:4408:5::b9" "2404:4400::/28" "9790"] : 1 occurrences ["2607:fb90:c13e:fff0:b77b:5ed3:0:aaaa" "2607:fb90:c13e::/48" "22140"] : 1 occurrences ["2001:1438:2:14::11" "2001:1438::/32" "8881"] : 1 occurrences ["2a04:e4c0:14::69" "2a04:e4c0:14::/48" "36692"] : 1 occurrences ["195.121.117.200" "195.121.64.0/18" "8737"] : 1 occurrences ["66.185.123.252" "66.185.123.0/24" "42"] : 1 occurrences ["185.22.47.150" "185.22.44.0/22" "60294"] : 1 occurrences ["162.158.201.67" "162.158.200.0/22" "13335"] : 1 occurrences ["212.142.48.77" "212.142.32.0/19" "6830"] : 1 occurrences ["184.83.74.52" "184.83.0.0/16" "11232"] : 1 occurrences ["213.13.28.71" "213.13.0.0/16" "3243"] : 1 occurrences Test #41687133 done at 2022-06-11T10:17:45Z
Which gives us a glimpse of the resolvers they use (AS 12335 is Cloudflare and AS 15169 is Google, both operating public DNS resolvers used by some probes).
Another service? country
gives you the
country of your DNS resolver, as a ISO 3166
two-letter code:
% dig TXT country.dyn.bortzmeyer.fr ... ;; ANSWER SECTION: country.dyn.bortzmeyer.fr. 3600 IN TXT "FR"
Two warnings: geolocation is far from perfect
(it relies on databases which are not always up-to-date; by the way,
we currently use ipinfo). And
remember that it typically indicates the country of the machine, not
the country of the company controlling it. For instance, currently,
in France, users of Google
Public DNS see their resolver in
Belgium because this is where Google
machines reside. (A similar service for geolocation of the resolver
is _country.pool.ntp.org
.)
Note that requesting the full
service will
give you all the informations given by bgp
and
country
:
% dig TXT full.dyn.bortzmeyer.fr ... ;; ANSWER SECTION: full.dyn.bortzmeyer.fr. 3600 IN TXT "185.49.141.27" "185.49.140.0/23" "8587" "NL"
The list of funny and useful (?) services is long: there is also
unit
which will give you access to unit
conversions. See here
a conversion of bars to
kilopascals. Or you prefer to convert
kilobytes to kibis?
% dig 150KB.KiB.unit.dyn.bortzmeyer.fr TXT ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20561 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ... ;; ANSWER SECTION: 150KB.KiB.unit.dyn.bortzmeyer.fr. 86400 IN TXT "146.48 KiB"
If you still live in the 19th century and want to convert from/to the old english units to the international ones, you can do it, too:
% dig 15000ft.m.unit.dyn.bortzmeyer.fr TXT ... ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32635 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ... ;; ANSWER SECTION: 15000ft.m.unit.dyn.bortzmeyer.fr. 86393 IN TXT "4571.78 m"
To get an idea of all the possible unit conversions, see the documentation of the library we use. A big warning, though: some unit names are case-sensitive but the DNS is supposed to be case-INsensitive. If you go through a resolver/forwarder which changes the case (which it is entitled to do), you may get errors such as "undetermined conversion".
Let's see another service, op
, to perform arithmetic (a
good opportunity to remind everyone that domain names are
not limited to letters, digits and hyphen):
% dig 2+2.op.dyn.bortzmeyer.fr TXT ... 2+2.op.dyn.bortzmeyer.fr. 86400 IN TXT "4" % dig 2+3\*4-\(5-2\).op.dyn.bortzmeyer.fr TXT ... 2+3*4-\(5-2\).op.dyn.bortzmeyer.fr. 86400 IN TXT "11"
Note that some characters were special for the Unix shell (not for the DNS) and had to be
escaped. Some were special both for the shell and for the zone file
format, this is why dig escaped them. (A similar service, but
requiring RPN, is at
rp.secret-wg.org
. See its
documentation.)
Not more useful, but funny, the number formatting service. You indicate a number and a language (as a language tag) and you get the number spelled in words:
% dig +short 42.en.number.dyn.bortzmeyer.fr TXT "forty-two" % dig +short 42.fr.number.dyn.bortzmeyer.fr TXT "quarante-deux" % dig +short 65656734.ca.number.dyn.bortzmeyer.fr TXT "seixanta-cinc milions sis-cent cinquanta-sis mil set-cent trenta-quatre"
Not all languages are represented, for disk space and memory
reasons. We currently have arabic
(ar
), catalan (ca
), german (de
),
english
(en
), french (fr
) and
dutch
(nl
). Note also that, for some languages
(surprinsingly, not for french), the answer may be not
ASCII but full
UTF-8. This will require a DNS client able to
display it, something that typical traditional DNS clients cannot
do. (Note that there is no standard for the
character set and
encoding of the
characters strings in a TXT record value. No way to tag them as
« UTF-8 ». This explains the lack of clients.) An example of such a
client is the DNS
Looking Glass, another is the fediverse
DNS bot.
And a last service, weather reports. You query for a city (here, Quimper) and whether you want current weather:
% dig quimper.now.weather.dyn.bortzmeyer.fr TXT ... ;; ANSWER SECTION: quimper.now.weather.dyn.bortzmeyer.fr. 1800 IN TXT "Quimper" "Sunny" "25.0 C" "precipitation 0.0 mm" "wind 15.1 km/h" "cloudiness 0 %" "humidity 44 %"
Or the weather for the next day (here, in La Paz):
% dig la-paz.tomorrow.weather.dyn.bortzmeyer.fr TXT ... ;; ANSWER SECTION: la-paz.tomorrow.weather.dyn.bortzmeyer.fr. 1800 IN TXT "La Paz" "Sunny" "9.2 C" "precipitation 0.0 mm" "wind 7.6 km/h" "cloudiness 0 %" "humidity 20 %"
Finally, note that Drink, unlike all (most?) other dynamic DNS services, is fully protected with DNSSEC. Responses are dynamically signed and can therefore be authenticated by the resolver:
% dig +dnssec random.dyn.bortzmeyer.fr LOC ... ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 ... ;; ANSWER SECTION: random.dyn.bortzmeyer.fr. 10 IN LOC 38 5 56.000 N 179 42 40.000 E 5741.00m 0.00m 0.00m 0.00m random.dyn.bortzmeyer.fr. 10 IN RRSIG LOC 8 4 10 ( 20230216040000 20230211154000 63937 dyn.bortzmeyer.fr. NaOD0XqTdT7L3b2QmuyzpW3ATDppIRdWq9YvGc97TRlW ... ;; Query time: 48 msec ;; WHEN: Sun Feb 12 10:51:08 CET 2023 ;; MSG SIZE rcvd: 290
(See the 'ad' flag? It means "Authenticated Data".) You can check the DNSSEC configuration with DNSviz if you want:
Drink can also report statistics about the traffic it saw. For that, you have to activate its IPC interface and the statistics service. Then, you can query it (here, formatting the JSON with jq):
% echo statistics | socat - UNIX-CONNECT:/var/run/drink/drink.sock | jq . { "protocols": { "tcp": 342, "udp": 15047 }, "qnames": { "1/0.op.dyn.bortzmeyer.fr": 1, "2 + 2 * 5 - 10 - 1.op.dyn.bortzmeyer.fr": 1, "2+2.op.dyn.bortzmeyer.fr": 1, "32mb.kib.unit.dyn.bortzmeyer.fr": 1, "OTHER NAME": 8556, "bgp.dyn.bortzmeyer.fr": 1, "bogus.bortzmeyer.fr": 1, "date.dyn.bortzmeyer.fr": 1, "dyn.bortzmeyer.fr": 1918, "ecs.dyn.bortzmeyer.fr": 3, "hello.dyn.bortzmeyer.fr": 5, "ip.dyn.bortzmeyer.fr": 10, "random.dyn.bortzmeyer.fr": 4383 }, "qtypes": { "A": 10656, "AAAA": 2218, "ANY": 7, "AXFR": 2, "CNAME": 3, "DNSKEY": 856, "DS": 1, "LOC": 2, "MX": 8, "NS": 18, "PTR": 9, "SOA": 1013, "SRV": 2, "TXT": 54, "URI": 2 }, "rcodes": { "NOERROR": 6326, "NXDOMAIN": 14, "REFUSED": 8542 }, "running-seconds": 59316, "running-time": "16 hours 28 minutes 36 seconds", "server-start": "2023-04-25T15:15:54.585281Z", "when": "2023-04-26T07:44:30.314052Z" }
OK, let's assume you're convinced and you want to install Drink
on your machines. Since it is written in the Elixir
programming language, which requires a
runtime, you need to install Elixir, and also
the Erlang sources, to compile the DNS
library (on Debian, this is apt
install elixir erlang-dev erlang-src
, on
Arch, pacman -S elixir
erlang-nox
). Then, get the code with
git and just type mix
deps.get
then mix run drink.exs
(see
the README.md
file for details). You will
probably need to create a configuration file or to provide options
on the command line, see again README.md
for
details.
For the ip
service, you can find similar
existing services:
resolver.00f.net
(A and AAAA, if the
request is over IPv4, it creates an IPv4-mapped IPv6 address, see
RFC 4291, section 2.5.5.2); besides Drink, it seems to
be one of the few whose source code is
available,dns.toys
is a bit different, it is not
a zone in the usual DNS tree, you need to query the authoritative
server directly (and so you need a clean DNS path, something which
is not always available, for instance at WiFi hotspots, and you
won't learn the IP address of your resolver); try
dig ip @dns.toys
(and see their other funny
services); its source code (in Go) is
available,_country.pool.ntp.org
(TXT queries)
displays the IP address of the client, its port, the country, and
ECS information.whoami.v4.powerdns.org
and
whoami.v6.powerdns.org
(TXT, A and
AAAA),o-o.myaddr.l.google.com
(only TXT
queries, does also
ECS),whoami.fastly.net
and
whoami6.fastly.net
(A, AAAA, and TXT queries,
the TXT queries also give you ECS information).whoami.akamai.net
(A and AAAA),resolver-identity.cloudfront.net
(A and
AAAA),whoami.ultradns.net
(only A requests,
even if the servers have IPv6),If you know other similar DNS services on the Internet, don't hesitate to report them. (They tend to be short-lived, many old ones no longer work, or return broken results.)
If you have ideas about services implemented on Drink, or bugs to report, please create a ticket.
Now, a few words about the source code. One of the reasons of the choice of the programming language Elixir is its excellent support of parallelism (or, rather, the excellent support of parallelism by the Erlang virtual machine). This is priceless for Internet servers. One process (an Elixir process, not an operating system process) is created for each connection (a connection being an UDP request or a "real" connection, with TCP) and the parallelism between clients is efficiently handled by the virtual machine. This isolation of processes (they share nothing, not even memory) also protects the server against malicious or broken clients, that can wait for a long time sending anything, or can send badly crafted DNS messages (something which is quite common on the Internet). A process may crash (for instance when attempting to decode a malformed DNS message) but the server will continue to serve the other clients.
The Drink server handles, of course, UDP and TCP (which is mandatory, see RFC 7766 and RFC 9210, but often forgotten in "custom" DNS servers). This is also something that is relatively easy with Elixir parallelism.
The DNS library I used saved me a lot of work, thanks to its author. But it also has some limitations. For instance, it has no support for EDNS options so I had to add it (not a big deal, but still annoying).
If you know Elixir, and its culture, you may be surprised to see that Drink does not use many things common in the Elixir world, such as OTP supervisors or protocols like GenServer. This is because, for this specific case, it seems to me they were not adding a lot of value. I may change my mind later.
One word about performance, testing with the excellent dnsperf tool. On a PC with two 2.6 Ghz cores, running dnsperf with one hundred parallel sending threads yields 10,000 requests per second and zero failure. (This is with Drink's logging and DNSSEC disabled, if you log each query, the DNS server will be much slower, and if you sign answers as well.)
% cat data ip.test A hello.test TXT test SOA % ./src/dnsperf -n 10000 -c 100 -s 127.0.0.1 -p 3553 -d data DNS Performance Testing Tool Version 2.9.0 [Status] Command line: dnsperf -n 10000 -c 100 -s 127.0.0.1 -p 3553 -d data [Status] Sending queries (to 127.0.0.1:3553) [Status] Started at: Tue May 31 21:12:09 2022 [Status] Stopping after 10000 runs through file [Status] Testing complete (end of file) Statistics: Queries sent: 30000 Queries completed: 30000 (100.00%) Queries lost: 0 (0.00%) Response codes: NOERROR 30000 (100.00%) Average packet size: request 25, response 90 Run time (s): 2.974810 Queries per second: 10084.677677 Average Latency (s): 0.009701 (min 0.000258, max 0.028758) Latency StdDev (s): 0.002650 Latency StdDev (s): 0.022502
Otherwise, Drink implementation has been the subject of a talk at OARC meeting #40 in Atlanta (february 2023), centered about its usage (slides are available online), and at FOSDEM (february 2023), mostly focused on internal implementation, slides and video are available online.
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)