rdata.html revision ecb6c5782ea248307e86c4bceac6c371d27576a6
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<TITLE>Adding new RDATA type</TITLE>
</HEAD>
<BODY>
<H2>Overview</H2>
The dns rdata routines (<CODE>dns_rdata_fromtext()</CODE>,
<CODE>dns_rdata_totext()</CODE>, <CODE>dns_rdata_fromwire()</CODE>,
<CODE>dns_rdata_towire()</CODE> <CODE>dns_rdata_fromstruct()</CODE>,
<CODE>dns_rdata_tostruct()</CODE> and <CODE>dns_rdata_compare()</CODE>)
are designed to provide a single set of routines
for encoding, decoding and comparing dns data preventing the problems that
occurred in BIND 8.x and earlier where there were multiple places in the
code base that
decoded wire format to internal format or compared rdata sometimes with
subtly different behaviour (bugs) or didn't support a particular type leading
to internal inconsistancy.
<P>
Each of these generic routines calls type specific routines that provide
the type specific details.
<P>
From time to time new types are defined and it is necessary to add these types
into the existing structure.
This document is written to provide instruction on how to do this.
<H2>Adding new RDATA types</H2>
Adding a new rdata type requires determining if the new rdata type is class
specific or generic.
Writing code to perform the following set of operations
and then integrating it into the build by placing the code into the rdata
hierachy at the correct place.
Running <CODE>make clean</CODE> followed <CODE>make</CODE> in
<P>
Each rdata module must perform the following operations:
<DL>
<DT>Convert from text format to internal format</DT>
<DT>Convert from internal format to text format</DT>
<DT>Convert from wire format to internal format</DT>
<DT>Convert from internal format to wire format</DT>
<DT>Convert from a structure to internal format</DT>
<DT>Convert from internal format to a structure</DT>
<DT>Compare two rdata in internal format<DT>
</DL>
<P>
There is an additional set of support <A HREF="#functions">functions</A> and
<A HREF="#macros">macros</A> only available to
to rdata code.
<H2>RDATA Hierarchy</H2>
The <CODE>rdata</CODE> hierarchy has the following format.
<PRE>
rdata/
generic/
<I>typename_typenumber</I>.h
<I>classname_classnumber</I>/
<I>typename_typenumber</I>.h
<PRE>
<P>
Initial rdata hierarchy:
<P>
<PRE>
rdata/
generic/
in_1/
any_255/
</PRE>
<H2>CLASSNAME and TYPENAME</H2>
Class and type names must be from the following alphabet and less that 11
characters in length or otherwise they will be ignored.
Permissible alphabet: a to z, 0 to 9 and dash (-).
Dash is mapped to underscore (_) for the C function names below.
<H2>Internal Format</H2>
The internal format chosen is DNS wire format without any compression being
applied to domain names in the rdata.
<H2>Convert from text format to internal format</H2>
The functions to convert from text format has the following call formats and
is declared as follows for class generic functions.
<PRE>
<CODE>static dns_result_t
fromtext_<I>typename</I>(dns_rdataclass_t class, dns_rdatatype_t type,
isc_lex_t *lexer, dns_name_t *origin,
isc_boolean_t downcase, isc_buffer_t *target);</CODE>
</PRE>
Class specific functions contain the class name in addition to the
type name.
<PRE>
<CODE>static dns_result_t
fromtext_<I>classname_typename</I>(dns_rdataclass_t class, dns_rdatatype_t type,
isc_lex_t *lexer, dns_name_t *origin,
isc_boolean_t downcase, isc_buffer_t *target);</CODE>
</PRE>
<DL>
<DT><CODE>class</CODE></DT>
<DD>
This argument should be ignored when used with a class generic RR type
otherwise <CODE>REQUIRE(class == #)</CODE> should be present at the start
of the function.
<DT><CODE>type</CODE></DT>
<DD>
This should be tested with a <CODE>REQUIRE(type == #)</CODE> statement at
the begining of the function.
<DT><CODE>lexer</CODE></DT>
<DD>
This is used to read the input text stream.
<DT><CODE>origin</CODE></DT>
<DD>
This is a absolute name used to qualify unqualified / partially qualified
domainnames in the text stream.
It is passed to the name parsing routines.
<DT><CODE>downcase</CODE></DT>
<DD>
This is passed to the name parsing routines to determine whether to downcase
the names it generates or leave them in the case they are pesented in.
<DT><CODE>target</CODE></DT>
<DD>
This is a <CODE>BINARY</CODE> buffer used to write the internal format of the rdata record being read in to.
</DL>
<CODE>fromtext_<I>typename</I>()</CODE> reads tokens from <CODE>lexer</CODE>,
up to but not including the end of line (EOL) token or end of file (EOF) token.
If the EOL / EOF token is read it should be returned to the input stream.
<A HREF="#gettoken"><CODE>gettoken()</CODE></A>
should be used to read the next token from the input stream and
will return EOL / EOF tokens
automatically unless
they are specifcally requested.
<CODE>isc_lex_ungettoken()</CODE> should
be used to return EOL / EOF (or any other token) to the input stream if
the EOL / EOF token is read.
Unused tokens will cause <CODE>dns_rdata_fromtext()</CODE> to return
<CODE>DNS_R_EXTRATOKEN</CODE> if <CODE>fromtext_<I>typename</I>()</CODE> was successful.
<P>
<CODE>fromtext_<I>typename</I>()</CODE> reads external input and as such is a high security area and must be paranoid about its input.
<H2>Convert from internal format to text format</H2>
<PRE>
<CODE>static dns_result_t
totext_<I>typename</I>(dns_rdata_t *rdata, dns_name_t *origin,
isc_buffer_t *target);</CODE>
</PRE>
<PRE>
<CODE>static dns_result_t
totext_<I>classname_typename</I>(dns_rdata_t *rdata, dns_name_t *origin,
isc_buffer_t *target);</CODE>
</PRE>
<DL>
<DT><CODE>rdata</CODE></DT>
<DD>
This is the rdata record to be converted from internal format to text.
<CODE>rdata->type</CODE> and <CODE>rdata->class</CODE> for class specific
RR types should be checked at the start of the function with
<CODE>REQUIRE(rdata->type ==�#)</CODE> statements.
<DT><CODE>origin</CODE></DT>
<DD>
If this in non <CODE>NULL</CODE> then any domainnames with this suffix
should be written out unqualified.
<A HREF="#name_prefix"><CODE>name_prefix()</CODE></A> can be used to
check if <CODE>origin</CODE> is <CODE>NULL</CODE> and provide the correct
arguments to the name conversion routines.
<DT><CODE>target</CODE></DT>
<DD>
This is a <CODE>TEXT</CODE> buffer used to hold the output.
</DL>
<H2>Convert from wire format to internal format</H2>
<PRE>
<CODE>static dns_result_t
fromwire_<I>typename</I>(dns_rdataclass_t class, dns_rdatatype_t type,
isc_buffer_t *source, dns_decompress_t *dctx,
isc_boolean_t downcase, isc_buffer_t *target);</CODE>
</PRE>
<PRE>
<CODE>static dns_result_t
fromwire_<I>classname_typename</I>(dns_rdataclass_t class, dns_rdatatype_t type,
isc_buffer_t *source, dns_decompress_t *dctx,
isc_boolean_t downcase, isc_buffer_t *target);</CODE>
</PRE>
<P>
<CODE>fromwire_<I>classname_typename</I>()</CODE> is required to set the valid
decompression methods if there is a domain name in the rdata.
<PRE>
<CODE>if (dns_decompress_edns(dctx) >= # || !dns_decompress_strict(dctx))
dns_decompress_setmethods(dctx, DNS_COMPRESS_ALL);
else
dns_decompress_setmethods(dctx, DNS_COMPRESS_GLOBAL14);</CODE>
</PRE>
<DL>
<DT><CODE>class</CODE></DT>
<DD>
This argument should be ignored when used with a class generic RR type
otherwise <CODE>REQUIRE(class == #)</CODE> should be present at the start
of the function.
<DT><CODE>type</CODE></DT>
<DD>
This should be tested with a <CODE>REQUIRE(type == #)</CODE> statement at
the begining of the function.
<DT><CODE>source</CODE></DT>
<DD>
This is a <CODE>BINARY</CODE> buffer with the <CODE>active</CODE> region
containing a RR record in wire format.
<DT><CODE>dctx</CODE></DT>
<DD>
This is the decompression context and is passed to
<CODE>dns_name_fromwire()</CODE>,
along with <CODE>downcase</CODE>, to enable a compressed domain name
to be extracted from the source.
<DT><CODE>downcase</CODE></DT>
<DD>
This is passed to <CODE>dns_name_fromwire()</CODE> to say whether the
extracted domainname should be downcased during the extraction.
<DT><CODE>target</CODE></DT>
<DD>
This is a <CODE>BINARY</CODE> buffer where the decompressed and checked
RR record is written.
</DL>
<CODE>fromwire_<I>typename</I>()</CODE> is a security sensitive routine
as it reads external data and should take extreme care to ensure that
the input data matches its description.
<P>
If the <CODE>active</CODE> buffer is not empty at completion and
<CODE>fromwire_<I>typename</I>()</CODE> was otherwise successful
<CODE>dns_rdata_fromwire()</CODE> will return <CODE>DNS_R_EXTRADATA</CODE>.
<H2>Convert from internal format to wire format</H2>
<PRE>
<CODE>static dns_result_t
towire_<I>typename</I>(dns_rdata_t *rdata, dns_compress_t *cctx,
isc_buffer_t *target);</CODE>
</PRE>
<PRE>
<CODE>static dns_result_t
towire_<I>classname_typename</I>(dns_rdata_t *rdata, dns_compress_t *cctx,
isc_buffer_t *target);<CODE>
</PRE>
<P>
<CODE>towire_<I>classname_typename</I>()</CODE> is required to set the
allowed name compression methods based on EDNS version if there is a
domain name in the rdata.
<PRE>
<CODE>if (dns_compress_getedns(cctx) >= #)
dns_compress_setmethods(cctx, DNS_COMPRESS_ALL);
else
dns_compress_setmethods(cctx, DNS_COMPRESS_GLOBAL14);</CODE>
</PRE>
<DL>
<DT><CODE>rdata</CODE></DT>
<DD>
This is the rdata record to be converted from internal format to text.
<CODE>rdata->type</CODE> and <CODE>rdata->class</CODE> for class specific
RR types should be checked at the start of the function with
<CODE>REQUIRE(rdata->type ==�#)</CODE> statements.
<DT><CODE>cctx</CODE></DT>
<DD>
This is the compression context, it should be passed to <CODE>dns_name_towire()</CODE> when putting domainnames on the wire.
<DT><CODE>target</CODE></DT>
<DD>
This is a <CODE>BINARY</CODE> buffer used to write the rdata to.
</DL>
Simple RR types without domainnames can use the following code to
transfer the contents of the <CODE>rdata</CODE> to the target buffer.
<PRE>
<CODE>return (<A HREF="#mem_tobuffer">mem_tobuffer</A>(target, rdata->data, rdata->length));</CODE>
</PRE>
<H2>Convert from a structure to internal format</H2>
<PRE>
<CODE>static dns_result_t
fromstruct_<I>typename</I>(dns_rdataclass_t class, dns_rdatatype_t type,
void *source, isc_buffer_t *target);</CODE>
</PRE>
<PRE>
<CODE>static dns_result_t
fromstruct_<I>classname_typename</I>(dns_rdataclass_t class, dns_rdatatype_t type,
void *source, isc_buffer_t *target);</CODE>
</PRE>
<DL>
<DT><CODE>class</CODE></DT>
<DD>
This argument should be ignored when used with a class generic RR type
otherwise <CODE>REQUIRE(class == #)</CODE> should be present at the start
of the function.
<DT><CODE>type</CODE></DT>
<DD>
This should be tested with a <CODE>REQUIRE(type == #)</CODE> statement at
the beginning of the function.
<DT><CODE>source</CODE></DT>
<DD>
This points to a type specific structure.
<DT><CODE>target</CODE></DT>
<DD>
This is a <CODE>BINARY</CODE> buffer used to write the internal format of the rdata record being read in to.
</DL>
<H2>Convert from internal format to a structure</H2>
<PRE>
<CODE>static dns_result_t
tostruct_<I>typename</I>(dns_rdata_t *rdata, void *target);</CODE>
</PRE>
<PRE>
<CODE>static dns_result_t
tostruct_<I>classname_typename</I>(dns_rdata_t *rdata, void *target);</CODE>
</PRE>
<DL>
<DT><CODE>rdata</CODE></DT>
<DD>
This is the rdata record to be converted from internal format to a structure.
<CODE>rdata->type</CODE> and <CODE>rdata->class</CODE> for class specific
RR types should be checked at the start of the function with
<CODE>REQUIRE(rdata->type ==�#)</CODE> statements.
<DT><CODE>target</CODE></DT>
<DD>
Pointer to a type specific structure.
</DL>
<H2>Compare two rdata in internal format</H2>
<PRE>
<CODE>static int
compare_<I>typename</I>(dns_rdata_t *rdata1, dns_rdata_t *rdata2);</CODE>
</PRE>
<PRE>
<CODE>static int
compare_<I>classname_typename</I>(dns_rdata_t *rdata1, dns_rdata_t *rdata2);</CODE>
</PRE>
Compares <CODE>rdata1</CODE> and <CODE>rdata2<CODE> as required for DNSSEC
ordering. The routine should
ensure that the <CODE>type</CODE> and <CODE>class</CODE> of the two rdata
match with <CODE>REQUIRE(rdata1->type == rdata2->type);</CODE> and
<CODE>REQUIRE(rdata1->class == rdata2->class);</CODE> statements. The
<CODE>rdata->type</CODE> should also be verified and if the RR type is
class specific the <CODE>rdata->class</CLASS>.
<P>
<CODE>compare_<I>classname_typename</I>()</CODE> returns -1, 0, 1.
<H2><A NAME="functions">Support Functions</A></H2>
The following static support functions are available to use.
<DL>
<DT><CODE>static unsigned int<BR>
name_length(dns_name_t *name);</CODE></DT>
<DD>
<P>
Returns the length of <CODE>name</CODE>.
<P>
<DT><CODE>static dns_result_t<BR>
txt_totext(isc_region_t *source, isc_buffer_t *target);</CODE></DT>
<DD>
<P>
Extracts the octet length tagged text string at the start of
<CODE>source</CODE> and writes it as a quoted string to <CODE>target</CODE>.
<CODE>source</CODE> is adjusted so that it points to first octet after the
text string.
<P>
Returns <CODE>DNS_R_NOSPACE</CODE> or <CODE>DNS_R_SUCCESS</CODE>.
<P>
<DT><CODE>static dns_result_t<BR>
txt_fromtext(isc_textregion_t *source, isc_buffer_t *target);</CODE></DT>
<DD>
<P>
Take the text region <CODE>source</CODE> and convert it to a length tagged
text string writing it to <CODE>target</CODE>.
<P>
Returns <CODE>DNS_R_NOSPACE</CODE>, <CODE>DNS_R_TEXTTOLONG</CODE>
or <CODE>DNS_R_SUCCESS</CODE>.
<P>
<DT><CODE>static dns_result_t<BR>
txt_fromwire(isc_buffer_t *source, isc_buffer_t *target);</CODE></DT>
<DD>
<P>
Read a octet length tagged text string from <CODE>source</CODE> and
write it to <CODE>target</CODE>.
Ensures that octet length tagged text string was wholly within the active
area of <CODE>source</CODE>.
Adjusts the active area of <CODE>source</CODE> so that it refers to the first
octet after the octet length tagged text string.
<P>
Returns <CODE>DNS_R_UNEXPECTEDEND</CODE>, <CODE>DNS_R_NOSPACE</CODE> or
<CODE>DNS_R_SUCCESS</CODE>.
<P>
<DT><A NAME="name_prefix"><CODE>static isc_boolean_t<BR>
name_prefix(dns_name_t *name, dns_name_t *origin, dns_name_t *target);</CODE>
</A></DT>
<DD>
<P>
If <CODE>origin</CODE> is NULL or the root label set <CODE>target<CODE> to
refer to <CODE>name</CODE> and return <CODE>ISC_FALSE</CODE>.
Otherwise see if <CODE>name</CODE> is a sub domain of <CODE>origin</CODE>
and are not equal.
If so make <CODE>target</CODE> refer to the prefix of <CODE>name</CODE> and
return <CODE>ISC_TRUE</CODE>.
Otherwise make <CODE>target</CODE> refer to <CODE>name</CODE> and return
<CODE>ISC_FALSE</CODE>.
<P>
Typical use:
<PRE><CODE>
static dns_result_t
totext_<I>typename</I>(dns_rdata_t *rdata, dns_name_t *origin,
isc_buffer_t * target)
{
isc_region_t region;
dns_name_t name, prefix;
isc_boolean_t sub;
dns_name_init(&name, NULL);
dns_name_init(&prefix, NULL);
dns_rdata_toregion(rdata, &region);
dns_name_fromregion(&name, &region);
sub = <B>name_prefix</B>(&name, origin, &prefix);
return (dns_name_totext(&prefix, sub, target));
}
</CODE></PRE>
<DT><CODE>static dns_result_t<BR>
str_totext(char *source, isc_buffer_t *target);</CODE></DT>
<DD>
<P>
This adds the <CODE>NULL</CODE> terminated string <CODE>source</CODE>
up to but not including <CODE>NULL</CODE> to <CODE>target</CODE>.
<P>
Returns <CODE>DNS_R_NOSPACE</CODE> and <CODE>DNS_R_SUCCESS</CODE>.
<P>
<DT><CODE>static isc_boolean_t<BR>
buffer_empty(isc_buffer_t *source);</CODE></DT>
<DD>
<P>
Returns <CODE>ISC_TRUE</CODE> if the active region of <CODE>source</CODE> is
empty otherwise <CODE>ISC_FALSE</CODE>.
<P>
<DT><CODE>static void<BR>
buffer_fromregion(isc_buffer_t *buffer, isc_region_t *region,
unsigned int type);</CODE></DT>
<DD>
<P>
Make <CODE>buffer</CODE> refer to the memory in <CODE>region</CODE> and
make it active.
<P>
<DT><CODE>static dns_result_t<BR>
uint32_tobuffer(isc_uint32_t value, isc_buffer_t *target);</CODE></DT>
<DD>
<P>
Write the 32 bit <CODE>value</CODE> in network order to <CODE>target</CODE>.
<P>
Returns <CODE>DNS_R_NOSPACE</CODE> and <CODE>DNS_R_SUCCESS</CODE>.
<P>
<DT><CODE>static dns_result_t<BR>
uint16_tobuffer(isc_uint32_t value, isc_buffer_t *target);</CODE></DT>
<DD>
<P>
Write them 16 bit <CODE>value</CODE> in network order to <CODE>target</CODE>.
<P>
Returns <CODE>DNS_R_RANGE</CODE>, <CODE>DNS_R_NOSPACE</CODE> and <CODE>DNS_R_SUCCESS</CODE>.
<P>
<DT><CODE>static isc_uint32_t<BR>
uint32_fromregion(isc_region_t *region);</CODE></DT>
<DD>
<P>
Returns the 32 bit at the start of <CODE>region</CODE> in host order.
<P>
Requires <CODE>(region->length >= 4)</CODE>.
<P>
<DT><CODE>static isc_uint16_t<BR>
uint16_fromregion(isc_region_t *region);</CODE></DT>
<DD>
<P>
Returns the 16 bit at the start of <CODE>region</CODE> in host order.
<P>
Requires <CODE>(region->length >= 2)</CODE>.
<P>
<DT><CODE>static dns_result_t<BR>
<A NAME="gettoken">gettoken</A>(isc_lex_t *lexer, isc_token_t *token, isc_tokentype_t expect, isc_boolean_t eol);</CODE></DT>
<DD>
<P>
Gets the next token from the input stream <CODE>lexer</CODE>. Ensure that the
returned token matches <CODE>expect</CODE> (isc_tokentype_qstring can also
return isc_tokentype_string), or isc_tokentype_eol and isc_tokentype_eof if
<CODE>eol</CODE> is <CODE>ISC_TRUE</CODE>.
<P>
Returns <CODE>DNS_R_UNEXPECTED</CODE>, <CODE>DNS_R_UNEXPECTEDEND</CODE>,
<CODE>DNS_R_UNEXPECTEDTOKEN</CODE> and <CODE>DNS_R_SUCCESS</CODE>.
<P>
</DT>
<DT><CODE>static dns_result_t<BR>
<A NAME="mem_tobuffer">mem_tobuffer</A>(isc_buffer_t *target, void *base, unsigned int length);</CODE></DT>
<DD>
<P>
Add the memory referred to by <CODE>base</CODE> to <CODE>target</CODE>.
<P>
Returns <CODE>DNS_R_NOSPACE</CODE> and <CODE>DNS_R_SUCCESS</CODE>.
<P>
<DT><CODE>static int<BR>
compare_region(isc_region_t *r1, isc_region_t *r2)</CODE></DT>
<DD>
<P>
Compares two regions returning -1, 0, 1 based on their DNSSEC ordering.
<P>
<DT><CODE>static int<BR>
hexvalue(char value);</CODE></DT>
<DD>
<P>
Returns the hexadecimal value of <CODE>value</CODE> or -1 if not
a hexadecimal character.
<P>
<DT><CODE>static int<BR>
decvalue(char value);</CODE></DT>
<DD>
<P>
Returns the decimal value of <CODE>value</CODE> or -1 if not
a decimal character.
<P>
<DT><CODE>static dns_result_t<BR>
base64_totext(isc_region_t *source, isc_buffer_t *target);</CODE></DT>
<DD>
<P>
Convert the region referred to by <CODE>source</CODE> to base64 encoded text
and put it into <CODE>target</CODE>.
<P>
Returns <CODE>DNS_R_NOSPACE</CODE> or <CODE>DNS_R_SUCCESS</CODE>.
<P>
<DT><CODE>static dns_result_t<BR>
base64_tobuffer(isc_lex_t *lexer, isc_buffer_t *target,
int length);</CODE></DT>
<DD>
<P>
Read a series of tokens from <CODE>lexer</CODE> that containing base64 data
until one of end of line, <CODE>length</CODE> (<CODE>length</CODE> >= 0)
bytes have been read or base64 pad characters are seen.
If <CODE>length</CODE> < 0 it is ignored otherwise it is an error if there
are not <CODE>length</CODE> octets of data or when processing a token
<CODE>length</CODE> octets would have been exceeded.
<P>
Returns <CODE>DNS_R_BADBASE64</CODE>, <CODE>DNS_R_UNEXPECTED</CODE>,
<CODE>DNS_R_UNEXPECTEDEND</CODE>, <CODE>DNS_R_UNEXPECTEDTOKEN</CODE>
and <CODE>DNS_R_SUCCESS</CODE>.
<P>
<DT><CODE>static dns_result_t<BR>
time_totext(unsigned long value, isc_buffer_t *target);</CODE></DT>
<DD>
<P>
Convert the date represented by <CODE>value</CODE> into YYYYMMDDHHMMSS format
taking into account the active epochs. This code is Y2K and Y2038 compliant.
<P>
Returns <CODE>DNS_R_NOSPACE</CODE> and <CODE>DNS_R_SUCCESS</CODE>.
<DT><CODE>static dns_result_t<BR>
time_tobuffer(char *source, isc_buffer_t *target);</CODE></DT>
<DD>
<P>
Take the date in <CODE>source</CODE> and convert it seconds since January 1,
1970 (ignoring leap seconds) and place the least significant 32 bits into
<CODE>target</CODE>.
<P>
Returns <CODE>DNS_R_RANGE</CODE>, <CODE>DNS_R_SYNTAX</CODE>,
<CODE>DNS_R_NOSPACE</CODE> and <CODE>DNS_R_SUCCESS</CODE>.
</DL>
<H2><A NAME="macros">Support Macros<A></H2>
The following macro is available:
<DL>
<DT><CODE>RETERR(x)</CODE><DT>
<DD>
<P>
Evaluate <CODE>x</CODE> and call <CODE>return (<I><value of x></I>);</CODE> if the result is not <CODE>DNS_R_SUCCESS</CODE>.
</DL>
</BODY>
</HTML>