An API Framework of Supporting Various Types of RDATA

0. About This Document

This document describes a proposal of a general framework for supporting the implementation of DNS RDATA.

1. Introduction

In libdns++ we define a separate derived class of Rdata for each RR type (NS, SOA, AAAA, etc). Each derived class implements its own version of common methods, such as "from text" and "from wire" constructors, toText() and toWire() methods, depending on the field representation and semantics of the RDATA.

Using a separate class will make the interface more type safe and help eliminate some corner cases that could lead to subtle bugs. For example, the type system ensures that the generic::NS class has exactly 1 field and its type is a domain name. On the other hand, the developer needs to provide a slightly different implementation for each type.

While implementing and testing these for a specific RR type wouldn't be that hard, it's not a trivial task. There are still many unimplemented types, and the total development cost won't be negligible.

Also, this development cost can be a barrier for an external developer in developing a new or experimental types of RR/RDATA.

It would be nicer if we can provide a simpler and generic way to support more types with less development cost. This proposal tries to address this issue.

We use some new concepts (which are not yet available) that are suggested in Zone loading API design.

2. Proposed Framework

The basic idea is to introduce stateless "strategy" classes (as in the strategy design pattern) for common RDATA fields (8, 16, 32-bit integer, string, domain name, etc). Each class for a specific field type defines (and implements) a set of algorithms corresponding to the common Rdata methods.

An abstract base class defines the interface of the strategy class:

class RdataFieldStrategy {
    // Extract one string token from the lexer, parse and convert it according
    // to the specific field type; the conversion result (type specific -
    // in many cases it would be wire-format representation of the data) will
    // be stored in the given vector.
    virtual void textToData(MasterLexer& lexer, const Name& origin,
                            vector<uint8_t>& data) const = 0;

    // Extract a field value from the given input buffer, and (after
    // conversion, if necessary) store it in the given vector.
    virtual void wireToData(InputBuffer& buffer, vector<uint8_t>& data) const
    = 0;

    // Interpret the given data as a value of the field type and convert it
    // to a textual representation.
    virtual std::string toText(const vector<uint8_t>& data) const = 0;

    // Interpret the given data as a value of the field type and render it
    // in the given buffer.
    virtual void toWire(const vector<uint8_t>& data,
                        util::OutputBuffer& buffer) const = 0;

    // Interpret the given data as a value of the field type and render it
    // in the given renderer.  In particular, if the field type is a domain
    // name, it specifies whether it has to be compressed or not.
    virtual void toWire(const vector<uint8_t>& data,
                        AbstractMessageRenderer& renderer) const = 0;

Then we define derived classes for some commonly used types of fields. Referring to we'd have the following derived classes:

class RdataUint8Field;          // for type "I1"
class RdataUint16Field;         // for type "I2"
class RdataUint32Field;         // for type "I4"
class RdataIPv4Field;           // for type "A"
class RdataIPv6Field;           // for type "AAAA"
class RdataNameField;           // for type "N"
class RdataStringField;         // for type "S"

The implementation of the class method should be pretty straightforward. For example:

RdataUint8Field::textToData(MasterLexer& lexer,
                            const Name&, // unnecessary for this type
                            vector<uint8_t>& data) const
    const string& token = lexer.getStringToken();
    try {
    } catch (const boost::bad_lexical_cast&) {
        isc_throw(RdataParseError, "failed to convert to 8-bit int: "
                  << token);

We also define a constant instance of these classes like this:

const RdataUint8Field RDATA_UINT8_FIELD;
const RdataUint16Field RDATA_UINT16_FIELD;
// ...

Since these are stateless and don't even have a member variable, they can be statically initialized and can be safely shared.

Then we can represent RDATA fields as a sequence of these constants. Again, referring to the dnsextlang" examples, we could define MX and SRV fields as follows:

const RdataFieldStrategy* mx_fields[] = {
const RdataFieldStrategy* srv_fields[] = {

(for simplicity we ignore qualifiers for the name fields here)

And then we can define a generic parser function that can convert a textual representation of RDATA into a set of vector<uint8_t> in RR-type independent manner:

createRdataFields(MasterLexer& lexer, const Name& origin,
                  const RdataFieldStrategy* field_strategies[],
                  vector<vector<uint8_t> >& field_values)
    const RdataFieldStrategy* strategy;
    for (size_t i = 0; (strategy = field_strategies[i]) != NULL; ++i) {
        vector<uint8_t> field_data;
        strategy->textToData(lexer, origin, field_data);

On top of these, a simplest form of new RDATA implementation can now be a simple wrapper of the generic function:

// Field definition
const RdataFieldStrategy* mytype_fields[] = {

// From text constructor
MyCoolType::MyCoolType(MasterLexer& lexer, const Name& origin) {
    // field_values_ is a member variable of type vector<vector<uint8_t> >
    createRdataFields(lexer, origin, mytype_fields, field_values_);

Other methods could be simplified this way.

Built-in RDATA types in libdns++ would probably need some more customized processing: internal representation may not be in the form of vector<vector<uint8_t> >, and/or it may need more specialized validation/conversion (such as converting a mnemonic of crypto algorithm into an iteger). To support these cases, each derived RdataFieldStrategy class could provide a non-virtual conversion method into some more type-specific value, e.g.

// This is a non virtual method specific to RdataUint8Field
RdataUint8Field::getUint8(MasterLexer& lexer,
                          const Name&, // unnecessary for this type
                          vector<uint8_t>& data) const
    const string& token = lexer.getStringToken();
    try {
        return (boost::lexical_cast<uint8_t>(token));
    } catch (const boost::bad_lexical_cast&) {
        isc_throw(RdataParseError, "failed to convert to 8-bit int: "
                  << token);

Using these interfaces, a revised version of "from text" constructor of the RRSIG RDATA class would look like this:

RRSIG::RRSIG(MasterLexer& lexer, const Name& origin) {
    const RRType covered_txt =
        RRType(RDATA_STRING_FIELD.getString(lexer, origin));
    const uint8_t algorithm = RDATA_UINT8_FIELD.getUint8(lexer, origin);
    const uint8_t labels = RDATA_UINT8_FIELD.getUint8(lexer, origin);
    const uint32_t orig_ttl = RDATA_UINT8_FIELD.getUint32(lexer, origin);
    const uint32_t timeexpire =
        timeFromText32(RDATA_STRING_FIELD.getString(lexer, origin));
    const uint32_t timeinception =
        timeFromText32(RDATA_STRING_FIELD.getString(lexer, origin));
    const uint16_t tag = RDATA_UINT16_FIELD.getUint32(lexer, origin);
    const Name& signer = RDATA_NAME_FIELD.getName(lexer, origin);
    vector<uint8_t> signature;
    RDATA_BASE64_FIELD.getData(signature, lexer, origin);

It will be much simpler than the current implementation, and yet more complete in error handling.

Last modified 6 years ago Last modified on Apr 21, 2012, 12:45:14 AM