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, 0 insertions, 426 deletions
diff --git a/core/core.go b/core/core.go
deleted file mode 100644
index 94dd06e52..000000000
--- a/core/core.go
+++ /dev/null
@@ -1,426 +0,0 @@
-// Package core 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 core.Start() to start CoreDNS, core.Stop()
-// to stop it, or core.Restart() to restart it.
-//
-// You should use core.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
-}