mirror of
https://github.com/coredns/coredns.git
synced 2025-11-12 15:02:17 -05:00
First commit
This commit is contained in:
388
core/caddy.go
Normal file
388
core/caddy.go
Normal file
@@ -0,0 +1,388 @@
|
||||
// Package caddy implements the Caddy 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 LoadCaddyfile() to get the Caddyfile (it
|
||||
// might have been piped in as part of a restart).
|
||||
// You should pass in your own Caddyfile loader.
|
||||
// 3. Call caddy.Start() to start Caddy, caddy.Stop()
|
||||
// to stop it, or caddy.Restart() to restart it.
|
||||
//
|
||||
// You should use caddy.Wait() to wait for all Caddy servers
|
||||
// to quit before your process exits.
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"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 (
|
||||
// caddyfile is the input configuration text used for this process
|
||||
caddyfile Input
|
||||
|
||||
// caddyfileMu protects caddyfile during changes
|
||||
caddyfileMu sync.Mutex
|
||||
|
||||
// errIncompleteRestart occurs if this process is a fork
|
||||
// of the parent but no Caddyfile 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 caddyfileGob
|
||||
|
||||
// startedBefore should be set to true if caddy 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 Caddy with the given Caddyfile. If cdyfile
|
||||
// is nil, the LoadCaddyfile 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 Caddyfiles 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(cdyfile 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 cdyfile == nil {
|
||||
cdyfile, err = LoadCaddyfile(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
caddyfileMu.Lock()
|
||||
caddyfile = cdyfile
|
||||
caddyfileMu.Unlock()
|
||||
|
||||
// load the server configs (activates Let's Encrypt)
|
||||
configs, err := loadConfigs(path.Base(cdyfile.Path()), bytes.NewReader(cdyfile.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 server.ListenerFile
|
||||
/*
|
||||
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[s.Addr]; ok {
|
||||
file := os.NewFile(fdIndex, "")
|
||||
|
||||
fln, err := net.FileListener(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ln, ok = fln.(server.ListenerFile)
|
||||
if !ok {
|
||||
return errors.New("listener for " + s.Addr + " was not a ListenerFile")
|
||||
}
|
||||
|
||||
file.Close()
|
||||
delete(loadedGob.ListenerFds, s.Addr)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
wg.Add(1)
|
||||
go func(s *server.Server, ln server.ListenerFile) {
|
||||
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
|
||||
// TODO(miek): for now will always be nil, so we will run ListenAndServe()
|
||||
if ln != nil {
|
||||
//errChan <- s.Serve(ln)
|
||||
} else {
|
||||
errChan <- s.ListenAndServe()
|
||||
}
|
||||
}(s, ln)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// LoadCaddyfile loads a Caddyfile, prioritizing a Caddyfile
|
||||
// piped from stdin as part of a restart (only happens on first call
|
||||
// to LoadCaddyfile). 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 Caddyfile.
|
||||
func LoadCaddyfile(loader func() (Input, error)) (cdyfile 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
|
||||
}
|
||||
cdyfile = loadedGob.Caddyfile
|
||||
atomic.StoreInt32(https.OnDemandIssuedCount, loadedGob.OnDemandTLSCertsIssued)
|
||||
}
|
||||
|
||||
// Try user's loader
|
||||
if cdyfile == nil && loader != nil {
|
||||
cdyfile, err = loader()
|
||||
}
|
||||
|
||||
// Otherwise revert to default
|
||||
if cdyfile == nil {
|
||||
cdyfile = DefaultInput()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CaddyfileFromPipe loads the Caddyfile 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 CaddyfileFromPipe(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 CaddyfileInput{
|
||||
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
|
||||
}
|
||||
|
||||
// Caddyfile returns the current Caddyfile
|
||||
func Caddyfile() Input {
|
||||
caddyfileMu.Lock()
|
||||
defer caddyfileMu.Unlock()
|
||||
return caddyfile
|
||||
}
|
||||
|
||||
// Input represents a Caddyfile; 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 Caddyfile 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
|
||||
}
|
||||
Reference in New Issue
Block a user