More cleansup and robustness

* make the resolver more robust
* more dnssec functions
This commit is contained in:
Miek Gieben 2010-12-28 09:41:54 +01:00
parent 73cc848e00
commit 4ed14b9785
16 changed files with 128 additions and 137 deletions

View File

@ -18,15 +18,3 @@ include $(GOROOT)/src/Make.pkg
examples: examples:
(cd examples; make) (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

13
README
View File

@ -1,19 +1,14 @@
Alternative aproach to a DNS library Alternative (more granular) approach 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.
Implemented RFCS: Implemented RFCS:
* RFC2671, EDNS * RFC2671, EDNS
* RFC1034/1035 * RFC1034/1035
* RFC4033/4034/4035 (todo: validation) * RFC4033/4034/4035
* RFC5155 (NSEC) * RFC5155 (NSEC3)
Loosly based upon: Loosely based upon:
* ldns * ldns
* NSD * NSD
* Net::DNS * Net::DNS

4
TODO
View File

@ -1,9 +1,9 @@
Todo: Todo:
* parse RRs from strings AToRR()
* DNSSEC validation * DNSSEC validation
* NSEC(3) secure denial of existence * NSEC(3) secure denial of existence
* Unknown RRs * Unknown RRs
* fix os.Erros usage * fix os.Erros usage
* AXFR/IXFR support
Tesing: Tesing:
* EDNS0 * EDNS0
@ -12,4 +12,4 @@ Tesing:
Issues: Issues:
* shortened ipv6 addresses are not parsed correctly * shortened ipv6 addresses are not parsed correctly
* quoted quotes in txt records * quoted quotes in txt records
* divide the code in multiple packages? dns, dnssec * divide the code in multiple packages? dns, dnssec?

View File

@ -2,6 +2,7 @@ package dns
import ( import (
"crypto/sha1" "crypto/sha1"
"crypto/sha256"
"encoding/hex" "encoding/hex"
"time" "time"
"io" "io"
@ -59,7 +60,9 @@ func (k *RR_DNSKEY) ToDS(hash int) *RR_DS {
io.WriteString(s, string(digest)) io.WriteString(s, string(digest))
ds.Digest = hex.EncodeToString(s.Sum()) ds.Digest = hex.EncodeToString(s.Sum())
case HashSHA256: case HashSHA256:
s := sha256.New()
io.WriteString(s, string(digest))
ds.Digest = hex.EncodeToString(s.Sum())
case HashGOST94: case HashGOST94:
default: default:
@ -69,7 +72,7 @@ func (k *RR_DNSKEY) ToDS(hash int) *RR_DS {
return ds return ds
} }
// Calculate the keytag of the DNSKEY // Calculate the keytag of the DNSKEY.
func (k *RR_DNSKEY) KeyTag() uint16 { func (k *RR_DNSKEY) KeyTag() uint16 {
var keytag int var keytag int
switch k.Algorithm { switch k.Algorithm {
@ -101,13 +104,22 @@ func (k *RR_DNSKEY) KeyTag() uint16 {
return uint16(keytag) return uint16(keytag)
} }
// Validate an rrset with the signature and key. Note the // Validate an rrset with the signature and key. This is the
// signature validate period is NOT checked. Used // cryptographic test, the validity period most be check separately.
// ValidSignaturePeriod for that func (s *RR_RRSIG) Secure(rrset []RR, key *RR_DNSKEY) bool {
func (s *RR_RRSIG) Valid(rrset []RR, key *RR_DNSKEY) bool {
return false 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. // Translate the RRSIG's incep. and expir. time to the correct date.
// Taking into account serial arithmetic (RFC 1982) // Taking into account serial arithmetic (RFC 1982)
func timeToDate(t uint32) string { func timeToDate(t uint32) string {
@ -119,9 +131,3 @@ func timeToDate(t uint32) string {
return ti.Format("20060102030405") 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)
}

35
dnssec_test.go Normal file
View File

@ -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()
}
}

23
edns.go
View File

@ -1,5 +1,3 @@
// EDNS0 OTP RR implementation. Define the OPT RR and some
// convience functions to operate on it.
package dns package dns
import ( import (
@ -21,16 +19,17 @@ type Option struct {
Data string "hex" Data string "hex"
} }
/* EDNS extended RR. /*
This is the EDNS0 Header * EDNS extended RR.
Name string "domain-name" * This is the EDNS0 Header
Opt uint16 // was type, but is always TypeOPT * Name string "domain-name"
UDPSize uint16 // was class * Opt uint16 // was type, but is always TypeOPT
ExtendedRcode uint8 // was TTL * UDPSize uint16 // was class
Version uint8 // was TTL * ExtendedRcode uint8 // was TTL
Z uint16 // was TTL (all flags should be put here) * Version uint8 // was TTL
Rdlength uint16 // length of data after the header * Z uint16 // was TTL (all flags should be put here)
*/ * Rdlength uint16 // length of data after the header
*/
type RR_OPT struct { type RR_OPT struct {
Hdr RR_Header Hdr RR_Header

View File

@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style # Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file. # 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 # too lazy to lookup how this works again in Makefiles
mx: mx.go mx: mx.go
@ -10,3 +10,7 @@ mx: mx.go
chaos: chaos.go chaos: chaos.go
6g -I ../_obj chaos.go && 6l -L ../_obj -o chaos chaos.6 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

View File

@ -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()
}

4
msg.go
View File

@ -25,6 +25,8 @@ import (
"encoding/hex" "encoding/hex"
) )
const defaultMsgSize = 4096
// Packing and unpacking. // Packing and unpacking.
// //
// All the packers and unpackers take a (msg []byte, off int) // 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, // Could work harder to calculate message size,
// but this is far more than we need and not // but this is far more than we need and not
// big enough to hurt the allocator. // 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. // Pack it in: header and then the pieces.
off := 0 off := 0

View File

@ -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)
}

View File

@ -7,21 +7,18 @@
// For every reply the resolver answers by sending the // For every reply the resolver answers by sending the
// received packet (with a possible error) back on the channel. // received packet (with a possible error) back on the channel.
// //
// Basic usage pattern: // Basic usage pattern for setting up a resolver:
// //
// res := new(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.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.MsgHdr.Recursion_desired = true // header bits
// m.Question = make([]Question, 1) // 1 RR in question sec. // m.Question = make([]Question, 1) // 1 RR in question sec.
// m.Question[0] = Question{"miek.nl", TypeSOA, ClassINET} // m.Question[0] = Question{"miek.nl", TypeSOA, ClassINET}
// ch <- DnsMsg{m, nil} // send the query // ch <- DnsMsg{m, nil} // send the query
// in := <-ch // wait for reply // in := <-ch // wait for reply
// //
package dns package dns
@ -32,12 +29,9 @@ import (
"net" "net"
) )
const defaultSize = 4096
// When communicating with a resolver, we use this structure // When communicating with a resolver, we use this structure
// to send packets to it, when sending Error must be nil. // to send packets to it, for sending Error must be nil.
// A resolver responds with a simular message and a possible // A resolver responds with a reply packet and a possible error.
// error.
// Sending a nil message instructs to resolver to stop. // Sending a nil message instructs to resolver to stop.
type DnsMsg struct { type DnsMsg struct {
Dns *Msg Dns *Msg
@ -69,6 +63,17 @@ func query(res *Resolver, msg chan DnsMsg) {
var c net.Conn var c net.Conn
var err os.Error var err os.Error
var in *Msg 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 { for {
select { select {
case out := <-msg: //msg received case out := <-msg: //msg received
@ -89,8 +94,7 @@ func query(res *Resolver, msg chan DnsMsg) {
} }
for i := 0; i < len(res.Servers); i++ { for i := 0; i < len(res.Servers); i++ {
// server := res.Servers[i] + ":" + res.Port server := res.Servers[i] + ":" + port
server := res.Servers[i] + ":53"
if res.Tcp == true { if res.Tcp == true {
c, cerr = net.Dial("tcp", "", server) c, cerr = net.Dial("tcp", "", server)
} else { } else {
@ -121,19 +125,30 @@ func query(res *Resolver, msg chan DnsMsg) {
// Send a request on the connection and hope for a reply. // Send a request on the connection and hope for a reply.
// Up to res.Attempts attempts. // Up to res.Attempts attempts.
func exchange(c net.Conn, m []byte, r *Resolver) (*Msg, os.Error) { func exchange(c net.Conn, m []byte, r *Resolver) (*Msg, os.Error) {
var timeout int64
var attempts int
if r.Mangle != nil { if r.Mangle != nil {
m = r.Mangle(m) 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) n, err := c.Write(m)
if err != nil { if err != nil {
return nil, err return nil, err
} }
c.SetReadTimeout(int64(r.Timeout) * 1e9) // nanoseconds c.SetReadTimeout(timeout * 1e9) // nanoseconds
// EDNS TODO buf := make([]byte, defaultMsgSize) // More than enough.
buf := make([]byte, defaultSize) // More than enough.
n, err = c.Read(buf) n, err = c.Read(buf)
if err != nil { if err != nil {
// More Go foo needed // More Go foo needed

View File

@ -26,8 +26,6 @@ func TestResolverEdns(t *testing.T) {
edns.UDPSize(4096, true) edns.UDPSize(4096, true)
edns.DoBit(true, true) edns.DoBit(true, true)
// edns.Nsid("mieks-server", true) // edns.Nsid("mieks-server", true)
// edns.Hdr.Class = ClassINET
// edns.Hdr.Ttl = 3600
// no options for now // no options for now
// edns.Option = make([]Option, 1) // edns.Option = make([]Option, 1)
// edns.Option[0].Code = OptionCodeNSID // edns.Option[0].Code = OptionCodeNSID

View File

@ -10,8 +10,6 @@ func TestResolver(t *testing.T) {
ch := NewQuerier(res) ch := NewQuerier(res)
res.Servers = []string{"127.0.0.1"} res.Servers = []string{"127.0.0.1"}
res.Timeout = 2
res.Attempts = 1
m := new(Msg) m := new(Msg)
m.MsgHdr.Recursion_desired = true //only set this bit m.MsgHdr.Recursion_desired = true //only set this bit

View File

@ -14,17 +14,22 @@ func TestSignature(t *testing.T) {
sig.Algorithm = AlgRSASHA1 sig.Algorithm = AlgRSASHA1
sig.Labels = 2 sig.Labels = 2
sig.OrigTtl = 4000 sig.OrigTtl = 4000
sig.Expiration = 1000 sig.Expiration = 1000 //Thu Jan 1 02:06:40 CET 1970
sig.Inception = 800 sig.Inception = 800 //Thu Jan 1 01:13:20 CET 1970
sig.KeyTag = 34641 sig.KeyTag = 34641
sig.SignerName = "miek.nl." sig.SignerName = "miek.nl."
sig.Sig = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ" sig.Sig = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ"
// Should not be valid // Should not be valid
if ValidSignaturePeriod(sig.Inception, sig.Expiration) { if sig.PeriodOK() {
t.Log("Should not be valid") t.Log("Should not be valid")
t.Fail() 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()
} }
} }

View File

@ -4,23 +4,19 @@
// Extended and bugfixes by Miek Gieben // Extended and bugfixes by Miek Gieben
// Package dns implements a full featured interface to the DNS. // Package dns implements a full featured interface to the DNS.
// Supported RFCs include: // Supported RFCs and features include:
// * 1034/1035 // * 1034/1035
// * 2671 - EDNS // * 2671 - EDNS
// * 4033/4034/4035 - DNSSEC + validation functions // * 4033/4034/4035 - DNSSEC + validation functions
// * 1982 - Serial Arithmetic // * 1982 - Serial Arithmetic
// * IP6 support // * 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. // Basic usage pattern for creating new Resource Record:
// To create quad-A record: "a.miek.nl" IN AAAA 2001:7b8:206:1:200:39ff:fe59:b187
// //
// Basic usage pattern: // r := new(RR_TXT)
// // r.TXT = "This is the content of the TXT record"
// import "net" // for IP functions // r.Hdr = RR_Header{Name: "a.miek.nl", Rrtype: TypeTXT, Class: ClassINET, Ttl: 3600}
// 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 package dns