| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | // +build !windows
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package core
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							|  |  |  | 	"bytes"
 | 
					
						
							|  |  |  | 	"encoding/gob"
 | 
					
						
							|  |  |  | 	"errors"
 | 
					
						
							|  |  |  | 	"io/ioutil"
 | 
					
						
							|  |  |  | 	"log"
 | 
					
						
							|  |  |  | 	"net"
 | 
					
						
							|  |  |  | 	"os"
 | 
					
						
							|  |  |  | 	"os/exec"
 | 
					
						
							|  |  |  | 	"path"
 | 
					
						
							|  |  |  | 	"sync/atomic"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/miekg/coredns/core/https"
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func init() {
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 	gob.Register(CorefileInput{})
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Restart restarts the entire application; gracefully with zero
 | 
					
						
							|  |  |  | // downtime if on a POSIX-compatible system, or forcefully if on
 | 
					
						
							|  |  |  | // Windows but with imperceptibly-short downtime.
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | // The restarted application will use newCorefile as its input
 | 
					
						
							|  |  |  | // configuration. If newCorefile is nil, the current (existing)
 | 
					
						
							|  |  |  | // Corefile configuration will be used.
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | //
 | 
					
						
							|  |  |  | // Note: The process must exist in the same place on the disk in
 | 
					
						
							|  |  |  | // order for this to work. Thus, multiple graceful restarts don't
 | 
					
						
							|  |  |  | // work if executing with `go run`, since the binary is cleaned up
 | 
					
						
							|  |  |  | // when `go run` sees the initial parent process exit.
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | func Restart(newCorefile Input) error {
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	log.Println("[INFO] Restarting")
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 	if newCorefile == nil {
 | 
					
						
							|  |  |  | 		corefileMu.Lock()
 | 
					
						
							|  |  |  | 		newCorefile = corefile
 | 
					
						
							|  |  |  | 		corefileMu.Unlock()
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 	// Get certificates for any new hosts in the new Corefile without causing downtime
 | 
					
						
							|  |  |  | 	err := getCertsForNewCorefile(newCorefile)
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return errors.New("TLS preload: " + err.Error())
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(os.Args) == 0 { // this should never happen, but...
 | 
					
						
							|  |  |  | 		os.Args = []string{""}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Tell the child that it's a restart
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 	os.Setenv("COREDNS_RESTART", "true")
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Prepare our payload to the child process
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 	crfileGob := corefileGob{
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 		ListenerFds:            make(map[string]uintptr),
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 		Corefile:               newCorefile,
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 		OnDemandTLSCertsIssued: atomic.LoadInt32(https.OnDemandIssuedCount),
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 	// Prepare a pipe to the fork's stdin so it can get the Corefile
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	rpipe, wpipe, err := os.Pipe()
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Prepare a pipe that the child process will use to communicate
 | 
					
						
							|  |  |  | 	// its success with us by sending > 0 bytes
 | 
					
						
							|  |  |  | 	sigrpipe, sigwpipe, err := os.Pipe()
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Pass along relevant file descriptors to child process; ordering
 | 
					
						
							|  |  |  | 	// is very important since we rely on these being in certain positions.
 | 
					
						
							|  |  |  | 	extraFiles := []*os.File{sigwpipe} // fd 3
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Add file descriptors of all the sockets
 | 
					
						
							|  |  |  | 	serversMu.Lock()
 | 
					
						
							|  |  |  | 	for i, s := range servers {
 | 
					
						
							|  |  |  | 		extraFiles = append(extraFiles, s.ListenerFd())
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 		crfileGob.ListenerFds[s.Addr] = uintptr(4 + i) // 4 fds come before any of the listeners
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 	serversMu.Unlock()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Set up the command
 | 
					
						
							|  |  |  | 	cmd := exec.Command(os.Args[0], os.Args[1:]...)
 | 
					
						
							|  |  |  | 	cmd.Stdin = rpipe      // fd 0
 | 
					
						
							|  |  |  | 	cmd.Stdout = os.Stdout // fd 1
 | 
					
						
							|  |  |  | 	cmd.Stderr = os.Stderr // fd 2
 | 
					
						
							|  |  |  | 	cmd.ExtraFiles = extraFiles
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Spawn the child process
 | 
					
						
							|  |  |  | 	err = cmd.Start()
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Immediately close our dup'ed fds and the write end of our signal pipe
 | 
					
						
							|  |  |  | 	for _, f := range extraFiles {
 | 
					
						
							|  |  |  | 		f.Close()
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 	// Feed Corefile to the child
 | 
					
						
							|  |  |  | 	err = gob.NewEncoder(wpipe).Encode(crfileGob)
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	wpipe.Close()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Determine whether child startup succeeded
 | 
					
						
							|  |  |  | 	answer, readErr := ioutil.ReadAll(sigrpipe)
 | 
					
						
							|  |  |  | 	if answer == nil || len(answer) == 0 {
 | 
					
						
							|  |  |  | 		cmdErr := cmd.Wait() // get exit status
 | 
					
						
							|  |  |  | 		log.Printf("[ERROR] Restart: child failed to initialize (%v) - changes not applied", cmdErr)
 | 
					
						
							|  |  |  | 		if readErr != nil {
 | 
					
						
							|  |  |  | 			log.Printf("[ERROR] Restart: additionally, error communicating with child process: %v", readErr)
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		return errIncompleteRestart
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Looks like child is successful; we can exit gracefully.
 | 
					
						
							|  |  |  | 	return Stop()
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | func getCertsForNewCorefile(newCorefile Input) error {
 | 
					
						
							|  |  |  | 	// parse the new corefile only up to (and including) TLS
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	// so we can know what we need to get certs for.
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 	configs, _, _, err := loadConfigsUpToIncludingTLS(path.Base(newCorefile.Path()), bytes.NewReader(newCorefile.Body()))
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	if err != nil {
 | 
					
						
							| 
									
										
										
										
											2016-04-28 11:07:44 -07:00
										 |  |  | 		return errors.New("loading Corefile: " + err.Error())
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// first mark the configs that are qualified for managed TLS
 | 
					
						
							|  |  |  | 	https.MarkQualified(configs)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// since we group by bind address to obtain certs, we must call
 | 
					
						
							|  |  |  | 	// EnableTLS to make sure the port is set properly first
 | 
					
						
							|  |  |  | 	// (can ignore error since we aren't actually using the certs)
 | 
					
						
							|  |  |  | 	https.EnableTLS(configs, false)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// find out if we can let the acme package start its own challenge listener
 | 
					
						
							|  |  |  | 	// on port 80
 | 
					
						
							|  |  |  | 	var proxyACME bool
 | 
					
						
							|  |  |  | 	serversMu.Lock()
 | 
					
						
							|  |  |  | 	for _, s := range servers {
 | 
					
						
							|  |  |  | 		_, port, _ := net.SplitHostPort(s.Addr)
 | 
					
						
							|  |  |  | 		if port == "80" {
 | 
					
						
							|  |  |  | 			proxyACME = true
 | 
					
						
							|  |  |  | 			break
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	serversMu.Unlock()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// place certs on the disk
 | 
					
						
							|  |  |  | 	err = https.ObtainCerts(configs, false, proxyACME)
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return errors.New("obtaining certs: " + err.Error())
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil
 | 
					
						
							|  |  |  | }
 |