aboutsummaryrefslogtreecommitdiff
path: root/core/core.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/core.go')
-rw-r--r--core/core.go426
1 files changed, 426 insertions, 0 deletions
diff --git a/core/core.go b/core/core.go
new file mode 100644
index 000000000..1c0eba49a
--- /dev/null
+++ b/core/core.go
@@ -0,0 +1,426 @@
+// Package caddy implements the CoreDNS web server as a service
+// in your own Go programs.
+//
+// To use this package, follow a few simple steps:
+//
+// 1. Set the AppName and AppVersion variables.
+// 2. Call LoadCorefile() to get the Corefile (it
+// might have been piped in as part of a restart).
+// You should pass in your own Corefile loader.
+// 3. Call caddy.Start() to start CoreDNS, caddy.Stop()
+// to stop it, or caddy.Restart() to restart it.
+//
+// You should use caddy.Wait() to wait for all CoreDNS servers
+// to quit before your process exits.
+package core
+
+import (
+ "bytes"
+ "encoding/gob"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net"
+ "os"
+ "path"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "github.com/miekg/coredns/core/https"
+ "github.com/miekg/coredns/server"
+)
+
+// Configurable application parameters
+var (
+ // AppName is the name of the application.
+ AppName string
+
+ // AppVersion is the version of the application.
+ AppVersion string
+
+ // Quiet when set to true, will not show any informative output on initialization.
+ Quiet bool
+
+ // PidFile is the path to the pidfile to create.
+ PidFile string
+
+ // GracefulTimeout is the maximum duration of a graceful shutdown.
+ GracefulTimeout time.Duration
+)
+
+var (
+ // corefile is the input configuration text used for this process
+ corefile Input
+
+ // corefileMu protects corefile during changes
+ corefileMu sync.Mutex
+
+ // errIncompleteRestart occurs if this process is a fork
+ // of the parent but no Corefile was piped in
+ errIncompleteRestart = errors.New("incomplete restart")
+
+ // servers is a list of all the currently-listening servers
+ servers []*server.Server
+
+ // serversMu protects the servers slice during changes
+ serversMu sync.Mutex
+
+ // wg is used to wait for all servers to shut down
+ wg sync.WaitGroup
+
+ // loadedGob is used if this is a child process as part of
+ // a graceful restart; it is used to map listeners to their
+ // index in the list of inherited file descriptors. This
+ // variable is not safe for concurrent access.
+ loadedGob corefileGob
+
+ // startedBefore should be set to true if CoreDNS has been started
+ // at least once (does not indicate whether currently running).
+ startedBefore bool
+)
+
+const (
+ // DefaultHost is the default host.
+ DefaultHost = ""
+ // DefaultPort is the default port.
+ DefaultPort = "53"
+ // DefaultRoot is the default root folder.
+ DefaultRoot = "."
+)
+
+// Start starts CoreDNS with the given Corefile. If crfile
+// is nil, the LoadCorefile function will be called to get
+// one.
+//
+// This function blocks until all the servers are listening.
+//
+// Note (POSIX): If Start is called in the child process of a
+// restart more than once within the duration of the graceful
+// cutoff (i.e. the child process called Start a first time,
+// then called Stop, then Start again within the first 5 seconds
+// or however long GracefulTimeout is) and the Corefiles have
+// at least one listener address in common, the second Start
+// may fail with "address already in use" as there's no
+// guarantee that the parent process has relinquished the
+// address before the grace period ends.
+func Start(crfile Input) (err error) {
+ // If we return with no errors, we must do two things: tell the
+ // parent that we succeeded and write to the pidfile.
+ defer func() {
+ if err == nil {
+ signalSuccessToParent() // TODO: Is doing this more than once per process a bad idea? Start could get called more than once in other apps.
+ if PidFile != "" {
+ err := writePidFile()
+ if err != nil {
+ log.Printf("[ERROR] Could not write pidfile: %v", err)
+ }
+ }
+ }
+ }()
+
+ // Input must never be nil; try to load something
+ if crfile == nil {
+ crfile, err = LoadCorefile(nil)
+ if err != nil {
+ return err
+ }
+ }
+
+ corefileMu.Lock()
+ corefile = crfile
+ corefileMu.Unlock()
+
+ // load the server configs (activates Let's Encrypt)
+ configs, err := loadConfigs(path.Base(crfile.Path()), bytes.NewReader(crfile.Body()))
+ if err != nil {
+ return err
+ }
+
+ // group zones by address
+ groupings, err := arrangeBindings(configs)
+ if err != nil {
+ return err
+ }
+
+ // Start each server with its one or more configurations
+ err = startServers(groupings)
+ if err != nil {
+ return err
+ }
+ startedBefore = true
+
+ // Show initialization output
+ if !Quiet && !IsRestart() {
+ var checkedFdLimit bool
+ for _, group := range groupings {
+ for _, conf := range group.Configs {
+ // Print address of site
+ fmt.Println(conf.Address())
+
+ // Note if non-localhost site resolves to loopback interface
+ if group.BindAddr.IP.IsLoopback() && !isLocalhost(conf.Host) {
+ fmt.Printf("Notice: %s is only accessible on this machine (%s)\n",
+ conf.Host, group.BindAddr.IP.String())
+ }
+ if !checkedFdLimit && !group.BindAddr.IP.IsLoopback() && !isLocalhost(conf.Host) {
+ checkFdlimit()
+ checkedFdLimit = true
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+// startServers starts all the servers in groupings,
+// taking into account whether or not this process is
+// a child from a graceful restart or not. It blocks
+// until the servers are listening.
+func startServers(groupings bindingGroup) error {
+ var startupWg sync.WaitGroup
+ errChan := make(chan error, len(groupings)) // must be buffered to allow Serve functions below to return if stopped later
+
+ for _, group := range groupings {
+ s, err := server.New(group.BindAddr.String(), group.Configs, GracefulTimeout)
+ if err != nil {
+ return err
+ }
+ // TODO(miek): does not work, because this callback uses http instead of dns
+ // s.ReqCallback = https.RequestCallback // ensures we can solve ACME challenges while running
+ if s.OnDemandTLS {
+ s.TLSConfig.GetCertificate = https.GetOrObtainCertificate // TLS on demand -- awesome!
+ } else {
+ s.TLSConfig.GetCertificate = https.GetCertificate
+ }
+
+ var (
+ ln net.Listener
+ pc net.PacketConn
+ )
+
+ if IsRestart() {
+ // Look up this server's listener in the map of inherited file descriptors; if we don't have one, we must make a new one (later).
+ if fdIndex, ok := loadedGob.ListenerFds["tcp"+s.Addr]; ok {
+ file := os.NewFile(fdIndex, "")
+
+ fln, err := net.FileListener(file)
+ if err != nil {
+ return err
+ }
+
+ ln, ok = fln.(*net.TCPListener)
+ if !ok {
+ return errors.New("listener for " + s.Addr + " was not a *net.TCPListener")
+ }
+
+ file.Close()
+ delete(loadedGob.ListenerFds, "tcp"+s.Addr)
+ }
+ if fdIndex, ok := loadedGob.ListenerFds["udp"+s.Addr]; ok {
+ file := os.NewFile(fdIndex, "")
+
+ fpc, err := net.FilePacketConn(file)
+ if err != nil {
+ return err
+ }
+
+ pc, ok = fpc.(*net.UDPConn)
+ if !ok {
+ return errors.New("packetConn for " + s.Addr + " was not a *net.PacketConn")
+ }
+
+ file.Close()
+ delete(loadedGob.ListenerFds, "udp"+s.Addr)
+ }
+ }
+
+ wg.Add(1)
+ go func(s *server.Server, ln net.Listener, pc net.PacketConn) {
+ defer wg.Done()
+
+ // run startup functions that should only execute when the original parent process is starting.
+ if !IsRestart() && !startedBefore {
+ err := s.RunFirstStartupFuncs()
+ if err != nil {
+ errChan <- err
+ return
+ }
+ }
+
+ // start the server
+ if ln != nil && pc != nil {
+ errChan <- s.Serve(ln, pc)
+ } else {
+ errChan <- s.ListenAndServe()
+ }
+ }(s, ln, pc)
+
+ startupWg.Add(1)
+ go func(s *server.Server) {
+ defer startupWg.Done()
+ s.WaitUntilStarted()
+ }(s)
+
+ serversMu.Lock()
+ servers = append(servers, s)
+ serversMu.Unlock()
+ }
+
+ // Close the remaining (unused) file descriptors to free up resources
+ if IsRestart() {
+ for key, fdIndex := range loadedGob.ListenerFds {
+ os.NewFile(fdIndex, "").Close()
+ delete(loadedGob.ListenerFds, key)
+ }
+ }
+
+ // Wait for all servers to finish starting
+ startupWg.Wait()
+
+ // Return the first error, if any
+ select {
+ case err := <-errChan:
+ // "use of closed network connection" is normal if it was a graceful shutdown
+ if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
+ return err
+ }
+ default:
+ }
+
+ return nil
+}
+
+// Stop stops all servers. It blocks until they are all stopped.
+// It does NOT execute shutdown callbacks that may have been
+// configured by middleware (they must be executed separately).
+func Stop() error {
+ https.Deactivate()
+
+ serversMu.Lock()
+ for _, s := range servers {
+ if err := s.Stop(); err != nil {
+ log.Printf("[ERROR] Stopping %s: %v", s.Addr, err)
+ }
+ }
+ servers = []*server.Server{} // don't reuse servers
+ serversMu.Unlock()
+
+ return nil
+}
+
+// Wait blocks until all servers are stopped.
+func Wait() {
+ wg.Wait()
+}
+
+// LoadCorefile loads a Corefile, prioritizing a Corefile
+// piped from stdin as part of a restart (only happens on first call
+// to LoadCorefile). If it is not a restart, this function tries
+// calling the user's loader function, and if that returns nil, then
+// this function resorts to the default configuration. Thus, if there
+// are no other errors, this function always returns at least the
+// default Corefile.
+func LoadCorefile(loader func() (Input, error)) (crfile Input, err error) {
+ // If we are a fork, finishing the restart is highest priority;
+ // piped input is required in this case.
+ if IsRestart() {
+ err := gob.NewDecoder(os.Stdin).Decode(&loadedGob)
+ if err != nil {
+ return nil, err
+ }
+ crfile = loadedGob.Corefile
+ atomic.StoreInt32(https.OnDemandIssuedCount, loadedGob.OnDemandTLSCertsIssued)
+ }
+
+ // Try user's loader
+ if crfile == nil && loader != nil {
+ crfile, err = loader()
+ }
+
+ // Otherwise revert to default
+ if crfile == nil {
+ crfile = DefaultInput()
+ }
+
+ return
+}
+
+// CorefileFromPipe loads the Corefile input from f if f is
+// not interactive input. f is assumed to be a pipe or stream,
+// such as os.Stdin. If f is not a pipe, no error is returned
+// but the Input value will be nil. An error is only returned
+// if there was an error reading the pipe, even if the length
+// of what was read is 0.
+func CorefileFromPipe(f *os.File) (Input, error) {
+ fi, err := f.Stat()
+ if err == nil && fi.Mode()&os.ModeCharDevice == 0 {
+ // Note that a non-nil error is not a problem. Windows
+ // will not create a stdin if there is no pipe, which
+ // produces an error when calling Stat(). But Unix will
+ // make one either way, which is why we also check that
+ // bitmask.
+ // BUG: Reading from stdin after this fails (e.g. for the let's encrypt email address) (OS X)
+ confBody, err := ioutil.ReadAll(f)
+ if err != nil {
+ return nil, err
+ }
+ return CorefileInput{
+ Contents: confBody,
+ Filepath: f.Name(),
+ }, nil
+ }
+
+ // not having input from the pipe is not itself an error,
+ // just means no input to return.
+ return nil, nil
+}
+
+// Corefile returns the current Corefile
+func Corefile() Input {
+ corefileMu.Lock()
+ defer corefileMu.Unlock()
+ return corefile
+}
+
+// Input represents a Corefile; its contents and file path
+// (which should include the file name at the end of the path).
+// If path does not apply (e.g. piped input) you may use
+// any understandable value. The path is mainly used for logging,
+// error messages, and debugging.
+type Input interface {
+ // Gets the Corefile contents
+ Body() []byte
+
+ // Gets the path to the origin file
+ Path() string
+
+ // IsFile returns true if the original input was a file on the file system
+ // that could be loaded again later if requested.
+ IsFile() bool
+}
+
+// TestServer returns a test server.
+// The ports can be retreived with server.LocalAddr(). The testserver itself can be stopped
+// with Stop(). It just takes a normal Corefile as input.
+func TestServer(t *testing.T, corefile string) (*server.Server, error) {
+
+ crfile := CorefileInput{Contents: []byte(corefile)}
+ configs, err := loadConfigs(path.Base(crfile.Path()), bytes.NewReader(crfile.Body()))
+ if err != nil {
+ return nil, err
+ }
+ groupings, err := arrangeBindings(configs)
+ if err != nil {
+ return nil, err
+ }
+ t.Logf("Starting %d servers", len(groupings))
+
+ group := groupings[0]
+ s, err := server.New(group.BindAddr.String(), group.Configs, time.Second)
+ return s, err
+}