Move all (almost all) Go files in middleware into their own packages. This makes for better naming and discoverability. Lot of changes elsewhere to make this change. The middleware.State was renamed to request.Request which is better, but still does not cover all use-cases. It was also moved out middleware because it is used by `dnsserver` as well. A pkg/dnsutil packages was added for shared, handy, dns util functions. All normalize functions are now put in normalize.go
245 lines
6.1 KiB
Go
245 lines
6.1 KiB
Go
package dnsserver
|
|
|
|
import (
|
|
"log"
|
|
"net"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/miekg/coredns/middleware/pkg/edns"
|
|
"github.com/miekg/coredns/request"
|
|
|
|
"github.com/miekg/dns"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// Server represents an instance of a server, which serves
|
|
// DNS requests at a particular address (host and port). A
|
|
// server is capable of serving numerous zones on
|
|
// the same address and the listener may be stopped for
|
|
// graceful termination (POSIX only).
|
|
type Server struct {
|
|
Addr string // Address we listen on
|
|
mux *dns.ServeMux
|
|
server [2]*dns.Server // 0 is a net.Listener, 1 is a net.PacketConn (a *UDPConn) in our case.
|
|
|
|
l net.Listener
|
|
p net.PacketConn
|
|
m sync.Mutex // protects listener and packetconn
|
|
|
|
zones map[string]*Config // zones keyed by their address
|
|
dnsWg sync.WaitGroup // used to wait on outstanding connections
|
|
connTimeout time.Duration // the maximum duration of a graceful shutdown
|
|
}
|
|
|
|
// NewServer returns a new CoreDNS server and compiles all middleware in to it.
|
|
func NewServer(addr string, group []*Config) (*Server, error) {
|
|
|
|
s := &Server{
|
|
Addr: addr,
|
|
zones: make(map[string]*Config),
|
|
connTimeout: 5 * time.Second, // TODO(miek): was configurable
|
|
}
|
|
mux := dns.NewServeMux()
|
|
mux.Handle(".", s) // wildcard handler, everything will go through here
|
|
s.mux = mux
|
|
|
|
// We have to bound our wg with one increment
|
|
// to prevent a "race condition" that is hard-coded
|
|
// into sync.WaitGroup.Wait() - basically, an add
|
|
// with a positive delta must be guaranteed to
|
|
// occur before Wait() is called on the wg.
|
|
// In a way, this kind of acts as a safety barrier.
|
|
s.dnsWg.Add(1)
|
|
|
|
for _, site := range group {
|
|
// set the config per zone
|
|
s.zones[site.Zone] = site
|
|
// compile custom middleware for everything
|
|
var stack Handler
|
|
for i := len(site.Middleware) - 1; i >= 0; i-- {
|
|
stack = site.Middleware[i](stack)
|
|
}
|
|
site.middlewareChain = stack
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// Serve starts the server with an existing listener. It blocks until the server stops.
|
|
func (s *Server) Serve(l net.Listener) error {
|
|
s.m.Lock()
|
|
s.server[tcp] = &dns.Server{Listener: l, Net: "tcp", Handler: s.mux}
|
|
s.m.Unlock()
|
|
|
|
return s.server[tcp].ActivateAndServe()
|
|
}
|
|
|
|
// ServePacket starts the server with an existing packetconn. It blocks until the server stops.
|
|
func (s *Server) ServePacket(p net.PacketConn) error {
|
|
s.m.Lock()
|
|
s.server[udp] = &dns.Server{PacketConn: p, Net: "udp", Handler: s.mux}
|
|
s.m.Unlock()
|
|
|
|
return s.server[udp].ActivateAndServe()
|
|
}
|
|
|
|
// Listen implements caddy.TCPServer interface.
|
|
func (s *Server) Listen() (net.Listener, error) {
|
|
l, err := net.Listen("tcp", s.Addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.m.Lock()
|
|
s.l = l
|
|
s.m.Unlock()
|
|
return l, nil
|
|
}
|
|
|
|
// ListenPacket implements caddy.UDPServer interface.
|
|
func (s *Server) ListenPacket() (net.PacketConn, error) {
|
|
p, err := net.ListenPacket("udp", s.Addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.m.Lock()
|
|
s.p = p
|
|
s.m.Unlock()
|
|
return p, nil
|
|
}
|
|
|
|
// Stop stops the server. It blocks until the server is
|
|
// totally stopped. On POSIX systems, it will wait for
|
|
// connections to close (up to a max timeout of a few
|
|
// seconds); on Windows it will close the listener
|
|
// immediately.
|
|
func (s *Server) Stop() (err error) {
|
|
|
|
if runtime.GOOS != "windows" {
|
|
// force connections to close after timeout
|
|
done := make(chan struct{})
|
|
go func() {
|
|
s.dnsWg.Done() // decrement our initial increment used as a barrier
|
|
s.dnsWg.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
// Wait for remaining connections to finish or
|
|
// force them all to close after timeout
|
|
select {
|
|
case <-time.After(s.connTimeout):
|
|
case <-done:
|
|
}
|
|
}
|
|
|
|
// Close the listener now; this stops the server without delay
|
|
s.m.Lock()
|
|
if s.l != nil {
|
|
err = s.l.Close()
|
|
}
|
|
if s.p != nil {
|
|
err = s.p.Close()
|
|
}
|
|
|
|
for _, s1 := range s.server {
|
|
err = s1.Shutdown()
|
|
}
|
|
s.m.Unlock()
|
|
return
|
|
}
|
|
|
|
// ServeDNS is the entry point for every request to the address that s
|
|
// is bound to. It acts as a multiplexer for the requests zonename as
|
|
// defined in the request so that the correct zone
|
|
// (configuration and middleware stack) will handle the request.
|
|
func (s *Server) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|
// TODO(miek): expensive to use defer
|
|
defer func() {
|
|
// In case the user doesn't enable error middleware, we still
|
|
// need to make sure that we stay alive up here
|
|
if rec := recover(); rec != nil {
|
|
DefaultErrorFunc(w, r, dns.RcodeServerFailure)
|
|
}
|
|
}()
|
|
|
|
if m, err := edns.Version(r); err != nil { // Wrong EDNS version, return at once.
|
|
w.WriteMsg(m)
|
|
return
|
|
}
|
|
|
|
q := r.Question[0].Name
|
|
b := make([]byte, len(q))
|
|
off, end := 0, false
|
|
ctx := context.Background()
|
|
|
|
for {
|
|
l := len(q[off:])
|
|
for i := 0; i < l; i++ {
|
|
b[i] = q[off+i]
|
|
// normalize the name for the lookup
|
|
if b[i] >= 'A' && b[i] <= 'Z' {
|
|
b[i] |= ('a' - 'A')
|
|
}
|
|
}
|
|
|
|
if h, ok := s.zones[string(b[:l])]; ok {
|
|
if r.Question[0].Qtype != dns.TypeDS {
|
|
rcode, _ := h.middlewareChain.ServeDNS(ctx, w, r)
|
|
if rcodeNoClientWrite(rcode) {
|
|
DefaultErrorFunc(w, r, rcode)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
off, end = dns.NextLabel(q, off)
|
|
if end {
|
|
break
|
|
}
|
|
}
|
|
// Wildcard match, if we have found nothing try the root zone as a last resort.
|
|
if h, ok := s.zones["."]; ok {
|
|
rcode, _ := h.middlewareChain.ServeDNS(ctx, w, r)
|
|
if rcodeNoClientWrite(rcode) {
|
|
DefaultErrorFunc(w, r, rcode)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Still here? Error out with REFUSED and some logging
|
|
remoteHost := w.RemoteAddr().String()
|
|
DefaultErrorFunc(w, r, dns.RcodeRefused)
|
|
log.Printf("[INFO] \"%s %s %s\" - No such zone at %s (Remote: %s)", dns.Type(r.Question[0].Qtype), dns.Class(r.Question[0].Qclass), q, s.Addr, remoteHost)
|
|
}
|
|
|
|
// DefaultErrorFunc responds to an DNS request with an error.
|
|
func DefaultErrorFunc(w dns.ResponseWriter, r *dns.Msg, rcode int) {
|
|
state := request.Request{W: w, Req: r}
|
|
|
|
answer := new(dns.Msg)
|
|
answer.SetRcode(r, rcode)
|
|
|
|
state.SizeAndDo(answer)
|
|
|
|
w.WriteMsg(answer)
|
|
}
|
|
|
|
func rcodeNoClientWrite(rcode int) bool {
|
|
switch rcode {
|
|
case dns.RcodeServerFailure:
|
|
fallthrough
|
|
case dns.RcodeRefused:
|
|
fallthrough
|
|
case dns.RcodeFormatError:
|
|
fallthrough
|
|
case dns.RcodeNotImplemented:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
const (
|
|
tcp = 0
|
|
udp = 1
|
|
)
|