2017-04-18 01:02:51 +00:00
package dns
import (
2018-05-04 21:39:27 +00:00
"fmt"
2017-04-18 01:02:51 +00:00
"time"
)
// Envelope is used when doing a zone transfer with a remote server.
type Envelope struct {
RR [ ] RR // The set of RRs in the answer section of the xfr reply message.
Error error // If something went wrong, this contains the error.
}
// A Transfer defines parameters that are used during a zone transfer.
type Transfer struct {
* Conn
DialTimeout time . Duration // net.DialTimeout, defaults to 2 seconds
ReadTimeout time . Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds
WriteTimeout time . Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds
2018-05-04 21:39:27 +00:00
TsigSecret map [ string ] string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
2017-04-18 01:02:51 +00:00
tsigTimersOnly bool
}
// Think we need to away to stop the transfer
// In performs an incoming transfer with the server in a.
// If you would like to set the source IP, or some other attribute
// of a Dialer for a Transfer, you can do so by specifying the attributes
// in the Transfer.Conn:
//
// d := net.Dialer{LocalAddr: transfer_source}
// con, err := d.Dial("tcp", master)
// dnscon := &dns.Conn{Conn:con}
// transfer = &dns.Transfer{Conn: dnscon}
// channel, err := transfer.In(message, master)
//
func ( t * Transfer ) In ( q * Msg , a string ) ( env chan * Envelope , err error ) {
2019-01-23 07:52:17 +00:00
switch q . Question [ 0 ] . Qtype {
case TypeAXFR , TypeIXFR :
default :
return nil , & Error { "unsupported question type" }
}
2017-04-18 01:02:51 +00:00
timeout := dnsTimeout
if t . DialTimeout != 0 {
timeout = t . DialTimeout
}
2019-01-23 07:52:17 +00:00
2017-04-18 01:02:51 +00:00
if t . Conn == nil {
t . Conn , err = DialTimeout ( "tcp" , a , timeout )
if err != nil {
return nil , err
}
}
2019-01-23 07:52:17 +00:00
2017-04-18 01:02:51 +00:00
if err := t . WriteMsg ( q ) ; err != nil {
return nil , err
}
2019-01-23 07:52:17 +00:00
2017-04-18 01:02:51 +00:00
env = make ( chan * Envelope )
2019-01-23 07:52:17 +00:00
switch q . Question [ 0 ] . Qtype {
case TypeAXFR :
go t . inAxfr ( q , env )
case TypeIXFR :
go t . inIxfr ( q , env )
}
2017-04-18 01:02:51 +00:00
return env , nil
}
2018-05-04 21:39:27 +00:00
func ( t * Transfer ) inAxfr ( q * Msg , c chan * Envelope ) {
2017-04-18 01:02:51 +00:00
first := true
defer t . Close ( )
defer close ( c )
timeout := dnsTimeout
if t . ReadTimeout != 0 {
timeout = t . ReadTimeout
}
for {
t . Conn . SetReadDeadline ( time . Now ( ) . Add ( timeout ) )
in , err := t . ReadMsg ( )
if err != nil {
c <- & Envelope { nil , err }
return
}
2018-05-04 21:39:27 +00:00
if q . Id != in . Id {
2017-04-18 01:02:51 +00:00
c <- & Envelope { in . Answer , ErrId }
return
}
if first {
2018-05-04 21:39:27 +00:00
if in . Rcode != RcodeSuccess {
c <- & Envelope { in . Answer , & Error { err : fmt . Sprintf ( errXFR , in . Rcode ) } }
return
}
2017-04-18 01:02:51 +00:00
if ! isSOAFirst ( in ) {
c <- & Envelope { in . Answer , ErrSoa }
return
}
first = ! first
// only one answer that is SOA, receive more
if len ( in . Answer ) == 1 {
t . tsigTimersOnly = true
c <- & Envelope { in . Answer , nil }
continue
}
}
if ! first {
t . tsigTimersOnly = true // Subsequent envelopes use this.
if isSOALast ( in ) {
c <- & Envelope { in . Answer , nil }
return
}
c <- & Envelope { in . Answer , nil }
}
}
}
2018-05-04 21:39:27 +00:00
func ( t * Transfer ) inIxfr ( q * Msg , c chan * Envelope ) {
2019-01-23 07:52:17 +00:00
var serial uint32 // The first serial seen is the current server serial
2018-05-04 21:39:27 +00:00
axfr := true
n := 0
qser := q . Ns [ 0 ] . ( * SOA ) . Serial
2017-04-18 01:02:51 +00:00
defer t . Close ( )
defer close ( c )
timeout := dnsTimeout
if t . ReadTimeout != 0 {
timeout = t . ReadTimeout
}
for {
t . SetReadDeadline ( time . Now ( ) . Add ( timeout ) )
in , err := t . ReadMsg ( )
if err != nil {
c <- & Envelope { nil , err }
return
}
2018-05-04 21:39:27 +00:00
if q . Id != in . Id {
2017-04-18 01:02:51 +00:00
c <- & Envelope { in . Answer , ErrId }
return
}
2018-05-04 21:39:27 +00:00
if in . Rcode != RcodeSuccess {
c <- & Envelope { in . Answer , & Error { err : fmt . Sprintf ( errXFR , in . Rcode ) } }
return
}
if n == 0 {
2017-04-18 01:02:51 +00:00
// Check if the returned answer is ok
if ! isSOAFirst ( in ) {
c <- & Envelope { in . Answer , ErrSoa }
return
}
// This serial is important
serial = in . Answer [ 0 ] . ( * SOA ) . Serial
2018-05-04 21:39:27 +00:00
// Check if there are no changes in zone
if qser >= serial {
c <- & Envelope { in . Answer , nil }
return
}
2017-04-18 01:02:51 +00:00
}
// Now we need to check each message for SOA records, to see what we need to do
2018-05-04 21:39:27 +00:00
t . tsigTimersOnly = true
for _ , rr := range in . Answer {
if v , ok := rr . ( * SOA ) ; ok {
2017-04-18 01:02:51 +00:00
if v . Serial == serial {
2018-05-04 21:39:27 +00:00
n ++
// quit if it's a full axfr or the the servers' SOA is repeated the third time
if axfr && n == 2 || n == 3 {
c <- & Envelope { in . Answer , nil }
return
}
} else if axfr {
// it's an ixfr
axfr = false
2017-04-18 01:02:51 +00:00
}
}
}
2018-05-04 21:39:27 +00:00
c <- & Envelope { in . Answer , nil }
2017-04-18 01:02:51 +00:00
}
}
// Out performs an outgoing transfer with the client connecting in w.
// Basic use pattern:
//
// ch := make(chan *dns.Envelope)
// tr := new(dns.Transfer)
2020-04-29 02:23:32 +00:00
// var wg sync.WaitGroup
// go func() {
// tr.Out(w, r, ch)
// wg.Done()
// }()
2017-04-18 01:02:51 +00:00
// ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}}
// close(ch)
2020-04-29 02:23:32 +00:00
// wg.Wait() // wait until everything is written out
// w.Close() // close connection
2017-04-18 01:02:51 +00:00
//
2020-04-29 02:23:32 +00:00
// The server is responsible for sending the correct sequence of RRs through the channel ch.
2017-04-18 01:02:51 +00:00
func ( t * Transfer ) Out ( w ResponseWriter , q * Msg , ch chan * Envelope ) error {
for x := range ch {
r := new ( Msg )
// Compress?
r . SetReply ( q )
r . Authoritative = true
// assume it fits TODO(miek): fix
r . Answer = append ( r . Answer , x . RR ... )
2019-06-09 00:01:48 +00:00
if tsig := q . IsTsig ( ) ; tsig != nil && w . TsigStatus ( ) == nil {
r . SetTsig ( tsig . Hdr . Name , tsig . Algorithm , tsig . Fudge , time . Now ( ) . Unix ( ) )
}
2017-04-18 01:02:51 +00:00
if err := w . WriteMsg ( r ) ; err != nil {
return err
}
2019-06-09 00:01:48 +00:00
w . TsigTimersOnly ( true )
2017-04-18 01:02:51 +00:00
}
return nil
}
// ReadMsg reads a message from the transfer connection t.
func ( t * Transfer ) ReadMsg ( ) ( * Msg , error ) {
m := new ( Msg )
p := make ( [ ] byte , MaxMsgSize )
n , err := t . Read ( p )
if err != nil && n == 0 {
return nil , err
}
p = p [ : n ]
if err := m . Unpack ( p ) ; err != nil {
return nil , err
}
if ts := m . IsTsig ( ) ; ts != nil && t . TsigSecret != nil {
if _ , ok := t . TsigSecret [ ts . Hdr . Name ] ; ! ok {
return m , ErrSecret
}
// Need to work on the original message p, as that was used to calculate the tsig.
err = TsigVerify ( p , t . TsigSecret [ ts . Hdr . Name ] , t . tsigRequestMAC , t . tsigTimersOnly )
t . tsigRequestMAC = ts . MAC
}
return m , err
}
// WriteMsg writes a message through the transfer connection t.
func ( t * Transfer ) WriteMsg ( m * Msg ) ( err error ) {
var out [ ] byte
if ts := m . IsTsig ( ) ; ts != nil && t . TsigSecret != nil {
if _ , ok := t . TsigSecret [ ts . Hdr . Name ] ; ! ok {
return ErrSecret
}
out , t . tsigRequestMAC , err = TsigGenerate ( m , t . TsigSecret [ ts . Hdr . Name ] , t . tsigRequestMAC , t . tsigTimersOnly )
} else {
out , err = m . Pack ( )
}
if err != nil {
return err
}
2019-01-23 07:52:17 +00:00
_ , err = t . Write ( out )
return err
2017-04-18 01:02:51 +00:00
}
func isSOAFirst ( in * Msg ) bool {
2019-01-23 07:52:17 +00:00
return len ( in . Answer ) > 0 &&
in . Answer [ 0 ] . Header ( ) . Rrtype == TypeSOA
2017-04-18 01:02:51 +00:00
}
func isSOALast ( in * Msg ) bool {
2019-01-23 07:52:17 +00:00
return len ( in . Answer ) > 0 &&
in . Answer [ len ( in . Answer ) - 1 ] . Header ( ) . Rrtype == TypeSOA
2017-04-18 01:02:51 +00:00
}
2018-05-04 21:39:27 +00:00
const errXFR = "bad xfr rcode: %d"