update documentation and content population

This commit is contained in:
bert hubert
2018-04-12 23:53:01 +02:00
parent 54c5271800
commit 13d3588de2
2 changed files with 203 additions and 19 deletions

View File

@ -58,11 +58,69 @@ rules, lots of problems can never happen.
The core or `tdns` therefore is the tree of nodes as intended in 1034,
containing DNS native objects like DNS Labels and DNS Names.
## The DNS Tree
# Objects
## DNS Objects
### DNSLabel
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");
if(a==b) cout<<"The same\n";
```
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
in the parsing of DNS Labels.
### DNSName
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
```
### DNSType, RCode, DNSSection
This is an enum that contains the names and numerical values of the DNS
types. This means for example that `DNSType::A` corresponds to 1 and
`DNSType::SOA` to 6.
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
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.
# The DNS Tree
The DNS Tree is of fundamental importance, and is used a number of times
within `tdns`.
When storing data for the org zone, it may look like this:
When storing the contents of the `org` zone, it may look like this:
*************************************************************************************************
* *
@ -85,7 +143,7 @@ When storing data for the org zone, it may look like this:
* *
*************************************************************************************************
This three has a depth of four. The top node has an empty name, and is
This tree has a depth of four. The top node has an empty name, and is
relative to the name of the zone, in this case `org`.
On layer 4, we find the names `ns1.ord.ietf.org` and `ns2.fra.ietf.org`. Key
@ -93,8 +151,9 @@ to looking up anything in DNS is to follow the tree downwards and to observe
what nodes are passed.
For example, a lookup for `www.ietf.org` starts as a lookup for `www.ietf`
in the `org` zone (if loaded, of course). Layer 1 is where we start, and we
look if there is a child node called `ietf`. And there is.
in the `org` zone (if loaded, of course). Layer 1 is where we start (and
find the Start of Authority record), and we look if there is a child node
called `ietf`. And there is.
As we look at that node, we could see NS records attached to it (`ietf.org NS
ns1.ord.ietf.org`) for example. This means our lookup is done: we've found
@ -107,18 +166,140 @@ downward again, starting at the apex with `ns1.ord.ietf` and going to the
`ietf`, `ord` and finally `ns1` labels. There we find attached the IP(v6)
addresses.
TBC..
## Objects
`tdns` uses a DNS tree in two places: 1) to quickly find the right zone for
a query 2) within that zone, to traverse the names.
The DNS tree within `tdns` consists of `DNSNode` objects, each of which can
have:
* Child nodes
* Pointer to a zone
* Attached RRSets, keyed on type
This is implemented in `dns-storage.cc` and `dns-storage.hh`.
The child nodes are always used in the DNS tree. The pointer to a zone is
only used when consulting the 'tree of zones'. The attached RRsets meanwhile
are only consulted when the right zone is found, to provide actual DNS
answers.
This lookup mechanism will tell you if a name is fully present in a zone, or
if it was matched by an NS record. It will also perform wildcard matching,
but not CNAME chasing.
## Manipulating the tree
To add nodes to the DNS tree, or to add things to existing nodes, use the
`add` method like this:
# Best practices
The code does not do any form of DNS escaping. Instead, DNS names are stored
and manipulated as a sequence of DNS labels. So instead of messing with
"www.powerdns.org", we use {"www", "powerdns", "org"}.
```
newzone->add({"www"})->addRRs(CNAMEGen::make({"server1","powerdns","org"}));
newzone->add({"www"})->rrsets[DNSType::CNAME].ttl = 1200;
```
The first line creates the `www` node, and provisions a CNAME there. The
second line updates the new node to set the ttl. Note that `addRRs` accepts
multiple 'generator' parameters, more about which later.
`add` accepts `DNSName`s as parameter, so to populate
www.fra.ietf.org, use `newzone->add({"www", "fra", "ietf", "org"})`.
Finding nodes in the tree uses a slightly more complicated method called
`find`. Unlike `add` it will not modify the tree, even though it has in
common that it will return a pointer to a node.
`find` however also returns some additional things: which parts of the
`DNSName` did not match a node, if a DNS zonecut was encountered while
traversing the tree, and what name it had.
The syntax:
```
DNSName searchname({"www", "ietf", "org"}), lastname, zonecutname;
DNSNode* passedZonecut;
DNSNode* node = bestzone->find(searchname, lastname, &passedZonecut, &zonecutname);
```
When this operates on the `org` zone tree displayed above, after the call to
`find`, `searchname` will be `www`, while `lastname` is `{"ietf", "org"}`.
What this means was that the `www` label could not be matched in the tree,
since it isn't there.
`passedZonecut` is set to the node that describes `ietf.org`, where NS
records live that describe the delegation. `zonecutname` is therefore set to
`ietf.org`.
To clarify this further, a lookup for `ns1.ord.ietf.org` would end up with:
* `searchname` empty: all labels of `ns1.ord.ietf.org` were matched
* `lastname` is then `ns1.ord.ietf.org`
* `passedZonecut` again points to the `{"ietf", "org"}` node, which has the
NS RRSet that describes the delegation
* `zonecutname` is set to `{"ietf", "org"}`.
The DNS Tree is aware of `*` semantics, and when traversing nodes and not
finding a match, it will look for a `*` node. The tree does not do any
special processing for CNAMEs though.
Based on the `find` method, implementing the RFC 1034 DNS algorithm is very
straightforward.
## Record generators
As noted above, `RRSet`s contain things like `CNAMEGen::make`. These are
generators that are stored in a `DNSNode` and that know how to put their
content into a `DNSMessageWriter`. Each implemented `DNSType` has at least
one associated generator. A more complete example of populating a zone looks
like this:
```
newzone->addRRs(SOAGen::make({"ns1", "powerdns", "org"}, {"admin", "powerdns", "org"}, 1),
NSGen::make({"ns1", "powerdns", "org"}), NSGen::make({"ns2", "powerdns", "org"}),
MXGen::make(25, {"server1", "powerdns", "org"})
);
newzone->add({"server1"})->addRRs(AGen::make("213.244.168.210"), AAAAGen::make("::1"));
```
This attaches SOA, NS and MX records to the apex of a zone, and defines a
`server1` node that is also referenced in the MX record.
Since there are many record types, it is imperative that adding a new one
needs to happen in only one place. Within `tdns`, it actually requires two
places: the `DNSType` enum needs to be updated with the numerical value of
the type, and a 'XGen` struct needs to be written. Luckily this is simple
enough. Here is the entire MX record implementation:
```
1 struct MXGen : RRGen
2 {
3 MXGen(uint16_t prio, const DNSName& name) : d_prio(prio), d_name(name) {}
4 static std::unique_ptr< RRGen > make(uint16_t prio, const DNSName& name)
5 {
6 return std::make_unique< MXGen >(prio, name);
7 }
8 void toMessage(DNSMessageWriter& dpw) override;
9 DNSType getType() const override { return DNSType::MX; }
10 uint16_t d_prio;
11 DNSName d_name;
12 };
...
13 void MXGen::toMessage(DNSMessageWriter& dmw)
14 {
15 dmw.putUInt16(d_prio);
16 dmw.putName(d_name);
17 }
```
Line 3 stores the priority and server name of this MX record (as defined in
lines 10 and 11).
Lines 4-7 are mechanics so we can make a smart pointer for an MXGen type
using a call to `make`. This smart pointer is sort of reference counted in
that its reference count is always 1. This means there is no overhead.
Line 8 defines the call that transposes this record into a
`DNSMessageWriter`. Line 9 announces to anyone who wants to know what the
`DNSType` of this generator is. This is used by `addRRs` as shown above to
put the generator in the right RRSet place.
13 to 17 show the construction of the actual DNS resource record in a
packet: the 16 bit priority, followed by the name.
<script>
window.markdeepOptions={};
window.markdeepOptions.tocStyle = "long";
</script>
<!-- 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>

View File

@ -6,20 +6,23 @@ void loadZones(DNSNode& zones)
auto zone = zones.add({"powerdns", "org"});
auto newzone = zone->zone = new DNSNode(); // XXX ICK
newzone->addRRs(SOAGen::make({"ns1", "powerdns", "org"}, {"admin", "powerdns", "org"}, 1));
newzone->addRRs(MXGen::make(25, {"server1", "powerdns", "org"}));
newzone->addRRs(SOAGen::make({"ns1", "powerdns", "org"}, {"admin", "powerdns", "org"}, 1),
NSGen::make({"ns1", "powerdns", "org"}), NSGen::make({"ns2", "powerdns", "org"}),
MXGen::make(25, {"server1", "powerdns", "org"})
);
newzone->add({"server1"})->addRRs(AGen::make("213.244.168.210"), AAAAGen::make("::1"));
newzone->addRRs(AGen::make("1.2.3.4"));
newzone->addRRs(AAAAGen::make("::1"));
newzone->rrsets[DNSType::AAAA].ttl= 900;
newzone->addRRs(NSGen::make({"ns1", "powerdns", "org"}), NSGen::make({"ns2", "powerdns", "org"}));
newzone->addRRs(TXTGen::make("Proudly served by tdns compiled on " __DATE__ " " __TIME__),
TXTGen::make("This is some more filler to make this packet exceed 512 bytes"));
newzone->add({"www"})->rrsets[DNSType::CNAME].add(CNAMEGen::make({"server1","powerdns","org"}));
newzone->add({"www2"})->rrsets[DNSType::CNAME].add(CNAMEGen::make({"nosuchserver1","powerdns","org"}));
newzone->add({"server1"})->addRRs(AGen::make("213.244.168.210"), AAAAGen::make("::1"));
newzone->add({"server2"})->addRRs(AGen::make("213.244.168.210"), AAAAGen::make("::1"));
newzone->add({"*", "nl"})->rrsets[DNSType::A].add(AGen::make("5.6.7.8"));