diff options
Diffstat (limited to 'core/core.go')
-rw-r--r-- | core/core.go | 426 |
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 -} |