Go DNS (update)

July 6, 2011

programming

I’m finally back to coding Go DNS and making it work with the latest Go releases. Also the API has changed quite significantly since the last time I blogged about it.

So this I will detail key2ds which is small utility that queries a zone and print any DNSKEY records as DS records on the fly, to show the new API and some sample usage.

% ./key2ds sidn.nl
sidn.nl.    0   IN  DS  42033 8 1 343F74674D36C9B5BE2CEB2C401AC4EDEB2A05B2
sidn.nl.    0   IN  DS  42033 8 2 BF985EC0738FACC89EE0B12FBD9261827C59191D9EA6A9BDFF55F9BDF3DBBFF3
sidn.nl.    0   IN  DS  39274 8 1 E79E031DFDE8E68EF1E2C6CA0943C2CC0DED1889
sidn.nl.    0   IN  DS  39274 8 2 8E8A8CFB40FD0C30BFA82E53752E1C257DAFB7B6206D12B9EDA43AF3EAB2157D

This util uses synchronous queries. I will explain the main-function:

func main() {
        conf, err := dns.ClientConfigFromFile("/etc/resolv.conf")
        if len(os.Args) != 2 || err != nil {
                fmt.Printf("%s DOMAIN\n", os.Args[0])
                os.Exit(1)
        }

Read the resolver config from /etc/resolv.conf and check if enough parameters have been given.

    m := new(dns.Msg)
    m.SetQuestion(os.Args[1], dns.TypeDNSKEY)

Prepare a new dns message to send to the other side. I’m interested in the DNSKEYs for the name given on the command line.

    // Set EDNS0's Do bit
    e := new(dns.RR_OPT)
    e.Hdr.Name = "."
    e.Hdr.Rrtype = dns.TypeOPT
    e.SetUDPSize(2048)
    e.SetDo()
    m.Extra = append(m.Extra, e)

This is DNSSEC so I must prepare an EDNS0 section, which is nothing more than adding an OPT RR to the additional section. I’m pondering making EDNS0 easier and provide a few helper functions (ideas welcome!). For now the whole OPT RR must be defined from scratch.

    c := dns.NewClient()
    r := c.Exchange(m, conf.Servers[0])
    if r == nil {
            fmt.Printf("*** no answer received for %s\n", os.Args[1])
            os.Exit(1)
    }

Create a new client and use Exchange() to perform send the query and wait for the reply. Note that we only use the first server defined in /etc/resolv.conf. (There is room for some improvements :-) )

    if r.Rcode != dns.RcodeSuccess {
            fmt.Printf(" *** invalid answer name %s after DNSKEY query for %s\n", os.Args[1], os.Args[1])
            os.Exit(1)
    }

If anything is wrong with the answer I bail out here.

    for _, k := range r.Answer {
            if key, ok := k.(*dns.RR_DNSKEY); ok {

Loop through the answer section and check each RR to see if it is a DNSKEY record with a type check.

                    ds := key.ToDS(dns.HashSHA1)
                    ds.Hdr.Ttl = 0
                    fmt.Printf("%v\n", ds)

For each DNSKEY convert it to a SHA1 DS records with ToDS()

                        ds = key.ToDS(dns.HashSHA256)
                        ds.Hdr.Ttl = 0
                        fmt.Printf("%v\n", ds)
                }
        }
}

And do the same for SHA256 and print that too.

Golang