aboutsummaryrefslogtreecommitdiff
path: root/core/config.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/config.go')
-rw-r--r--core/config.go346
1 files changed, 346 insertions, 0 deletions
diff --git a/core/config.go b/core/config.go
new file mode 100644
index 000000000..8c376d878
--- /dev/null
+++ b/core/config.go
@@ -0,0 +1,346 @@
+package core
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "sync"
+
+ "github.com/miekg/coredns/core/https"
+ "github.com/miekg/coredns/core/parse"
+ "github.com/miekg/coredns/core/setup"
+ "github.com/miekg/coredns/server"
+)
+
+const (
+ // DefaultConfigFile is the name of the configuration file that is loaded
+ // by default if no other file is specified.
+ DefaultConfigFile = "Corefile"
+)
+
+func loadConfigsUpToIncludingTLS(filename string, input io.Reader) ([]server.Config, []parse.ServerBlock, int, error) {
+ var configs []server.Config
+
+ // Each server block represents similar hosts/addresses, since they
+ // were grouped together in the Caddyfile.
+ serverBlocks, err := parse.ServerBlocks(filename, input, true)
+ if err != nil {
+ return nil, nil, 0, err
+ }
+ if len(serverBlocks) == 0 {
+ newInput := DefaultInput()
+ serverBlocks, err = parse.ServerBlocks(newInput.Path(), bytes.NewReader(newInput.Body()), true)
+ if err != nil {
+ return nil, nil, 0, err
+ }
+ }
+
+ var lastDirectiveIndex int // we set up directives in two parts; this stores where we left off
+
+ // Iterate each server block and make a config for each one,
+ // executing the directives that were parsed in order up to the tls
+ // directive; this is because we must activate Let's Encrypt.
+ for i, sb := range serverBlocks {
+ onces := makeOnces()
+ storages := makeStorages()
+
+ for j, addr := range sb.Addresses {
+ config := server.Config{
+ Host: addr.Host,
+ Port: addr.Port,
+ Root: Root,
+ ConfigFile: filename,
+ AppName: AppName,
+ AppVersion: AppVersion,
+ }
+
+ // It is crucial that directives are executed in the proper order.
+ for k, dir := range directiveOrder {
+ // Execute directive if it is in the server block
+ if tokens, ok := sb.Tokens[dir.name]; ok {
+ // Each setup function gets a controller, from which setup functions
+ // get access to the config, tokens, and other state information useful
+ // to set up its own host only.
+ controller := &setup.Controller{
+ Config: &config,
+ Dispenser: parse.NewDispenserTokens(filename, tokens),
+ OncePerServerBlock: func(f func() error) error {
+ var err error
+ onces[dir.name].Do(func() {
+ err = f()
+ })
+ return err
+ },
+ ServerBlockIndex: i,
+ ServerBlockHostIndex: j,
+ ServerBlockHosts: sb.HostList(),
+ ServerBlockStorage: storages[dir.name],
+ }
+ // execute setup function and append middleware handler, if any
+ midware, err := dir.setup(controller)
+ if err != nil {
+ return nil, nil, lastDirectiveIndex, err
+ }
+ if midware != nil {
+ config.Middleware = append(config.Middleware, midware)
+ }
+ storages[dir.name] = controller.ServerBlockStorage // persist for this server block
+ }
+
+ // Stop after TLS setup, since we need to activate Let's Encrypt before continuing;
+ // it makes some changes to the configs that middlewares might want to know about.
+ if dir.name == "tls" {
+ lastDirectiveIndex = k
+ break
+ }
+ }
+
+ configs = append(configs, config)
+ }
+ }
+ return configs, serverBlocks, lastDirectiveIndex, nil
+}
+
+// loadConfigs reads input (named filename) and parses it, returning the
+// server configurations in the order they appeared in the input. As part
+// of this, it activates Let's Encrypt for the configs that are produced.
+// Thus, the returned configs are already optimally configured for HTTPS.
+func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
+ configs, serverBlocks, lastDirectiveIndex, err := loadConfigsUpToIncludingTLS(filename, input)
+ if err != nil {
+ return nil, err
+ }
+
+ // Now we have all the configs, but they have only been set up to the
+ // point of tls. We need to activate Let's Encrypt before setting up
+ // the rest of the middlewares so they have correct information regarding
+ // TLS configuration, if necessary. (this only appends, so our iterations
+ // over server blocks below shouldn't be affected)
+ if !IsRestart() && !Quiet {
+ fmt.Println("Activating privacy features...")
+ }
+ /* TODO(miek): stopped for now
+ configs, err = https.Activate(configs)
+ if err != nil {
+ return nil, err
+ } else if !IsRestart() && !Quiet {
+ fmt.Println(" done.")
+ }
+ */
+
+ // Finish setting up the rest of the directives, now that TLS is
+ // optimally configured. These loops are similar to above except
+ // we don't iterate all the directives from the beginning and we
+ // don't create new configs.
+ configIndex := -1
+ for i, sb := range serverBlocks {
+ onces := makeOnces()
+ storages := makeStorages()
+
+ for j := range sb.Addresses {
+ configIndex++
+
+ for k := lastDirectiveIndex + 1; k < len(directiveOrder); k++ {
+ dir := directiveOrder[k]
+
+ if tokens, ok := sb.Tokens[dir.name]; ok {
+ controller := &setup.Controller{
+ Config: &configs[configIndex],
+ Dispenser: parse.NewDispenserTokens(filename, tokens),
+ OncePerServerBlock: func(f func() error) error {
+ var err error
+ onces[dir.name].Do(func() {
+ err = f()
+ })
+ return err
+ },
+ ServerBlockIndex: i,
+ ServerBlockHostIndex: j,
+ ServerBlockHosts: sb.HostList(),
+ ServerBlockStorage: storages[dir.name],
+ }
+ midware, err := dir.setup(controller)
+ if err != nil {
+ return nil, err
+ }
+ if midware != nil {
+ configs[configIndex].Middleware = append(configs[configIndex].Middleware, midware)
+ }
+ storages[dir.name] = controller.ServerBlockStorage // persist for this server block
+ }
+ }
+ }
+ }
+
+ return configs, nil
+}
+
+// makeOnces makes a map of directive name to sync.Once
+// instance. This is intended to be called once per server
+// block when setting up configs so that Setup functions
+// for each directive can perform a task just once per
+// server block, even if there are multiple hosts on the block.
+//
+// We need one Once per directive, otherwise the first
+// directive to use it would exclude other directives from
+// using it at all, which would be a bug.
+func makeOnces() map[string]*sync.Once {
+ onces := make(map[string]*sync.Once)
+ for _, dir := range directiveOrder {
+ onces[dir.name] = new(sync.Once)
+ }
+ return onces
+}
+
+// makeStorages makes a map of directive name to interface{}
+// so that directives' setup functions can persist state
+// between different hosts on the same server block during the
+// setup phase.
+func makeStorages() map[string]interface{} {
+ storages := make(map[string]interface{})
+ for _, dir := range directiveOrder {
+ storages[dir.name] = nil
+ }
+ return storages
+}
+
+// arrangeBindings groups configurations by their bind address. For example,
+// a server that should listen on localhost and another on 127.0.0.1 will
+// be grouped into the same address: 127.0.0.1. It will return an error
+// if an address is malformed or a TLS listener is configured on the
+// same address as a plaintext HTTP listener. The return value is a map of
+// bind address to list of configs that would become VirtualHosts on that
+// server. Use the keys of the returned map to create listeners, and use
+// the associated values to set up the virtualhosts.
+func arrangeBindings(allConfigs []server.Config) (bindingGroup, error) {
+ var groupings bindingGroup
+
+ // Group configs by bind address
+ for _, conf := range allConfigs {
+ // use default port if none is specified
+ if conf.Port == "" {
+ conf.Port = Port
+ }
+
+ bindAddr, warnErr, fatalErr := resolveAddr(conf)
+ if fatalErr != nil {
+ return groupings, fatalErr
+ }
+ if warnErr != nil {
+ log.Printf("[WARNING] Resolving bind address for %s: %v", conf.Address(), warnErr)
+ }
+
+ // Make sure to compare the string representation of the address,
+ // not the pointer, since a new *TCPAddr is created each time.
+ var existing bool
+ for i := 0; i < len(groupings); i++ {
+ if groupings[i].BindAddr.String() == bindAddr.String() {
+ groupings[i].Configs = append(groupings[i].Configs, conf)
+ existing = true
+ break
+ }
+ }
+ if !existing {
+ groupings = append(groupings, bindingMapping{
+ BindAddr: bindAddr,
+ Configs: []server.Config{conf},
+ })
+ }
+ }
+
+ // Don't allow HTTP and HTTPS to be served on the same address
+ for _, group := range groupings {
+ isTLS := group.Configs[0].TLS.Enabled
+ for _, config := range group.Configs {
+ if config.TLS.Enabled != isTLS {
+ thisConfigProto, otherConfigProto := "HTTP", "HTTP"
+ if config.TLS.Enabled {
+ thisConfigProto = "HTTPS"
+ }
+ if group.Configs[0].TLS.Enabled {
+ otherConfigProto = "HTTPS"
+ }
+ return groupings, fmt.Errorf("configuration error: Cannot multiplex %s (%s) and %s (%s) on same address",
+ group.Configs[0].Address(), otherConfigProto, config.Address(), thisConfigProto)
+ }
+ }
+ }
+
+ return groupings, nil
+}
+
+// resolveAddr determines the address (host and port) that a config will
+// bind to. The returned address, resolvAddr, should be used to bind the
+// listener or group the config with other configs using the same address.
+// The first error, if not nil, is just a warning and should be reported
+// but execution may continue. The second error, if not nil, is a real
+// problem and the server should not be started.
+//
+// This function does not handle edge cases like port "http" or "https" if
+// they are not known to the system. It does, however, serve on the wildcard
+// host if resolving the address of the specific hostname fails.
+func resolveAddr(conf server.Config) (resolvAddr *net.TCPAddr, warnErr, fatalErr error) {
+ resolvAddr, warnErr = net.ResolveTCPAddr("tcp", net.JoinHostPort(conf.BindHost, conf.Port))
+ if warnErr != nil {
+ // the hostname probably couldn't be resolved, just bind to wildcard then
+ resolvAddr, fatalErr = net.ResolveTCPAddr("tcp", net.JoinHostPort("", conf.Port))
+ if fatalErr != nil {
+ return
+ }
+ }
+
+ return
+}
+
+// validDirective returns true if d is a valid
+// directive; false otherwise.
+func validDirective(d string) bool {
+ for _, dir := range directiveOrder {
+ if dir.name == d {
+ return true
+ }
+ }
+ return false
+}
+
+// DefaultInput returns the default Caddyfile input
+// to use when it is otherwise empty or missing.
+// It uses the default host and port (depends on
+// host, e.g. localhost is 2015, otherwise 443) and
+// root.
+func DefaultInput() CaddyfileInput {
+ port := Port
+ if https.HostQualifies(Host) && port == DefaultPort {
+ port = "443"
+ }
+ return CaddyfileInput{
+ Contents: []byte(fmt.Sprintf("%s:%s\nroot %s", Host, port, Root)),
+ }
+}
+
+// These defaults are configurable through the command line
+var (
+ // Root is the site root
+ Root = DefaultRoot
+
+ // Host is the site host
+ Host = DefaultHost
+
+ // Port is the site port
+ Port = DefaultPort
+)
+
+// bindingMapping maps a network address to configurations
+// that will bind to it. The order of the configs is important.
+type bindingMapping struct {
+ BindAddr *net.TCPAddr
+ Configs []server.Config
+}
+
+// bindingGroup maps network addresses to their configurations.
+// Preserving the order of the groupings is important
+// (related to graceful shutdown and restart)
+// so this is a slice, not a literal map.
+type bindingGroup []bindingMapping