Cleanup and fixes (#223)

* Set version to 001
* Remove k8stest, test fails is k8s is not there: touch luck
* Remove server directory: not used anymore
* Disable k8s test (for now)
* gometalinter changes
This commit is contained in:
Miek Gieben 2016-08-20 23:03:36 +01:00 committed by GitHub
parent 9ac3cab1b7
commit 416603383d
17 changed files with 33 additions and 1071 deletions

View file

@ -6,21 +6,19 @@ import (
// plug in the standard directives
_ "github.com/miekg/coredns/middleware/bind"
_ "github.com/miekg/coredns/middleware/health"
_ "github.com/miekg/coredns/middleware/pprof"
_ "github.com/miekg/coredns/middleware/errors"
_ "github.com/miekg/coredns/middleware/loadbalance"
_ "github.com/miekg/coredns/middleware/log"
_ "github.com/miekg/coredns/middleware/metrics"
_ "github.com/miekg/coredns/middleware/rewrite"
_ "github.com/miekg/coredns/middleware/cache"
_ "github.com/miekg/coredns/middleware/chaos"
_ "github.com/miekg/coredns/middleware/dnssec"
_ "github.com/miekg/coredns/middleware/errors"
_ "github.com/miekg/coredns/middleware/etcd"
_ "github.com/miekg/coredns/middleware/file"
_ "github.com/miekg/coredns/middleware/health"
_ "github.com/miekg/coredns/middleware/kubernetes"
_ "github.com/miekg/coredns/middleware/loadbalance"
_ "github.com/miekg/coredns/middleware/log"
_ "github.com/miekg/coredns/middleware/metrics"
_ "github.com/miekg/coredns/middleware/pprof"
_ "github.com/miekg/coredns/middleware/proxy"
_ "github.com/miekg/coredns/middleware/rewrite"
_ "github.com/miekg/coredns/middleware/secondary"
)

View file

@ -9,7 +9,7 @@ package dnsserver
// feel the effects of all other middleware below
// (after) them during a request, but they must not
// care what middleware above them are doing.
var Directives = []string{
var directives = []string{
"bind",
"health",
"pprof",

View file

@ -13,7 +13,7 @@ const serverType = "dns"
func init() {
caddy.RegisterServerType(serverType, caddy.ServerType{
Directives: Directives,
Directives: directives,
DefaultInput: func() caddy.Input {
if Port == DefaultPort && Zone != "" {
return caddy.CaddyfileInput{
@ -32,8 +32,6 @@ func init() {
})
}
var TestNewContext = newContext
func newContext() caddy.Context {
return &dnsContext{keysToConfigs: make(map[string]*Config)}
}
@ -103,8 +101,8 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
}
// AddMiddleware adds a middleware to a site's middleware stack.
func (sc *Config) AddMiddleware(m Middleware) {
sc.Middleware = append(sc.Middleware, m)
func (c *Config) AddMiddleware(m Middleware) {
c.Middleware = append(c.Middleware, m)
}
// groupSiteConfigsByListenAddr groups site configs by their listen

View file

@ -32,6 +32,7 @@ type Server struct {
connTimeout time.Duration // the maximum duration of a graceful shutdown
}
// NewServer returns a new CoreDNS server and compiles all middleware in to it.
func NewServer(addr string, group []*Config) (*Server, error) {
s := &Server{
@ -65,20 +66,6 @@ func NewServer(addr string, group []*Config) (*Server, error) {
return s, nil
}
// LocalAddr return the addresses where the server is bound to.
func (s *Server) LocalAddr() net.Addr {
s.m.Lock()
defer s.m.Unlock()
return s.l.Addr()
}
// LocalAddrPacket return the net.PacketConn address where the server is bound to.
func (s *Server) LocalAddrPacket() net.Addr {
s.m.Lock()
defer s.m.Unlock()
return s.p.LocalAddr()
}
// Serve starts the server with an existing listener. It blocks until the server stops.
func (s *Server) Serve(l net.Listener) error {
s.m.Lock()
@ -97,6 +84,7 @@ func (s *Server) ServePacket(p net.PacketConn) error {
return s.server[udp].ActivateAndServe()
}
// Listen implements caddy.TCPServer interface.
func (s *Server) Listen() (net.Listener, error) {
l, err := net.Listen("tcp", s.Addr)
if err != nil {
@ -108,6 +96,7 @@ func (s *Server) Listen() (net.Listener, error) {
return l, nil
}
// ListenPacket implements caddy.UDPServer interface.
func (s *Server) ListenPacket() (net.PacketConn, error) {
p, err := net.ListenPacket("udp", s.Addr)
if err != nil {
@ -197,7 +186,7 @@ func (s *Server) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
if h, ok := s.zones[string(b[:l])]; ok {
if r.Question[0].Qtype != dns.TypeDS {
rcode, _ := h.middlewareChain.ServeDNS(ctx, w, r)
if RcodeNoClientWrite(rcode) {
if rcodeNoClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)
}
return
@ -211,7 +200,7 @@ func (s *Server) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
// Wildcard match, if we have found nothing try the root zone as a last resort.
if h, ok := s.zones["."]; ok {
rcode, _ := h.middlewareChain.ServeDNS(ctx, w, r)
if RcodeNoClientWrite(rcode) {
if rcodeNoClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)
}
return
@ -234,7 +223,7 @@ func DefaultErrorFunc(w dns.ResponseWriter, r *dns.Msg, rcode int) {
w.WriteMsg(answer)
}
func RcodeNoClientWrite(rcode int) bool {
func rcodeNoClientWrite(rcode int) bool {
switch rcode {
case dns.RcodeServerFailure:
fallthrough

View file

@ -10,9 +10,15 @@ import (
//go:generate go run plugin_generate.go
func main() {
// Set some flags/options specific for CoreDNS.
// Default values for flags for CoreDNS.
flag.Set("type", "dns")
// Values specific for CoreDNS.
caddy.DefaultConfigFile = "Corefile"
caddy.AppName = "coredns"
caddy.AppVersion = version
caddymain.Run()
}
const version = "001"

View file

@ -56,4 +56,4 @@ func doDDD(b []byte) {
}
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
func dddToByte(s []byte) byte { return byte((s[1]-'0')*100 + (s[2]-'0')*10 + (s[3] - '0')) }
func dddToByte(s []byte) byte { return (s[1]-'0')*100 + (s[2]-'0')*10 + (s[3] - '0') }

View file

@ -1,120 +0,0 @@
package middleware
import (
"errors"
"runtime"
"unicode"
"github.com/flynn/go-shlex"
)
var runtimeGoos = runtime.GOOS
// SplitCommandAndArgs takes a command string and parses it
// shell-style into the command and its separate arguments.
func SplitCommandAndArgs(command string) (cmd string, args []string, err error) {
var parts []string
if runtimeGoos == "windows" {
parts = parseWindowsCommand(command) // parse it Windows-style
} else {
parts, err = parseUnixCommand(command) // parse it Unix-style
if err != nil {
err = errors.New("error parsing command: " + err.Error())
return
}
}
if len(parts) == 0 {
err = errors.New("no command contained in '" + command + "'")
return
}
cmd = parts[0]
if len(parts) > 1 {
args = parts[1:]
}
return
}
// parseUnixCommand parses a unix style command line and returns the
// command and its arguments or an error
func parseUnixCommand(cmd string) ([]string, error) {
return shlex.Split(cmd)
}
// parseWindowsCommand parses windows command lines and
// returns the command and the arguments as an array. It
// should be able to parse commonly used command lines.
// Only basic syntax is supported:
// - spaces in double quotes are not token delimiters
// - double quotes are escaped by either backspace or another double quote
// - except for the above case backspaces are path separators (not special)
//
// Many sources point out that escaping quotes using backslash can be unsafe.
// Use two double quotes when possible. (Source: http://stackoverflow.com/a/31413730/2616179 )
//
// This function has to be used on Windows instead
// of the shlex package because this function treats backslash
// characters properly.
func parseWindowsCommand(cmd string) []string {
const backslash = '\\'
const quote = '"'
var parts []string
var part string
var inQuotes bool
var lastRune rune
for i, ch := range cmd {
if i != 0 {
lastRune = rune(cmd[i-1])
}
if ch == backslash {
// put it in the part - for now we don't know if it's an
// escaping char or path separator
part += string(ch)
continue
}
if ch == quote {
if lastRune == backslash {
// remove the backslash from the part and add the escaped quote instead
part = part[:len(part)-1]
part += string(ch)
continue
}
if lastRune == quote {
// revert the last change of the inQuotes state
// it was an escaping quote
inQuotes = !inQuotes
part += string(ch)
continue
}
// normal escaping quotes
inQuotes = !inQuotes
continue
}
if unicode.IsSpace(ch) && !inQuotes && len(part) > 0 {
parts = append(parts, part)
part = ""
continue
}
part += string(ch)
}
if len(part) > 0 {
parts = append(parts, part)
part = ""
}
return parts
}

View file

@ -1,291 +0,0 @@
package middleware
import (
"fmt"
"runtime"
"strings"
"testing"
)
func TestParseUnixCommand(t *testing.T) {
tests := []struct {
input string
expected []string
}{
// 0 - emtpy command
{
input: ``,
expected: []string{},
},
// 1 - command without arguments
{
input: `command`,
expected: []string{`command`},
},
// 2 - command with single argument
{
input: `command arg1`,
expected: []string{`command`, `arg1`},
},
// 3 - command with multiple arguments
{
input: `command arg1 arg2`,
expected: []string{`command`, `arg1`, `arg2`},
},
// 4 - command with single argument with space character - in quotes
{
input: `command "arg1 arg1"`,
expected: []string{`command`, `arg1 arg1`},
},
// 5 - command with multiple spaces and tab character
{
input: "command arg1 arg2\targ3",
expected: []string{`command`, `arg1`, `arg2`, `arg3`},
},
// 6 - command with single argument with space character - escaped with backspace
{
input: `command arg1\ arg2`,
expected: []string{`command`, `arg1 arg2`},
},
// 7 - single quotes should escape special chars
{
input: `command 'arg1\ arg2'`,
expected: []string{`command`, `arg1\ arg2`},
},
}
for i, test := range tests {
errorPrefix := fmt.Sprintf("Test [%d]: ", i)
errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input)
actual, _ := parseUnixCommand(test.input)
if len(actual) != len(test.expected) {
t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual)
continue
}
for j := 0; j < len(actual); j++ {
if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart {
t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j)
}
}
}
}
func TestParseWindowsCommand(t *testing.T) {
tests := []struct {
input string
expected []string
}{
{ // 0 - empty command - do not fail
input: ``,
expected: []string{},
},
{ // 1 - cmd without args
input: `cmd`,
expected: []string{`cmd`},
},
{ // 2 - multiple args
input: `cmd arg1 arg2`,
expected: []string{`cmd`, `arg1`, `arg2`},
},
{ // 3 - multiple args with space
input: `cmd "combined arg" arg2`,
expected: []string{`cmd`, `combined arg`, `arg2`},
},
{ // 4 - path without spaces
input: `mkdir C:\Windows\foo\bar`,
expected: []string{`mkdir`, `C:\Windows\foo\bar`},
},
{ // 5 - command with space in quotes
input: `"command here"`,
expected: []string{`command here`},
},
{ // 6 - argument with escaped quotes (two quotes)
input: `cmd ""arg""`,
expected: []string{`cmd`, `"arg"`},
},
{ // 7 - argument with escaped quotes (backslash)
input: `cmd \"arg\"`,
expected: []string{`cmd`, `"arg"`},
},
{ // 8 - two quotes (escaped) inside an inQuote element
input: `cmd "a ""quoted value"`,
expected: []string{`cmd`, `a "quoted value`},
},
// TODO - see how many quotes are dislayed if we use "", """, """""""
{ // 9 - two quotes outside an inQuote element
input: `cmd a ""quoted value`,
expected: []string{`cmd`, `a`, `"quoted`, `value`},
},
{ // 10 - path with space in quotes
input: `mkdir "C:\directory name\foobar"`,
expected: []string{`mkdir`, `C:\directory name\foobar`},
},
{ // 11 - space without quotes
input: `mkdir C:\ space`,
expected: []string{`mkdir`, `C:\`, `space`},
},
{ // 12 - space in quotes
input: `mkdir "C:\ space"`,
expected: []string{`mkdir`, `C:\ space`},
},
{ // 13 - UNC
input: `mkdir \\?\C:\Users`,
expected: []string{`mkdir`, `\\?\C:\Users`},
},
{ // 14 - UNC with space
input: `mkdir "\\?\C:\Program Files"`,
expected: []string{`mkdir`, `\\?\C:\Program Files`},
},
{ // 15 - unclosed quotes - treat as if the path ends with quote
input: `mkdir "c:\Program files`,
expected: []string{`mkdir`, `c:\Program files`},
},
{ // 16 - quotes used inside the argument
input: `mkdir "c:\P"rogra"m f"iles`,
expected: []string{`mkdir`, `c:\Program files`},
},
}
for i, test := range tests {
errorPrefix := fmt.Sprintf("Test [%d]: ", i)
errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input)
actual := parseWindowsCommand(test.input)
if len(actual) != len(test.expected) {
t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual)
continue
}
for j := 0; j < len(actual); j++ {
if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart {
t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j)
}
}
}
}
func TestSplitCommandAndArgs(t *testing.T) {
// force linux parsing. It's more robust and covers error cases
runtimeGoos = "linux"
defer func() {
runtimeGoos = runtime.GOOS
}()
var parseErrorContent = "error parsing command:"
var noCommandErrContent = "no command contained in"
tests := []struct {
input string
expectedCommand string
expectedArgs []string
expectedErrContent string
}{
// 0 - emtpy command
{
input: ``,
expectedCommand: ``,
expectedArgs: nil,
expectedErrContent: noCommandErrContent,
},
// 1 - command without arguments
{
input: `command`,
expectedCommand: `command`,
expectedArgs: nil,
expectedErrContent: ``,
},
// 2 - command with single argument
{
input: `command arg1`,
expectedCommand: `command`,
expectedArgs: []string{`arg1`},
expectedErrContent: ``,
},
// 3 - command with multiple arguments
{
input: `command arg1 arg2`,
expectedCommand: `command`,
expectedArgs: []string{`arg1`, `arg2`},
expectedErrContent: ``,
},
// 4 - command with unclosed quotes
{
input: `command "arg1 arg2`,
expectedCommand: "",
expectedArgs: nil,
expectedErrContent: parseErrorContent,
},
// 5 - command with unclosed quotes
{
input: `command 'arg1 arg2"`,
expectedCommand: "",
expectedArgs: nil,
expectedErrContent: parseErrorContent,
},
}
for i, test := range tests {
errorPrefix := fmt.Sprintf("Test [%d]: ", i)
errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input)
actualCommand, actualArgs, actualErr := SplitCommandAndArgs(test.input)
// test if error matches expectation
if test.expectedErrContent != "" {
if actualErr == nil {
t.Errorf(errorPrefix+"Expected error with content [%s], found no error."+errorSuffix, test.expectedErrContent)
} else if !strings.Contains(actualErr.Error(), test.expectedErrContent) {
t.Errorf(errorPrefix+"Expected error with content [%s], found [%v]."+errorSuffix, test.expectedErrContent, actualErr)
}
} else if actualErr != nil {
t.Errorf(errorPrefix+"Expected no error, found [%v]."+errorSuffix, actualErr)
}
// test if command matches
if test.expectedCommand != actualCommand {
t.Errorf(errorPrefix+"Expected command: [%s], actual: [%s]."+errorSuffix, test.expectedCommand, actualCommand)
}
// test if arguments match
if len(test.expectedArgs) != len(actualArgs) {
t.Errorf(errorPrefix+"Wrong number of arguments! Expected [%v], actual [%v]."+errorSuffix, test.expectedArgs, actualArgs)
} else {
// test args only if the count matches.
for j, actualArg := range actualArgs {
expectedArg := test.expectedArgs[j]
if actualArg != expectedArg {
t.Errorf(errorPrefix+"Argument at position [%d] differ! Expected [%s], actual [%s]"+errorSuffix, j, expectedArg, actualArg)
}
}
}
}
}
func ExampleSplitCommandAndArgs() {
var commandLine string
var command string
var args []string
// just for the test - change GOOS and reset it at the end of the test
runtimeGoos = "windows"
defer func() {
runtimeGoos = runtime.GOOS
}()
commandLine = `mkdir /P "C:\Program Files"`
command, args, _ = SplitCommandAndArgs(commandLine)
fmt.Printf("Windows: %s: %s [%s]\n", commandLine, command, strings.Join(args, ","))
// set GOOS to linux
runtimeGoos = "linux"
commandLine = `mkdir -p /path/with\ space`
command, args, _ = SplitCommandAndArgs(commandLine)
fmt.Printf("Linux: %s: %s [%s]\n", commandLine, command, strings.Join(args, ","))
// Output:
// Windows: mkdir /P "C:\Program Files": mkdir [/P,C:\Program Files]
// Linux: mkdir -p /path/with\ space: mkdir [-p,/path/with space]
}

View file

@ -1,11 +0,0 @@
package k8stest
import (
"net/http"
)
// checkKubernetesRunning performs a basic
func CheckKubernetesRunning() bool {
_, err := http.Get("http://localhost:8080/api/v1")
return err == nil
}

View file

@ -7,7 +7,6 @@ import (
"github.com/miekg/coredns/core/dnsserver"
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/server"
"github.com/hashicorp/go-syslog"
"github.com/mholt/caddy"
@ -64,7 +63,7 @@ func setup(c *caddy.Controller) error {
})
dnsserver.GetConfig(c).AddMiddleware(func(next dnsserver.Handler) dnsserver.Handler {
return Logger{Next: next, Rules: rules, ErrorFunc: server.DefaultErrorFunc}
return Logger{Next: next, Rules: rules, ErrorFunc: dnsserver.DefaultErrorFunc}
})
return nil

View file

@ -1,76 +0,0 @@
package server
import (
"net"
"github.com/miekg/coredns/middleware"
)
// Config configuration for a single server.
type Config struct {
// The hostname or IP on which to serve
Host string
// The host address to bind on - defaults to (virtual) Host if empty
BindHost string
// The port to listen on
Port string
// The directory from which to parse db files
Root string
// HTTPS configuration
TLS TLSConfig
// Middleware stack
Middleware []middleware.Middleware
// Startup is a list of functions (or methods) to execute at
// server startup and restart; these are executed before any
// parts of the server are configured, and the functions are
// blocking. These are good for setting up middlewares and
// starting goroutines.
Startup []func() error
// FirstStartup is like Startup but these functions only execute
// during the initial startup, not on subsequent restarts.
//
// (Note: The server does not ever run these on its own; it is up
// to the calling application to do so, and do so only once, as the
// server itself has no notion whether it's a restart or not.)
FirstStartup []func() error
// Functions (or methods) to execute when the server quits;
// these are executed in response to SIGINT and are blocking. These
// function are *also* called when we are restarting.
Shutdown []func() error
// The path to the configuration file from which this was loaded
ConfigFile string
// The name of the application
AppName string
// The application's version
AppVersion string
}
// Address returns the host:port of c as a string.
func (c Config) Address() string {
return net.JoinHostPort(c.Host, c.Port)
}
// TLSConfig describes how TLS should be configured and used.
type TLSConfig struct {
Enabled bool // will be set to true if TLS is enabled
LetsEncryptEmail string
Manual bool // will be set to true if user provides own certs and keys
Managed bool // will be set to true if config qualifies for implicit automatic/managed HTTPS
OnDemand bool // will be set to true if user enables on-demand TLS (obtain certs during handshakes)
Ciphers []uint16
ProtocolMinVersion uint16
ProtocolMaxVersion uint16
PreferServerCipherSuites bool
ClientCerts []string
}

View file

@ -1,25 +0,0 @@
package server
import "testing"
func TestConfigAddress(t *testing.T) {
cfg := Config{Host: "foobar", Port: "1234"}
if actual, expected := cfg.Address(), "foobar:1234"; expected != actual {
t.Errorf("Expected '%s' but got '%s'", expected, actual)
}
cfg = Config{Host: "", Port: "1234"}
if actual, expected := cfg.Address(), ":1234"; expected != actual {
t.Errorf("Expected '%s' but got '%s'", expected, actual)
}
cfg = Config{Host: "foobar", Port: ""}
if actual, expected := cfg.Address(), "foobar:"; expected != actual {
t.Errorf("Expected '%s' but got '%s'", expected, actual)
}
cfg = Config{Host: "::1", Port: "443"}
if actual, expected := cfg.Address(), "[::1]:443"; expected != actual {
t.Errorf("Expected '%s' but got '%s'", expected, actual)
}
}

View file

@ -1,467 +0,0 @@
// Package server implements a configurable, general-purpose web server.
// It relies on configurations obtained from the adjacent config package
// and can execute middleware as defined by the adjacent middleware package.
package server
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net"
"os"
"runtime"
"sync"
"time"
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/metrics"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
// Server represents an instance of a server, which serves
// DNS requests at a particular address (host and port). A
// server is capable of serving numerous zones on
// the same address and the listener may be stopped for
// graceful termination (POSIX only).
type Server struct {
Addr string // Address we listen on
mux *dns.ServeMux
server [2]*dns.Server // by convention 0 is tcp and 1 is udp
listenerMu sync.Mutex // protects listener and packetconn inside server
tls bool // whether this server is serving all HTTPS hosts or not
TLSConfig *tls.Config
OnDemandTLS bool // whether this server supports on-demand TLS (load certs at handshake-time)
zones map[string]zone // zones keyed by their address
dnsWg sync.WaitGroup // used to wait on outstanding connections
startChan chan struct{} // used to block until server is finished starting
connTimeout time.Duration // the maximum duration of a graceful shutdown
ReqCallback OptionalCallback // if non-nil, is executed at the beginning of every request
SNICallback func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error)
}
// OptionalCallback is a function that may or may not handle a request.
// It returns whether or not it handled the request. If it handled the
// request, it is presumed that no further request handling should occur.
type OptionalCallback func(dns.ResponseWriter, *dns.Msg) bool
// New creates a new Server which will bind to addr and serve
// the sites/hosts configured in configs. Its listener will
// gracefully close when the server is stopped which will take
// no longer than gracefulTimeout.
//
// This function does not start serving.
//
// Do not re-use a server (start, stop, then start again). We
// could probably add more locking to make this possible, but
// as it stands, you should dispose of a server after stopping it.
// The behavior of serving with a spent server is undefined.
func New(addr string, configs []Config, gracefulTimeout time.Duration) (*Server, error) {
var useTLS, useOnDemandTLS bool
if len(configs) > 0 {
useTLS = configs[0].TLS.Enabled
useOnDemandTLS = configs[0].TLS.OnDemand
}
s := &Server{
Addr: addr,
TLSConfig: new(tls.Config),
// TODO: Make these values configurable?
// ReadTimeout: 2 * time.Minute,
// WriteTimeout: 2 * time.Minute,
// MaxHeaderBytes: 1 << 16,
tls: useTLS,
OnDemandTLS: useOnDemandTLS,
zones: make(map[string]zone),
startChan: make(chan struct{}),
connTimeout: gracefulTimeout,
}
mux := dns.NewServeMux()
mux.Handle(".", s) // wildcard handler, everything will go through here
s.mux = mux
// We have to bound our wg with one increment
// to prevent a "race condition" that is hard-coded
// into sync.WaitGroup.Wait() - basically, an add
// with a positive delta must be guaranteed to
// occur before Wait() is called on the wg.
// In a way, this kind of acts as a safety barrier.
s.dnsWg.Add(1)
// Set up each zone
for _, conf := range configs {
if _, exists := s.zones[conf.Host]; exists {
return nil, fmt.Errorf("cannot serve %s - host already defined for address %s", conf.Address(), s.Addr)
}
z := zone{config: conf}
// Build middleware stack
err := z.buildStack()
if err != nil {
return nil, err
}
s.zones[conf.Host] = z
}
return s, nil
}
// Serve starts the server with an existing listener. It blocks until the server stops.
func (s *Server) Serve(ln net.Listener, pc net.PacketConn) error {
err := s.setup()
if err != nil {
close(s.startChan) // MUST defer so error is properly reported, same with all cases in this file
return err
}
s.listenerMu.Lock()
s.server[0] = &dns.Server{Listener: ln, Net: "tcp", Handler: s.mux}
s.server[1] = &dns.Server{PacketConn: pc, Net: "udp", Handler: s.mux}
s.listenerMu.Unlock()
go func() {
s.server[0].ActivateAndServe()
}()
close(s.startChan)
return s.server[1].ActivateAndServe()
}
// ListenAndServe starts the server with a new listener. It blocks until the server stops.
func (s *Server) ListenAndServe() error {
err := s.setup()
// defer close(s.startChan) // Don't understand why defer wouldn't actually work in this method (prolly cause the last ActivateAndServe does not actually return?
if err != nil {
close(s.startChan)
return err
}
l, err := net.Listen("tcp", s.Addr)
if err != nil {
close(s.startChan)
return err
}
pc, err := net.ListenPacket("udp", s.Addr)
if err != nil {
close(s.startChan)
return err
}
s.listenerMu.Lock()
s.server[0] = &dns.Server{Listener: l, Net: "tcp", Handler: s.mux}
s.server[1] = &dns.Server{PacketConn: pc, Net: "udp", Handler: s.mux}
s.listenerMu.Unlock()
go func() {
s.server[0].ActivateAndServe()
}()
close(s.startChan)
return s.server[1].ActivateAndServe()
}
// setup prepares the server s to begin listening; it should be
// called just before the listener announces itself on the network
// and should only be called when the server is just starting up.
func (s *Server) setup() error {
// Execute startup functions now
for _, z := range s.zones {
for _, startupFunc := range z.config.Startup {
err := startupFunc()
if err != nil {
return err
}
}
}
return nil
}
/*
TODO(miek): no such thing in the glorious Go DNS.
// serveTLS serves TLS with SNI and client auth support if s has them enabled. It
// blocks until s quits.
func serveTLS(s *Server, ln net.Listener, tlsConfigs []TLSConfig) error {
// Customize our TLS configuration
s.TLSConfig.MinVersion = tlsConfigs[0].ProtocolMinVersion
s.TLSConfig.MaxVersion = tlsConfigs[0].ProtocolMaxVersion
s.TLSConfig.CipherSuites = tlsConfigs[0].Ciphers
s.TLSConfig.PreferServerCipherSuites = tlsConfigs[0].PreferServerCipherSuites
// TLS client authentication, if user enabled it
err := setupClientAuth(tlsConfigs, s.TLSConfig)
if err != nil {
defer close(s.startChan)
return err
}
// Create TLS listener - note that we do not replace s.listener
// with this TLS listener; tls.listener is unexported and does
// not implement the File() method we need for graceful restarts
// on POSIX systems.
ln = tls.NewListener(ln, s.TLSConfig)
close(s.startChan) // unblock anyone waiting for this to start listening
return s.Serve(ln)
}
*/
// Stop stops the server. It blocks until the server is
// totally stopped. On POSIX systems, it will wait for
// connections to close (up to a max timeout of a few
// seconds); on Windows it will close the listener
// immediately.
func (s *Server) Stop() (err error) {
if runtime.GOOS != "windows" {
// force connections to close after timeout
done := make(chan struct{})
go func() {
s.dnsWg.Done() // decrement our initial increment used as a barrier
s.dnsWg.Wait()
close(done)
}()
// Wait for remaining connections to finish or
// force them all to close after timeout
select {
case <-time.After(s.connTimeout):
case <-done:
}
}
// Close the listener now; this stops the server without delay
s.listenerMu.Lock()
defer s.listenerMu.Unlock()
for _, s1 := range s.server {
if s1.Listener != nil {
err = s1.Listener.Close()
}
if s1.PacketConn != nil {
err = s1.PacketConn.Close()
}
err = s1.Shutdown()
}
return
}
// WaitUntilStarted blocks until the server s is started, meaning
// that practically the next instruction is to start the server loop.
// It also unblocks if the server encounters an error during startup.
func (s *Server) WaitUntilStarted() {
<-s.startChan
}
// ListenerFd gets a dup'ed file of the listener. If there
// is no underlying file, the return value will be nil. It
// is the caller's responsibility to close the file.
func (s *Server) ListenerFd() *os.File {
s.listenerMu.Lock()
defer s.listenerMu.Unlock()
if s.server[0].Listener != nil {
file, _ := s.server[0].Listener.(*net.TCPListener).File()
return file
}
return nil
}
// PacketConnFd gets a dup'ed file of the packetconn. If there
// is no underlying file, the return value will be nil. It
// is the caller's responsibility to close the file.
func (s *Server) PacketConnFd() *os.File {
s.listenerMu.Lock()
defer s.listenerMu.Unlock()
if s.server[1].PacketConn != nil {
file, _ := s.server[1].PacketConn.(*net.UDPConn).File()
return file
}
return nil
}
// ServeDNS is the entry point for every request to the address that s
// is bound to. It acts as a multiplexer for the requests zonename as
// defined in the request so that the correct zone
// (configuration and middleware stack) will handle the request.
func (s *Server) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
defer func() {
// In case the user doesn't enable error middleware, we still
// need to make sure that we stay alive up here
if rec := recover(); rec != nil {
DefaultErrorFunc(w, r, dns.RcodeServerFailure)
}
}()
if m, err := middleware.Edns0Version(r); err != nil { // Wrong EDNS version, return at once.
rc := middleware.RcodeToString(dns.RcodeBadVers)
state := middleware.State{W: w, Req: r}
metrics.Report(state, metrics.Dropped, rc, m.Len(), time.Now())
w.WriteMsg(m)
return
}
// Execute the optional request callback if it exists
if s.ReqCallback != nil && s.ReqCallback(w, r) {
return
}
q := r.Question[0].Name
b := make([]byte, len(q))
off, end := 0, false
ctx := context.Background()
for {
l := len(q[off:])
for i := 0; i < l; i++ {
b[i] = q[off+i]
// normalize the name for the lookup
if b[i] >= 'A' && b[i] <= 'Z' {
b[i] |= ('a' - 'A')
}
}
if h, ok := s.zones[string(b[:l])]; ok {
if r.Question[0].Qtype != dns.TypeDS {
rcode, _ := h.stack.ServeDNS(ctx, w, r)
if RcodeNoClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)
}
return
}
}
off, end = dns.NextLabel(q, off)
if end {
break
}
}
// Wildcard match, if we have found nothing try the root zone as a last resort.
if h, ok := s.zones["."]; ok {
rcode, _ := h.stack.ServeDNS(ctx, w, r)
if RcodeNoClientWrite(rcode) {
DefaultErrorFunc(w, r, rcode)
}
return
}
// Still here? Error out with REFUSED and some logging
remoteHost := w.RemoteAddr().String()
DefaultErrorFunc(w, r, dns.RcodeRefused)
log.Printf("[INFO] \"%s %s %s\" - No such zone at %s (Remote: %s)", dns.Type(r.Question[0].Qtype), dns.Class(r.Question[0].Qclass), q, s.Addr, remoteHost)
}
// DefaultErrorFunc responds to an DNS request with an error.
func DefaultErrorFunc(w dns.ResponseWriter, r *dns.Msg, rcode int) {
state := middleware.State{W: w, Req: r}
rc := middleware.RcodeToString(rcode)
answer := new(dns.Msg)
answer.SetRcode(r, rcode)
state.SizeAndDo(answer)
metrics.Report(state, metrics.Dropped, rc, answer.Len(), time.Now())
w.WriteMsg(answer)
}
// setupClientAuth sets up TLS client authentication only if
// any of the TLS configs specified at least one cert file.
func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error {
var clientAuth bool
for _, cfg := range tlsConfigs {
if len(cfg.ClientCerts) > 0 {
clientAuth = true
break
}
}
if clientAuth {
pool := x509.NewCertPool()
for _, cfg := range tlsConfigs {
for _, caFile := range cfg.ClientCerts {
caCrt, err := ioutil.ReadFile(caFile) // Anyone that gets a cert from this CA can connect
if err != nil {
return err
}
if !pool.AppendCertsFromPEM(caCrt) {
return fmt.Errorf("error loading client certificate '%s': no certificates were successfully parsed", caFile)
}
}
}
config.ClientCAs = pool
config.ClientAuth = tls.RequireAndVerifyClientCert
}
return nil
}
// RunFirstStartupFuncs runs all of the server's FirstStartup
// callback functions unless one of them returns an error first.
// It is the caller's responsibility to call this only once and
// at the correct time. The functions here should not be executed
// at restarts or where the user does not explicitly start a new
// instance of the server.
func (s *Server) RunFirstStartupFuncs() error {
for _, z := range s.zones {
for _, f := range z.config.FirstStartup {
if err := f(); err != nil {
return err
}
}
}
return nil
}
// ShutdownCallbacks executes all the shutdown callbacks
// for all the virtualhosts in servers, and returns all the
// errors generated during their execution. In other words,
// an error executing one shutdown callback does not stop
// execution of others. Only one shutdown callback is executed
// at a time. You must protect the servers that are passed in
// if they are shared across threads.
func ShutdownCallbacks(servers []*Server) []error {
var errs []error
for _, s := range servers {
for _, zone := range s.zones {
for _, shutdownFunc := range zone.config.Shutdown {
err := shutdownFunc()
if err != nil {
errs = append(errs, err)
}
}
}
}
return errs
}
func StartupCallbacks(servers []*Server) []error {
var errs []error
for _, s := range servers {
for _, zone := range s.zones {
for _, startupFunc := range zone.config.Startup {
err := startupFunc()
if err != nil {
errs = append(errs, err)
}
}
}
}
return errs
}
func RcodeNoClientWrite(rcode int) bool {
switch rcode {
case dns.RcodeServerFailure:
fallthrough
case dns.RcodeRefused:
fallthrough
case dns.RcodeFormatError:
fallthrough
case dns.RcodeNotImplemented:
return true
}
return false
}

View file

@ -1,28 +0,0 @@
package server
import "github.com/miekg/coredns/middleware"
// zone represents a DNS zone. While a Server
// is what actually binds to the address, a user may want to serve
// multiple zones on a single address, and this is what a
// zone allows us to do.
type zone struct {
config Config
stack middleware.Handler
}
// buildStack builds the server's middleware stack based
// on its config. This method should be called last before
// ListenAndServe begins.
func (z *zone) buildStack() error {
z.compile(z.config.Middleware)
return nil
}
// compile is an elegant alternative to nesting middleware function
// calls like handler1(handler2(handler3(finalHandler))).
func (z *zone) compile(layers []middleware.Middleware) {
for i := len(layers) - 1; i >= 0; i-- {
z.stack = layers[i](z.stack)
}
}

View file

@ -77,10 +77,10 @@ func TestEtcdStubAndProxyLookup(t *testing.T) {
t.Error("Expected to at least one RR in the answer section, got none")
}
if resp.Answer[0].Header().Rrtype != dns.TypeA {
t.Error("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)
t.Errorf("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)
}
if resp.Answer[0].(*dns.A).A.String() != "93.184.216.34" {
t.Error("Expected 93.184.216.34, got: %d", resp.Answer[0].(*dns.A).A.String())
t.Errorf("Expected 93.184.216.34, got: %d", resp.Answer[0].(*dns.A).A.String())
}
}

View file

@ -7,8 +7,6 @@ import (
"log"
"testing"
"github.com/miekg/coredns/middleware/kubernetes/k8stest"
"github.com/miekg/dns"
)
@ -63,17 +61,13 @@ var testdataLookupSRV = []struct {
{"*.*.coredns.local.", 1, 1}, // One SRV record, via namespace and service wildcard
}
func TestK8sIntegration(t *testing.T) {
func testK8sIntegration(t *testing.T) {
// subtests here (Go 1.7 feature).
testLookupA(t)
testLookupSRV(t)
}
func testLookupA(t *testing.T) {
if !k8stest.CheckKubernetesRunning() {
t.Skip("Skipping Kubernetes Integration tests. Kubernetes is not running")
}
corefile :=
`.:0 {
kubernetes coredns.local {
@ -104,7 +98,7 @@ func testLookupA(t *testing.T) {
res, _, err := dnsClient.Exchange(dnsMessage, udp)
if err != nil {
t.Fatal("Could not send query: %s", err)
t.Fatalf("Could not send query: %s", err)
}
// Count A records in the answer section
ARecordCount := 0
@ -124,10 +118,6 @@ func testLookupA(t *testing.T) {
}
func testLookupSRV(t *testing.T) {
if !k8stest.CheckKubernetesRunning() {
t.Skip("Skipping Kubernetes Integration tests. Kubernetes is not running")
}
corefile :=
`.:0 {
kubernetes coredns.local {
@ -159,7 +149,7 @@ func testLookupSRV(t *testing.T) {
res, _, err := dnsClient.Exchange(dnsMessage, udp)
if err != nil {
t.Fatal("Could not send query: %s", err)
t.Fatalf("Could not send query: %s", err)
}
// Count SRV records in the answer section
srvRecordCount := 0

View file

@ -56,9 +56,9 @@ func TestLookupProxy(t *testing.T) {
t.Error("Expected to at least one RR in the answer section, got none")
}
if resp.Answer[0].Header().Rrtype != dns.TypeA {
t.Error("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)
t.Errorf("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)
}
if resp.Answer[0].(*dns.A).A.String() != "127.0.0.1" {
t.Error("Expected 127.0.0.1, got: %d", resp.Answer[0].(*dns.A).A.String())
t.Errorf("Expected 127.0.0.1, got: %d", resp.Answer[0].(*dns.A).A.String())
}
}