2018-04-11 22:32:46 +07:00
|
|
|
<meta charset="utf-8" emacsmode="-*- markdown -*-">
|
|
|
|
**A warm welcome to DNS**
|
2018-04-13 16:02:59 +07:00
|
|
|
<!--<link rel="stylesheet" href="https://casual-effects.com/markdeep/latest/apidoc.css?">-->
|
|
|
|
Note: this page is part of the
|
|
|
|
'[hello-dns](https://powerdns.org/hello-dns/)' documentation effort.
|
2018-04-11 22:32:46 +07:00
|
|
|
|
2018-10-25 21:28:42 +07:00
|
|
|
# teaching DNS: Library, Authoritative, Resolver
|
2018-10-25 17:22:46 +07:00
|
|
|
Welcome to tdns, a 'from scratch' teaching DNS library. Based on `tdns`,
|
|
|
|
[`tauth`](tauth.md.html) and [`tres`](tres.md.html) implement all of [basic
|
2018-10-25 21:28:42 +07:00
|
|
|
DNS](../basic.md.html) and large parts of DNSSEC in ~~2000~~ ~~3000~~ 3100
|
|
|
|
lines of code. Code is
|
2018-04-14 05:12:53 +07:00
|
|
|
[here](https://github.com/ahupowerdns/hello-dns/tree/master/tdns). To
|
2018-04-14 02:13:37 +07:00
|
|
|
compile, see the end of this document.
|
2018-04-13 16:42:23 +07:00
|
|
|
|
|
|
|
Even though the 'hello-dns' documents describe how basic DNS works, and how
|
2018-10-25 17:22:46 +07:00
|
|
|
servers should function, nothing quite says how to do things
|
2018-04-13 16:42:23 +07:00
|
|
|
like actual running code. `tdns` is small enough to read in one sitting and
|
|
|
|
shows how DNS packets are parsed and generated. `tdns` is currently written
|
2018-04-13 18:02:01 +07:00
|
|
|
in C++ 2014, and is MIT licensed. Reimplementations in other languages are
|
|
|
|
highly welcome, as these may be more accessible to other programmers.
|
|
|
|
|
|
|
|
Please contact bert.hubert@powerdns.com or
|
|
|
|
[@PowerDNS_Bert](https://twitter.com/PowerDNS_Bert) if you have plans or
|
|
|
|
feedback.
|
2018-04-10 05:05:41 +07:00
|
|
|
|
2018-10-25 17:22:46 +07:00
|
|
|
The goals of `tdns`, `tauth` & `tres` are:
|
2018-04-10 05:05:41 +07:00
|
|
|
|
2018-04-13 16:42:23 +07:00
|
|
|
* Showing the DNS algorithms 'in code'
|
|
|
|
* Protocol correctness, except where the protocol needs updating
|
2018-04-10 05:05:41 +07:00
|
|
|
* Suitable for educational purposes
|
2018-04-13 16:02:59 +07:00
|
|
|
* Display best practices, both in DNS and security
|
2018-04-18 02:44:53 +07:00
|
|
|
* **Be a living warning for how hard it is to write a nameserver correctly**
|
2018-04-10 05:05:41 +07:00
|
|
|
|
|
|
|
Non-goals are:
|
2018-04-13 16:02:59 +07:00
|
|
|
|
2018-10-25 17:22:46 +07:00
|
|
|
* Performance
|
2018-04-13 16:02:59 +07:00
|
|
|
* Implementing more features (unless very educational)
|
2018-10-25 21:28:42 +07:00
|
|
|
* DNSSEC signing, validation
|
2018-04-18 02:44:53 +07:00
|
|
|
|
|
|
|
A more narrative explanation of what `tdns` is and what we hope it will
|
|
|
|
achieve can be found [here](intro.md.html).
|
2018-04-10 05:05:41 +07:00
|
|
|
|
2018-10-25 21:54:08 +07:00
|
|
|
The code for `tdns` can be found on [GitHub](https://github.com/ahupowerdns/hello-dns/blob/master/tdns/) and is also documented
|
|
|
|
using [Doxygen](codedocs/html).
|
|
|
|
|
2018-10-25 17:22:46 +07:00
|
|
|
# Objects in `tdns`
|
2018-04-13 18:02:01 +07:00
|
|
|
These are found in [dns-storage.hh](https://github.com/ahupowerdns/hello-dns/blob/master/tdns/dns-storage.hh)
|
|
|
|
and
|
|
|
|
[dns-storage.cc](https://github.com/ahupowerdns/hello-dns/blob/master/tdns/dns-storage.hh).
|
|
|
|
|
2018-04-13 05:02:37 +07:00
|
|
|
## DNSLabel
|
2018-04-13 04:53:01 +07:00
|
|
|
The most basic object in `tdns` is DNSLabel. `www.powerdns.com` consists of
|
|
|
|
three labels, `www`, `powerdns` and `com`. DNS is fundamentally case
|
|
|
|
insensitive (in its own unique way), and so is DNSLabel. So for example:
|
|
|
|
|
|
|
|
```
|
|
|
|
DNSLabel a("www"), b("WWW");
|
2018-10-25 17:22:46 +07:00
|
|
|
if(a==b) cout << "The same\n";
|
2018-04-13 04:53:01 +07:00
|
|
|
```
|
|
|
|
Will print 'the same'.
|
|
|
|
|
|
|
|
In DNS a label consists of between 1 and 63 characters, and these characters
|
|
|
|
can be any 8 bit value, including `0x0`. By making our fundamental data type
|
|
|
|
`DNSLabel` behave like this, all the rest of `tdns` automatically gets all
|
|
|
|
of this right.
|
|
|
|
|
|
|
|
When DNS labels contain spaces or other non-ascii characters, and a label
|
|
|
|
needs to be converted for screen display or entry, escaping rules apply. The
|
|
|
|
only place in a nameserver where these escaping rules should be enabled is
|
2018-04-13 16:02:59 +07:00
|
|
|
in the parsing or printing of DNS Labels.
|
|
|
|
|
|
|
|
The input to a `DNSLabel` is an unescaped binary string. The escaping
|
|
|
|
example from RFC 4343 thus works like this:
|
|
|
|
|
|
|
|
```
|
|
|
|
DNSLabel dl("Donald E. Eastlake 3rd");
|
|
|
|
cout << dl << endl; // prints: Donald\032E\.\032Eastlake\0323rd
|
|
|
|
```
|
2018-04-13 04:53:01 +07:00
|
|
|
|
2018-04-13 05:02:37 +07:00
|
|
|
## DNSName
|
2018-04-13 04:53:01 +07:00
|
|
|
A sequence of DNS Labels makes a DNS name. We store such a sequence as a
|
|
|
|
`DNSName`. To make this safe, even in the face of embedded dots, spaces and
|
|
|
|
other things, within `tdns` we make no effort to parse `www.powerdns.com` in
|
|
|
|
the code. Instead, use this:
|
|
|
|
|
|
|
|
```
|
|
|
|
DNSName sample({"www", "powerdns", "com"});
|
|
|
|
cout << sample <<"\n"; // prints www.powerdns.com.
|
|
|
|
|
|
|
|
sample.pop_back();
|
|
|
|
cout << sample << ", size: " << sample.size() << sample.size() << '\n';
|
|
|
|
// prints www.powerdns., size 2
|
|
|
|
|
|
|
|
```
|
|
|
|
|
2018-10-25 17:22:46 +07:00
|
|
|
Note: for convenience, when parsing human-generated input, `makeDNSName()`
|
|
|
|
is available to make a DNSName from a string.
|
|
|
|
|
2018-04-13 16:02:59 +07:00
|
|
|
Since a `DNSName` consists of `DNSLabel`s, it gets the same escaping. To
|
|
|
|
again emphasise how we interpret the input as binary, ponder:
|
|
|
|
|
|
|
|
```
|
|
|
|
DNSName test({"powerdns", "com."});
|
|
|
|
cout << test << endl; // prints: powerdns.com\..
|
|
|
|
|
|
|
|
const char zero[]="p\x0werdns";
|
|
|
|
DNSName test2({std::string(zero, sizeof(zero)-1), "com"});
|
|
|
|
|
|
|
|
cout << test2 << endl; // prints: p\000werdns.com.
|
|
|
|
```
|
|
|
|
|
2018-04-13 05:02:37 +07:00
|
|
|
## DNSType, RCode, DNSSection
|
2018-10-25 21:28:42 +07:00
|
|
|
These is an enums that contains the names and numerical values of the DNS
|
|
|
|
types and error codes. This means for example that `DNSType::A` corresponds
|
|
|
|
to 1 and `DNSType::SOA` to 6.
|
2018-04-13 04:53:01 +07:00
|
|
|
|
|
|
|
To make life a little bit easier, an operator has been defined which allows
|
|
|
|
the printing of `DNSTypes` as symbolic names. Sample:
|
|
|
|
|
|
|
|
```
|
|
|
|
DNSType a = DNSType::CNAME;
|
|
|
|
cout << a << "\n"; // prints: CNAME
|
2018-04-13 16:02:59 +07:00
|
|
|
|
2018-04-13 04:53:01 +07:00
|
|
|
a = (DNSType) 6;
|
|
|
|
cout << a <<" is "<< (int)a << "\n"; // prints: SOA is 6
|
|
|
|
```
|
|
|
|
|
|
|
|
Similar enums are defined for RCodes (response codes, RCode::Nxdomain for
|
|
|
|
example) and DNS Sections (Question, Answer, Nameserver/Authority,
|
|
|
|
Additional). These too can be printed.
|
|
|
|
|
2018-04-17 21:22:44 +07:00
|
|
|
## `tdig`
|
|
|
|
To discover how `tdns` works, let's start with the basics: sending DNS
|
|
|
|
queries and parsing responses. For this purpose, the `tdig` tool is
|
|
|
|
provided, somewhat modelled after the famous `dig` program created by ISC.
|
|
|
|
|
|
|
|
The code:
|
2018-10-25 17:22:46 +07:00
|
|
|
``` C++ linenumbers
|
|
|
|
int main(int argc, char** argv)
|
|
|
|
{
|
|
|
|
/* ... */
|
|
|
|
DNSName dn = makeDNSName(argv[1]);
|
|
|
|
DNSType dt = makeDNSType(argv[2]);
|
|
|
|
ComboAddress server(argv[3]);
|
|
|
|
|
|
|
|
DNSMessageWriter dmw(dn, dt);
|
|
|
|
dmw.dh.rd = true;
|
|
|
|
dmw.setEDNS(4000, false);
|
2018-04-17 21:22:44 +07:00
|
|
|
```
|
|
|
|
|
|
|
|
This starts out with the basics: it reads a `DNSName` from the first
|
|
|
|
argument to `tdns`, a `DNSType` from the second and finally a server IP
|
|
|
|
address from the third argument.
|
|
|
|
|
|
|
|
With this knowledge, in line 8 we create a `DNSMessageWriter` to make a
|
|
|
|
question for query name `dn` and query type `dt`. In addition, we set the
|
|
|
|
'recursion desired' flag.
|
|
|
|
|
|
|
|
Finally on line 10, we indicate our support for up to 4000 byte responses,
|
|
|
|
but we set the 'DNSSEC Ok' flag to false.
|
|
|
|
|
|
|
|
Next, mechanics:
|
|
|
|
|
|
|
|
```
|
|
|
|
1 Socket sock(server.sin4.sin_family, SOCK_DGRAM);
|
|
|
|
2 SConnect(sock, server);
|
|
|
|
3 SWrite(sock, dmw.serialize());
|
2018-10-25 21:28:42 +07:00
|
|
|
4 string resp = SRecvfrom(sock, 65535, server);
|
2018-04-17 21:22:44 +07:00
|
|
|
5
|
|
|
|
6 DNSMessageReader dmr(resp);
|
|
|
|
```
|
|
|
|
|
|
|
|
In line 1 we create a datagram socket appropriate for the protocol of
|
|
|
|
`server`. This is based on a small set of socket wrappers called
|
|
|
|
[simplesockets](https://github.com/ahuPowerDNS/simplesocket). On line 2 we
|
|
|
|
connect and on line 3 we serialize our DNSMessageWriter and send the
|
|
|
|
resulting packet. On line 4 we receive a response.
|
|
|
|
|
|
|
|
Finally on line 6 we parse that response into a `DNSMessageReader`.
|
|
|
|
|
|
|
|
```
|
|
|
|
1 DNSSection rrsection;
|
|
|
|
2 uint32_t ttl;
|
|
|
|
3
|
|
|
|
4 dmr.getQuestion(dn, dt);
|
|
|
|
5
|
|
|
|
6 cout<<"Received " << resp.size() << " byte response with RCode ";
|
|
|
|
7 cout << (RCode)dmr.dh.rcode << ", qname " << dn << ", qtype " << dt << endl;
|
|
|
|
8 std::unique_ptr< RRGen > rr;
|
|
|
|
9 while(dmr.getRR(rrsection, dn, dt, ttl, rr)) {
|
|
|
|
10 cout << dn<< " IN " << dt << " " << ttl << " " << rr->toString() << endl;
|
|
|
|
11 }
|
|
|
|
```
|
|
|
|
|
|
|
|
On lines 1 and 2 we declare some variable we'll need later to actually
|
|
|
|
retrieve the resource records. On line 4 we retrieved the name and type we
|
|
|
|
received an answer for, and on line 6 this all is displayed.
|
|
|
|
|
|
|
|
Line 8 declares 'rr' ready to receive our Resource Records, which are then
|
|
|
|
retrieved using the `getRR` method from the `DNSMessageReader` on line 9.
|
|
|
|
|
|
|
|
On line 10 we print what we found. Note that the `RRGen` object helpfully
|
|
|
|
has a `toString()` method for human friendly output.
|
|
|
|
|
|
|
|
|
2018-04-13 16:02:59 +07:00
|
|
|
|
2018-04-13 18:02:01 +07:00
|
|
|
|
|
|
|
# Parsing and generating DNS Messages
|
|
|
|
This code is in [dnsmessages.cc](https://github.com/ahupowerdns/hello-dns/blob/master/tdns/dnsmessages.cc)
|
|
|
|
and [dnsmessages.hh](https://github.com/ahupowerdns/hello-dns/blob/master/tdns/dnsmessages.hh).
|
|
|
|
|
2018-10-25 21:28:42 +07:00
|
|
|
## `RRGen`s: dealing with all the record types
|
|
|
|
DNS knows many record types, so we need a unified interface that can pass
|
|
|
|
all of them. For this purpose, `tdns` uses `RRGen` instances. `RRGen`s are
|
|
|
|
classes, one for each record type, all deriving from the `RRGen` base.
|
|
|
|
|
|
|
|
Each `RRGen` has a method called `toString()` which emits the record's
|
|
|
|
contents in familiar 'zonefile' format.
|
|
|
|
|
|
|
|
`RRGen`s can be created using their specific instance types, for example
|
|
|
|
like this:
|
|
|
|
|
|
|
|
```
|
|
|
|
ComboAddress ip("203.0.113.1");
|
|
|
|
auto agen = AGen::make(ip);
|
|
|
|
|
|
|
|
cout << agen->toString() << endl; // prints 203.0.113.1
|
|
|
|
|
|
|
|
auto soagen = SOAGen::make({"ns1", "powerdns", "com"},
|
|
|
|
{"bert.hubert", "powerdns", "com"}, 2018102301);
|
|
|
|
```
|
|
|
|
|
|
|
|
`RRGen`s also know how to serialize themselves from a `DNSMessageReader`, or how to
|
|
|
|
write themselves out to a `DNSMessageWriter`.
|
|
|
|
|
|
|
|
When reading DNS Messages (see below), `DNSMessageReader::getRR()` will
|
|
|
|
return `RRGen` instances to you, if you want to do more than print their
|
|
|
|
contents, you need to cast them to the specific type, for example:
|
|
|
|
|
|
|
|
```
|
|
|
|
ComboAddress ret;
|
|
|
|
ret.sin4.sin_family = 0;
|
|
|
|
if(auto ptr = dynamic_cast<AGen*>(rr.get()))
|
|
|
|
ret=ptr->getIP();
|
|
|
|
else if(auto ptr = dynamic_cast<AAAAGen*>(rr.get()))
|
|
|
|
ret=ptr->getIP();
|
|
|
|
```
|
|
|
|
|
|
|
|
This code from `tres` checks if a record is an IP or IPv6 address and
|
|
|
|
extracts the IP address - all without using ASCII.
|
|
|
|
|
2018-04-13 18:02:01 +07:00
|
|
|
## DNSMessageReader
|
|
|
|
This class reads a DNS message, and makes available:
|
|
|
|
|
|
|
|
* The query name (qname) and type (qtype)
|
|
|
|
* The dnsheader containing the flags
|
|
|
|
* EDNS buffer size and value of DNSSEC Ok flag
|
|
|
|
|
|
|
|
Of specific security note, this is one area where we might potentially have
|
|
|
|
to do pointer arithmetic. For security purposes, `DNSMessageReader` uses
|
|
|
|
bounds checking access methods exclusively.
|
|
|
|
|
2018-10-25 21:28:42 +07:00
|
|
|
Somewhat unexpectedly, parsing a packet does not immediately give the user
|
|
|
|
access to the query and type of the query (or response). The reason for this
|
|
|
|
is that there are packets that have no query defined. So to get the query,
|
|
|
|
call `getQuery()`.
|
|
|
|
|
|
|
|
Getting resource records from a `DNSMessageReader` happens via `getRR` which
|
|
|
|
returns record details and a smart pointer to an `RRGen` instance (as
|
|
|
|
described above).
|
|
|
|
|
|
|
|
A good example of how `DNSMessageReader` works can be found in
|
|
|
|
[`tdig.cc`](https://github.com/ahupowerdns/hello-dns/blob/master/tdns/dns-storage.hh).
|
|
|
|
|
2018-04-13 18:02:01 +07:00
|
|
|
## DNSMessageWriter
|
2018-04-13 23:08:49 +07:00
|
|
|
This class creates DNS messages, and in its constructor it needs to know the
|
|
|
|
name and type it is creating a message for.
|
|
|
|
|
|
|
|
Packets are only written in order. So it is not possible to
|
|
|
|
change the `qname` after adding a resource record. Resource records must
|
|
|
|
also be added together as RRSets, and in 'section order'.
|
2018-04-13 18:02:01 +07:00
|
|
|
|
|
|
|
Internally `DNSMessageWriter` again only uses bounds checked methods for
|
|
|
|
modifying its state.
|
|
|
|
|
2018-10-25 21:28:42 +07:00
|
|
|
A `DNSMessageWriter` has a maximum length (set via its constructor). If new
|
|
|
|
resource record, as written by `putRR`, would exceed this maximum length,
|
|
|
|
that record is rolled back and a std::out_of_range() exception is thrown.
|
|
|
|
This allows the caller to either truncate or decide this data was optional
|
|
|
|
anyhow.
|
|
|
|
|
|
|
|
Writing actual records to DNSMessageWriter proceeds via `putRR()` which
|
|
|
|
serializes `RRGen` instances to the message.
|
|
|
|
|
|
|
|
Samples of how to do this can be found in
|
|
|
|
[tres.cc](https://github.com/ahupowerdns/hello-dns/blob/master/tdns/dns-storage.hh)
|
|
|
|
and
|
|
|
|
[tauth.cc](https://github.com/ahupowerdns/hello-dns/blob/master/tdns/dns-storage.hh).
|
2018-04-13 18:02:01 +07:00
|
|
|
|
2018-04-14 05:12:53 +07:00
|
|
|
### Compression
|
2018-10-25 17:22:46 +07:00
|
|
|
DNS compression is unreasonably difficult to get right. In what happens to
|
|
|
|
be a coincidence, it turns out the DNS Tree can also be used to perform
|
2018-04-14 05:12:53 +07:00
|
|
|
DNS name compression.
|
|
|
|
|
|
|
|
For every invocation of `putName()` in `DNSMessageWriter()` we check the DNS
|
|
|
|
tree if it has a match on the full name, and if not, we add the name
|
|
|
|
and its components of the name to a DNS tree.
|
|
|
|
|
|
|
|
This effectively gets us the desired compression behaviour, except special
|
|
|
|
care has to be taken to not do wildcard processing.
|
|
|
|
|
2018-04-13 18:02:01 +07:00
|
|
|
## EDNS and truncation
|
|
|
|
EDNS tells us that a larger buffer size is available. However, even with
|
|
|
|
such a larger buffer size, a packet may exceed the available space. In that
|
|
|
|
case, the standard tells us to truncate the packet, and then still put an
|
|
|
|
EDNS record in the response.
|
|
|
|
|
2018-04-13 20:23:00 +07:00
|
|
|
The DNSMessageWriter, in somewhat of a layering violation, takes care of
|
|
|
|
this in `serialize()`.
|
2018-04-13 18:02:01 +07:00
|
|
|
|
2018-10-25 21:28:42 +07:00
|
|
|
|
2018-04-13 18:02:01 +07:00
|
|
|
# Internals
|
|
|
|
`tdns` uses several small pieces of code not core to dns:
|
|
|
|
|
|
|
|
* [nenum](https://github.com/ahupowerdns/hello-dns/blob/master/tdns/nenum.hh)
|
|
|
|
this is a simple 'named ENUM' construct that enables the printing of
|
|
|
|
DNSName::A
|
|
|
|
* [Simplesocket](https://github.com/ahupowerdns/simplesocket) a small set
|
|
|
|
of convenience functions for making sockets, parsing IP addresses etc.
|
|
|
|
* [Catch2](https://github.com/catchorg/Catch2) a unit test framework
|
|
|
|
|
|
|
|
# Compiling and running tdns
|
|
|
|
This requires a recent compiler version that supports C++ 2014. If you
|
|
|
|
encounter problems, please let me know (see above for address details).
|
|
|
|
|
|
|
|
```
|
|
|
|
$ git clone https://github.com/ahupowerdns/hello-dns.git
|
|
|
|
$ cd hello-dns/tdns
|
|
|
|
$ git submodule init
|
|
|
|
$ git submodule update
|
|
|
|
$ make
|
2018-10-25 17:22:46 +07:00
|
|
|
$ ./tauth [::1]:5300 &
|
2018-04-13 18:02:01 +07:00
|
|
|
$ dig -t any time.powerdns.org @::1 -p 5300 +short
|
|
|
|
time.powerdns.org. 3600 IN TXT "The time is Fri, 13 Apr 2018 12:55:54 +0200"
|
|
|
|
```
|
2018-04-14 02:13:37 +07:00
|
|
|
|
2018-10-25 21:47:33 +07:00
|
|
|
For more detauls, read on about [`tauth`](tauth.md.html), [`tres`](tres.md.html)
|
|
|
|
or the [C API](c-api.md.html).
|
2018-10-25 17:22:46 +07:00
|
|
|
|
2018-04-13 04:53:01 +07:00
|
|
|
<script>
|
|
|
|
window.markdeepOptions={};
|
|
|
|
window.markdeepOptions.tocStyle = "long";
|
|
|
|
</script>
|
2018-04-13 22:45:17 +07:00
|
|
|
<!-- Markdeep: --><style class="fallback">body{visibility:hidden;white-space:pre;font-family:monospace}</style><script src="../ext/markdeep.min.js"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>
|