[nsd-users] Adding a RR type in nsd

Yuri Schaeffer yuri at nlnetlabs.nl
Mon Jul 23 08:27:43 UTC 2012

Hi Stephane,

> 1) Is there a HOWTO-HACK somewhere? Apparently, I need to modify
> dns.h, dns.c (to describe the RR type) and of course zparser.y (to
> describe the presentation format) and that's all? It seems too simple
> :-)

A little late perhaps and you don't seem to need it but I'll post it
anyway. When I started working at NLnet Labs a couple of
years ago and first touching NSD code I wrote some text about
HOWTO-HACK-NEW-RRTYPE which I found useful at the time.

Yuri Schaeffer

Adding a new RR type

Say you want to implement a new resource record type. Let us introduce
the following imaginary and useless RR RANDINT:

    <owner> IN RANDINT <value (V)>

The RANDINT type gives the client a random 32 bit integer V. V is
present in the zonefile but is overwritten at (database) compile
time with a random value.

So what code do we need to update in order to handle this new type?
We first start with loading the record in to the database by zonec.
Zonec need to know our new type, these are defined in dns.h. We add
the following line, with C the assigned codepoint for our new type.

    #define TYPE_RANDINT	<C>	/* Random 32 bit int */

Now it needs to know what this type means, i.e. what sort of data it
contains. NSD knows what a 32 bit integer is (RDATA_ZF_LONG) but we
need some extra processing: picking a random number. Thus we make up
a new rdata, also in dns.h:

    RDATA_WF_RANDINT,		     /* Random 32 bit int */


    RDATA_ZF_RANDINT,		     /* Random 32 bit int */

and insert them in the appropriate enums. The first is how the data
appears on the wire and the latter in the zonefile. Now we can, in
dns.c, specify what our new type means:

    /* <C> */
      { RDATA_ZF_RANDINT } },

The numbers indicate the minimal and maximum occurrences of the
RDATA, in our case exactly one. T_RANDINT is meant for Yacc which is
used to generate a zonefile parser. In zparser.y one must specify
what a T_RANDINT is and how it should be handled (which functions
need to be called). For example a T_TEXT type does finally expand to
a sequence of STR. For each STR zadd_rdata_txt_wireformat() is
called from zonec.c.

We will not go in to any details about Yacc at this point. One
should be aware on the notion of regions at this point. zparser.y
has a global

    zparser_type *parser;

which is defined in zonec.h. parser has two regions `region' and
`rr_region'. The latter is rather volatile and will only exist
during the parsing of the current resource record. Data that needs
to stay goes in `region'.

Important variables are parser->current_rr.rdatas, an array pointing
to the rdata atoms, in our case this will be only one. And,
parser->current_rr.rdata_count, the length of former array. The
format for each of these rdatas is the wireformat, the first 2 bytes
the length and the following bytes binary data of that length.

If everything is well zonec now knows how to interpret our new
record type. NSD will be perfectly able to serve it too. NSD does
not care much about the format of our type. It just reads it from
the database as binary data and serves it on request. We still need
to implement the reverse operation, that is print our record to a
string. This is used by nsd-patch to write all the runtime changes
back to the zonefile. More specific, we need to translate our new
rdata type RDATA_WF_RANDINT.

rdata.c defines the `rdata_to_string_table'. For each RDATA_WF_X it
has a pointer to the rdata_X_to_string function. We insert our
custom function here at the appropriate place (hint: the `enum
rdata_wireformat' from dns.h). At last we need to specify the length
of our rdata atom in `rdata_wireformat_to_rdata_atoms()' in the
switch statement. Then we are done.

More information about the nsd-users mailing list