diff --git a/Makefile b/Makefile index bad1541f..b734cc0a 100644 --- a/Makefile +++ b/Makefile @@ -18,15 +18,3 @@ include $(GOROOT)/src/Make.pkg examples: (cd examples; make) - -progs: dnssectest keytest readtest - -# too lazy to lookup how this works again in Makefiles -dnssectest: dnssectest.go $(GOFILES) - 6g -I _obj dnssectest.go && 6l -L _obj -o dnssectest dnssectest.6 - -keytest: keytest.go $(GOFILES) - 6g -I _obj keytest.go && 6l -L _obj -o keytest keytest.6 - -readtest: readtest.go $(GOFILES) - 6g -I _obj readtest.go && 6l -L _obj -o readtest readtest.6 diff --git a/README b/README index 4a3c14b3..2990bc3f 100644 --- a/README +++ b/README @@ -1,19 +1,14 @@ -Alternative aproach to a DNS library - -Much more control as you prepare a dns packet and then call the resolver -to send it. - -The library is asynchronise (thanks to Go) from the get go. +Alternative (more granular) approach to a DNS library Implemented RFCS: * RFC2671, EDNS * RFC1034/1035 -* RFC4033/4034/4035 (todo: validation) -* RFC5155 (NSEC) +* RFC4033/4034/4035 +* RFC5155 (NSEC3) -Loosly based upon: +Loosely based upon: * ldns * NSD * Net::DNS diff --git a/TODO b/TODO index f2f9ce47..62693f06 100644 --- a/TODO +++ b/TODO @@ -1,9 +1,9 @@ Todo: -* parse RRs from strings AToRR() * DNSSEC validation * NSEC(3) secure denial of existence * Unknown RRs * fix os.Erros usage +* AXFR/IXFR support Tesing: * EDNS0 @@ -12,4 +12,4 @@ Tesing: Issues: * shortened ipv6 addresses are not parsed correctly * quoted quotes in txt records -* divide the code in multiple packages? dns, dnssec +* divide the code in multiple packages? dns, dnssec? diff --git a/dnssec.go b/dnssec.go index 34aff85e..19ee4263 100644 --- a/dnssec.go +++ b/dnssec.go @@ -2,6 +2,7 @@ package dns import ( "crypto/sha1" + "crypto/sha256" "encoding/hex" "time" "io" @@ -59,7 +60,9 @@ func (k *RR_DNSKEY) ToDS(hash int) *RR_DS { io.WriteString(s, string(digest)) ds.Digest = hex.EncodeToString(s.Sum()) case HashSHA256: - + s := sha256.New() + io.WriteString(s, string(digest)) + ds.Digest = hex.EncodeToString(s.Sum()) case HashGOST94: default: @@ -69,7 +72,7 @@ func (k *RR_DNSKEY) ToDS(hash int) *RR_DS { return ds } -// Calculate the keytag of the DNSKEY +// Calculate the keytag of the DNSKEY. func (k *RR_DNSKEY) KeyTag() uint16 { var keytag int switch k.Algorithm { @@ -101,13 +104,22 @@ func (k *RR_DNSKEY) KeyTag() uint16 { return uint16(keytag) } -// Validate an rrset with the signature and key. Note the -// signature validate period is NOT checked. Used -// ValidSignaturePeriod for that -func (s *RR_RRSIG) Valid(rrset []RR, key *RR_DNSKEY) bool { +// Validate an rrset with the signature and key. This is the +// cryptographic test, the validity period most be check separately. +func (s *RR_RRSIG) Secure(rrset []RR, key *RR_DNSKEY) bool { return false } +// Using RFC1982 calculate if a signature period is valid +func (s *RR_RRSIG) PeriodOK() bool { + utc := time.UTC().Seconds() + modi := (int64(s.Inception) - utc) / year68 + mode := (int64(s.Expiration) - utc) / year68 + ti := int64(s.Inception) + (modi * year68) + te := int64(s.Expiration) + (mode * year68) + return ti <= utc && utc <= te +} + // Translate the RRSIG's incep. and expir. time to the correct date. // Taking into account serial arithmetic (RFC 1982) func timeToDate(t uint32) string { @@ -119,9 +131,3 @@ func timeToDate(t uint32) string { return ti.Format("20060102030405") } -// Work on a signature RR_RRSIG -// 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) -} diff --git a/dnssec_test.go b/dnssec_test.go new file mode 100644 index 00000000..9c460d77 --- /dev/null +++ b/dnssec_test.go @@ -0,0 +1,35 @@ +package dns + +import ( + "testing" +) + +func TestSignature(t *testing.T) { + sig := new(RR_RRSIG) + sig.Hdr.Name = "miek.nl." + sig.Hdr.Rrtype = TypeRRSIG + sig.Hdr.Class = ClassINET + sig.Hdr.Ttl = 3600 + sig.TypeCovered = TypeDNSKEY + sig.Algorithm = AlgRSASHA1 + sig.Labels = 2 + sig.OrigTtl = 4000 + sig.Expiration = 1000 //Thu Jan 1 02:06:40 CET 1970 + sig.Inception = 800 //Thu Jan 1 01:13:20 CET 1970 + sig.KeyTag = 34641 + sig.SignerName = "miek.nl." + sig.Sig = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ" + + // Should not be valid + if sig.PeriodOK() { + t.Log("Should not be valid") + t.Fail() + } + + sig.Inception = 315565800 //Tue Jan 1 10:10:00 CET 1980 + sig.Expiration = 4102477800 //Fri Jan 1 10:10:00 CET 2100 + if ! sig.PeriodOK() { + t.Log("Should be valid") + t.Fail() + } +} diff --git a/edns.go b/edns.go index 9dc6353a..c092e497 100644 --- a/edns.go +++ b/edns.go @@ -1,5 +1,3 @@ -// EDNS0 OTP RR implementation. Define the OPT RR and some -// convience functions to operate on it. package dns import ( @@ -21,16 +19,17 @@ type Option struct { Data string "hex" } -/* EDNS extended RR. -This is the EDNS0 Header - Name string "domain-name" - Opt uint16 // was type, but is always TypeOPT - UDPSize uint16 // was class - ExtendedRcode uint8 // was TTL - Version uint8 // was TTL - Z uint16 // was TTL (all flags should be put here) - Rdlength uint16 // length of data after the header -*/ +/* + * EDNS extended RR. + * This is the EDNS0 Header + * Name string "domain-name" + * Opt uint16 // was type, but is always TypeOPT + * UDPSize uint16 // was class + * ExtendedRcode uint8 // was TTL + * Version uint8 // was TTL + * Z uint16 // was TTL (all flags should be put here) + * Rdlength uint16 // length of data after the header + */ type RR_OPT struct { Hdr RR_Header diff --git a/examples/Makefile b/examples/Makefile index 45c7c738..812c230f 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -2,7 +2,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -all: mx chaos +all: mx chaos dnssectest # too lazy to lookup how this works again in Makefiles mx: mx.go @@ -10,3 +10,7 @@ mx: mx.go chaos: chaos.go 6g -I ../_obj chaos.go && 6l -L ../_obj -o chaos chaos.6 + +dnssectest: dnssectest.go + 6g -I ../_obj dnssectest.go && 6l -L ../_obj -o dnssectest dnssectest.6 + diff --git a/dnssectest.go b/examples/dnssectest.go similarity index 100% rename from dnssectest.go rename to examples/dnssectest.go diff --git a/keytest.go b/keytest.go deleted file mode 100644 index d4d88801..00000000 --- a/keytest.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "dns" - "fmt" -) - -func main() { - key := new(dns.RR_DNSKEY) - key.Hdr.Name = "miek.nl" - key.Hdr.Rrtype = dns.TypeDNSKEY - key.Hdr.Class = dns.ClassINET - key.Hdr.Ttl = 3600 - key.Flags = 256 - key.Protocol = 3 - key.Algorithm = dns.AlgRSASHA256 - key.PubKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" - - tag := key.KeyTag() - fmt.Printf("%v\n", key) - fmt.Printf("Wrong key tag: %d\n", tag) - - m := new(dns.Msg) - m.Ns = make([]dns.RR, 1) - m.Ns[0] = key - m.Pack() -} diff --git a/msg.go b/msg.go index c2f315ee..c8fbe1d1 100644 --- a/msg.go +++ b/msg.go @@ -25,6 +25,8 @@ import ( "encoding/hex" ) +const defaultMsgSize = 4096 + // Packing and unpacking. // // All the packers and unpackers take a (msg []byte, off int) @@ -614,7 +616,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, defaultSize) // TODO, calculate REAL size + msg = make([]byte, defaultMsgSize) // TODO, calculate REAL size // Pack it in: header and then the pieces. off := 0 diff --git a/readtest.go b/readtest.go deleted file mode 100644 index bd839747..00000000 --- a/readtest.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "dns" - "fmt" -) - -func main() { - key := new(dns.RR_DNSKEY) - key.Hdr.Name = "miek.nl." - key.Hdr.Rrtype = dns.TypeDNSKEY - key.Hdr.Class = dns.ClassINET - key.Hdr.Ttl = 3600 - key.Flags = 256 - key.Protocol = 3 - key.Algorithm = dns.AlgRSASHA256 - key.PubKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" - - fmt.Printf("%v\n", key) - - s := "miek.nl. 3600 IN DNSKEY 256 3 8 AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz" - dns.ParseString(s) -} diff --git a/resolver.go b/resolver.go index fa0c2704..7826cd1f 100644 --- a/resolver.go +++ b/resolver.go @@ -7,21 +7,18 @@ // For every reply the resolver answers by sending the // received packet (with a possible error) back on the channel. // -// Basic usage pattern: +// Basic usage pattern for setting up a resolver: // // res := new(Resolver) -// ch := NewQuerier(res) // start 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 := 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 +// ch <- DnsMsg{m, nil} // send the query +// in := <-ch // wait for reply // package dns @@ -32,12 +29,9 @@ import ( "net" ) -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. +// to send packets to it, for sending Error must be nil. +// A resolver responds with a reply packet and a possible error. // Sending a nil message instructs to resolver to stop. type DnsMsg struct { Dns *Msg @@ -69,6 +63,17 @@ func query(res *Resolver, msg chan DnsMsg) { var c net.Conn var err os.Error var in *Msg + var port string + if len(res.Servers) == 0 { + msg <- DnsMsg{nil, nil} + return + } + if res.Port == "" { + port = "53" + } else { + port = res.Port + } + for { select { case out := <-msg: //msg received @@ -89,8 +94,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] + ":53" + server := res.Servers[i] + ":" + port if res.Tcp == true { c, cerr = net.Dial("tcp", "", server) } else { @@ -121,19 +125,30 @@ func query(res *Resolver, msg chan DnsMsg) { // 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) { + var timeout int64 + var attempts int if r.Mangle != nil { m = r.Mangle(m) } + if r.Timeout == 0 { + timeout = 1 + } else { + timeout = int64(r.Timeout) + } + if r.Attempts == 0 { + attempts = 1 + } else { + attempts = r.Attempts + } - for attempt := 0; attempt < r.Attempts; attempt++ { + for a:= 0; a < attempts; a++ { n, err := c.Write(m) if err != nil { return nil, err } - c.SetReadTimeout(int64(r.Timeout) * 1e9) // nanoseconds - // EDNS TODO - buf := make([]byte, defaultSize) // More than enough. + c.SetReadTimeout(timeout * 1e9) // nanoseconds + buf := make([]byte, defaultMsgSize) // 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 348f1ad1..e584deda 100644 --- a/resolverEdns_test.go +++ b/resolverEdns_test.go @@ -26,8 +26,6 @@ func TestResolverEdns(t *testing.T) { edns.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 diff --git a/resolver_test.go b/resolver_test.go index a75fdb9c..c63310ac 100644 --- a/resolver_test.go +++ b/resolver_test.go @@ -10,8 +10,6 @@ func TestResolver(t *testing.T) { ch := NewQuerier(res) res.Servers = []string{"127.0.0.1"} - res.Timeout = 2 - res.Attempts = 1 m := new(Msg) m.MsgHdr.Recursion_desired = true //only set this bit diff --git a/signature_test.go b/signature_test.go index 9fb51793..9c460d77 100644 --- a/signature_test.go +++ b/signature_test.go @@ -14,17 +14,22 @@ func TestSignature(t *testing.T) { sig.Algorithm = AlgRSASHA1 sig.Labels = 2 sig.OrigTtl = 4000 - sig.Expiration = 1000 - sig.Inception = 800 + sig.Expiration = 1000 //Thu Jan 1 02:06:40 CET 1970 + sig.Inception = 800 //Thu Jan 1 01:13:20 CET 1970 sig.KeyTag = 34641 sig.SignerName = "miek.nl." sig.Sig = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ" // Should not be valid - if ValidSignaturePeriod(sig.Inception, sig.Expiration) { + if sig.PeriodOK() { t.Log("Should not be valid") t.Fail() - } else { - t.Logf("Valid sig period:\n%v\n", sig) + } + + sig.Inception = 315565800 //Tue Jan 1 10:10:00 CET 1980 + sig.Expiration = 4102477800 //Fri Jan 1 10:10:00 CET 2100 + if ! sig.PeriodOK() { + t.Log("Should be valid") + t.Fail() } } diff --git a/types.go b/types.go index 4bc69df8..d5e1bcdd 100644 --- a/types.go +++ b/types.go @@ -4,23 +4,19 @@ // Extended and bugfixes by Miek Gieben // Package dns implements a full featured interface to the DNS. -// Supported RFCs include: +// Supported RFCs and features 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. +// The package allows 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 +// Basic usage pattern for creating new Resource Record: // -// Basic usage pattern: -// -// 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} +// r := new(RR_TXT) +// r.TXT = "This is the content of the TXT record" +// r.Hdr = RR_Header{Name: "a.miek.nl", Rrtype: TypeTXT, Class: ClassINET, Ttl: 3600} // package dns