mirror of
https://github.com/coredns/coredns.git
synced 2025-11-26 21:54:03 -05:00
First commit
This commit is contained in:
75
server/config.go
Normal file
75
server/config.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/miekg/coredns/middleware"
|
||||
)
|
||||
|
||||
// Config configuration for a single server.
|
||||
type Config struct {
|
||||
// The hostname or IP on which to serve
|
||||
Host string
|
||||
|
||||
// The host address to bind on - defaults to (virtual) Host if empty
|
||||
BindHost string
|
||||
|
||||
// The port to listen on
|
||||
Port string
|
||||
|
||||
// The directory from which to parse db files
|
||||
Root string
|
||||
|
||||
// HTTPS configuration
|
||||
TLS TLSConfig
|
||||
|
||||
// Middleware stack
|
||||
Middleware []middleware.Middleware
|
||||
|
||||
// Startup is a list of functions (or methods) to execute at
|
||||
// server startup and restart; these are executed before any
|
||||
// parts of the server are configured, and the functions are
|
||||
// blocking. These are good for setting up middlewares and
|
||||
// starting goroutines.
|
||||
Startup []func() error
|
||||
|
||||
// FirstStartup is like Startup but these functions only execute
|
||||
// during the initial startup, not on subsequent restarts.
|
||||
//
|
||||
// (Note: The server does not ever run these on its own; it is up
|
||||
// to the calling application to do so, and do so only once, as the
|
||||
// server itself has no notion whether it's a restart or not.)
|
||||
FirstStartup []func() error
|
||||
|
||||
// Functions (or methods) to execute when the server quits;
|
||||
// these are executed in response to SIGINT and are blocking
|
||||
Shutdown []func() error
|
||||
|
||||
// The path to the configuration file from which this was loaded
|
||||
ConfigFile string
|
||||
|
||||
// The name of the application
|
||||
AppName string
|
||||
|
||||
// The application's version
|
||||
AppVersion string
|
||||
}
|
||||
|
||||
// Address returns the host:port of c as a string.
|
||||
func (c Config) Address() string {
|
||||
return net.JoinHostPort(c.Host, c.Port)
|
||||
}
|
||||
|
||||
// TLSConfig describes how TLS should be configured and used.
|
||||
type TLSConfig struct {
|
||||
Enabled bool // will be set to true if TLS is enabled
|
||||
LetsEncryptEmail string
|
||||
Manual bool // will be set to true if user provides own certs and keys
|
||||
Managed bool // will be set to true if config qualifies for implicit automatic/managed HTTPS
|
||||
OnDemand bool // will be set to true if user enables on-demand TLS (obtain certs during handshakes)
|
||||
Ciphers []uint16
|
||||
ProtocolMinVersion uint16
|
||||
ProtocolMaxVersion uint16
|
||||
PreferServerCipherSuites bool
|
||||
ClientCerts []string
|
||||
}
|
||||
25
server/config_test.go
Normal file
25
server/config_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package server
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestConfigAddress(t *testing.T) {
|
||||
cfg := Config{Host: "foobar", Port: "1234"}
|
||||
if actual, expected := cfg.Address(), "foobar:1234"; expected != actual {
|
||||
t.Errorf("Expected '%s' but got '%s'", expected, actual)
|
||||
}
|
||||
|
||||
cfg = Config{Host: "", Port: "1234"}
|
||||
if actual, expected := cfg.Address(), ":1234"; expected != actual {
|
||||
t.Errorf("Expected '%s' but got '%s'", expected, actual)
|
||||
}
|
||||
|
||||
cfg = Config{Host: "foobar", Port: ""}
|
||||
if actual, expected := cfg.Address(), "foobar:"; expected != actual {
|
||||
t.Errorf("Expected '%s' but got '%s'", expected, actual)
|
||||
}
|
||||
|
||||
cfg = Config{Host: "::1", Port: "443"}
|
||||
if actual, expected := cfg.Address(), "[::1]:443"; expected != actual {
|
||||
t.Errorf("Expected '%s' but got '%s'", expected, actual)
|
||||
}
|
||||
}
|
||||
76
server/graceful.go
Normal file
76
server/graceful.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// newGracefulListener returns a gracefulListener that wraps l and
|
||||
// uses wg (stored in the host server) to count connections.
|
||||
func newGracefulListener(l ListenerFile, wg *sync.WaitGroup) *gracefulListener {
|
||||
gl := &gracefulListener{ListenerFile: l, stop: make(chan error), httpWg: wg}
|
||||
go func() {
|
||||
<-gl.stop
|
||||
gl.Lock()
|
||||
gl.stopped = true
|
||||
gl.Unlock()
|
||||
gl.stop <- gl.ListenerFile.Close()
|
||||
}()
|
||||
return gl
|
||||
}
|
||||
|
||||
// gracefuListener is a net.Listener which can
|
||||
// count the number of connections on it. Its
|
||||
// methods mainly wrap net.Listener to be graceful.
|
||||
type gracefulListener struct {
|
||||
ListenerFile
|
||||
stop chan error
|
||||
stopped bool
|
||||
sync.Mutex // protects the stopped flag
|
||||
httpWg *sync.WaitGroup // pointer to the host's wg used for counting connections
|
||||
}
|
||||
|
||||
// Accept accepts a connection.
|
||||
func (gl *gracefulListener) Accept() (c net.Conn, err error) {
|
||||
c, err = gl.ListenerFile.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c = gracefulConn{Conn: c, httpWg: gl.httpWg}
|
||||
gl.httpWg.Add(1)
|
||||
return
|
||||
}
|
||||
|
||||
// Close immediately closes the listener.
|
||||
func (gl *gracefulListener) Close() error {
|
||||
gl.Lock()
|
||||
if gl.stopped {
|
||||
gl.Unlock()
|
||||
return syscall.EINVAL
|
||||
}
|
||||
gl.Unlock()
|
||||
gl.stop <- nil
|
||||
return <-gl.stop
|
||||
}
|
||||
|
||||
// gracefulConn represents a connection on a
|
||||
// gracefulListener so that we can keep track
|
||||
// of the number of connections, thus facilitating
|
||||
// a graceful shutdown.
|
||||
type gracefulConn struct {
|
||||
net.Conn
|
||||
httpWg *sync.WaitGroup // pointer to the host server's connection waitgroup
|
||||
}
|
||||
|
||||
// Close closes c's underlying connection while updating the wg count.
|
||||
func (c gracefulConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// close can fail on http2 connections (as of Oct. 2015, before http2 in std lib)
|
||||
// so don't decrement count unless close succeeds
|
||||
c.httpWg.Done()
|
||||
return nil
|
||||
}
|
||||
431
server/server.go
Normal file
431
server/server.go
Normal file
@@ -0,0 +1,431 @@
|
||||
// Package server implements a configurable, general-purpose web server.
|
||||
// It relies on configurations obtained from the adjacent config package
|
||||
// and can execute middleware as defined by the adjacent middleware package.
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// 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
|
||||
tls bool // whether this server is serving all HTTPS hosts or not
|
||||
TLSConfig *tls.Config
|
||||
OnDemandTLS bool // whether this server supports on-demand TLS (load certs at handshake-time)
|
||||
zones map[string]zone // zones keyed by their address
|
||||
listener ListenerFile // the listener which is bound to the socket
|
||||
listenerMu sync.Mutex // protects listener
|
||||
dnsWg sync.WaitGroup // used to wait on outstanding connections
|
||||
startChan chan struct{} // used to block until server is finished starting
|
||||
connTimeout time.Duration // the maximum duration of a graceful shutdown
|
||||
ReqCallback OptionalCallback // if non-nil, is executed at the beginning of every request
|
||||
SNICallback func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error)
|
||||
}
|
||||
|
||||
// ListenerFile represents a listener.
|
||||
type ListenerFile interface {
|
||||
net.Listener
|
||||
File() (*os.File, error)
|
||||
}
|
||||
|
||||
// OptionalCallback is a function that may or may not handle a request.
|
||||
// It returns whether or not it handled the request. If it handled the
|
||||
// request, it is presumed that no further request handling should occur.
|
||||
type OptionalCallback func(dns.ResponseWriter, *dns.Msg) bool
|
||||
|
||||
// New creates a new Server which will bind to addr and serve
|
||||
// the sites/hosts configured in configs. Its listener will
|
||||
// gracefully close when the server is stopped which will take
|
||||
// no longer than gracefulTimeout.
|
||||
//
|
||||
// This function does not start serving.
|
||||
//
|
||||
// Do not re-use a server (start, stop, then start again). We
|
||||
// could probably add more locking to make this possible, but
|
||||
// as it stands, you should dispose of a server after stopping it.
|
||||
// The behavior of serving with a spent server is undefined.
|
||||
func New(addr string, configs []Config, gracefulTimeout time.Duration) (*Server, error) {
|
||||
var useTLS, useOnDemandTLS bool
|
||||
if len(configs) > 0 {
|
||||
useTLS = configs[0].TLS.Enabled
|
||||
useOnDemandTLS = configs[0].TLS.OnDemand
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Addr: addr,
|
||||
TLSConfig: new(tls.Config),
|
||||
// TODO: Make these values configurable?
|
||||
// ReadTimeout: 2 * time.Minute,
|
||||
// WriteTimeout: 2 * time.Minute,
|
||||
// MaxHeaderBytes: 1 << 16,
|
||||
tls: useTLS,
|
||||
OnDemandTLS: useOnDemandTLS,
|
||||
zones: make(map[string]zone),
|
||||
startChan: make(chan struct{}),
|
||||
connTimeout: gracefulTimeout,
|
||||
}
|
||||
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)
|
||||
|
||||
// Set up each zone
|
||||
for _, conf := range configs {
|
||||
// TODO(miek): something better here?
|
||||
if _, exists := s.zones[conf.Host]; exists {
|
||||
return nil, fmt.Errorf("cannot serve %s - host already defined for address %s", conf.Address(), s.Addr)
|
||||
}
|
||||
|
||||
z := zone{config: conf}
|
||||
|
||||
// Build middleware stack
|
||||
err := z.buildStack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.zones[conf.Host] = z
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Serve starts the server with an existing listener. It blocks until the
|
||||
// server stops.
|
||||
/*
|
||||
func (s *Server) Serve(ln ListenerFile) error {
|
||||
// TODO(miek): Go DNS has no server stuff that allows you to give it a listener
|
||||
// and use that.
|
||||
err := s.setup()
|
||||
if err != nil {
|
||||
defer close(s.startChan) // MUST defer so error is properly reported, same with all cases in this file
|
||||
return err
|
||||
}
|
||||
return s.serve(ln)
|
||||
}
|
||||
*/
|
||||
|
||||
// ListenAndServe starts the server with a new listener. It blocks until the server stops.
|
||||
func (s *Server) ListenAndServe() error {
|
||||
err := s.setup()
|
||||
once := sync.Once{}
|
||||
|
||||
if err != nil {
|
||||
close(s.startChan)
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(miek): redo to make it more like caddy
|
||||
// - error handling, re-introduce what Caddy did.
|
||||
go func() {
|
||||
if err := dns.ListenAndServe(s.Addr, "tcp", s.mux); err != nil {
|
||||
log.Printf("[ERROR] %v\n", err)
|
||||
defer once.Do(func() { close(s.startChan) })
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
if err := dns.ListenAndServe(s.Addr, "udp", s.mux); err != nil {
|
||||
log.Printf("[ERROR] %v\n", err)
|
||||
defer once.Do(func() { close(s.startChan) })
|
||||
return
|
||||
}
|
||||
}()
|
||||
once.Do(func() { close(s.startChan) }) // unblock anyone waiting for this to start listening
|
||||
// but block here, as this is what caddy expects
|
||||
for {
|
||||
select {}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setup prepares the server s to begin listening; it should be
|
||||
// called just before the listener announces itself on the network
|
||||
// and should only be called when the server is just starting up.
|
||||
func (s *Server) setup() error {
|
||||
// Execute startup functions now
|
||||
for _, z := range s.zones {
|
||||
for _, startupFunc := range z.config.Startup {
|
||||
err := startupFunc()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
TODO(miek): no such thing in the glorious Go DNS.
|
||||
// serveTLS serves TLS with SNI and client auth support if s has them enabled. It
|
||||
// blocks until s quits.
|
||||
func serveTLS(s *Server, ln net.Listener, tlsConfigs []TLSConfig) error {
|
||||
// Customize our TLS configuration
|
||||
s.TLSConfig.MinVersion = tlsConfigs[0].ProtocolMinVersion
|
||||
s.TLSConfig.MaxVersion = tlsConfigs[0].ProtocolMaxVersion
|
||||
s.TLSConfig.CipherSuites = tlsConfigs[0].Ciphers
|
||||
s.TLSConfig.PreferServerCipherSuites = tlsConfigs[0].PreferServerCipherSuites
|
||||
|
||||
// TLS client authentication, if user enabled it
|
||||
err := setupClientAuth(tlsConfigs, s.TLSConfig)
|
||||
if err != nil {
|
||||
defer close(s.startChan)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create TLS listener - note that we do not replace s.listener
|
||||
// with this TLS listener; tls.listener is unexported and does
|
||||
// not implement the File() method we need for graceful restarts
|
||||
// on POSIX systems.
|
||||
ln = tls.NewListener(ln, s.TLSConfig)
|
||||
|
||||
close(s.startChan) // unblock anyone waiting for this to start listening
|
||||
return s.Serve(ln)
|
||||
}
|
||||
*/
|
||||
|
||||
// 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.listenerMu.Lock()
|
||||
if s.listener != nil {
|
||||
err = s.listener.Close()
|
||||
}
|
||||
s.listenerMu.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WaitUntilStarted blocks until the server s is started, meaning
|
||||
// that practically the next instruction is to start the server loop.
|
||||
// It also unblocks if the server encounters an error during startup.
|
||||
func (s *Server) WaitUntilStarted() {
|
||||
<-s.startChan
|
||||
}
|
||||
|
||||
// ListenerFd gets a dup'ed file of the listener. If there
|
||||
// is no underlying file, the return value will be nil. It
|
||||
// is the caller's responsibility to close the file.
|
||||
func (s *Server) ListenerFd() *os.File {
|
||||
s.listenerMu.Lock()
|
||||
defer s.listenerMu.Unlock()
|
||||
if s.listener != nil {
|
||||
file, _ := s.listener.File()
|
||||
return file
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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 {
|
||||
// TODO(miek): serverfailure return?
|
||||
}
|
||||
}()
|
||||
|
||||
// Execute the optional request callback if it exists
|
||||
if s.ReqCallback != nil && s.ReqCallback(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
q := r.Question[0].Name
|
||||
b := make([]byte, len(q))
|
||||
off, end := 0, false
|
||||
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.stack.ServeDNS(w, r)
|
||||
if rcode > 0 {
|
||||
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.stack.ServeDNS(w, r)
|
||||
if rcode > 0 {
|
||||
DefaultErrorFunc(w, r, rcode)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Still here? Error out with SERVFAIL and some logging
|
||||
remoteHost := w.RemoteAddr().String()
|
||||
DefaultErrorFunc(w, r, dns.RcodeServerFailure)
|
||||
|
||||
fmt.Fprintf(w, "No such zone at %s", s.Addr)
|
||||
log.Printf("[INFO] %s - No such zone at %s (Remote: %s)", q, s.Addr, remoteHost)
|
||||
}
|
||||
|
||||
// DefaultErrorFunc responds to an HTTP request with a simple description
|
||||
// of the specified HTTP status code.
|
||||
func DefaultErrorFunc(w dns.ResponseWriter, r *dns.Msg, rcode int) {
|
||||
answer := new(dns.Msg)
|
||||
answer.SetRcode(r, rcode)
|
||||
w.WriteMsg(answer)
|
||||
}
|
||||
|
||||
// setupClientAuth sets up TLS client authentication only if
|
||||
// any of the TLS configs specified at least one cert file.
|
||||
func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error {
|
||||
var clientAuth bool
|
||||
for _, cfg := range tlsConfigs {
|
||||
if len(cfg.ClientCerts) > 0 {
|
||||
clientAuth = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if clientAuth {
|
||||
pool := x509.NewCertPool()
|
||||
for _, cfg := range tlsConfigs {
|
||||
for _, caFile := range cfg.ClientCerts {
|
||||
caCrt, err := ioutil.ReadFile(caFile) // Anyone that gets a cert from this CA can connect
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !pool.AppendCertsFromPEM(caCrt) {
|
||||
return fmt.Errorf("error loading client certificate '%s': no certificates were successfully parsed", caFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
config.ClientCAs = pool
|
||||
config.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunFirstStartupFuncs runs all of the server's FirstStartup
|
||||
// callback functions unless one of them returns an error first.
|
||||
// It is the caller's responsibility to call this only once and
|
||||
// at the correct time. The functions here should not be executed
|
||||
// at restarts or where the user does not explicitly start a new
|
||||
// instance of the server.
|
||||
func (s *Server) RunFirstStartupFuncs() error {
|
||||
for _, z := range s.zones {
|
||||
for _, f := range z.config.FirstStartup {
|
||||
if err := f(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
||||
// connections. It's used by ListenAndServe and ListenAndServeTLS so
|
||||
// dead TCP connections (e.g. closing laptop mid-download) eventually
|
||||
// go away.
|
||||
//
|
||||
// Borrowed from the Go standard library.
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
// Accept accepts the connection with a keep-alive enabled.
|
||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
// File implements ListenerFile; returns the underlying file of the listener.
|
||||
func (ln tcpKeepAliveListener) File() (*os.File, error) {
|
||||
return ln.TCPListener.File()
|
||||
}
|
||||
|
||||
// ShutdownCallbacks executes all the shutdown callbacks
|
||||
// for all the virtualhosts in servers, and returns all the
|
||||
// errors generated during their execution. In other words,
|
||||
// an error executing one shutdown callback does not stop
|
||||
// execution of others. Only one shutdown callback is executed
|
||||
// at a time. You must protect the servers that are passed in
|
||||
// if they are shared across threads.
|
||||
func ShutdownCallbacks(servers []*Server) []error {
|
||||
var errs []error
|
||||
for _, s := range servers {
|
||||
for _, zone := range s.zones {
|
||||
for _, shutdownFunc := range zone.config.Shutdown {
|
||||
err := shutdownFunc()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
28
server/zones.go
Normal file
28
server/zones.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package server
|
||||
|
||||
import "github.com/miekg/coredns/middleware"
|
||||
|
||||
// zone represents a DNS zone. While a Server
|
||||
// is what actually binds to the address, a user may want to serve
|
||||
// multiple zones on a single address, and this is what a
|
||||
// zone allows us to do.
|
||||
type zone struct {
|
||||
config Config
|
||||
stack middleware.Handler
|
||||
}
|
||||
|
||||
// buildStack builds the server's middleware stack based
|
||||
// on its config. This method should be called last before
|
||||
// ListenAndServe begins.
|
||||
func (z *zone) buildStack() error {
|
||||
z.compile(z.config.Middleware)
|
||||
return nil
|
||||
}
|
||||
|
||||
// compile is an elegant alternative to nesting middleware function
|
||||
// calls like handler1(handler2(handler3(finalHandler))).
|
||||
func (z *zone) compile(layers []middleware.Middleware) {
|
||||
for i := len(layers) - 1; i >= 0; i-- {
|
||||
z.stack = layers[i](z.stack)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user