[bind10-dev] DNS message API: the message class
JINMEI Tatuya / 神明達哉
jinmei at isc.org
Thu Oct 1 08:22:42 UTC 2009
As I submitted a shorter report last night (PDT), I've committed the
first prototype implementation of the DNS message class with sample
code to show its feasibility. This is a more comprehensive
description of the class and its related API design.
Comments/questions/suggestions are highly appreciated as usual.
General Design Goals:
easy to use: this API is expected to be used by external developers,
so the interface should be as simple and intuitive as possible.
standard conformance: it should support all major protocol features,
such as TSIG/SIG(0), EDNS, dynamic update.
performance-aware: still, the underlying implementation should be
able to be very efficient. for example, we should allow a
pre-compiled, wire-formatted data source to construct a DNS
message based on its data with no or very few copies.
extensibility: it should be able to (at least) support BIND9's
feature set, such as rrset-order, preferred-glue, sortlist (even
if these are not available in the 1st-year version). It should
also be possible (and easy) for an external developer to add their
own extensions.
(ambitious goals, aren't they? :-)
Existing APIs:
I've referenced various existing libraries/APIs that provide similar
functionalities:
- BIND9 libdns (the message module): good in the sense of
conformance, but not easy to use and can be slower due to its
redundant (generalized) design
- ldns: looks good, but IMO we could provide more convenient APIs.
it also (seemingly) misses some protocol features as I noted in
the truncation discussion.
- script based tools (dnspython and dnsruby): intuitive, but the
implementation is straightforward (which is not a bad thing
itself) and would run slower. they'd also be slower due to the
obvious reason: being a script.
The Class/API Description:
Message class: this is a container of an abstract notion of a "DNS
message". A wire-format message is converted to/from this message
object, and the application manipulates a DNS message through an
instance of this object. All existing APIs (see above) have a
similar class/structure, so, it should be easy to understand what's
the message class at a high level.
See the (tentative) class definition in
experiments/jinmei-messageapi/dnsmessage.hh of the svn repository.
I believe the class methods (member functions) are generally
straightforward. These include:
- accessor to the DNS header fields: {get,set}_qid(),
{get,set}_{qr,tc,rd,ra,ad,cd}(), {get,set}_opcode(),
{get,set}_rcode()
- manipulate RRsets for a specified section
(question/answer/auth/additional): add_rrset(), get_section(),
etc.
- rendering and parsing to/from wire format: to_wire(), parse()
- other advanced features such as TSIG/EDNS (TBD)
For extensibility, I've introduced some helper classes that are
expected to be work with the message class:
- MessageRenderer
Encapsulates states and algorithm for rendering a message in wire
format. This is an abstract class, and the user will use a
specific concrete subclass depending on their usage. In the
initial prototype I wrote a simple subclass
"MessageRendererToBuffer", which just encapsulates a packet buffer
(and something like this will be a "default renderer" when the
user doesn't have a particular preference). We may want to add
more optional/conditional strategies to this or other subclasses,
e.g., what to do when the message is too large.
Another future possibility I have in my mind is an "iovector-based
renderer" for higher performance. It will be used with a
pre-compiled, wire-formatted data source, and build a packet as a
vector of pointers to the pre-formatted data. We'll then use
sendmsg() with this vector, thereby minimizing data copies.
- NameCompressor
Encapsulates states and algorithm of DNS name compression. This
class will be used with a renderer and a message. In the initial
prototype version, it's just empty (i.e., name compression isn't
supported). A straightforward implementation would be something
like BIND9's dns_compress_t. A performance aware version should
also be possible (although I don't have a specific idea)
- RRSetsSorter
Encapsulates the algorithm of how to sort RRsets in a given
section (for rendering). I'm intending to use this as a
generalized algorithm class for the "preferred-glue" option (A
then AAAA or vice versa). BIND9 hard-codes the logic of this
option in the rendering function, but I thought we should separate
the logic into a dedicated class for future extensibility and for
making the main rendering code logic simpler. See
Message::to_wire() defined in dnsmessage.cc to understand how this
class would be used.
- MessageParser
Encapsulates states and algorithm for parsing a wire-format
message to construct a DNS Message class instance. Like
MessageRenderer, this is an abstract class, and a specific
subclass will be used as an actual parser, depending on the user
usage.
I've not given a complete example subclass in the initial
prototype. But a straightforward implementation would be, well,
straightforward:-) it would parse the wire-format RRs one by one,
construct an RRSet instance containing concrete Rdata instances
for the RRs, and add it to the message. A higher performance
version would probably delay the actual instantiation and just
maintain a pointer or something to represent the internal
structure.
Performance Considerations:
Some performance related considerations are given above. Here's one
more point: I considered how we can optimize the rendering process
when we use optimized data source. If we convert the source data to
a generic RRset containing a list of generic Rdata, it will be
pretty expensive and slow. What I'm currently imagining is
something like this:
we have a specialized (sub)class of RRset. It has the same
interface as the generic RRset, which contains name/rdclass/rdtype
and list of rdata in a straightforward way, but the specialized
version implements the data more efficiently:
SpecialRRset {
public:
to_wire(); // same interface
private:
char* name; //already wire-format
char* commondata; // wire-format class, type, TTL
char* rdata; // list of rdata in wire-format
};
and, while the generic RRset's to_wire() would call the to_wire()
method of its internal name, TTL, rdclass, rdtype instances and
iterate over its internal list of Rdata (for its to_wire() method),
the special version's to_wire() would directly copy (or even just
make a reference to) its already wire-formatted data stored in the
corresponding data source.
This is actually similar to what the recent versions of NSD does, if
I understand the code correctly. So, if we can do this well, we'd
be able to achieve at least the same or close level of response
performance as NSD while still providing more generic and intuitive
APIs.
---
JINMEI, Tatuya
More information about the bind10-dev
mailing list