2N/A<!
DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
2N/A <
TITLE>Adding new RDATA type</
TITLE>
2N/AThe dns rdata routines (<
CODE>dns_rdata_fromtext()</
CODE>,
2N/A<
CODE>dns_rdata_totext()</
CODE>, <
CODE>dns_rdata_fromwire()</
CODE>,
2N/A<
CODE>dns_rdata_towire()</
CODE> <
CODE>dns_rdata_fromstruct()</
CODE>,
2N/A<
CODE>dns_rdata_tostruct()</
CODE> and <
CODE>dns_rdata_compare()</
CODE>)
2N/Aare designed to provide a single set of routines
2N/Afor encoding, decoding and comparing dns data preventing the problems that
2N/Aoccurred in BIND
8.x and earlier where there were multiple places in the
2N/Adecoded wire format to internal format or compared rdata sometimes with
2N/Asubtly different behaviour (bugs) or didn't support a particular type leading
2N/Ato internal inconsistancy.
2N/AEach of these generic routines calls type specific routines that provide
2N/Athe type specific details.
2N/AFrom time to time new types are defined and it is necessary to add these types
2N/Ainto the existing structure.
2N/AThis document is written to provide instruction on how to do this.
2N/A<
H2>Adding new RDATA types</
H2>
2N/AAdding a new rdata type requires determining if the new rdata type is class
2N/AWriting code to perform the following set of operations
2N/Aand then integrating it into the build by placing the code into the rdata
2N/Ahierachy at the correct place.
2N/ARunning <
CODE>make clean</
CODE> followed <
CODE>make</
CODE> in
2N/A<
CODE>
lib/
dns</
CODE> will cause the new rdata type to be picked up.
2N/AEach rdata module must perform the following operations:
2N/A<
DT>Convert from text format to internal format</
DT>
2N/A<
DT>Convert from internal format to text format</
DT>
2N/A<
DT>Convert from wire format to internal format</
DT>
2N/A<
DT>Convert from internal format to wire format</
DT>
2N/A<
DT>Convert from a structure to internal format</
DT>
2N/A<
DT>Convert from internal format to a structure</
DT>
2N/A<
DT>Compare two rdata in internal format<
DT>
2N/AThere is an additional set of support <
A HREF="#functions">functions</
A> and
2N/A<
A HREF="#macros">macros</
A> only available to
2N/A<
H2>RDATA Hierarchy</
H2>
2N/AThe <
CODE>rdata</
CODE> hierarchy has the following format.
<
I>typename_typenumber</
I>.h
<
I>classname_classnumber</
I>/
<
I>typename_typenumber</
I>.h
<
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.
The internal format choosen 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.
<
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>
Class specific functions contain the class name in addition to the
<
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>
<
DT><
CODE>class</
CODE></
DT>
This argument should be ignored when used with a class generic RR type
otherwise <
CODE>REQUIRE(class == #)</
CODE> should be present at the start
<
DT><
CODE>type</
CODE></
DT>
This should be tested with a <
CODE>REQUIRE(type == #)</
CODE> statement at
the begining of the function.
<
DT><
CODE>lexer</
CODE></
DT>
This is used to read the input text stream.
<
DT><
CODE>origin</
CODE></
DT>
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>
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>
This is a <
CODE>BINARY</
CODE> buffer used to write the internal format of the rdata record being read in to.
<
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
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.
<
CODE>fromtext_<
I>typename</
I>()</
CODE> reads external input and as such is a high security area and must be paranoid about its input.
<
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>
<
CODE>static dns_result_t
totext_<
I>typename</
I>(dns_rdata_t *rdata, dns_name_t *origin,
isc_buffer_t *target);</
CODE>
<
CODE>static dns_result_t
totext_<
I>classname_typename</
I>(dns_rdata_t *rdata, dns_name_t *origin,
isc_buffer_t *target);</
CODE>
<
DT><
CODE>rdata</
CODE></
DT>
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>
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>
This is a <
CODE>TEXT</
CODE> buffer used to hold the output.
<
H2>Convert from wire format to internal format</
H2>
<
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>
<
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>
<
CODE>fromwire_<
I>classname_typename</
I>()</
CODE> is required to set the valid
decompression methods if there is a domain name in the rdata.
<
CODE>if (dns_decompress_edns(dctx) >= # || !dns_decompress_strict(dctx))
dns_decompress_setmethods(dctx, DNS_COMPRESS_ALL);
dns_decompress_setmethods(dctx, DNS_COMPRESS_LOCAL);</
CODE>
<
DT><
CODE>class</
CODE></
DT>
This argument should be ignored when used with a class generic RR type
otherwise <
CODE>REQUIRE(class == #)</
CODE> should be present at the start
<
DT><
CODE>type</
CODE></
DT>
This should be tested with a <
CODE>REQUIRE(type == #)</
CODE> statement at
the begining of the function.
<
DT><
CODE>source</
CODE></
DT>
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>
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>
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>
This is a <
CODE>BINARY</
CODE> buffer where the decompressed and checked
<
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.
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>
<
CODE>static dns_result_t
towire_<
I>typename</
I>(dns_rdata_t *rdata, dns_compress_t *cctx,
isc_buffer_t *target);</
CODE>
<
CODE>static dns_result_t
towire_<
I>classname_typename</
I>(dns_rdata_t *rdata, dns_compress_t *cctx,
isc_buffer_t *target);<
CODE>
<
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.
<
CODE>if (dns_compress_getedns(cctx) >= #)
dns_compress_setmethods(cctx, DNS_COMPRESS_ALL);
dns_compress_setmethods(cctx, DNS_COMPRESS_LOCAL);</
CODE>
<
DT><
CODE>rdata</
CODE></
DT>
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>
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>
This is a <
CODE>BINARY</
CODE> buffer used to write the rdata to.
Simple RR types without domainnames can use the following code to
transfer the contents of the <
CODE>rdata</
CODE> to the target buffer.
<
CODE>return (<
A HREF="#mem_tobuffer">mem_tobuffer</
A>(target, rdata->data, rdata->length));</
CODE>
<
H2>Convert from a structure to internal format</
H2>
<
CODE>static dns_result_t
fromstruct_<
I>typename</
I>(dns_rdataclass_t class, dns_rdatatype_t type,
void *source, isc_buffer_t *target);</
CODE>
<
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>
<
DT><
CODE>class</
CODE></
DT>
This argument should be ignored when used with a class generic RR type
otherwise <
CODE>REQUIRE(class == #)</
CODE> should be present at the start
<
DT><
CODE>type</
CODE></
DT>
This should be tested with a <
CODE>REQUIRE(type == #)</
CODE> statement at
the beginning of the function.
<
DT><
CODE>source</
CODE></
DT>
This points to a type specific structure.
<
DT><
CODE>target</
CODE></
DT>
This is a <
CODE>BINARY</
CODE> buffer used to write the internal format of the rdata record being read in to.
<
H2>Convert from internal format to a structure</
H2>
<
CODE>static dns_result_t
tostruct_<
I>typename</
I>(dns_rdata_t *rdata, void *target);</
CODE>
<
CODE>static dns_result_t
tostruct_<
I>classname_typename</
I>(dns_rdata_t *rdata, void *target);</
CODE>
<
DT><
CODE>rdata</
CODE></
DT>
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>target</
CODE></
DT>
Pointer to a type specific structure.
<
H2>Compare two rdata in internal format</
H2>
compare_<
I>typename</
I>(dns_rdata_t *rdata1, dns_rdata_t *rdata2);</
CODE>
compare_<
I>classname_typename</
I>(dns_rdata_t *rdata1, dns_rdata_t *rdata2);</
CODE>
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>.
<
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.
<
DT><
CODE>static unsigned int<
BR>
name_length(dns_name_t *name);</
CODE></
DT>
Returns the length of <
CODE>name</
CODE>.
<
DT><
CODE>static dns_result_t<
BR>
txt_totext(isc_region_t *source, isc_buffer_t *target);</
CODE></
DT>
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
Returns <
CODE>DNS_R_NOSPACE</
CODE> or <
CODE>DNS_R_SUCCESS</
CODE>.
<
DT><
CODE>static dns_result_t<
BR>
txt_fromtext(isc_textregion_t *source, isc_buffer_t *target);</
CODE></
DT>
Take the text region <
CODE>source</
CODE> and convert it to a length tagged
text string writing it to <
CODE>target</
CODE>.
Returns <
CODE>DNS_R_NOSPACE</
CODE>, <
CODE>DNS_R_TEXTTOLONG</
CODE>
or <
CODE>DNS_R_SUCCESS</
CODE>.
<
DT><
CODE>static dns_result_t<
BR>
txt_fromwire(isc_buffer_t *source, isc_buffer_t *target);</
CODE></
DT>
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.
Returns <
CODE>DNS_R_UNEXPECTEDEND</
CODE>, <
CODE>DNS_R_NOSPACE</
CODE> or
<
CODE>DNS_R_SUCCESS</
CODE>.
<
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>
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>
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
totext_<
I>typename</
I>(dns_rdata_t *rdata, dns_name_t *origin,
dns_name_init(&name, NULL);
dns_name_init(&prefix, NULL);
dns_rdata_toregion(rdata, ®ion);
dns_name_fromregion(&name, ®ion);
sub = <
B>name_prefix</
B>(&name, origin, &prefix);
return (dns_name_totext(&prefix, sub, target));
<
DT><
CODE>static dns_result_t<
BR>
str_totext(char *source, isc_buffer_t *target);</
CODE></
DT>
This adds the <
CODE>NULL</
CODE> terminated string <
CODE>source</
CODE>
up to but not including <
CODE>NULL</
CODE> to <
CODE>target</
CODE>.
Returns <
CODE>DNS_R_NOSPACE</
CODE> and <
CODE>DNS_R_SUCCESS</
CODE>.
<
DT><
CODE>static isc_boolean_t<
BR>
buffer_empty(isc_buffer_t *source);</
CODE></
DT>
Returns <
CODE>ISC_TRUE</
CODE> if the active region of <
CODE>source</
CODE> is
empty otherwise <
CODE>ISC_FALSE</
CODE>.
<
DT><
CODE>static void<
BR>
buffer_fromregion(isc_buffer_t *buffer, isc_region_t *region,
unsigned int type);</
CODE></
DT>
Make <
CODE>buffer</
CODE> refer to the memory in <
CODE>region</
CODE> and
<
DT><
CODE>static dns_result_t<
BR>
uint32_tobuffer(isc_uint32_t value, isc_buffer_t *target);</
CODE></
DT>
Write them 32 bit <
CODE>value</
CODE> in network order to <
CODE>target</
CODE>.
Returns <
CODE>DNS_R_NOSPACE</
CODE> and <
CODE>DNS_R_SUCCESS</
CODE>.
<
DT><
CODE>static dns_result_t<
BR>
uint16_tobuffer(isc_uint32_t value, isc_buffer_t *target);</
CODE></
DT>
Write them 16 bit <
CODE>value</
CODE> in network order to <
CODE>target</
CODE>.
Returns <
CODE>DNS_R_RANGE</
CODE>, <
CODE>DNS_R_NOSPACE</
CODE> and <
CODE>DNS_R_SUCCESS</
CODE>.
<
DT><
CODE>static isc_uint32_t<
BR>
uint32_fromregion(isc_region_t *region);</
CODE></
DT>
Returns the 32 bit at the start of <
CODE>region</
CODE> in host order.
Requires <
CODE>(region->length >= 4)</
CODE>.
<
DT><
CODE>static isc_uint16_t<
BR>
uint16_fromregion(isc_region_t *region);</
CODE></
DT>
Returns the 16 bit at the start of <
CODE>region</
CODE> in host order.
Requires <
CODE>(region->length >= 2)</
CODE>.
<
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>
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>.
Returns <
CODE>DNS_R_UNEXPECTED</
CODE>, <
CODE>DNS_R_UNEXPECTEDEND</
CODE>,
<
CODE>DNS_R_UNEXPECTEDTOKEN</
CODE> and <
CODE>DNS_R_SUCCESS</
CODE>.
<
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>
Add the memory referred to by <
CODE>base</
CODE> to <
CODE>target</
CODE>.
Returns <
CODE>DNS_R_NOSPACE</
CODE> and <
CODE>DNS_R_SUCCESS</
CODE>.
compare_region(isc_region_t *r1, isc_region_t *r2)</
CODE></
DT>
Compares to regions returning -1, 0, 1 based on their DNSSEC ordering.
hexvalue(char value);</
CODE></
DT>
Returns the hexadecimal value of <
CODE>value</
CODE> or -1 if not
decvalue(char value);</
CODE></
DT>
Returns the decimal value of <
CODE>value</
CODE> or -1 if not
<
DT><
CODE>static dns_result_t<
BR>
base64_totext(isc_region_t *source, isc_buffer_t *target);</
CODE></
DT>
Convert the region referred to by <
CODE>source</
CODE> to base64 encode text
and put it into <
CODE>target</
CODE>.
Returns <
CODE>DNS_R_NOSPACE</
CODE> or <
CODE>DNS_R_SUCCESS</
CODE>.
<
DT><
CODE>static dns_result_t<
BR>
base64_tobuffer(isc_lex_t *lexer, isc_buffer_t *target,
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.
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>.
<
DT><
CODE>static dns_result_t<
BR>
time_totext(unsigned long value, isc_buffer_t *target);</
CODE></
DT>
Convert the date represented by <
CODE>value</
CODE> into YYYYMMDDHHMMSS format
taking into account the active epochs. This code is Y2K and Y2038 compliant.
Returns <
CODE>DNS_R_NOSPACE</
CODE> and <
DNS_R_SUCCESS>.
<
DT><
CODE>static dns_result_t<
BR>
time_tobuffer(char *source, isc_buffer_t *target);</
CODE></
DT>
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
Returns <
CODE>DNS_R_RANGE</
CODE>, <
CODE>DNS_R_SYNTAX</
CODE>,
<
CODE>DNS_R_NOSPACE</
CODE> and <
CODE>DNS_R_SUCCESS</
CODE>.
<
H2><
A NAME="macros">Support Macros<
A></
H2>
The following macro is available:
<
DT><
CODE>RETERR(x)</
CODE><
DT>
Evaluate <
CODE>x</
CODE> and call <
CODE>return (<
I><value of x></
I>);</
CODE> if the result is not <
CODE>DNS_R_SUCCESS</
CODE>.