| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | package transfer
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							|  |  |  | 	"context"
 | 
					
						
							|  |  |  | 	"errors"
 | 
					
						
							|  |  |  | 	"net"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/plugin"
 | 
					
						
							|  |  |  | 	clog "github.com/coredns/coredns/plugin/pkg/log"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/request"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/miekg/dns"
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var log = clog.NewWithPlugin("transfer")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Transfer is a plugin that handles zone transfers.
 | 
					
						
							|  |  |  | type Transfer struct {
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	Transferers []Transferer // List of plugins that implement Transferer
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	xfrs        []*xfr
 | 
					
						
							| 
									
										
										
										
											2022-06-27 15:48:34 -04:00
										 |  |  | 	tsigSecret  map[string]string
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	Next        plugin.Handler
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type xfr struct {
 | 
					
						
							|  |  |  | 	Zones []string
 | 
					
						
							|  |  |  | 	to    []string
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Transferer may be implemented by plugins to enable zone transfers
 | 
					
						
							|  |  |  | type Transferer interface {
 | 
					
						
							|  |  |  | 	// Transfer returns a channel to which it writes responses to the transfer request.
 | 
					
						
							|  |  |  | 	// If the plugin is not authoritative for the zone, it should immediately return the
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	// transfer.ErrNotAuthoritative error. This is important otherwise the transfer plugin can
 | 
					
						
							|  |  |  | 	// use plugin X while it should transfer the data from plugin Y.
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// If serial is 0, handle as an AXFR request. Transfer should send all records
 | 
					
						
							|  |  |  | 	// in the zone to the channel. The SOA should be written to the channel first, followed
 | 
					
						
							| 
									
										
										
										
											2023-08-10 23:06:48 +08:00
										 |  |  | 	// by all other records, including all NS + glue records. The implementation is also responsible
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	// for sending the last SOA record (to signal end of the transfer). This plugin will just grab
 | 
					
						
							|  |  |  | 	// these records and send them back to the requester, there is little validation done.
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	// If serial is not 0, it will be handled as an IXFR request. If the serial is equal to or greater (newer) than
 | 
					
						
							|  |  |  | 	// the current serial for the zone, send a single SOA record to the channel and then close it.
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	// If the serial is less (older) than the current serial for the zone, perform an AXFR fallback
 | 
					
						
							|  |  |  | 	// by proceeding as if an AXFR was requested (as above).
 | 
					
						
							|  |  |  | 	Transfer(zone string, serial uint32) (<-chan []dns.RR, error)
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var (
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	// ErrNotAuthoritative is returned by Transfer() when the plugin is not authoritative for the zone.
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	ErrNotAuthoritative = errors.New("not authoritative for zone")
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ServeDNS implements the plugin.Handler interface.
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | func (t *Transfer) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	state := request.Request{W: w, Req: r}
 | 
					
						
							|  |  |  | 	if state.QType() != dns.TypeAXFR && state.QType() != dns.TypeIXFR {
 | 
					
						
							|  |  |  | 		return plugin.NextOrFailure(t.Name(), t.Next, ctx, w, r)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-05 10:51:29 +01:00
										 |  |  | 	if state.Proto() != "tcp" {
 | 
					
						
							|  |  |  | 		return dns.RcodeRefused, nil
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	x := longestMatch(t.xfrs, state.QName())
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	if x == nil {
 | 
					
						
							|  |  |  | 		return plugin.NextOrFailure(t.Name(), t.Next, ctx, w, r)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !x.allowed(state) {
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 		// write msg here, so logging will pick it up
 | 
					
						
							|  |  |  | 		m := new(dns.Msg)
 | 
					
						
							|  |  |  | 		m.SetRcode(r, dns.RcodeRefused)
 | 
					
						
							|  |  |  | 		w.WriteMsg(m)
 | 
					
						
							|  |  |  | 		return 0, nil
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	// Get serial from request if this is an IXFR.
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	var serial uint32
 | 
					
						
							|  |  |  | 	if state.QType() == dns.TypeIXFR {
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 		if len(r.Ns) != 1 {
 | 
					
						
							|  |  |  | 			return dns.RcodeServerFailure, nil
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 		soa, ok := r.Ns[0].(*dns.SOA)
 | 
					
						
							|  |  |  | 		if !ok {
 | 
					
						
							|  |  |  | 			return dns.RcodeServerFailure, nil
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		serial = soa.Serial
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	// Get a receiving channel from the first Transferer plugin that returns one.
 | 
					
						
							|  |  |  | 	var pchan <-chan []dns.RR
 | 
					
						
							|  |  |  | 	var err error
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	for _, p := range t.Transferers {
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 		pchan, err = p.Transfer(state.QName(), serial)
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 		if err == ErrNotAuthoritative {
 | 
					
						
							|  |  |  | 			// plugin was not authoritative for the zone, try next plugin
 | 
					
						
							|  |  |  | 			continue
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		if err != nil {
 | 
					
						
							|  |  |  | 			return dns.RcodeServerFailure, err
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		break
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	if pchan == nil {
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 		return plugin.NextOrFailure(t.Name(), t.Next, ctx, w, r)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Send response to client
 | 
					
						
							|  |  |  | 	ch := make(chan *dns.Envelope)
 | 
					
						
							|  |  |  | 	tr := new(dns.Transfer)
 | 
					
						
							| 
									
										
										
										
											2022-06-27 15:48:34 -04:00
										 |  |  | 	if r.IsTsig() != nil {
 | 
					
						
							|  |  |  | 		tr.TsigSecret = t.tsigSecret
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2021-01-13 09:16:01 +01:00
										 |  |  | 	errCh := make(chan error)
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	go func() {
 | 
					
						
							| 
									
										
										
										
											2021-01-13 09:16:01 +01:00
										 |  |  | 		if err := tr.Out(w, r, ch); err != nil {
 | 
					
						
							|  |  |  | 			errCh <- err
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		close(errCh)
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	}()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rrs := []dns.RR{}
 | 
					
						
							|  |  |  | 	l := 0
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	var soa *dns.SOA
 | 
					
						
							|  |  |  | 	for records := range pchan {
 | 
					
						
							|  |  |  | 		if x, ok := records[0].(*dns.SOA); ok && soa == nil {
 | 
					
						
							|  |  |  | 			soa = x
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		rrs = append(rrs, records...)
 | 
					
						
							|  |  |  | 		if len(rrs) > 500 {
 | 
					
						
							| 
									
										
										
										
											2021-01-13 09:16:01 +01:00
										 |  |  | 			select {
 | 
					
						
							|  |  |  | 			case ch <- &dns.Envelope{RR: rrs}:
 | 
					
						
							|  |  |  | 			case err := <-errCh:
 | 
					
						
							| 
									
										
										
										
											2025-09-05 23:13:11 +03:00
										 |  |  | 				// Client errored; drain pchan to avoid blocking the producer goroutine.
 | 
					
						
							|  |  |  | 				go func() {
 | 
					
						
							|  |  |  | 					for range pchan {
 | 
					
						
							|  |  |  | 					}
 | 
					
						
							|  |  |  | 				}()
 | 
					
						
							| 
									
										
										
										
											2021-01-13 09:16:01 +01:00
										 |  |  | 				return dns.RcodeServerFailure, err
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 			l += len(rrs)
 | 
					
						
							|  |  |  | 			rrs = []dns.RR{}
 | 
					
						
							| 
									
										
										
										
											2020-07-08 09:00:26 -07:00
										 |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2020-07-07 21:38:07 +02:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	// if we are here and we only hold 1 soa (len(rrs) == 1) and soa != nil, and IXFR fallback should
 | 
					
						
							|  |  |  | 	// be performed. We haven't send anything on ch yet, so that can be closed (and waited for), and we only
 | 
					
						
							|  |  |  | 	// need to return the SOA back to the client and return.
 | 
					
						
							|  |  |  | 	if len(rrs) == 1 && soa != nil { // soa should never be nil...
 | 
					
						
							|  |  |  | 		close(ch)
 | 
					
						
							| 
									
										
										
										
											2021-01-13 09:16:01 +01:00
										 |  |  | 		err := <-errCh
 | 
					
						
							|  |  |  | 		if err != nil {
 | 
					
						
							|  |  |  | 			return dns.RcodeServerFailure, err
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		m := new(dns.Msg)
 | 
					
						
							|  |  |  | 		m.SetReply(r)
 | 
					
						
							|  |  |  | 		m.Answer = []dns.RR{soa}
 | 
					
						
							|  |  |  | 		w.WriteMsg(m)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		log.Infof("Outgoing noop, incremental transfer for up to date zone %q to %s for %d SOA serial", state.QName(), state.IP(), soa.Serial)
 | 
					
						
							|  |  |  | 		return 0, nil
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	if len(rrs) > 0 {
 | 
					
						
							| 
									
										
										
										
											2025-09-05 23:13:11 +03:00
										 |  |  | 		ch <- &dns.Envelope{RR: rrs}
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 		l += len(rrs)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-13 09:16:01 +01:00
										 |  |  | 	close(ch)     // Even though we close the channel here, we still have
 | 
					
						
							|  |  |  | 	err = <-errCh // to wait before we can return and close the connection.
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return dns.RcodeServerFailure, err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	logserial := uint32(0)
 | 
					
						
							|  |  |  | 	if soa != nil {
 | 
					
						
							|  |  |  | 		logserial = soa.Serial
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 	log.Infof("Outgoing transfer of %d records of zone %q to %s for %d SOA serial", l, state.QName(), state.IP(), logserial)
 | 
					
						
							|  |  |  | 	return 0, nil
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (x xfr) allowed(state request.Request) bool {
 | 
					
						
							|  |  |  | 	for _, h := range x.to {
 | 
					
						
							|  |  |  | 		if h == "*" {
 | 
					
						
							|  |  |  | 			return true
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		to, _, err := net.SplitHostPort(h)
 | 
					
						
							|  |  |  | 		if err != nil {
 | 
					
						
							|  |  |  | 			return false
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | 		// If remote IP matches we accept. TODO(): make this works with ranges
 | 
					
						
							|  |  |  | 		if to == state.IP() {
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | 			return true
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return false
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-24 11:30:39 -07:00
										 |  |  | // Find the first transfer instance for which the queried zone is the longest match. When nothing
 | 
					
						
							|  |  |  | // is found nil is returned.
 | 
					
						
							|  |  |  | func longestMatch(xfrs []*xfr, name string) *xfr {
 | 
					
						
							|  |  |  | 	// TODO(xxx): optimize and make it a map (or maps)
 | 
					
						
							|  |  |  | 	var x *xfr
 | 
					
						
							|  |  |  | 	zone := "" // longest zone match wins
 | 
					
						
							|  |  |  | 	for _, xfr := range xfrs {
 | 
					
						
							|  |  |  | 		if z := plugin.Zones(xfr.Zones).Matches(name); z != "" {
 | 
					
						
							|  |  |  | 			if z > zone {
 | 
					
						
							|  |  |  | 				zone = z
 | 
					
						
							|  |  |  | 				x = xfr
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return x
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-01 12:02:43 -04:00
										 |  |  | // Name implements the Handler interface.
 | 
					
						
							|  |  |  | func (Transfer) Name() string { return "transfer" }
 |