diff --git a/TODO.markdown b/TODO.markdown index cfd82f21..73ed2cce 100644 --- a/TODO.markdown +++ b/TODO.markdown @@ -1,7 +1,6 @@ # TODO -* outgoing [AI]xfr -* fix 'q' standardize ipv6 input with [::1]#53 ? +* 'q' standardize ipv6 input with [::1]#53 ? ## Nice to have diff --git a/client.go b/client.go index 1a1370c0..52b499d6 100644 --- a/client.go +++ b/client.go @@ -155,7 +155,7 @@ func (w *reply) receive() (*Msg, error) { p = make([]byte, DefaultMsgSize) } n, err := w.readClient(p) - if err != nil || n == 0 { + if err != nil && n == 0 { return nil, err } p = p[:n] @@ -180,7 +180,7 @@ func (w *reply) readClient(p []byte) (n int, err error) { if w.conn == nil { return 0, ErrConnEmpty } - if len(p) < 1 { + if len(p) < 2 { return 0, io.ErrShortBuffer } attempts := w.client.Attempts @@ -279,9 +279,6 @@ func (w *reply) writeClient(p []byte) (n int, err error) { if attempts == 0 { attempts = 1 } - if err = w.dial(); err != nil { - return 0, err - } switch w.client.Net { case "tcp", "tcp4", "tcp6": if len(p) < 2 { diff --git a/client_test.go b/client_test.go index 03013506..ca7dfa9b 100644 --- a/client_test.go +++ b/client_test.go @@ -66,16 +66,14 @@ func TestClientTsigAXFR(t *testing.T) { return } else { for ex := range a { - t.Log(ex.Reply.String()) if ex.Error != nil { t.Logf("Error %s\n", ex.Error.Error()) t.Fail() break } - if ex.Reply.Rcode != RcodeSuccess { - break + for _, rr := range ex.RR { + t.Logf("%s\n", rr.String()) } - t.Logf("%s\n", ex.Reply.String()) } } } @@ -86,7 +84,6 @@ func TestClientAXFRMultipleMessages(t *testing.T) { c := new(Client) c.Net = "tcp" - // timeout? if a, err := c.XfrReceive(m, "85.223.71.124:53"); err != nil { t.Log("Failed to setup axfr" + err.Error()) @@ -94,15 +91,11 @@ func TestClientAXFRMultipleMessages(t *testing.T) { return } else { for ex := range a { - t.Log(ex.Reply.String()) if ex.Error != nil { t.Logf("Error %s\n", ex.Error.Error()) t.Fail() break } - if ex.Reply.Rcode != RcodeSuccess { - break - } } } } diff --git a/ex/axfr/axfr.go b/ex/axfr/axfr.go index 6fa0a0af..e5d13c70 100644 --- a/ex/axfr/axfr.go +++ b/ex/axfr/axfr.go @@ -1,9 +1,9 @@ package main import ( - "github.com/miekg/dns" "flag" "fmt" + "github.com/miekg/dns" "strings" "time" ) @@ -33,7 +33,11 @@ func main() { if t, e := client.XfrReceive(m, *nameserver); e == nil { for r := range t { if r.Error == nil { - fmt.Printf("%v\n", r.Reply) + for _, rr := range r.RR { + fmt.Printf("%v\n", rr) + } + } else { + fmt.Printf("error: %s\n", r.Error.Error()) } } } else { diff --git a/ex/reflect/reflect.go b/ex/reflect/reflect.go index b1d7182d..cd9672d8 100644 --- a/ex/reflect/reflect.go +++ b/ex/reflect/reflect.go @@ -28,6 +28,7 @@ import ( "runtime/pprof" "strconv" "strings" + "syscall" "time" ) @@ -76,6 +77,24 @@ func handleReflect(w dns.ResponseWriter, r *dns.Msg) { t.Txt = []string{str} switch r.Question[0].Qtype { + case dns.TypeAXFR: + c := make(chan *dns.XfrToken) + var e *error + if err := dns.XfrSend(w, r, c, e); err != nil { + close(c) + return + } + soa, _ := dns.NewRR(`whoami.miek.nl. IN SOA elektron.atoom.net. miekg.atoom.net. ( + 2009032802 + 21600 + 7200 + 604800 + 3600)`) + c <- &dns.XfrToken{RR: []dns.RR{soa, t, rr, soa}} + close(c) + w.Hijack() + // w.Close() // Client closes + return case dns.TypeTXT: m.Answer = append(m.Answer, t) m.Extra = append(m.Extra, rr) @@ -86,7 +105,7 @@ func handleReflect(w dns.ResponseWriter, r *dns.Msg) { m.Extra = append(m.Extra, t) } - if r.IsTsig() { + if r.IsTsig() != nil { if w.TsigStatus() == nil { m.SetTsig(r.Extra[len(r.Extra)-1].(*dns.RR_TSIG).Hdr.Name, dns.HmacMD5, 300, time.Now().Unix()) } else { @@ -142,12 +161,12 @@ func main() { go serve("tcp", name, secret) go serve("udp", name, secret) sig := make(chan os.Signal) - signal.Notify(sig) + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) forever: for { select { - case <-sig: - fmt.Printf("Signal received, stopping\n") + case s:=<-sig: + fmt.Printf("Signal (%d) received, stopping\n", s) break forever } } diff --git a/server.go b/server.go index 15d1ceff..0b1317ee 100644 --- a/server.go +++ b/server.go @@ -22,12 +22,19 @@ type Handler interface { type ResponseWriter interface { // RemoteAddr returns the net.Addr of the client that sent the current request. RemoteAddr() net.Addr - // TsigStatus returns the status of the Tsig. - TsigStatus() error // Write writes a reply back to the client. Write(*Msg) error // WriteBuf writes a raw buffer back to the client. WriteBuf([]byte) error + // Close closes the connection. + Close() error + // TsigStatus returns the status of the Tsig. + TsigStatus() error + // TsigTimersOnly sets the tsig timers only boolean. + TsigTimersOnly(bool) + // Hijack lets the caller take over the connection. + // After a call to Hijack(), the DNS package will not do anything with the connection + Hijack() } type conn struct { @@ -36,12 +43,16 @@ type conn struct { request []byte // bytes read _UDP *net.UDPConn // i/o connection if UDP was used _TCP *net.TCPConn // i/o connection if TCP was used - hijacked bool // connection has been hijacked by hander TODO(mg) tsigSecret map[string]string // the tsig secrets } type response struct { conn *conn +<<<<<<< HEAD +======= + hijacked bool // connection has been hijacked by handler + req *Msg +>>>>>>> axfr tsigStatus error tsigTimersOnly bool tsigRequestMAC string @@ -303,18 +314,6 @@ func newConn(t *net.TCPConn, u *net.UDPConn, a net.Addr, buf []byte, handler Han return c, nil } -// Close the connection. -func (c *conn) close() { - switch { - case c._UDP != nil: - c._UDP.Close() - c._UDP = nil - case c._TCP != nil: - c._TCP.Close() - c._TCP = nil - } -} - // Serve a new connection. func (c *conn) serve() { // for block to make it easy to break out to close the tcp connection @@ -338,17 +337,26 @@ func (c *conn) serve() { w.tsigStatus = ErrKeyAlg } w.tsigStatus = TsigVerify(c.request, w.conn.tsigSecret[secret], "", false) - w.tsigTimersOnly = false // Will this ever be true? + w.tsigTimersOnly = false w.tsigRequestMAC = req.Extra[len(req.Extra)-1].(*RR_TSIG).MAC } c.handler.ServeDNS(w, req) // this does the writing back to the client if c.hijacked { + // client takes care of the connection, i.e. calls Close() return } break } + // quite elaborate, but this was the original c.close() function if c._TCP != nil { - c.close() // Listen and Serve is closed then + switch { + case c._UDP != nil: + c._UDP.Close() + c._UDP = nil + case c._TCP != nil: + c._TCP.Close() + c._TCP = nil + } } } @@ -421,3 +429,25 @@ func (w *response) RemoteAddr() net.Addr { return w.conn.remoteAddr } // TsigStatus implements the ResponseWriter.TsigStatus method. func (w *response) TsigStatus() error { return w.tsigStatus } + +// TsigTimersOnly implements the ResponseWriter.TsigTimersOnly method. +func (w *response) TsigTimersOnly(b bool) { w.tsigTimersOnly = b } + +// Hijack implements the ResponseWriter.Hijack method. +func (w *response) Hijack() { w.hijacked = true } + +// Close implements the ResponseWriter.Close method +func (w *response) Close() error { + if w.conn._UDP != nil { + e := w.conn._UDP.Close() + w.conn._UDP = nil + return e + } + if w.conn._TCP != nil { + e := w.conn._TCP.Close() + w.conn._TCP = nil + return e + } + // no-op + return nil +} diff --git a/xfr.go b/xfr.go index fb3ac8e9..a7cb74d1 100644 --- a/xfr.go +++ b/xfr.go @@ -1,21 +1,13 @@ package dns -import ( - "net" - "time" -) - -// XfrMsg is used when doing [IA]xfr with a remote server. -type XfrMsg struct { - Request *Msg // the question sent - Reply *Msg // the answer to the question that was sent - Rtt time.Duration // round trip time - RemoteAddr net.Addr // address of the server - Error error // if something went wrong, this contains the error +// XfrToken is used when doing [IA]xfr with a remote server. +type XfrToken struct { + RR []RR // the set of RRs in the answer section of the AXFR reply message + Error error // if something went wrong, this contains the error } // XfrReceive performs a [AI]xfr request (depends on the message's Qtype). It returns -// a channel of XfrMsg on which the replies from the server are sent. At the end of +// a channel of XfrToken on which the replies from the server are sent. At the end of // the transfer the channel is closed. // It panics if the Qtype does not equal TypeAXFR or TypeIXFR. The messages are TSIG checked if // needed, no other post-processing is performed. The caller must dissect the returned @@ -23,12 +15,12 @@ type XfrMsg struct { // // Basic use pattern for receiving an AXFR: // -// // m contains the [AI]xfr request +// // m contains the AXFR request // t, e := client.XfrReceive(m, "127.0.0.1:53") // for r := range t { -// // ... deal with r.Reply or r.Error +// // ... deal with r.RR or r.Error // } -func (c *Client) XfrReceive(q *Msg, a string) (chan *XfrMsg, error) { +func (c *Client) XfrReceive(q *Msg, a string) (chan *XfrToken, error) { w := new(reply) w.client = c w.addr = a @@ -39,13 +31,13 @@ func (c *Client) XfrReceive(q *Msg, a string) (chan *XfrMsg, error) { if err := w.send(q); err != nil { return nil, err } - e := make(chan *XfrMsg) + e := make(chan *XfrToken) switch q.Question[0].Qtype { case TypeAXFR: - go w.axfrReceive(e) + go w.axfrReceive(q, e) return e, nil case TypeIXFR: - go w.ixfrReceive(e) + go w.ixfrReceive(q, e) return e, nil default: return nil, ErrXfrType @@ -53,23 +45,23 @@ func (c *Client) XfrReceive(q *Msg, a string) (chan *XfrMsg, error) { panic("dns: not reached") } -func (w *reply) axfrReceive(c chan *XfrMsg) { +func (w *reply) axfrReceive(q *Msg, c chan *XfrToken) { first := true defer w.conn.Close() defer close(c) for { in, err := w.receive() if err != nil { - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: err} + c <- &XfrToken{in.Answer, err} return } - if w.req.Id != in.Id { - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: ErrId} + if in.Id != q.Id { + c <- &XfrToken{in.Answer, ErrId} return } if first { if !checkXfrSOA(in, true) { - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: ErrXfrSoa} + c <- &XfrToken{in.Answer, ErrXfrSoa} return } first = !first @@ -78,16 +70,16 @@ func (w *reply) axfrReceive(c chan *XfrMsg) { if !first { w.tsigTimersOnly = true // Subsequent envelopes use this. if checkXfrSOA(in, false) { - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: nil} + c <- &XfrToken{in.Answer, nil} return } - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: nil} + c <- &XfrToken{in.Answer, nil} } } panic("dns: not reached") } -func (w *reply) ixfrReceive(c chan *XfrMsg) { +func (w *reply) ixfrReceive(q *Msg, c chan *XfrToken) { var serial uint32 // The first serial seen is the current server serial first := true defer w.conn.Close() @@ -95,23 +87,23 @@ func (w *reply) ixfrReceive(c chan *XfrMsg) { for { in, err := w.receive() if err != nil { - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: err} + c <- &XfrToken{in.Answer, err} return } - if w.req.Id != in.Id { - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: ErrId} + if q.Id != in.Id { + c <- &XfrToken{in.Answer, ErrId} return } if first { // A single SOA RR signals "no changes" if len(in.Answer) == 1 && checkXfrSOA(in, true) { - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: nil} + c <- &XfrToken{in.Answer, nil} return } // Check if the returned answer is ok if !checkXfrSOA(in, true) { - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: ErrXfrSoa} + c <- &XfrToken{in.Answer, ErrXfrSoa} return } // This serial is important @@ -125,82 +117,19 @@ func (w *reply) ixfrReceive(c chan *XfrMsg) { // If the last record in the IXFR contains the servers' SOA, we should quit if v, ok := in.Answer[len(in.Answer)-1].(*RR_SOA); ok { if v.Serial == serial { - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr(), Error: nil} + c <- &XfrToken{in.Answer, nil} return } } - c <- &XfrMsg{Request: w.req, Reply: in, Rtt: w.rtt, RemoteAddr: w.conn.RemoteAddr()} + c <- &XfrToken{in.Answer, nil} } } panic("dns: not reached") } -// XfrSend performs an outgoing Ixfr or Axfr. The function is [AI]xfr agnostic, it is -// up to the caller to correctly send the sequence of messages. -func XfrSend(w ResponseWriter, q *Msg, a string) error { - switch q.Question[0].Qtype { - case TypeAXFR, TypeIXFR: - // go d.xfrWrite(q, m, e) - default: - return ErrXfrType - } - return nil -} - -/* -// Just send the zone -func (d *Conn) axfrSend(q *Msg, m chan *Xfr, e chan os.Error) { - out := new(Msg) - out.Id = q.Id - out.Question = q.Question - out.Answer = make([]RR, 1001) // TODO(mg) look at this number - out.MsgHdr.Response = true - out.MsgHdr.Authoritative = true - first := true - var soa *RR_SOA - i := 0 - for r := range m { - out.Answer[i] = r.RR - if soa == nil { - if r.RR.Header().Rrtype != TypeSOA { - e <- ErrXfrSoa - return - } else { - soa = r.RR.(*RR_SOA) - } - } - i++ - if i > 1000 { - // Send it - err := d.WriteMsg(out) - if err != nil { - e <- err - return - } - i = 0 - // Gaat dit goed? - out.Answer = out.Answer[:0] - if first { - if d.Tsig != nil { - d.Tsig.TimersOnly = true - } - first = !first - } - } - } - // Everything is sent, only the closing soa is left. - out.Answer[i] = soa - out.Answer = out.Answer[:i+1] - err := d.WriteMsg(out) - if err != nil { - e <- err - } -} -*/ - // Check if he SOA record exists in the Answer section of // the packet. If first is true the first RR must be a SOA -// if false, the last one should be a SOA +// if false, the last one should be a SOA. func checkXfrSOA(in *Msg, first bool) bool { if len(in.Answer) > 0 { if first { @@ -211,3 +140,56 @@ func checkXfrSOA(in *Msg, first bool) bool { } return false } + + + +// XfrSend performs an outgoing [AI]xfr depending on the request message. The +// caller is responsible for sending the correct sequence of RR sets through +// the channel c. For reasons of symmetry XfrToken is re-used. +// Errors are signaled via the error pointer, when an error occurs the function +// sets the error and returns (it does not close the channel). +// TSIG and enveloping is handled by XfrSend. +// +// Basic use pattern for sending an AXFR: +// +// // q contains the AXFR request +// c := make(chan *XfrToken) +// var e *error +// err := XfrSend(w, q, c, e) +// w.Hijack() // hijack the connection so that the library doesn't close it +// for _, rrset := range rrsets { // rrset is a []RR +// c <- &{XfrToken{RR: rrset} +// if e != nil { +// close(c) +// break +// } +// } +// // w.Close() // Don't! Let the client close the connection +func XfrSend(w ResponseWriter, q *Msg, c chan *XfrToken, e *error) error { + switch q.Question[0].Qtype { + case TypeAXFR, TypeIXFR: + go axfrSend(w, q, c, e) + return nil + default: + return ErrXfrType + } + panic("not reached") +} + +// TODO(mg): count the RRs and the resulting size. +func axfrSend(w ResponseWriter, req *Msg, c chan *XfrToken, e *error) { + rep := new(Msg) + rep.SetReply(req) + rep.MsgHdr.Authoritative = true + + for x := range c { + // assume it fits + rep.Answer = append(rep.Answer, x.RR...) + if err := w.Write(rep); e != nil { + *e = err + return + } + w.TsigTimersOnly(true) + rep.Answer = nil + } +}