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.