aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--middleware/etcd/README.md18
-rw-r--r--middleware/etcd/setup.go21
-rw-r--r--middleware/file/setup.go18
-rw-r--r--middleware/pkg/dnsutil/host.go82
-rw-r--r--middleware/pkg/dnsutil/host_test.go85
-rw-r--r--middleware/proxy/README.md8
-rw-r--r--middleware/proxy/upstream.go36
7 files changed, 212 insertions, 56 deletions
diff --git a/middleware/etcd/README.md b/middleware/etcd/README.md
index edb47e54f..80eff35cf 100644
--- a/middleware/etcd/README.md
+++ b/middleware/etcd/README.md
@@ -37,7 +37,8 @@ etcd [ZONES...] {
* **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2397".
* `upstream` upstream resolvers to be used resolve external names found in etcd (think CNAMEs)
pointing to external names. If you want CoreDNS to act as a proxy for clients, you'll need to add
- the proxy middleware.
+ the proxy middleware. **ADDRESS* can be an IP address, and IP:port or a string pointing to a file
+ that is structured as /etc/resolv.conf.
* `tls` followed the cert, key and the CA's cert filenames.
* `debug` allows for debug queries. Prefix the name with `o-o.debug.` to retrieve extra information in the
additional section of the reply in the form of TXT records.
@@ -61,6 +62,21 @@ This is the default SkyDNS setup, with everying specified in full:
}
~~~
+Or a setup where we use `/etc/resolv.conf` as the basis for the proxy and the upstream
+when resolving external pointing CNAMEs.
+
+~~~
+.:53 {
+ etcd skydns.local {
+ path /skydns
+ upstream /etc/resolv.conf
+ }
+ cache 160 skydns.local
+ proxy . /etc/resolv.conf
+}
+~~~
+
+
### Reverse zones
Reverse zones are supported. You need to make CoreDNS aware of the fact that you are also
diff --git a/middleware/etcd/setup.go b/middleware/etcd/setup.go
index 5bb9cf260..0da711705 100644
--- a/middleware/etcd/setup.go
+++ b/middleware/etcd/setup.go
@@ -10,6 +10,7 @@ import (
"github.com/miekg/coredns/core/dnsserver"
"github.com/miekg/coredns/middleware"
+ "github.com/miekg/coredns/middleware/pkg/dnsutil"
"github.com/miekg/coredns/middleware/pkg/singleflight"
"github.com/miekg/coredns/middleware/proxy"
@@ -93,13 +94,11 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
if len(args) == 0 {
return &Etcd{}, false, c.ArgErr()
}
- for i := 0; i < len(args); i++ {
- h, p, e := net.SplitHostPort(args[i])
- if e != nil && p == "" {
- args[i] = h + ":53"
- }
+ ups, err := dnsutil.ParseHostPortOrFile(args...)
+ if err != nil {
+ return &Etcd{}, false, err
}
- etc.Proxy = proxy.New(args)
+ etc.Proxy = proxy.New(ups)
case "tls": // cert key cacertfile
args := c.RemainingArgs()
if len(args) != 3 {
@@ -133,13 +132,11 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
if len(args) == 0 {
return &Etcd{}, false, c.ArgErr()
}
- for i := 0; i < len(args); i++ {
- h, p, e := net.SplitHostPort(args[i])
- if e != nil && p == "" {
- args[i] = h + ":53"
- }
+ ups, err := dnsutil.ParseHostPortOrFile(args...)
+ if err != nil {
+ return &Etcd{}, false, c.ArgErr()
}
- etc.Proxy = proxy.New(args)
+ etc.Proxy = proxy.New(ups)
case "tls": // cert key cacertfile
args := c.RemainingArgs()
if len(args) != 3 {
diff --git a/middleware/file/setup.go b/middleware/file/setup.go
index 9dd108ee6..e358558e0 100644
--- a/middleware/file/setup.go
+++ b/middleware/file/setup.go
@@ -2,12 +2,12 @@ package file
import (
"fmt"
- "net"
"os"
"path"
"github.com/miekg/coredns/core/dnsserver"
"github.com/miekg/coredns/middleware"
+ "github.com/miekg/coredns/middleware/pkg/dnsutil"
"github.com/mholt/caddy"
)
@@ -125,24 +125,26 @@ func TransferParse(c *caddy.Controller, secondary bool) (tos, froms []string, er
tos = c.RemainingArgs()
for i := range tos {
if tos[i] != "*" {
- if x := net.ParseIP(tos[i]); x == nil {
- return nil, nil, fmt.Errorf("must specify an IP address: `%s'", tos[i])
+ normalized, err := dnsutil.ParseHostPort(tos[i], "53")
+ if err != nil {
+ return nil, nil, err
}
- tos[i] = middleware.Addr(tos[i]).Normalize()
+ tos[i] = normalized
}
}
}
if value == "from" {
if !secondary {
- return nil, nil, fmt.Errorf("can't use `transfer from` when not being a seconary")
+ return nil, nil, fmt.Errorf("can't use `transfer from` when not being a secondary")
}
froms = c.RemainingArgs()
for i := range froms {
if froms[i] != "*" {
- if x := net.ParseIP(froms[i]); x == nil {
- return nil, nil, fmt.Errorf("must specify an IP address: `%s'", froms[i])
+ normalized, err := dnsutil.ParseHostPort(froms[i], "53")
+ if err != nil {
+ return nil, nil, err
}
- froms[i] = middleware.Addr(froms[i]).Normalize()
+ froms[i] = normalized
} else {
return nil, nil, fmt.Errorf("can't use '*' in transfer from")
}
diff --git a/middleware/pkg/dnsutil/host.go b/middleware/pkg/dnsutil/host.go
new file mode 100644
index 000000000..e38eb9e08
--- /dev/null
+++ b/middleware/pkg/dnsutil/host.go
@@ -0,0 +1,82 @@
+package dnsutil
+
+import (
+ "fmt"
+ "net"
+ "os"
+
+ "github.com/miekg/dns"
+)
+
+// PorseHostPortOrFile parses the strings in s, each string can either be a address,
+// address:port or a filename. The address part is checked and the filename case a
+// resolv.conf like file is parsed and the nameserver found are returned.
+func ParseHostPortOrFile(s ...string) ([]string, error) {
+ var servers []string
+ for _, host := range s {
+ addr, _, err := net.SplitHostPort(host)
+ if err != nil {
+ // Parse didn't work, it is not a addr:port combo
+ if net.ParseIP(host) == nil {
+ // Not an IP address.
+ ss, err := tryFile(host)
+ if err == nil {
+ servers = append(servers, ss...)
+ continue
+ }
+ return servers, fmt.Errorf("not an IP address or file: %q", host)
+ }
+ ss := net.JoinHostPort(host, "53")
+ servers = append(servers, ss)
+ continue
+ }
+
+ if net.ParseIP(addr) == nil {
+ // No an IP address.
+ ss, err := tryFile(host)
+ if err == nil {
+ servers = append(servers, ss...)
+ continue
+ }
+ return servers, fmt.Errorf("not an IP address or file: %q", host)
+ }
+ servers = append(servers, host)
+ }
+ return servers, nil
+}
+
+// Try to open this is a file first.
+func tryFile(s string) ([]string, error) {
+ c, err := dns.ClientConfigFromFile(s)
+ if err == os.ErrNotExist {
+ return nil, fmt.Errorf("failed to open file %q: %q", s, err)
+ } else if err != nil {
+ return nil, err
+ }
+
+ servers := []string{}
+ for _, s := range c.Servers {
+ servers = append(servers, net.JoinHostPort(s, c.Port))
+ }
+ return servers, nil
+}
+
+// ParseHostPort will check if the host part is a valid IP address, if the
+// IP address is valid, but no port is found, defaultPort is added.
+func ParseHostPort(s, defaultPort string) (string, error) {
+ addr, port, err := net.SplitHostPort(s)
+ if port == "" {
+ port = defaultPort
+ }
+ if err != nil {
+ if net.ParseIP(s) == nil {
+ return "", fmt.Errorf("must specify an IP address: `%s'", s)
+ }
+ return net.JoinHostPort(s, port), nil
+ }
+
+ if net.ParseIP(addr) == nil {
+ return "", fmt.Errorf("must specify an IP address: `%s'", addr)
+ }
+ return net.JoinHostPort(addr, port), nil
+}
diff --git a/middleware/pkg/dnsutil/host_test.go b/middleware/pkg/dnsutil/host_test.go
new file mode 100644
index 000000000..cc55f4570
--- /dev/null
+++ b/middleware/pkg/dnsutil/host_test.go
@@ -0,0 +1,85 @@
+package dnsutil
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+func TestParseHostPortOrFile(t *testing.T) {
+ tests := []struct {
+ in string
+ expected string
+ shouldErr bool
+ }{
+ {
+ "8.8.8.8",
+ "8.8.8.8:53",
+ false,
+ },
+ {
+ "8.8.8.8:153",
+ "8.8.8.8:153",
+ false,
+ },
+ {
+ "/etc/resolv.conf:53",
+ "",
+ true,
+ },
+ {
+ "resolv.conf",
+ "127.0.0.1:53",
+ false,
+ },
+ }
+
+ err := ioutil.WriteFile("resolv.conf", []byte("nameserver 127.0.0.1\n"), 0600)
+ if err != nil {
+ t.Fatalf("Failed to write test resolv.conf")
+ }
+ defer os.Remove("resolv.conf")
+
+ for i, tc := range tests {
+ got, err := ParseHostPortOrFile(tc.in)
+ if err == nil && tc.shouldErr {
+ t.Errorf("Test %d, expected error, got nil", i)
+ continue
+ }
+ if err != nil && tc.shouldErr {
+ continue
+ }
+ if got[0] != tc.expected {
+ t.Errorf("Test %d, expected %q, got %q", i, tc.expected, got[0])
+ }
+ }
+}
+
+func TestParseHostPort(t *testing.T) {
+ tests := []struct {
+ in string
+ expected string
+ shouldErr bool
+ }{
+ {"8.8.8.8:53", "8.8.8.8:53", false},
+ {"a.a.a.a:153", "", true},
+ {"8.8.8.8", "8.8.8.8:53", false},
+ {"8.8.8.8:", "8.8.8.8:53", false},
+ {"8.8.8.8::53", "", true},
+ {"resolv.conf", "", true},
+ }
+
+ for i, tc := range tests {
+ got, err := ParseHostPort(tc.in, "53")
+ if err == nil && tc.shouldErr {
+ t.Errorf("Test %d, expected error, got nil", i)
+ continue
+ }
+ if err != nil && !tc.shouldErr {
+ t.Errorf("Test %d, expected no error, got %q", i, err)
+ }
+ if got != tc.expected {
+ t.Errorf("Test %d, expected %q, got %q", i, tc.expected, got)
+ }
+ }
+}
diff --git a/middleware/proxy/README.md b/middleware/proxy/README.md
index 83ca573d1..837f166fc 100644
--- a/middleware/proxy/README.md
+++ b/middleware/proxy/README.md
@@ -10,7 +10,7 @@
In its most basic form, a simple reverse proxy uses this syntax:
~~~
-proxy FROM To
+proxy FROM TO
~~~
* **FROM** is the base path to match for the request to be proxied
@@ -68,13 +68,13 @@ proxy example.org localhost:9005
Load-balance all requests between three backends (using random policy):
~~~
-proxy . web1.local:53 web2.local:1053 web3.local
+proxy . dns1.local:53 dns2.local:1053 dns3.local
~~~
Same as above, but round-robin style:
~~~
-proxy . web1.local:53 web2.local:1053 web3.local {
+proxy . dns1.local:53 dns2.local:1053 dns3.local {
policy round_robin
}
~~~
@@ -82,7 +82,7 @@ proxy . web1.local:53 web2.local:1053 web3.local {
With health checks and proxy headers to pass hostname, IP, and scheme upstream:
~~~
-proxy . web1.local:53 web2.local:53 web3.local:53 {
+proxy . dns1.local:53 dns2.local:53 dns3.local:53 {
policy round_robin
health_check /health:8080
}
diff --git a/middleware/proxy/upstream.go b/middleware/proxy/upstream.go
index 02575d1a9..61ada110b 100644
--- a/middleware/proxy/upstream.go
+++ b/middleware/proxy/upstream.go
@@ -1,18 +1,17 @@
package proxy
import (
- "fmt"
"io"
"io/ioutil"
"net"
"net/http"
- "os"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/miekg/coredns/middleware"
+ "github.com/miekg/coredns/middleware/pkg/dnsutil"
"github.com/mholt/caddy/caddyfile"
"github.com/miekg/dns"
@@ -68,26 +67,9 @@ func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
}
// process the host list, substituting in any nameservers in files
- var toHosts []string
- for _, host := range to {
- h, _, err := net.SplitHostPort(host)
- if err != nil {
- h = host
- }
- if x := net.ParseIP(h); x == nil {
- // it's a file, parse as resolv.conf
- c, err := dns.ClientConfigFromFile(host)
- if err == os.ErrNotExist {
- return upstreams, fmt.Errorf("not an IP address or file: `%s'", h)
- } else if err != nil {
- return upstreams, err
- }
- for _, s := range c.Servers {
- toHosts = append(toHosts, net.JoinHostPort(s, c.Port))
- }
- } else {
- toHosts = append(toHosts, host)
- }
+ toHosts, err := dnsutil.ParseHostPortOrFile(to...)
+ if err != nil {
+ return upstreams, err
}
for c.NextBlock() {
@@ -99,7 +81,7 @@ func NewStaticUpstreams(c *caddyfile.Dispenser) ([]Upstream, error) {
upstream.Hosts = make([]*UpstreamHost, len(toHosts))
for i, host := range toHosts {
uh := &UpstreamHost{
- Name: defaultHostPort(host),
+ Name: host,
Conns: 0,
Fails: 0,
FailTimeout: upstream.FailTimeout,
@@ -297,11 +279,3 @@ func (u *staticUpstream) IsAllowedPath(name string) bool {
}
return true
}
-
-func defaultHostPort(s string) string {
- _, _, e := net.SplitHostPort(s)
- if e == nil {
- return s
- }
- return net.JoinHostPort(s, "53")
-}