# Adding new RR types to GO DNS


Inspired by 
[NLnet Labs](https://www.nlnetlabs.nl/blog/2012/09/20/howto-add-new-rrtypes-to-nsd/) and
[PowerDNS](http://bert-hubert.blogspot.nl/2012/11/adding-new-dns-record-types-to-powerdns.html), 
I figured I couldn't stay behind, so here is how to add new RRs to [Go DNS](http://github.com/miekg/dns).

A small note before I delve into the details, I haven't optimized Go DNS for adding new types, as this
is a relative infrequent event. There are a few items that need to be added before Go DNS
understands the new RR type.

1. Adding the type itself (as a structure) and the four methods needed to implement the
    `RR` interface;
1. Adding the type number and the text string belonging it;
1. Parsing from text, i.e. when parsing zonefiles.

Lets take the new DANE ([RFC6698](http://datatracker.ietf.org/doc/rfc6698/)) as an example. 
The record is called TLSA, and looks like: 
 
    _443._tcp.www.example.com. IN TLSA 0 0 1 d2abde240d7cd3ee6b4b28c 54df034b9

The rdata consists out of four fields: 3 numbers (0 0 1) all encoded as `uint8`s and a
string.

# Adding the type
For the type to be known by Go DNS, we need to tweak two files: `types.go` and `msg.go`. 
In the file `types.go`, at the top we need to add the code point for TLSA:

    TypeTLSA       uint16 = 52

and at the bottom we need to add a new function in the `rr_mk` map:

    TypeTLSA:       func() RR { return new(RR_TLSA) },

Next, in the file `msg.go` we add TLSA to the `Rr_str` map:

    TypeTLSA:       "TSLA",

After this ground work we can add the actual definition of the record itself. The TLSA
record consists out of a header (as all RR types do) and in this case four rdata fields,
three `uint8` values and a string encoded as hex. So we add the struct:

First we create the structure:

    type RR_TLSA struct {
            Hdr          RR_Header
            Usage        uint8
            Selector     uint8
            MatchingType uint8
            Certificate  string `dns:"hex"`
    }

The first four items are almost directly copied from the RFC, the tag `dns:"hex"` tells Go
DNS that Certificate is a string which should be encoded as Hexadecimal.

And then the four methods needed to make the RR_TLSA structure implement the RR interface:
`Header()` -- returns the header of the RR, `Copy()` -- needed for DNSSEC when parts
of the RR need to be lowercased, `Len()` -- returns the length of the RR, and `String()` --
returns the string representation of the record:

    func (rr *RR_TLSA) Header() *RR_Header { return &rr.Hdr }
    func (rr *RR_TLSA) Len() int           { return rr.Hdr.Len() + 3 + len(rr.Certificate)/2 }
    func (rr *RR_TLSA) Copy() RR {
	return &RR_TLSA{*rr.Hdr.CopyHeader(), rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate}
    }

A finally `String()`:

    func (rr *RR_TLSA) String() string {
        return rr.Hdr.String() +
                " " + strconv.Itoa(int(rr.Usage)) +
                " " + strconv.Itoa(int(rr.Selector)) +
                " " + strconv.Itoa(int(rr.MatchingType)) +
                " " + rr.Certificate
    }

After this, Go DNS knowns about the record, but it is not able to parse it from a string. 

# Parsing
Adding parsing support is done in the file `zscan_rr.go`, it entails of adding a whole function
that actually parses the rdata. In the main `switch` in the function `setRR`, there are two places
where are new function call (`setTLSA`) can be added. The ones following with `goto Slurp` have a fixed
ending, the ones without `goto Slurp` have a variable ending. The TLSA record has be variable ending
because the certificate length is not explicitly encoded in the RR. The MX record, for instance,
has a fixed ending, because the length of the domain name is encoded in the record.

So we need to add the TLSA parsing in the bottom half:

    case TypeTLSA:
        return setTLSA(h, c, f)

> Some `setXX` functions also have an `o` parameter, which is the current origin. It is used
> for resource records which have unqualified domain names in the rdata, to which that origin
> should be concatenated.

Finally we write the `setTLSA` function, which looks like:

    func setTLSA(h RR_Header, c chan lex, f string) (RR, *ParseError) {
            rr := new(RR_TLSA)
            rr.Hdr = h
            l := <-c
            if i, e := strconv.Atoi(l.token); e != nil {
                    return nil, &ParseError{f, "bad TLSA Usage", l}
            } else {
                    rr.Usage = uint8(i)
            }
            <-c // _BLANK
            l = <-c
            if i, e := strconv.Atoi(l.token); e != nil {
                    return nil, &ParseError{f, "bad TLSA Selector", l}
            } else {
                    rr.Selector = uint8(i)
            }
            <-c // _BLANK
            l = <-c
            if i, e := strconv.Atoi(l.token); e != nil {
                    return nil, &ParseError{f, "bad TLSA MatchingType", l}
            } else {
                    rr.MatchingType = uint8(i)
            }
            s, e := endingToString(c, "bad TLSA Certificate", f)
            if e != nil {
                    return nil, e
            }
            rr.Certificate = s
            return rr, nil
    }

The channel `c` returns *normalized* tokens, i.e. all tokens are separated by only one space and the
resource record is ended when a `_NEWLINE` token is seen. So it's a matter of picking of the tokens
and converting them to the correct type(s) for the record.


