diff --git a/Makefile b/Makefile index 00257663..e1658950 100644 --- a/Makefile +++ b/Makefile @@ -20,11 +20,8 @@ include $(GOROOT)/src/Make.pkg examples: (cd examples; make) -progs: manglertest dnssectest +progs: dnssectest # too lazy to lookup how this works again in Makefiles -manglertest: manglertest.go $(GOFILES) - 6g -I _obj manglertest.go && 6l -L _obj -o manglertest manglertest.6 - dnssectest: dnssectest.go $(GOFILES) 6g -I _obj dnssectest.go && 6l -L _obj -o dnssectest dnssectest.6 diff --git a/dnssec.go b/dnssec.go index f89f8afe..85254deb 100644 --- a/dnssec.go +++ b/dnssec.go @@ -5,23 +5,22 @@ import "time" // All DNSSEC verification const ( - Year68 = 2 << (32 - 1) + year68 = 2 << (32 - 1) ) -// Translate the RRSIG's incep. and expir. time -// to the correct date, taking into account serial -// arithmetic +// Translate the RRSIG's incep. and expir. time to the correct date. +// Taking into account serial arithmetic (RFC 1982) func timeToDate(t uint32) string { utc := time.UTC().Seconds() - mod := (int64(t) - utc) / Year68 + mod := (int64(t) - utc) / year68 - // If needed assume wrap around(s) - ti := time.SecondsToUTC(int64(t) + (mod * Year68)) // abs()? TODO + // If needed assume wrap around(s) + ti := time.SecondsToUTC(int64(t) + (mod * year68)) // abs()? TODO return ti.Format("20060102030405") } -// Is the signature (RRSIG) valid? +// Using RFC1982 calculate if a signature is valid func validSignaturePeriod(start, end uint32) bool { - utc := time.UTC().Seconds() // maybe as parameter?? TODO MG - return int64(start) <= utc && utc <= int64(end) + utc := time.UTC().Seconds() // maybe as parameter?? TODO MG + return int64(start) <= utc && utc <= int64(end) } diff --git a/edns.go b/edns.go index 2c51d3cf..43115516 100644 --- a/edns.go +++ b/edns.go @@ -1,10 +1,10 @@ package dns -// Implementation of EDNS0, RFC 2671 +// EDNS0 option codes const ( - OptionCodeLLQ = 1 - OptionCodeUL = 2 - OptionCodeNSID = 3 + OptionCodeLLQ = 1 // Not used + OptionCodeUL = 2 // Not used + OptionCodeNSID = 3 // NSID, RFC5001 // EDNS flag bits (put in Z section) _DO = 1 << 15 // dnssec ok ) diff --git a/examples/chaos.go b/examples/chaos.go index c76dd627..e44c259a 100644 --- a/examples/chaos.go +++ b/examples/chaos.go @@ -2,77 +2,78 @@ package main // Print the version.bind and hostname.bind for each // address of NAMESERVER +// (c) Miek Gieben - 2011 import ( "dns" - "os" - "fmt" - "net" + "os" + "fmt" + "net" ) func main() { r := new(dns.Resolver) qr := dns.NewQuerier(r) - r.Servers = []string{"127.0.0.1"} - r.Timeout = 2 - r.Attempts = 1 - var in dns.DnsMsg + r.Servers = []string{"127.0.0.1"} + r.Timeout = 2 + r.Attempts = 1 + var in dns.DnsMsg - if len(os.Args) != 2 { - fmt.Printf("%s NAMESERVER\n", os.Args[0]) - os.Exit(1) - } + if len(os.Args) != 2 { + fmt.Printf("%s NAMESERVER\n", os.Args[0]) + os.Exit(1) + } - m := new(dns.Msg) - m.Question = make([]dns.Question, 1) - for _, a := range addresses(qr, os.Args[0]) { - // set the resolver to query the NS directly - r.Servers = []string{a.String()} - m.Question[0] = dns.Question{"version.bind.", dns.TypeTXT, dns.ClassCHAOS} - qr <- dns.DnsMsg{m, nil} - in = <-qr - if in.Dns != nil && in.Dns.Answer != nil { - fmt.Printf("%v\n", in.Dns.Answer[0]) - } - m.Question[0] = dns.Question{"hostname.bind.", dns.TypeTXT, dns.ClassCHAOS} - qr <- dns.DnsMsg{m, nil} - in = <-qr - if in.Dns != nil && in.Dns.Answer != nil { - fmt.Printf("%v\n", in.Dns.Answer[0]) - } - } + m := new(dns.Msg) + m.Question = make([]dns.Question, 1) + for _, a := range addresses(qr, os.Args[0]) { + // set the resolver to query the NS directly + r.Servers = []string{a.String()} + m.Question[0] = dns.Question{"version.bind.", dns.TypeTXT, dns.ClassCHAOS} + qr <- dns.DnsMsg{m, nil} + in = <-qr + if in.Dns != nil && in.Dns.Answer != nil { + fmt.Printf("%v\n", in.Dns.Answer[0]) + } + m.Question[0] = dns.Question{"hostname.bind.", dns.TypeTXT, dns.ClassCHAOS} + qr <- dns.DnsMsg{m, nil} + in = <-qr + if in.Dns != nil && in.Dns.Answer != nil { + fmt.Printf("%v\n", in.Dns.Answer[0]) + } + } - // Stop the resolver, send it a null mesg - qr <- dns.DnsMsg{nil, nil} - <-qr + // Stop the resolver, send it a null mesg + qr <- dns.DnsMsg{nil, nil} + <-qr } func addresses(qr chan dns.DnsMsg, name string) []net.IP { - m := new(dns.Msg) - m.MsgHdr.Recursion_desired = true //only set this bit - m.Question = make([]dns.Question, 1) - var ips []net.IP + m := new(dns.Msg) + m.MsgHdr.Recursion_desired = true //only set this bit + m.Question = make([]dns.Question, 1) + var ips []net.IP - m.Question[0] = dns.Question{os.Args[1], dns.TypeA, dns.ClassINET} - qr <- dns.DnsMsg{m, nil} - in := <-qr + m.Question[0] = dns.Question{os.Args[1], dns.TypeA, dns.ClassINET} + qr <- dns.DnsMsg{m, nil} + in := <-qr - if in.Dns.Rcode != dns.RcodeSuccess { - return nil - } - // Stuff must be in the answer section - for _, a := range in.Dns.Answer { - ips = append(ips, a.(*dns.RR_A).A) - } - m.Question[0] = dns.Question{os.Args[1], dns.TypeAAAA, dns.ClassINET} - qr <- dns.DnsMsg{m, nil} - in = <-qr + if in.Dns.Rcode != dns.RcodeSuccess { + return nil + } + // Stuff must be in the answer section + for _, a := range in.Dns.Answer { + ips = append(ips, a.(*dns.RR_A).A) + } + m.Question[0] = dns.Question{os.Args[1], dns.TypeAAAA, dns.ClassINET} + qr <- dns.DnsMsg{m, nil} + in = <-qr - if in.Dns.Rcode != dns.RcodeSuccess { - return nil - } - // Stuff must be in the answer section - for _, a := range in.Dns.Answer { - ips = append(ips, a.(*dns.RR_AAAA).AAAA) - } - return ips + if in.Dns.Rcode != dns.RcodeSuccess { + return nil + } + // Stuff must be in the answer section + for _, a := range in.Dns.Answer { + ips = append(ips, a.(*dns.RR_AAAA).AAAA) + } + return ips } diff --git a/examples/mx.go b/examples/mx.go index 008c3cd2..b9638d1e 100644 --- a/examples/mx.go +++ b/examples/mx.go @@ -1,6 +1,7 @@ package main // Print the MX records of a domain +// (c) Miek Gieben - 2011 import ( "dns" "os" diff --git a/manglertest.go b/manglertest.go deleted file mode 100644 index 7162145e..00000000 --- a/manglertest.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "dns" - "time" - "fmt" -) - -const ( - NLOOP = 5 -) - -func identity(msg []byte) []byte { - return msg -} - -func byteflip(msg []byte) []byte { - msg[len(msg) - 1] = 0 - msg[2] = 0 // See what happens - return msg -} - -func bitflip(msg []byte) []byte { - return msg -} - -func main() { - res := new(dns.Resolver) - ch := dns.NewQuerier(res) - - // configure the resolver - res.Servers = []string{"192.168.1.2"} - res.Timeout = 2 - res.Attempts = 1 - res.Mangle = byteflip - - // Setup done, now for some real work - // Create a new message - m := new(dns.Msg) - m.MsgHdr.Recursion_desired = true //only set this bit - m.Question = make([]dns.Question, 1) - - // ask something - m.Question[0] = dns.Question{"miek.nl", dns.TypeSOA, dns.ClassINET} - ch <- dns.DnsMsg{m, nil} - - // wait for an reply - in := <-ch - fmt.Printf("%v\n", in.Dns) - - ch <- dns.DnsMsg{nil, nil} - - time.Sleep(1.0e9) // wait for Go routine to do something -} diff --git a/msg.go b/msg.go index f09a428b..344ece7e 100644 --- a/msg.go +++ b/msg.go @@ -4,8 +4,7 @@ // DNS packet assembly. See RFC 1035. // -// This is intended to support name resolution during net.Dial. -// It doesn't have to be blazing fast. +// This is not (yet) optimized for speed. // // Rather than write the usual handful of routines to pack and // unpack every message that can appear on the wire, we use @@ -13,13 +12,7 @@ // use it. Thus, if in the future we need to define new message // structs, no new pack/unpack/printing code needs to be written. // -// The first half of this file defines the DNS message formats. -// The second half implements the conversion to and from wire format. -// A few of the structure elements have string tags to aid the -// generic pack/unpack routines. -// -// TODO(miekg): - +// package dns import ( @@ -171,7 +164,6 @@ Loop: return s, off1, true } -// TODO(rsc): Move into generic library? // Pack a reflect.StructValue into msg. Struct members can only be uint8, uint16, uint32, string, // slices and other (often anonymous) structs. func packStructValue(val *reflect.StructValue, msg []byte, off int) (off1 int, ok bool) { @@ -503,6 +495,8 @@ type MsgHdr struct { Rcode int } +// Convert a MsgHdr to a string, mimic the way dig displays +// headers: //;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48404 //;; flags: qr aa rd ra; func (h *MsgHdr) String() string { @@ -544,6 +538,7 @@ func (h *MsgHdr) String() string { return s } +// The layout of a DNS message. type Msg struct { MsgHdr Question []Question @@ -598,7 +593,7 @@ func (dns *Msg) Pack() (msg []byte, ok bool) { // Could work harder to calculate message size, // but this is far more than we need and not // big enough to hurt the allocator. - msg = make([]byte, 4096) // TODO, calculate REAL size + msg = make([]byte, defaultSize) // TODO, calculate REAL size // Pack it in: header and then the pieces. off := 0 @@ -665,6 +660,7 @@ func (dns *Msg) Unpack(msg []byte) bool { return true } +// Convert a complete message to a string. Again use dig-like output func (dns *Msg) String() string { if dns == nil { return " MsgHdr" diff --git a/resolver.go b/resolver.go index 306debdd..21dea579 100644 --- a/resolver.go +++ b/resolver.go @@ -5,9 +5,23 @@ // DNS resolver client: see RFC 1035. // A dns resolver is to be run as a seperate goroutine. // For every reply the resolver answers by sending the -// received packet back on the channel. -// TODO: resolverFromConf (/etc/resolv.conf) parsing?? - +// received packet (with a possible error) back on the channel. +// A simple resolver can be setup with the following code: +// +// res := new(Resolver) +// ch := NewQuerier(res) // start new resolver +// +// res.Servers = []string{"127.0.0.1"} // set the nameserver +// res.Timeout = 2 // some optional extra config +// res.Attempts = 1 +// +// m := new(Msg) // prepare a new message +// m.MsgHdr.Recursion_desired = true // header bits +// m.Question = make([]Question, 1) // 1 RR in question sec. +// m.Question[0] = Question{"miek.nl", TypeSOA, ClassINET} +// ch <- DnsMsg{m, nil} // send the query +// in := <-ch // wait for reply +// package dns import ( @@ -17,37 +31,40 @@ import ( "net" ) -// For communicating with a resolver -// A nil msg ends the resolver goroutine +const defaultSize = 4096 + +// When communicating with a resolver, we use this structure +// to send packets to it, when sending Error must be nil. +// A resolver responds with a simular message and a possible +// error. +// Sending a nil message instructs to resolver to stop. type DnsMsg struct { - Dns *Msg + Dns *Msg Error os.Error } type Resolver struct { - Servers []string // servers to use - rtt []int // round trip times for each NS (TODO) - Search []string // suffixes to append to local name - Port string // what port to use - Ndots int // number of dots in name to trigger absolute lookup - Timeout int // seconds before giving up on packet - Attempts int // lost packets before giving up on server - Rotate bool // round robin among servers - Tcp bool // use TCP - Mangle func([]byte) []byte // Mangle the packet + Servers []string // servers to use + Search []string // suffixes to append to local name + Port string // what port to use + Ndots int // number of dots in name to trigger absolute lookup + Timeout int // seconds before giving up on packet + Attempts int // lost packets before giving up on server + Rotate bool // round robin among servers + Tcp bool // use TCP + Mangle func([]byte) []byte // Mangle the packet } -// Start a new querier as a goroutine, return -// the communication channel +// Start a new resolver as a goroutine, return the communication channel func NewQuerier(res *Resolver) (ch chan DnsMsg) { ch = make(chan DnsMsg) go query(res, ch) return } - -// do it +// The query function. func query(res *Resolver, msg chan DnsMsg) { + // TODO port number, error checking, robustness var c net.Conn var err os.Error var in *Msg @@ -56,8 +73,8 @@ func query(res *Resolver, msg chan DnsMsg) { case out := <-msg: //msg received if out.Dns == nil { // nil message, quit the goroutine - msg <- DnsMsg{nil, nil} - close(msg) + msg <- DnsMsg{nil, nil} + close(msg) return } @@ -71,7 +88,7 @@ func query(res *Resolver, msg chan DnsMsg) { } for i := 0; i < len(res.Servers); i++ { -// server := res.Servers[i] + ":" + res.Port + // server := res.Servers[i] + ":" + res.Port server := res.Servers[i] + ":53" if res.Tcp == true { c, cerr = net.Dial("tcp", "", server) @@ -100,8 +117,6 @@ func query(res *Resolver, msg chan DnsMsg) { return } -// Use Pack to create a DNS question, from a msg - // Send a request on the connection and hope for a reply. // Up to res.Attempts attempts. func exchange(c net.Conn, m []byte, r *Resolver) (*Msg, os.Error) { @@ -117,7 +132,7 @@ func exchange(c net.Conn, m []byte, r *Resolver) (*Msg, os.Error) { c.SetReadTimeout(int64(r.Timeout) * 1e9) // nanoseconds // EDNS TODO - buf := make([]byte, 2000) // More than enough. + buf := make([]byte, defaultSize) // More than enough. n, err = c.Read(buf) if err != nil { // More Go foo needed diff --git a/resolverEdns_test.go b/resolverEdns_test.go index 57673f40..cafc5a24 100644 --- a/resolverEdns_test.go +++ b/resolverEdns_test.go @@ -21,8 +21,13 @@ func TestResolverEdns(t *testing.T) { edns := new(RR_OPT) edns.Hdr.Name = "." // must . be for edns edns.Hdr.Rrtype = TypeOPT - edns.Hdr.Class = ClassINET - edns.Hdr.Ttl = 3600 + // You can handle an OTP RR as any other, but there + // are some convience functions + ends.UDPSize(4096, true) + edns.DoBit(true, true) +// edns.Nsid("mieks-server", true) +// edns.Hdr.Class = ClassINET +// edns.Hdr.Ttl = 3600 // no options for now // edns.Option = make([]Option, 1) // edns.Option[0].Code = OptionCodeNSID @@ -30,7 +35,7 @@ func TestResolverEdns(t *testing.T) { // ask something m.Question[0] = Question{"miek.nl", TypeSOA, ClassINET} - m.Ns[0] = edns + m.Extra[0] = edns ch <- DnsMsg{m, nil} in := <-ch @@ -41,5 +46,5 @@ func TestResolverEdns(t *testing.T) { t.Fail() } ch <- DnsMsg{nil, nil} - <-ch + <-ch // wait for ch to close channel } diff --git a/strconv.go b/strconv.go index 3613dfa6..9d54da5a 100644 --- a/strconv.go +++ b/strconv.go @@ -1,21 +1,18 @@ package dns -// subpackage? - -// Convert a string to an resource record -// The string must fit on one line and must be fully formatted -// IN A 192.168.1.1 // not ok +// Convert a string to an resource record. The string must fir on one line. // miek.nl. 3600 IN A 192.168.1.1 // ok // miek.nl. IN A 192.168.1.1 // ok, ttl may be omitted // miek.nl. A 192.168.1.1 // ok, ttl and class omitted // miek.nl. 3600 A 192.168.1.1 // ok, class omitted +// IN A 192.168.1.1 // not ok func AtoRR(s string) *RR { - // up to first whitespace is domainname - // next word is: - // -> TTL - // IN|CH|HS -> Class - // -> Type - // When the type is seen, we can read the rest - // of the string in an rr-specific manner - return nil + // up to first whitespace is domainname + // next word is: + // -> TTL + // IN|CH|HS -> Class + // -> Type + // When the type is seen, we can read the rest + // of the string in an rr-specific manner + return nil } diff --git a/types.go b/types.go index ff6393fc..2f43eef9 100644 --- a/types.go +++ b/types.go @@ -1,10 +1,25 @@ // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Extended and bugfixes by Miek Gieben -// DNS Resource Records Types. See RFC 1035 and ... +// Package dns implements a full featured interface to the DNS. +// Supported RFCs include: +// * 1034/1035 +// * 2671 - EDNS +// * 4033/4034/4035 - DNSSEC + validation functions +// * 1982 - Serial Arithmetic +// * IP6 support +// The package allow full control over what is send out to the DNS. +// +// DNS RR types definitions. See RFC 1035/.../4034 and many more. +// To create quad-A record: "a.miek.nl" IN AAAA 2001:7b8:206:1:200:39ff:fe59:b187 +// +// import "net" // for IP functions +// r := new(RR_AAAA) +// r.AAAA = net.ParseIP("2001:7b8:206:1:200:39ff:fe59:b187").To16() +// r.Hdr = RR_Header{Name: "a.miek.nl", Rrtype: TypeAAAA, Class: ClassINET, Ttl: 3600} // - package dns import ( @@ -88,8 +103,7 @@ const ( _CD = 1 << 4 // checking disabled ) -// Why not use the official cryptstuff here too??? -// or at least map them +// DNSSEC encryption algorithm codes. const ( // DNSSEC algorithms AlgRSAMD5 = 1 @@ -102,6 +116,7 @@ const ( AlgECCGOST = 12 ) +// DNSSEC hashing codes. const ( HashSHA1 = 1 //? HashSHA256 = 2 //? @@ -457,7 +472,7 @@ type RR_NSEC3 struct { Salt string "hex" HashLength uint8 NextDomain string "domain-name" - TypeBitMap []int "NSEC3" // &{TypeSOA,TypeDS,etc} + TypeBitMap []int "NSEC3" // &{TypeSOA,TypeDS,etc} } func (rr *RR_NSEC3) Header() *RR_Header {