Default to upstream to self (#2436)

* Default to upstream to self

This is a backwards incompatible change.

This is a massive (cleanup) PR where we default to resolving external
names by the coredns process itself, instead of directly forwarding them
to some upstream.

This ignores any arguments `upstream` may have had and makes it depend
on proxy/forward configuration in the Corefile. This allows resolved
upstream names to be cached and we have better healthchecking of the
upstreams. It also means there is only one way to resolve names, by
either using the proxy or forward plugin.

The proxy/forward lookup.go functions have been removed. This also
lessen the dependency on proxy, meaning deprecating proxy will become
easier. Some tests have been removed as well, or moved to the top-level
test directory as they now require a full coredns process instead of
just the plugin.

For the etcd plugin, the entire StubZone resolving is *dropped*! This
was a hacky (but working) solution to say the least. If someone cares
deeply it can be brought back (maybe)?

The pkg/upstream is now very small and almost does nothing. Also the
New() function was changed to return a pointer to upstream.Upstream. It
also returns only one parameter, so any stragglers using it will
encounter a compile error.

All documentation has been adapted. This affected the following plugins:
* etcd
* file
* auto
* secondary
* federation
* template
* route53

A followup PR will make any upstream directives with arguments an error,
right now they are ignored.

Signed-off-by: Miek Gieben <miek@miek.nl>

* Fix etcd build - probably still fails unit test

Signed-off-by: Miek Gieben <miek@miek.nl>

* Slightly smarter lookup check in upstream

Signed-off-by: Miek Gieben <miek@miek.nl>

* Compilez

Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
Miek Gieben 2019-01-13 16:54:49 +00:00 committed by GitHub
parent 6b56a9c921
commit 9c16ed1d14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 184 additions and 1349 deletions

View file

@ -18,7 +18,7 @@ auto [ZONES...] {
directory DIR [REGEXP ORIGIN_TEMPLATE [TIMEOUT]]
reload DURATION
no_reload
upstream [ADDRESS...]
upstream
}
~~~
@ -37,9 +37,7 @@ are used.
and reloads zone when serial changes.
* `no_reload` deprecated. Sets reload to 0.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names. **ADDRESS** can be an IP address, an IP:port or a string pointing to
a file that is structured as /etc/resolv.conf. If no **ADDRESS** is given, CoreDNS will resolve CNAMEs
against itself.
pointing to external names. CoreDNS will resolve CNAMEs against itself.
All directives from the *file* plugin are supported. Note that *auto* will load all zones found,
even though the directive might only receive queries for a specific zone. I.e:

View file

@ -33,7 +33,7 @@ type (
// In the future this should be something like ZoneMeta that contains all this stuff.
transferTo []string
ReloadInterval time.Duration
upstream upstream.Upstream // Upstream for looking up names during the resolution process.
upstream *upstream.Upstream // Upstream for looking up names during the resolution process.
duration time.Duration
}

View file

@ -155,15 +155,8 @@ func autoParse(c *caddy.Controller) (Auto, error) {
a.loader.ReloadInterval = 0
case "upstream":
args := c.RemainingArgs()
if len(args) == 0 {
return a, c.ArgErr()
}
var err error
a.loader.upstream, err = upstream.New(args)
if err != nil {
return a, err
}
c.RemainingArgs() // eat remaining args
a.loader.upstream = upstream.New()
default:
t, _, e := parse.Transfer(c, false)

View file

@ -28,28 +28,22 @@ If you want to `round robin` A and AAAA responses look at the `loadbalance` plug
~~~
etcd [ZONES...] {
stubzones
fallthrough [ZONES...]
path PATH
endpoint ENDPOINT...
upstream [ADDRESS...]
upstream
tls CERT KEY CACERT
}
~~~
* `stubzones` enables the stub zones feature. The stubzone is *only* done in the etcd tree located
under the *first* zone specified.
* `fallthrough` If zone matches but no record can be generated, pass request to the next plugin.
If **[ZONES...]** is omitted, then fallthrough happens for all zones for which the plugin
is authoritative. If specific zones are listed (for example `in-addr.arpa` and `ip6.arpa`), then only
queries for those zones will be subject to fallthrough.
* **PATH** the path inside etcd. Defaults to "/skydns".
* **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2379".
* `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 plugin. If no **ADDRESS** is given, CoreDNS will resolve CNAMEs against itself.
**ADDRESS** can be an IP address, and IP:port or a string pointing to a file that is structured
as /etc/resolv.conf.
* `upstream` resolve names found in etcd (think CNAMEs) If you want CoreDNS to act as a proxy for clients,
you'll need to add the forward plugin. CoreDNS will resolve CNAMEs against itself.
* `tls` followed by:
* no arguments, if the server certificate is signed by a system-installed CA and no client cert is needed
@ -79,10 +73,9 @@ This is the default SkyDNS setup, with everything specified in full:
~~~ corefile
. {
etcd skydns.local {
stubzones
path /skydns
endpoint http://localhost:2379
upstream 8.8.8.8:53 8.8.4.4:53
upstream
}
prometheus
cache 160 skydns.local
@ -98,7 +91,7 @@ when resolving external pointing CNAMEs.
. {
etcd skydns.local {
path /skydns
upstream /etc/resolv.conf
upstream
}
cache 160 skydns.local
proxy . /etc/resolv.conf
@ -125,7 +118,6 @@ need to add the zone `0.0.10.in-addr.arpa` to the list of zones. Showing a snipp
~~~
etcd skydns.local 10.0.0.0/24 {
stubzones
...
~~~

View file

@ -12,7 +12,6 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/request"
"github.com/coredns/coredns/plugin/pkg/upstream"
@ -35,10 +34,9 @@ type Etcd struct {
Fall fall.F
Zones []string
PathPrefix string
Upstream upstream.Upstream // Proxy for looking up names during the resolution process
Upstream *upstream.Upstream
Client *etcdcv3.Client
Ctx context.Context
Stubmap *map[string]proxy.Proxy // list of proxies for stub resolving.
endpoints []string // Stored here as well, to aid in testing.
}

View file

@ -14,20 +14,6 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
opt := plugin.Options{}
state := request.Request{W: w, Req: r, Context: ctx}
name := state.Name()
// We need to check stubzones first, because we may get a request for a zone we
// are not auth. for *but* do have a stubzone forward for. If we do the stubzone
// handler will handle the request.
if e.Stubmap != nil && len(*e.Stubmap) > 0 {
for zone := range *e.Stubmap {
if plugin.Name(zone).Matches(name) {
stub := Stub{Etcd: e, Zone: zone}
return stub.ServeDNS(ctx, w, r)
}
}
}
zone := plugin.Zones(e.Zones).Matches(state.Name())
if zone == "" {
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)

View file

@ -12,7 +12,6 @@ import (
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/tls"
"github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
@ -283,9 +282,8 @@ func newEtcdPlugin() *Etcd {
tlsc, _ := tls.NewTLSConfigFromArgs()
client, _ := newEtcdClient(endpoints, tlsc)
p := proxy.NewLookup([]string{"8.8.8.8:53"})
return &Etcd{
Upstream: upstream.Upstream{Forward: &p},
Upstream: upstream.New(),
PathPrefix: "skydns",
Ctx: context.Background(),
Zones: []string{"skydns.test.", "skydns_extra.test.", "skydns_zonea.test.", "skydns_zoneb.test.", "skydns_zonec.test.", "skydns_zoned.test.", "in-addr.arpa."},

View file

@ -9,7 +9,6 @@ import (
clog "github.com/coredns/coredns/plugin/pkg/log"
mwtls "github.com/coredns/coredns/plugin/pkg/tls"
"github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/coredns/coredns/plugin/proxy"
etcdcv3 "github.com/coreos/etcd/clientv3"
"github.com/mholt/caddy"
@ -25,18 +24,11 @@ func init() {
}
func setup(c *caddy.Controller) error {
e, stubzones, err := etcdParse(c)
e, err := etcdParse(c)
if err != nil {
return plugin.Error("etcd", err)
}
if stubzones {
c.OnStartup(func() error {
e.UpdateStubZones()
return nil
})
}
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
e.Next = next
return e
@ -45,20 +37,17 @@ func setup(c *caddy.Controller) error {
return nil
}
func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
stub := make(map[string]proxy.Proxy)
func etcdParse(c *caddy.Controller) (*Etcd, error) {
etc := Etcd{
// Don't default to a proxy for lookups.
// Proxy: proxy.NewLookup([]string{"8.8.8.8:53", "8.8.4.4:53"}),
PathPrefix: "skydns",
Ctx: context.Background(),
Stubmap: &stub,
}
var (
tlsConfig *tls.Config
err error
endpoints = []string{defaultEndpoint}
stubzones = false
)
for c.Next() {
etc.Zones = c.RemainingArgs()
@ -74,38 +63,35 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
for {
switch c.Val() {
case "stubzones":
stubzones = true
// ignored, remove later.
case "fallthrough":
etc.Fall.SetZonesFromArgs(c.RemainingArgs())
case "debug":
/* it is a noop now */
case "path":
if !c.NextArg() {
return &Etcd{}, false, c.ArgErr()
return &Etcd{}, c.ArgErr()
}
etc.PathPrefix = c.Val()
case "endpoint":
args := c.RemainingArgs()
if len(args) == 0 {
return &Etcd{}, false, c.ArgErr()
return &Etcd{}, c.ArgErr()
}
endpoints = args
case "upstream":
args := c.RemainingArgs()
u, err := upstream.New(args)
if err != nil {
return nil, false, err
}
etc.Upstream = u
// check args != 0 and error in the future
c.RemainingArgs() // clear buffer
etc.Upstream = upstream.New()
case "tls": // cert key cacertfile
args := c.RemainingArgs()
tlsConfig, err = mwtls.NewTLSConfigFromArgs(args...)
if err != nil {
return &Etcd{}, false, err
return &Etcd{}, err
}
default:
if c.Val() != "}" {
return &Etcd{}, false, c.Errf("unknown property '%s'", c.Val())
return &Etcd{}, c.Errf("unknown property '%s'", c.Val())
}
}
@ -117,14 +103,14 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
}
client, err := newEtcdClient(endpoints, tlsConfig)
if err != nil {
return &Etcd{}, false, err
return &Etcd{}, err
}
etc.Client = client
etc.endpoints = endpoints
return &etc, stubzones, nil
return &etc, nil
}
return &Etcd{}, false, nil
return &Etcd{}, nil
}
func newEtcdClient(endpoints []string, cc *tls.Config) (*etcdcv3.Client, error) {

View file

@ -56,7 +56,7 @@ func TestSetupEtcd(t *testing.T) {
for i, test := range tests {
c := caddy.NewTestController("dns", test.input)
etcd, _ /*stubzones*/, err := etcdParse(c)
etcd, err := etcdParse(c)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)

View file

@ -1,81 +0,0 @@
package etcd
import (
"net"
"strconv"
"time"
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// UpdateStubZones checks etcd for an update on the stubzones.
func (e *Etcd) UpdateStubZones() {
go func() {
for {
e.updateStubZones()
time.Sleep(15 * time.Second)
}
}()
}
// Look in .../dns/stub/<zone>/xx for msg.Services. Loop through them
// extract <zone> and add them as forwarders (ip:port-combos) for
// the stub zones. Only numeric (i.e. IP address) hosts are used.
// Only the first zone configured on e is used for the lookup.
func (e *Etcd) updateStubZones() {
zone := e.Zones[0]
fakeState := request.Request{W: nil, Req: new(dns.Msg)}
fakeState.Req.SetQuestion(stubDomain+"."+zone, dns.TypeA)
services, err := e.Records(fakeState, false)
if err != nil {
return
}
stubmap := make(map[string]proxy.Proxy)
// track the nameservers on a per domain basis, but allow a list on the domain.
nameservers := map[string][]string{}
Services:
for _, serv := range services {
if serv.Port == 0 {
serv.Port = 53
}
ip := net.ParseIP(serv.Host)
if ip == nil {
log.Warningf("Non IP address stub nameserver: %s", serv.Host)
continue
}
domain := msg.Domain(serv.Key)
labels := dns.SplitDomainName(domain)
// If the remaining name equals any of the zones we have, we ignore it.
for _, z := range e.Zones {
// Chop of left most label, because that is used as the nameserver place holder
// and drop the right most labels that belong to zone.
// We must *also* chop of dns.stub. which means cutting two more labels.
domain = dnsutil.Join(labels[1 : len(labels)-dns.CountLabel(z)-2]...)
if domain == z {
log.Warningf("Skipping nameserver for domain we are authoritative for: %s", domain)
continue Services
}
}
nameservers[domain] = append(nameservers[domain], net.JoinHostPort(serv.Host, strconv.Itoa(serv.Port)))
}
for domain, nss := range nameservers {
stubmap[domain] = proxy.NewLookup(nss)
}
// atomic swap (at least that's what we hope it is)
if len(stubmap) > 0 {
e.Stubmap = &stubmap
}
return
}

View file

@ -1,83 +0,0 @@
package etcd
import (
"context"
"errors"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// Stub wraps an Etcd. We have this type so that it can have a ServeDNS method.
type Stub struct {
*Etcd
Zone string // for what zone (and thus what nameservers are we called)
}
// ServeDNS implements the plugin.Handler interface.
func (s Stub) ServeDNS(ctx context.Context, w dns.ResponseWriter, req *dns.Msg) (int, error) {
if hasStubEdns0(req) {
log.Warningf("Forwarding cycle detected, refusing msg: %s", req.Question[0].Name)
return dns.RcodeRefused, errors.New("stub forward cycle")
}
req = addStubEdns0(req)
proxy, ok := (*s.Etcd.Stubmap)[s.Zone]
if !ok { // somebody made a mistake..
return dns.RcodeServerFailure, nil
}
state := request.Request{W: w, Req: req}
m, e := proxy.Forward(state)
if e != nil {
return dns.RcodeServerFailure, e
}
w.WriteMsg(m)
return dns.RcodeSuccess, nil
}
// hasStubEdns0 checks if the message is carrying our special edns0 zero option.
func hasStubEdns0(m *dns.Msg) bool {
option := m.IsEdns0()
if option == nil {
return false
}
for _, o := range option.Option {
if o.Option() == ednsStubCode && len(o.(*dns.EDNS0_LOCAL).Data) == 1 &&
o.(*dns.EDNS0_LOCAL).Data[0] == 1 {
return true
}
}
return false
}
// addStubEdns0 adds our special option to the message's OPT record.
func addStubEdns0(m *dns.Msg) *dns.Msg {
option := m.IsEdns0()
// Add a custom EDNS0 option to the packet, so we can detect loops when 2 stubs are forwarding to each other.
if option != nil {
option.Option = append(option.Option, &dns.EDNS0_LOCAL{Code: ednsStubCode, Data: []byte{1}})
return m
}
m.Extra = append(m.Extra, ednsStub)
return m
}
const (
ednsStubCode = dns.EDNS0LOCALSTART + 10
stubDomain = "stub.dns"
)
var ednsStub = func() *dns.OPT {
o := new(dns.OPT)
o.Hdr.Name = "."
o.Hdr.Rrtype = dns.TypeOPT
o.SetUDPSize(4096)
e := new(dns.EDNS0_LOCAL)
e.Code = ednsStubCode
e.Data = []byte{1}
o.Option = append(o.Option, e)
return o
}()

View file

@ -1,87 +0,0 @@
// +build etcd
package etcd
import (
"net"
"strconv"
"testing"
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
)
func fakeStubServerExampleNet(t *testing.T) (*dns.Server, string) {
server, addr, err := test.UDPServer("127.0.0.1:0")
if err != nil {
t.Fatalf("Failed to create a UDP server: %s", err)
}
// add handler for example.net
dns.HandleFunc("example.net.", func(w dns.ResponseWriter, r *dns.Msg) {
m := new(dns.Msg)
m.SetReply(r)
m.Answer = []dns.RR{test.A("example.net. 86400 IN A 93.184.216.34")}
w.WriteMsg(m)
})
return server, addr
}
func TestStubLookup(t *testing.T) {
server, addr := fakeStubServerExampleNet(t)
defer server.Shutdown()
host, p, _ := net.SplitHostPort(addr)
port, _ := strconv.Atoi(p)
exampleNetStub := &msg.Service{Host: host, Port: port, Key: "a.example.net.stub.dns.skydns.test."}
servicesStub = append(servicesStub, exampleNetStub)
etc := newEtcdPlugin()
for _, serv := range servicesStub {
set(t, etc, serv.Key, 0, serv)
defer delete(t, etc, serv.Key)
}
etc.updateStubZones()
for _, tc := range dnsTestCasesStub {
m := tc.Msg()
rec := dnstest.NewRecorder(&test.ResponseWriter{})
_, err := etc.ServeDNS(ctxt, rec, m)
if err != nil && m.Question[0].Name == "example.org." {
// This is OK, we expect this backend to *not* work.
continue
}
if err != nil {
t.Errorf("Expected no error, got %v for %s\n", err, m.Question[0].Name)
}
resp := rec.Msg
if resp == nil {
// etcd not running?
continue
}
test.SortAndCheck(t, resp, tc)
}
}
var servicesStub = []*msg.Service{
// Two tests, ask a question that should return servfail because remote it no accessible
// and one with edns0 option added, that should return refused.
{Host: "127.0.0.1", Port: 666, Key: "b.example.org.stub.dns.skydns.test."},
}
var dnsTestCasesStub = []test.Case{
{
Qname: "example.org.", Qtype: dns.TypeA, Rcode: dns.RcodeServerFailure,
},
{
Qname: "example.net.", Qtype: dns.TypeA,
Answer: []dns.RR{test.A("example.net. 86400 IN A 93.184.216.34")},
},
}

View file

@ -17,16 +17,14 @@ Enabling *federation* without also having *kubernetes* is a noop.
~~~
federation [ZONES...] {
NAME DOMAIN
upstream [ADDRESS...]
upstream
}
~~~
* Each **NAME** and **DOMAIN** defines federation membership. One entry for each. A duplicate
**NAME** will silently overwrite any previous value.
* `upstream` [**ADDRESS**...] defines the upstream resolvers used for resolving the `CNAME` target
produced by this plugin. If no **ADDRESS** is given, CoreDNS
will resolve External Services against itself. **ADDRESS** can be an IP, an IP:port, or a path
to a file structured like resolv.conf.
* `upstream` [**ADDRESS**...] resolve the `CNAME` target produced by this plugin. CoreDNS
will resolve External Services against itself.
## Examples

View file

@ -64,12 +64,8 @@ func federationParse(c *caddy.Controller) (*Federation, error) {
x := c.Val()
switch x {
case "upstream":
args := c.RemainingArgs()
u, err := upstream.New(args)
if err != nil {
return nil, err
}
fed.Upstream = &u
c.RemainingArgs()
fed.Upstream = upstream.New()
default:
args := c.RemainingArgs()
if x := len(args); x != 1 {

View file

@ -29,7 +29,7 @@ file DBFILE [ZONES... ] {
transfer to ADDRESS...
reload DURATION
no_reload
upstream [ADDRESS...]
upstream
}
~~~
@ -41,11 +41,9 @@ file DBFILE [ZONES... ] {
Value of `0` means to not scan for changes and reload. For example, `30s` checks the zonefile every 30 seconds
and reloads the zone when serial changes.
* `no_reload` deprecated. Sets reload to 0.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names. This is only really useful when CoreDNS is configured as a proxy; for
normal authoritative serving you don't need *or* want to use this. **ADDRESS** can be an IP
address, an IP:port or a string pointing to a file that is structured as /etc/resolv.conf.
If no **ADDRESS** is given, CoreDNS will resolve CNAMEs against itself.
* `upstream` resolve external names found (think CNAMEs) pointing to external names. This is only
really useful when CoreDNS is configured as a proxy; for normal authoritative serving you don't
need *or* want to use this. CoreDNS will resolve CNAMEs against itself.
## Examples

View file

@ -1,124 +0,0 @@
package file
import (
"context"
"strings"
"testing"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
)
func TestLookupCNAMEChain(t *testing.T) {
name := "example.org."
zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin", 0)
if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err)
}
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}}
ctx := context.TODO()
for _, tc := range cnameTestCases {
m := tc.Msg()
rec := dnstest.NewRecorder(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
var cnameTestCases = []test.Case{
{
Qname: "a.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("a.example.org. 1800 IN A 127.0.0.1"),
},
},
{
Qname: "www3.example.org.", Qtype: dns.TypeCNAME,
Answer: []dns.RR{
test.CNAME("www3.example.org. 1800 IN CNAME www2.example.org."),
},
},
{
Qname: "dangling.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.CNAME("dangling.example.org. 1800 IN CNAME foo.example.org."),
},
},
{
Qname: "www3.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.A("a.example.org. 1800 IN A 127.0.0.1"),
test.CNAME("www.example.org. 1800 IN CNAME a.example.org."),
test.CNAME("www1.example.org. 1800 IN CNAME www.example.org."),
test.CNAME("www2.example.org. 1800 IN CNAME www1.example.org."),
test.CNAME("www3.example.org. 1800 IN CNAME www2.example.org."),
},
},
}
func TestLookupCNAMEExternal(t *testing.T) {
name := "example.org."
zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin", 0)
if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err)
}
zone.Upstream, _ = upstream.New([]string{"8.8.8.8:53"}) // TODO(miek): point to local instance
fm := File{Next: test.ErrorHandler(), Zones: Zones{Z: map[string]*Zone{name: zone}, Names: []string{name}}}
ctx := context.TODO()
for _, tc := range exernalTestCases {
m := tc.Msg()
rec := dnstest.NewRecorder(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
test.SortAndCheck(t, resp, tc)
}
}
var exernalTestCases = []test.Case{
{
Qname: "external.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.CNAME("external.example.org. 1800 CNAME www.example.net."),
// magic 303 TTL that says: don't check TTL.
test.A("www.example.net. 303 IN A 93.184.216.34"),
},
},
}
const dbExampleCNAME = `
$TTL 30M
$ORIGIN example.org.
@ IN SOA linode.atoom.net. miek.miek.nl. (
1282630057 ; Serial
4H ; Refresh
1H ; Retry
7D ; Expire
4H ) ; Negative Cache TTL
a IN A 127.0.0.1
www3 IN CNAME www2
www2 IN CNAME www1
www1 IN CNAME www
www IN CNAME a
dangling IN CNAME foo
external IN CNAME www.example.net.`

View file

@ -1,5 +1,8 @@
package file
/*
TODO(miek): move to test/ for full server testing
import (
"context"
"strings"
@ -294,3 +297,4 @@ ns.example.org. 1800 IN A 127.0.0.1
RXpMdvaE6ZDwalWldLjC3h8QDywDoFdndoRY
eHOsmTvvtWWqtO6Fa5A8gmHT5HA= )
`
*/

View file

@ -374,7 +374,6 @@ func cnameForType(targets []dns.RR, origQtype uint16) []dns.RR {
func (z *Zone) externalLookup(state request.Request, target string, qtype uint16) []dns.RR {
m, e := z.Upstream.Lookup(state, target, qtype)
if e != nil {
// TODO(miek): Log, or return error here?
return nil
}
if m == nil {

View file

@ -93,7 +93,7 @@ func fileParse(c *caddy.Controller) (Zones, error) {
}
reload := 1 * time.Minute
upstr := upstream.Upstream{}
upstr := upstream.New()
t := []string{}
var e error
@ -116,11 +116,8 @@ func fileParse(c *caddy.Controller) (Zones, error) {
reload = 0
case "upstream":
args := c.RemainingArgs()
upstr, err = upstream.New(args)
if err != nil {
return Zones{}, err
}
// ignore args, will be error later.
c.RemainingArgs() // clear buffer
default:
return Zones{}, c.Errf("unknown property '%s'", c.Val())

View file

@ -57,8 +57,8 @@ func TestFileParse(t *testing.T) {
`file ` + zoneFileName1 + ` example.net. {
upstream a
}`,
true,
Zones{Names: []string{}},
false, // OK for now as we disregard any options for the `upstream`.
Zones{Names: []string{"example.net."}},
},
{
`file ` + zoneFileName1 + ` example.net. {

View file

@ -32,7 +32,7 @@ type Zone struct {
LastReloaded time.Time
reloadMu sync.RWMutex
reloadShutdown chan bool
Upstream upstream.Upstream // Upstream for looking up names during the resolution process
Upstream *upstream.Upstream // Upstream for looking up external names during the resolution process
}
// Apex contains the apex records of a zone: SOA, NS and their potential signatures.

View file

@ -1,69 +0,0 @@
package forward
import (
"testing"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
func TestForward(t *testing.T) {
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
ret := new(dns.Msg)
ret.SetReply(r)
ret.Answer = append(ret.Answer, test.A("example.org. IN A 127.0.0.1"))
w.WriteMsg(ret)
})
defer s.Close()
p := NewProxy(s.Addr, transport.DNS)
f := New()
f.SetProxy(p)
defer f.Close()
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
state.Req.SetQuestion("example.org.", dns.TypeA)
resp, err := f.Forward(state)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
// expect answer section with A record in it
if len(resp.Answer) == 0 {
t.Fatalf("Expected to at least one RR in the answer section, got none: %s", resp)
}
if resp.Answer[0].Header().Rrtype != dns.TypeA {
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.Errorf("Expected 127.0.0.1, got: %s", resp.Answer[0].(*dns.A).A.String())
}
}
func TestForwardRefused(t *testing.T) {
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
ret := new(dns.Msg)
ret.SetReply(r)
ret.Rcode = dns.RcodeRefused
w.WriteMsg(ret)
})
defer s.Close()
p := NewProxy(s.Addr, transport.DNS)
f := New()
f.SetProxy(p)
defer f.Close()
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
state.Req.SetQuestion("example.org.", dns.TypeA)
resp, err := f.Forward(state)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
if resp.Rcode != dns.RcodeRefused {
t.Errorf("Expected rcode to be %d, got %d", dns.RcodeRefused, resp.Rcode)
}
}

View file

@ -1,89 +0,0 @@
// Package forward implements a forwarding proxy. It caches an upstream net.Conn for some time, so if the same
// client returns the upstream's Conn will be precached. Depending on how you benchmark this looks to be
// 50% faster than just opening a new connection for every client. It works with UDP and TCP and uses
// inband healthchecking.
package forward
import (
"context"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// Forward forward the request in state as-is. Unlike Lookup that adds EDNS0 suffix to the message.
// Forward may be called with a nil f, an error is returned in that case.
func (f *Forward) Forward(state request.Request) (*dns.Msg, error) {
if f == nil {
return nil, ErrNoForward
}
fails := 0
var upstreamErr error
for _, proxy := range f.List() {
if proxy.Down(f.maxfails) {
fails++
if fails < len(f.proxies) {
continue
}
// All upstream proxies are dead, assume healtcheck is complete broken and randomly
// select an upstream to connect to.
proxy = f.List()[0]
}
ret, err := proxy.Connect(context.Background(), state, f.opts)
upstreamErr = err
if err != nil {
if fails < len(f.proxies) {
continue
}
break
}
// Check if the reply is correct; if not return FormErr.
if !state.Match(ret) {
return state.ErrorMessage(dns.RcodeFormatError), nil
}
ret = state.Scrub(ret)
return ret, err
}
if upstreamErr != nil {
return nil, upstreamErr
}
return nil, ErrNoHealthy
}
// Lookup will use name and type to forge a new message and will send that upstream. It will
// set any EDNS0 options correctly so that downstream will be able to process the reply.
// Lookup may be called with a nil f, an error is returned in that case.
func (f *Forward) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) {
if f == nil {
return nil, ErrNoForward
}
req := new(dns.Msg)
req.SetQuestion(name, typ)
state.SizeAndDo(req)
state2 := request.Request{W: state.W, Req: req}
return f.Forward(state2)
}
// NewLookup returns a Forward that can be used for plugin that need an upstream to resolve external names.
// Note that the caller MUST run Close on the forward to stop the health checking goroutines.
func NewLookup(addr []string) *Forward {
f := New()
for i := range addr {
p := NewProxy(addr[i], transport.DNS)
f.SetProxy(p)
}
return f
}

View file

@ -1,43 +0,0 @@
package forward
import (
"testing"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
func TestLookup(t *testing.T) {
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
ret := new(dns.Msg)
ret.SetReply(r)
ret.Answer = append(ret.Answer, test.A("example.org. IN A 127.0.0.1"))
w.WriteMsg(ret)
})
defer s.Close()
p := NewProxy(s.Addr, transport.DNS)
f := New()
f.SetProxy(p)
defer f.Close()
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := f.Lookup(state, "example.org.", dns.TypeA)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
// expect answer section with A record in it
if len(resp.Answer) == 0 {
t.Fatalf("Expected to at least one RR in the answer section, got none: %s", resp)
}
if resp.Answer[0].Header().Rrtype != dns.TypeA {
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.Errorf("Expected 127.0.0.1, got: %s", resp.Answer[0].(*dns.A).A.String())
}
}

View file

@ -32,7 +32,7 @@ import (
type Kubernetes struct {
Next plugin.Handler
Zones []string
Upstream upstream.Upstream
Upstream *upstream.Upstream
APIServerList []string
APIProxy *apiProxy
APICertAuth string

View file

@ -241,12 +241,8 @@ func ParseStanza(c *caddy.Controller) (*Kubernetes, error) {
case "fallthrough":
k8s.Fall.SetZonesFromArgs(c.RemainingArgs())
case "upstream":
args := c.RemainingArgs()
u, err := upstream.New(args)
if err != nil {
return nil, err
}
k8s.Upstream = u
c.RemainingArgs() // eat remaining args
k8s.Upstream = upstream.New()
case "ttl":
args := c.RemainingArgs()
if len(args) == 0 {

View file

@ -7,7 +7,6 @@ import (
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/plugin/proxy"
"github.com/mholt/caddy"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -23,7 +22,6 @@ func TestKubernetesParse(t *testing.T) {
expectedLabelSelector string // expected label selector value
expectedPodMode string
expectedFallthrough fall.F
expectedUpstreams []string
}{
// positive
{
@ -36,7 +34,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local test.local`,
@ -48,7 +45,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -61,7 +57,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -75,7 +70,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -89,7 +83,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -103,7 +96,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -117,7 +109,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -131,7 +122,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -145,7 +135,6 @@ func TestKubernetesParse(t *testing.T) {
"environment=prod",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -159,7 +148,6 @@ func TestKubernetesParse(t *testing.T) {
"application=nginx,environment in (production,qa,staging)",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local test.local {
@ -177,7 +165,6 @@ func TestKubernetesParse(t *testing.T) {
"application=nginx,environment in (production,qa,staging)",
podModeDisabled,
fall.Root,
nil,
},
// negative
{
@ -192,7 +179,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -206,7 +192,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -220,7 +205,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -234,7 +218,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -248,7 +231,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -262,7 +244,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -276,7 +257,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
// pods disabled
{
@ -291,7 +271,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
nil,
},
// pods insecure
{
@ -306,7 +285,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeInsecure,
fall.Zero,
nil,
},
// pods verified
{
@ -321,7 +299,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeVerified,
fall.Zero,
nil,
},
// pods invalid
{
@ -336,7 +313,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeVerified,
fall.Zero,
nil,
},
// fallthrough with zones
{
@ -351,12 +327,11 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.F{Zones: []string{"ip6.arpa.", "inaddr.arpa.", "foo.com."}},
nil,
},
// Valid upstream
{
`kubernetes coredns.local {
upstream 13.14.15.16:53
upstream
}`,
false,
"",
@ -366,22 +341,6 @@ func TestKubernetesParse(t *testing.T) {
"",
podModeDisabled,
fall.Zero,
[]string{"13.14.15.16:53"},
},
// Invalid upstream
{
`kubernetes coredns.local {
upstream 13.14.15.16orange
}`,
true,
"not an IP address or file: \"13.14.15.16orange\"",
-1,
0,
defaultResyncPeriod,
"",
podModeDisabled,
fall.Zero,
nil,
},
// More than one Kubernetes not allowed
{
@ -395,7 +354,6 @@ kubernetes cluster.local`,
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -409,7 +367,6 @@ kubernetes cluster.local`,
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -423,7 +380,6 @@ kubernetes cluster.local`,
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -437,7 +393,6 @@ kubernetes cluster.local`,
"",
podModeDisabled,
fall.Zero,
nil,
},
{
`kubernetes coredns.local {
@ -519,31 +474,6 @@ kubernetes cluster.local`,
if !k8sController.Fall.Equal(test.expectedFallthrough) {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with fallthrough '%v'. Instead found fallthrough '%v' for input '%s'", i, test.expectedFallthrough, k8sController.Fall, test.input)
}
// upstream
var foundUpstreams *[]proxy.Upstream
if k8sController.Upstream.Forward != nil {
foundUpstreams = k8sController.Upstream.Forward.Upstreams
}
if test.expectedUpstreams == nil {
if foundUpstreams != nil {
t.Errorf("Test %d: Expected kubernetes controller to not be initialized with upstreams for input '%s'", i, test.input)
}
} else {
if foundUpstreams == nil {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with upstreams for input '%s'", i, test.input)
} else {
if len(*foundUpstreams) != len(test.expectedUpstreams) {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d upstreams. Instead found %d upstreams for input '%s'", i, len(test.expectedUpstreams), len(*foundUpstreams), test.input)
}
for j, want := range test.expectedUpstreams {
got := (*foundUpstreams)[j].Select().Name
if got != want {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with upstream '%s'. Instead found upstream '%s' for input '%s'", i, want, got, test.input)
}
}
}
}
}
}

View file

@ -1,58 +1,35 @@
// Package upstream abstracts a upstream lookups so that plugins
// can handle them in an unified way.
// Package upstream abstracts a upstream lookups so that plugins can handle them in an unified way.
package upstream
import (
"fmt"
"github.com/miekg/dns"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/plugin/pkg/nonwriter"
"github.com/coredns/coredns/plugin/pkg/parse"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/request"
)
// Upstream is used to resolve CNAME targets
type Upstream struct {
self bool
Forward *proxy.Proxy
}
// Upstream is used to resolve CNAME or other external targets via CoreDNS itself.
type Upstream struct{}
// New creates a new Upstream for given destination(s). If dests is empty it default to upstreaming to
// the coredns process.
func New(dests []string) (Upstream, error) {
u := Upstream{}
if len(dests) == 0 {
u.self = true
return u, nil
}
u.self = false
ups, err := parse.HostPortOrFile(dests...)
if err != nil {
return u, err
}
p := proxy.NewLookup(ups)
u.Forward = &p
return u, nil
}
// New creates a new Upstream to resolve names using the the coredns process.
func New() *Upstream { return &Upstream{} }
// Lookup routes lookups to our selves or forward to a remote.
func (u Upstream) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) {
if u.self {
func (u *Upstream) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) {
server, ok := state.Context.Value(dnsserver.Key{}).(*dnsserver.Server)
if !ok {
return nil, fmt.Errorf("no full server is running")
}
req := new(dns.Msg)
req.SetQuestion(name, typ)
nw := nonwriter.New(state.W)
server := state.Context.Value(dnsserver.Key{}).(*dnsserver.Server)
server.ServeDNS(state.Context, nw, req)
return nw.Msg, nil
}
if u.Forward != nil {
return u.Forward.Lookup(state, name, typ)
}
return nil, nil
}

View file

@ -1,60 +0,0 @@
package proxy
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/mholt/caddy/caddyfile"
"github.com/miekg/dns"
)
func TestUnhealthy(t *testing.T) {
// High HC interval, we want to test the HC after failed queries.
config := "proxy . %s {\n health_check /healthcheck:%s 10s \nfail_timeout 100ms\n}"
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Body.Close()
w.Write([]byte("OK"))
}))
defer backend.Close()
port := backend.URL[17:] // Remove all crap up to the port
back := backend.URL[7:] // Remove http://
c := caddyfile.NewDispenser("testfile", strings.NewReader(fmt.Sprintf(config, back, port)))
upstreams, err := NewStaticUpstreams(&c)
if err != nil {
t.Errorf("Expected no error. Got: %s", err)
}
p := &Proxy{Upstreams: &upstreams}
m := new(dns.Msg)
m.SetQuestion("example.org.", dns.TypeA)
state := request.Request{W: &test.ResponseWriter{}, Req: m}
// Should all fail.
for j := 0; j < failureCheck; j++ {
if _, err := p.Forward(state); err == nil {
t.Errorf("Expected error. Got: nil")
}
}
fails := atomic.LoadInt32(&upstreams[0].(*staticUpstream).Hosts[0].Fails)
if fails != 3 {
t.Errorf("Expected %d fails, got %d", 3, fails)
}
// HC should be kicked off, and reset the counter to 0
i := 0
for fails != 0 {
fails = atomic.LoadInt32(&upstreams[0].(*staticUpstream).Hosts[0].Fails)
time.Sleep(100 * time.Microsecond)
i++
}
}

View file

@ -1,127 +0,0 @@
package proxy
// functions other plugin might want to use to do lookup in the same style as the proxy.
import (
"context"
"fmt"
"net"
"sync/atomic"
"time"
"github.com/coredns/coredns/plugin/pkg/healthcheck"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
// NewLookup create a new proxy with the hosts in host and a Random policy.
func NewLookup(hosts []string) Proxy { return NewLookupWithOption(hosts, Options{}) }
// NewLookupWithOption process creates a simple round robin forward with potentially forced proto for upstream.
func NewLookupWithOption(hosts []string, opts Options) Proxy {
p := Proxy{Next: nil}
// TODO(miek): this needs to be unified with upstream.go's NewStaticUpstreams, caddy uses NewHost
// we should copy/make something similar.
upstream := &staticUpstream{
from: ".",
HealthCheck: healthcheck.HealthCheck{
FailTimeout: 5 * time.Second,
MaxFails: 3,
},
ex: newDNSExWithOption(opts),
}
upstream.Hosts = make([]*healthcheck.UpstreamHost, len(hosts))
for i, host := range hosts {
uh := &healthcheck.UpstreamHost{
Name: host,
FailTimeout: upstream.FailTimeout,
CheckDown: checkDownFunc(upstream),
}
upstream.Hosts[i] = uh
}
p.Upstreams = &[]Upstream{upstream}
return p
}
// Lookup will use name and type to forge a new message and will send that upstream. It will
// set any EDNS0 options correctly so that downstream will be able to process the reply.
func (p Proxy) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) {
req := new(dns.Msg)
req.SetQuestion(name, typ)
state.SizeAndDo(req)
state2 := request.Request{W: state.W, Req: req}
return p.lookup(state2)
}
// Forward forward the request in state as-is. Unlike Lookup that adds EDNS0 suffix to the message.
func (p Proxy) Forward(state request.Request) (*dns.Msg, error) {
return p.lookup(state)
}
func (p Proxy) lookup(state request.Request) (*dns.Msg, error) {
upstream := p.match(state)
if upstream == nil {
return nil, errInvalidDomain
}
for {
start := time.Now()
var reply *dns.Msg
var backendErr error
// Since Select() should give us "up" hosts, keep retrying
// hosts until timeout (or until we get a nil host).
for time.Since(start) < tryDuration {
host := upstream.Select()
if host == nil {
return nil, fmt.Errorf("%s: %s", errUnreachable, "no upstream host")
}
// duplicated from proxy.go, but with a twist, we don't write the
// reply back to the client, we return it and there is no monitoring to update here.
atomic.AddInt64(&host.Conns, 1)
reply, backendErr = upstream.Exchanger().Exchange(context.TODO(), host.Name, state)
atomic.AddInt64(&host.Conns, -1)
if backendErr == nil {
if !state.Match(reply) {
return state.ErrorMessage(dns.RcodeFormatError), nil
}
return reply, nil
}
if oe, ok := backendErr.(*net.OpError); ok {
if oe.Timeout() { // see proxy.go for docs.
continue
}
}
timeout := host.FailTimeout
if timeout == 0 {
timeout = defaultFailTimeout
}
atomic.AddInt32(&host.Fails, 1)
fails := atomic.LoadInt32(&host.Fails)
go func(host *healthcheck.UpstreamHost, timeout time.Duration) {
time.Sleep(timeout)
atomic.AddInt32(&host.Fails, -1)
if fails%failureCheck == 0 { // Kick off healthcheck on eveyry third failure.
host.HealthCheckURL()
}
}(host, timeout)
}
return nil, fmt.Errorf("%s: %s", errUnreachable, backendErr)
}
}

View file

@ -9,12 +9,7 @@ import (
"testing"
"time"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/mholt/caddy/caddyfile"
"github.com/miekg/dns"
)
func TestStop(t *testing.T) {
@ -75,25 +70,3 @@ func TestStop(t *testing.T) {
})
}
}
func TestProxyRefused(t *testing.T) {
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
ret := new(dns.Msg)
ret.SetReply(r)
ret.Rcode = dns.RcodeRefused
w.WriteMsg(ret)
})
defer s.Close()
p := NewLookup([]string{s.Addr})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
state.Req.SetQuestion("example.org.", dns.TypeA)
resp, err := p.Forward(state)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
if resp.Rcode != dns.RcodeRefused {
t.Errorf("Expected rcode to be %d, got %d", dns.RcodeRefused, resp.Rcode)
}
}

View file

@ -6,8 +6,9 @@
## Description
The route53 plugin is useful for serving zones from resource record sets in AWS route53. This plugin
supports all Amazon Route 53 records (https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/ResourceRecordTypes.html).
The route53 plugin is useful for serving zones from resource record
sets in AWS route53. This plugin supports all Amazon Route 53 records
([https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/ResourceRecordTypes.html](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/ResourceRecordTypes.html)).
The route53 plugin can be used when coredns is deployed on AWS or elsewhere.
## Syntax
@ -15,32 +16,39 @@ The route53 plugin can be used when coredns is deployed on AWS or elsewhere.
~~~ txt
route53 [ZONE:HOSTED_ZONE_ID...] {
[aws_access_key AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY]
upstream [ADDRESS...]
upstream
credentials PROFILE [FILENAME]
fallthrough [ZONES...]
}
~~~
* **ZONE** the name of the domain to be accessed. When there are multiple zones with overlapping domains
(private vs. public hosted zone), CoreDNS does the lookup in the given order here. Therefore, for a
non-existing resource record, SOA response will be from the rightmost zone.
* **HOSTED_ZONE_ID** the ID of the hosted zone that contains the resource record sets to be accessed.
* **AWS_ACCESS_KEY_ID** and **AWS_SECRET_ACCESS_KEY** the AWS access key ID and secret access key
* **ZONE** the name of the domain to be accessed. When there are multiple zones with overlapping
domains (private vs. public hosted zone), CoreDNS does the lookup in the given order here.
Therefore, for a non-existing resource record, SOA response will be from the rightmost zone.
* **HOSTED*ZONE*ID** the ID of the hosted zone that contains the resource record sets to be
accessed.
* **AWS*ACCESS*KEY_ID** and **AWS*SECRET*ACCESS_KEY** the AWS access key ID and secret access key
to be used when query AWS (optional). If they are not provided, then coredns tries to access
AWS credentials the same way as AWS CLI, e.g., environmental variables, AWS credentials file,
instance profile credentials, etc.
* `upstream` [**ADDRESS**...] specifies upstream resolver(s) used for resolving services that point
to external hosts (eg. used to resolve CNAMEs). If no **ADDRESS** is given, CoreDNS will resolve
against itself. **ADDRESS** can be an IP, an IP:port or a path to a file structured like
resolv.conf.
* `credentials` used for reading the credential file and setting the profile name for a given zone.
* `upstream`is used for resolving services that point to external hosts (eg. used to resolve
CNAMEs). CoreDNS will resolve against itself.
* `credentials` used for reading the credential file and setting the profile name for a given
zone.
* **PROFILE** AWS account profile name. Defaults to `default`.
* **FILENAME** AWS credentials filename. Defaults to `~/.aws/credentials`
are used.
* **FILENAME** AWS credentials filename. Defaults to `~/.aws/credentials` are used.
* `fallthrough` If zone matches and no record can be generated, pass request to the next plugin.
If **[ZONES...]** is omitted, then fallthrough happens for all zones for which the plugin
is authoritative. If specific zones are listed (for example `in-addr.arpa` and `ip6.arpa`), then only
queries for those zones will be subject to fallthrough.
If **[ZONES...]** is omitted, then fallthrough happens for all zones for which the plugin is
authoritative. If specific zones are listed (for example `in-addr.arpa` and `ip6.arpa`), then
only queries for those zones will be subject to fallthrough.
* **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block
## Examples

View file

@ -170,7 +170,7 @@ func (h *Route53) updateZones(ctx context.Context) error {
for i, hostedZone := range z {
newZ := file.NewZone(zName, "")
newZ.Upstream = *h.upstream
newZ.Upstream = h.upstream
in := &route53.ListResourceRecordSetsInput{
HostedZoneId: aws.String(hostedZone.id),
}

View file

@ -48,7 +48,7 @@ func setup(c *caddy.Controller, f func(*credentials.Credentials) route53iface.Ro
var providers []credentials.Provider
var fall fall.F
up, _ := upstream.New(nil)
up := upstream.New()
for c.Next() {
args := c.RemainingArgs()
@ -83,12 +83,7 @@ func setup(c *caddy.Controller, f func(*credentials.Credentials) route53iface.Ro
},
})
case "upstream":
args := c.RemainingArgs()
var err error
up, err = upstream.New(args)
if err != nil {
return c.Errf("invalid upstream: %v", err)
}
c.RemainingArgs() // eats args
case "credentials":
if c.NextArg() {
sharedProvider.Profile = c.Val()
@ -109,7 +104,7 @@ func setup(c *caddy.Controller, f func(*credentials.Credentials) route53iface.Ro
client := f(credentials.NewChainCredentials(providers))
ctx := context.Background()
h, err := New(ctx, client, keys, &up)
h, err := New(ctx, client, keys, up)
if err != nil {
return c.Errf("failed to create Route53 plugin: %v", err)
}

View file

@ -23,18 +23,16 @@ A working syntax would be:
secondary [zones...] {
transfer from ADDRESS
transfer to ADDRESS
upstream [ADDRESS...]
upstream
}
~~~
* `transfer from` specifies from which address to fetch the zone. It can be specified multiple times;
if one does not work, another will be tried.
* `transfer to` can be enabled to allow this secondary zone to be transferred again.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names. This is only really useful when CoreDNS is configured as a proxy, for
normal authoritative serving you don't need *or* want to use this. **ADDRESS** can be an IP
address, and IP:port or a string pointing to a file that is structured as /etc/resolv.conf.
If no **ADDRESS** is given, CoreDNS will resolve CNAMEs against itself.
* `upstream` resolve external names found (think CNAMEs) pointing to external names. This is only
really useful when CoreDNS is configured as a proxy; for normal authoritative serving you don't
need *or* want to use this. CoreDNS will resolve CNAMEs against itself.
When a zone is due to be refreshed (Refresh timer fires) a random jitter of 5 seconds is
applied, before fetching. In the case of retry this will be 2 seconds. If there are any errors

View file

@ -49,7 +49,7 @@ func setup(c *caddy.Controller) error {
func secondaryParse(c *caddy.Controller) (file.Zones, error) {
z := make(map[string]*file.Zone)
names := []string{}
upstr := upstream.Upstream{}
upstr := upstream.New()
for c.Next() {
if c.Val() == "secondary" {
@ -78,12 +78,7 @@ func secondaryParse(c *caddy.Controller) (file.Zones, error) {
return file.Zones{}, e
}
case "upstream":
args := c.RemainingArgs()
var err error
upstr, err = upstream.New(args)
if err != nil {
return file.Zones{}, err
}
c.RemainingArgs() // eat args
default:
return file.Zones{}, c.Errf("unknown property '%s'", c.Val())
}

View file

@ -12,14 +12,13 @@ The *template* plugin allows you to dynamically respond to queries by just writi
~~~
template CLASS TYPE [ZONE...] {
[match REGEX...]
[answer RR]
[additional RR]
[authority RR]
[...]
[rcode CODE]
[upstream [ADDRESS...]]
[fallthrough [ZONE...]]
match REGEX...
answer RR
additional RR
authority RR
rcode CODE
upstream
fallthrough [ZONE...]
}
~~~
@ -30,9 +29,7 @@ template CLASS TYPE [ZONE...] {
* `answer|additional|authority` **RR** A [RFC 1035](https://tools.ietf.org/html/rfc1035#section-5) style resource record fragment
built by a [Go template](https://golang.org/pkg/text/template/) that contains the reply.
* `rcode` **CODE** A response code (`NXDOMAIN, SERVFAIL, ...`). The default is `SUCCESS`.
* `upstream` [**ADDRESS**...] defines the upstream resolvers used for resolving CNAME.
If no **ADDRESS** is given, CoreDNS will resolve CNAMEs against itself. **ADDRESS**
can be an IP, an IP:port, or a path to a file structured like resolv.conf.
* `upstream` defines the upstream resolvers used for resolving CNAMEs. CoreDNS will resolve CNAMEs against itself.
* `fallthrough` Continue with the next plugin if the zone matched but no regex matched.
If specific zones are listed (for example `in-addr.arpa` and `ip6.arpa`), then only queries for
those zones will be subject to fallthrough.

View file

@ -144,12 +144,8 @@ func templateParse(c *caddy.Controller) (handler Handler, err error) {
t.fall.SetZonesFromArgs(c.RemainingArgs())
case "upstream":
args := c.RemainingArgs()
u, err := upstream.New(args)
if err != nil {
return handler, err
}
t.upstream = &u
c.RemainingArgs() // eat remaining args
t.upstream = upstream.New()
default:
return handler, c.ArgErr()
}

View file

@ -148,13 +148,6 @@ func TestSetupParse(t *testing.T) {
}`,
false,
},
{
`template ANY ANY up.stream.local {
answer "up.stream.local 5 IN CNAME up.river.local"
upstream invalid-upstream-argument
}`,
true,
},
}
for i, test := range tests {
c := caddy.NewTestController("dns", test.inputFileRules)

View file

@ -7,10 +7,6 @@ import (
"testing"
"time"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -34,10 +30,9 @@ func TestAuto(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "www.example.org.", dns.TypeA)
m := new(dns.Msg)
m.SetQuestion("www.example.org.", dns.TypeA)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
@ -52,7 +47,7 @@ func TestAuto(t *testing.T) {
time.Sleep(1500 * time.Millisecond) // wait for it to be picked up
resp, err = p.Lookup(state, "www.example.org.", dns.TypeA)
resp, err = dns.Exchange(m, udp)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
@ -64,7 +59,7 @@ func TestAuto(t *testing.T) {
os.Remove(filepath.Join(tmpdir, "db.example.org"))
time.Sleep(1100 * time.Millisecond) // wait for it to be picked up
resp, err = p.Lookup(state, "www.example.org.", dns.TypeA)
resp, err = dns.Exchange(m, udp)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
@ -99,10 +94,9 @@ func TestAutoNonExistentZone(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
m := new(dns.Msg)
m.SetQuestion("example.org.", dns.TypeA)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
@ -145,12 +139,9 @@ func TestAutoAXFR(t *testing.T) {
time.Sleep(1100 * time.Millisecond) // wait for it to be picked up
p := proxy.NewLookup([]string{udp})
m := new(dns.Msg)
m.SetAxfr("example.org.")
state := request.Request{W: &test.ResponseWriter{}, Req: m}
resp, err := p.Lookup(state, "example.org.", dns.TypeAXFR)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}

View file

@ -3,9 +3,7 @@ package test
import (
"testing"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -40,21 +38,20 @@ func TestLookupCache(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
t.Run("Long TTL", func(t *testing.T) {
testCase(t, state, p, "example.org.", 2, 10)
testCase(t, "example.org.", udp, 2, 10)
})
t.Run("Short TTL", func(t *testing.T) {
testCase(t, state, p, "short.example.org.", 1, 5)
testCase(t, "short.example.org.", udp, 1, 5)
})
}
func testCase(t *testing.T, state request.Request, p proxy.Proxy, name string, expectAnsLen int, expectTTL uint32) {
resp, err := p.Lookup(state, name, dns.TypeA)
func testCase(t *testing.T, name, addr string, expectAnsLen int, expectTTL uint32) {
m := new(dns.Msg)
m.SetQuestion(name, dns.TypeA)
resp, err := dns.Exchange(m, addr)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}

View file

@ -3,9 +3,7 @@ package test
import (
"testing"
"github.com/coredns/coredns/plugin/proxy"
mtest "github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -47,11 +45,10 @@ func TestLookupDS(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &mtest.ResponseWriter{}, Req: new(dns.Msg)}
for _, tc := range dsTestCases {
resp, err := p.Lookup(state, tc.Qname, tc.Qtype)
m := new(dns.Msg)
m.SetQuestion(tc.Qname, tc.Qtype)
resp, err := dns.Exchange(m, udp)
if err != nil || resp == nil {
t.Fatalf("Expected to receive reply, but didn't for %s %d", tc.Qname, tc.Qtype)
}

View file

@ -7,9 +7,6 @@ import (
"testing"
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -38,16 +35,15 @@ func TestEtcdCache(t *testing.T) {
defer delete(ctx, t, etc, serv.Key)
}
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "b.example.skydns.test.", dns.TypeA)
m := new(dns.Msg)
m.SetQuestion("b.example.skydns.test.", dns.TypeA)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Errorf("Expected to receive reply, but didn't: %s", err)
}
checkResponse(t, resp)
resp, err = p.Lookup(state, "b.example.skydns.test.", dns.TypeA)
resp, err = dns.Exchange(m, udp)
if err != nil {
t.Errorf("Expected to receive reply, but didn't: %s", err)
}

View file

@ -10,9 +10,6 @@ import (
"github.com/coredns/coredns/plugin/etcd"
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
etcdcv3 "github.com/coreos/etcd/clientv3"
"github.com/miekg/dns"
@ -38,7 +35,7 @@ func TestEtcdStubAndProxyLookup(t *testing.T) {
stubzones
path /skydns
endpoint http://localhost:2379
upstream 8.8.8.8:53 8.8.4.4:53
upstream
fallthrough
}
proxy . 8.8.8.8:53
@ -58,9 +55,9 @@ func TestEtcdStubAndProxyLookup(t *testing.T) {
defer delete(ctx, t, etc, serv.Key)
}
p := proxy.NewLookup([]string{udp}) // use udp port from the server
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "example.com.", dns.TypeA)
m := new(dns.Msg)
m.SetQuestion("example.com.", dns.TypeA)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatalf("Expected to receive reply, but didn't: %v", err)
}

View file

@ -11,6 +11,5 @@ short.example.org. 1 IN A 127.0.0.3
*.w.example.org. IN TXT "Wildcard"
a.b.c.w.example.org. IN TXT "Not a wildcard"
cname.example.org. IN CNAME www.example.net.
service.example.org. IN SRV 8080 10 10 example.org.
`

View file

@ -3,10 +3,6 @@ package test
import (
"testing"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -30,10 +26,9 @@ func TestZoneExternalCNAMELookupWithoutProxy(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "cname.example.org.", dns.TypeA)
m := new(dns.Msg)
m.SetQuestion("cname.example.org.", dns.TypeA)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatalf("Expected to receive reply, but didn't: %s", err)
}
@ -52,11 +47,12 @@ func TestZoneExternalCNAMELookupWithProxy(t *testing.T) {
}
defer rm()
// Corefile with for example without proxy section.
corefile := `example.org:0 {
file ` + name + ` {
upstream 8.8.8.8
// Corefile with for example proxy section.
corefile := `.:0 {
file ` + name + ` example.org {
upstream
}
proxy . 8.8.8.8 8.8.4.4
}
`
i, udp, _, err := CoreDNSServerAndPorts(corefile)
@ -65,10 +61,9 @@ func TestZoneExternalCNAMELookupWithProxy(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "cname.example.org.", dns.TypeA)
m := new(dns.Msg)
m.SetQuestion("cname.example.org.", dns.TypeA)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatalf("Expected to receive reply, but didn't: %s", err)
}

View file

@ -6,9 +6,6 @@ import (
"time"
"github.com/coredns/coredns/plugin/file"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -39,10 +36,9 @@ example.net:0 {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
m := new(dns.Msg)
m.SetQuestion("example.org.", dns.TypeA)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatalf("Expected to receive reply, but didn't: %s", err)
}
@ -55,7 +51,7 @@ example.net:0 {
time.Sleep(2 * time.Second) // reload time
resp, err = p.Lookup(state, "example.org.", dns.TypeA)
resp, err = dns.Exchange(m, udp)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}

View file

@ -3,10 +3,6 @@ package test
import (
"testing"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -30,10 +26,9 @@ func TestZoneSRVAdditional(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "service.example.org.", dns.TypeSRV)
m := new(dns.Msg)
m.SetQuestion("service.example.org.", dns.TypeSRV)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatalf("Expected to receive reply, but didn't: %s", err)
}

View file

@ -3,10 +3,6 @@ package test
import (
"testing"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -24,10 +20,9 @@ func TestHostsInlineLookup(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
m := new(dns.Msg)
m.SetQuestion("example.org.", dns.TypeA)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}

View file

@ -3,36 +3,9 @@ package test
import (
"testing"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
func TestProxyErratic(t *testing.T) {
corefile := `example.org:0 {
erratic {
drop 2
}
}
`
backend, udp, _, err := CoreDNSServerAndPorts(corefile)
if err != nil {
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
}
defer backend.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
// We do one lookup that should not time out.
// After this the backend is marked unhealthy anyway. So basically this
// tests that it times out.
p.Lookup(state, "example.org.", dns.TypeA)
}
func TestProxyThreeWay(t *testing.T) {
// Run 3 CoreDNS server, 2 upstream ones and a proxy. 1 Upstream is unhealthy after 1 query, but after
// that we should still be able to send to the other one.

View file

@ -1,86 +0,0 @@
package test
import (
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
func TestProxyWithHTTPCheckOK(t *testing.T) {
healthCheckServer := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
io.WriteString(w, "OK\n")
}))
defer healthCheckServer.Close()
healthCheckURL, err := url.Parse(healthCheckServer.URL)
if err != nil {
t.Fatal(err)
}
// TODO: use URL.Port() (Go 1.8+) once we've deprecated Go 1.7 support
var healthCheckPort string
if _, healthCheckPort, err = net.SplitHostPort(healthCheckURL.Host); err != nil {
healthCheckPort = "80"
}
name, rm, err := test.TempFile(".", exampleOrg)
if err != nil {
t.Fatalf("Failed to create zone: %s", err)
}
defer rm()
// We have to bind to 127.0.0.1 because the server started by
// httptest.NewServer does, and the IP addresses of the backend
// DNS and HTTP servers must match.
authoritativeCorefile := `example.org:0 {
bind 127.0.0.1
file ` + name + `
}
`
authoritativeInstance, authoritativeAddr, _, err := CoreDNSServerAndPorts(authoritativeCorefile)
if err != nil {
t.Fatalf("Could not get CoreDNS authoritative instance: %s", err)
}
defer authoritativeInstance.Stop()
proxyCorefile := `example.org:0 {
proxy . ` + authoritativeAddr + ` {
health_check /health:` + healthCheckPort + ` 1s
}
}
`
proxyInstance, proxyAddr, _, err := CoreDNSServerAndPorts(proxyCorefile)
if err != nil {
t.Fatalf("Could not get CoreDNS proxy instance: %s", err)
}
defer proxyInstance.Stop()
p := proxy.NewLookup([]string{proxyAddr})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
// expect answer section with A record in it
if len(resp.Answer) == 0 {
t.Fatalf("Expected to at least one RR in the answer section, got none: %s", resp)
}
if resp.Answer[0].Header().Rrtype != dns.TypeA {
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.Errorf("Expected 127.0.0.1, got: %s", resp.Answer[0].(*dns.A).A.String())
}
}

View file

@ -3,9 +3,7 @@ package test
import (
"testing"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -29,46 +27,9 @@ func TestLookupProxy(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
// expect answer section with A record in it
if len(resp.Answer) == 0 {
t.Fatalf("Expected to at least one RR in the answer section, got none: %s", resp)
}
if resp.Answer[0].Header().Rrtype != dns.TypeA {
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.Errorf("Expected 127.0.0.1, got: %s", resp.Answer[0].(*dns.A).A.String())
}
}
func TestLookupDnsWithForcedTcp(t *testing.T) {
t.Parallel()
name, rm, err := test.TempFile(".", exampleOrg)
if err != nil {
t.Fatalf("Failed to create zone: %s", err)
}
defer rm()
corefile := `example.org:0 {
file ` + name + `
}
`
i, _, tcp, err := CoreDNSServerAndPorts(corefile)
if err != nil {
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
}
defer i.Stop()
p := proxy.NewLookupWithOption([]string{tcp}, proxy.Options{ForceTCP: true})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
m := new(dns.Msg)
m.SetQuestion("example.org.", dns.TypeA)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}
@ -108,13 +69,12 @@ func BenchmarkProxyLookup(b *testing.B) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
m := new(dns.Msg)
m.SetQuestion("example.org.", dns.TypeA)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := p.Lookup(state, "example.org.", dns.TypeA)
if err != nil {
if _, err := dns.Exchange(m, udp); err != nil {
b.Fatal("Expected to receive reply, but didn't")
}
}

View file

@ -3,10 +3,6 @@ package test
import (
"testing"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -26,9 +22,9 @@ func TestReverseCorefile(t *testing.T) {
t.Fatalf("Could not get UDP listening port")
}
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "17.0.0.10.in-addr.arpa.", dns.TypePTR)
m := new(dns.Msg)
m.SetQuestion("17.0.0.10.in-addr.arpa.", dns.TypePTR)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}

View file

@ -3,9 +3,7 @@ package test
import (
"testing"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -25,10 +23,9 @@ func TestEmptySecondaryZone(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
resp, err := p.Lookup(state, "www.example.org.", dns.TypeA)
m := new(dns.Msg)
m.SetQuestion("www.example.org.", dns.TypeA)
resp, err := dns.Exchange(m, udp)
if err != nil {
t.Fatal("Expected to receive reply, but didn't")
}

View file

@ -3,9 +3,7 @@ package test
import (
"testing"
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
)
@ -29,11 +27,10 @@ func TestLookupWildcard(t *testing.T) {
}
defer i.Stop()
p := proxy.NewLookup([]string{udp})
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
for _, lookup := range []string{"a.w.example.org.", "a.a.w.example.org."} {
resp, err := p.Lookup(state, lookup, dns.TypeTXT)
m := new(dns.Msg)
m.SetQuestion(lookup, dns.TypeTXT)
resp, err := dns.Exchange(m, udp)
if err != nil || resp == nil {
t.Fatalf("Expected to receive reply, but didn't for %s", lookup)
}
@ -64,7 +61,9 @@ func TestLookupWildcard(t *testing.T) {
}
for _, lookup := range []string{"w.example.org.", "a.w.example.org.", "a.a.w.example.org."} {
resp, err := p.Lookup(state, lookup, dns.TypeSRV)
m := new(dns.Msg)
m.SetQuestion(lookup, dns.TypeSRV)
resp, err := dns.Exchange(m, udp)
if err != nil || resp == nil {
t.Fatal("Expected to receive reply, but didn't", lookup)
}