update documentation and content population
This commit is contained in:
209
tdns/README.md
209
tdns/README.md
@ -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>
|
@ -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"));
|
||||
|
Reference in New Issue
Block a user