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:
parent
6b56a9c921
commit
9c16ed1d14
55 changed files with 184 additions and 1349 deletions
|
@ -18,7 +18,7 @@ auto [ZONES...] {
|
||||||
directory DIR [REGEXP ORIGIN_TEMPLATE [TIMEOUT]]
|
directory DIR [REGEXP ORIGIN_TEMPLATE [TIMEOUT]]
|
||||||
reload DURATION
|
reload DURATION
|
||||||
no_reload
|
no_reload
|
||||||
upstream [ADDRESS...]
|
upstream
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
@ -37,9 +37,7 @@ are used.
|
||||||
and reloads zone when serial changes.
|
and reloads zone when serial changes.
|
||||||
* `no_reload` deprecated. Sets reload to 0.
|
* `no_reload` deprecated. Sets reload to 0.
|
||||||
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
|
* `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
|
pointing to external names. CoreDNS will resolve CNAMEs against itself.
|
||||||
a file that is structured as /etc/resolv.conf. If no **ADDRESS** is given, CoreDNS will resolve CNAMEs
|
|
||||||
against itself.
|
|
||||||
|
|
||||||
All directives from the *file* plugin are supported. Note that *auto* will load all zones found,
|
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:
|
even though the directive might only receive queries for a specific zone. I.e:
|
||||||
|
|
|
@ -33,7 +33,7 @@ type (
|
||||||
// In the future this should be something like ZoneMeta that contains all this stuff.
|
// In the future this should be something like ZoneMeta that contains all this stuff.
|
||||||
transferTo []string
|
transferTo []string
|
||||||
ReloadInterval time.Duration
|
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
|
duration time.Duration
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,15 +155,8 @@ func autoParse(c *caddy.Controller) (Auto, error) {
|
||||||
a.loader.ReloadInterval = 0
|
a.loader.ReloadInterval = 0
|
||||||
|
|
||||||
case "upstream":
|
case "upstream":
|
||||||
args := c.RemainingArgs()
|
c.RemainingArgs() // eat remaining args
|
||||||
if len(args) == 0 {
|
a.loader.upstream = upstream.New()
|
||||||
return a, c.ArgErr()
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
a.loader.upstream, err = upstream.New(args)
|
|
||||||
if err != nil {
|
|
||||||
return a, err
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
t, _, e := parse.Transfer(c, false)
|
t, _, e := parse.Transfer(c, false)
|
||||||
|
|
|
@ -28,28 +28,22 @@ If you want to `round robin` A and AAAA responses look at the `loadbalance` plug
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
etcd [ZONES...] {
|
etcd [ZONES...] {
|
||||||
stubzones
|
|
||||||
fallthrough [ZONES...]
|
fallthrough [ZONES...]
|
||||||
path PATH
|
path PATH
|
||||||
endpoint ENDPOINT...
|
endpoint ENDPOINT...
|
||||||
upstream [ADDRESS...]
|
upstream
|
||||||
tls CERT KEY CACERT
|
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.
|
* `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
|
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
|
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.
|
queries for those zones will be subject to fallthrough.
|
||||||
* **PATH** the path inside etcd. Defaults to "/skydns".
|
* **PATH** the path inside etcd. Defaults to "/skydns".
|
||||||
* **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2379".
|
* **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2379".
|
||||||
* `upstream` upstream resolvers to be used resolve external names found in etcd (think CNAMEs)
|
* `upstream` resolve names found in etcd (think CNAMEs) If you want CoreDNS to act as a proxy for clients,
|
||||||
pointing to external names. If you want CoreDNS to act as a proxy for clients, you'll need to add
|
you'll need to add the forward plugin. CoreDNS will resolve CNAMEs against itself.
|
||||||
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.
|
|
||||||
* `tls` followed by:
|
* `tls` followed by:
|
||||||
|
|
||||||
* no arguments, if the server certificate is signed by a system-installed CA and no client cert is needed
|
* 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
|
~~~ corefile
|
||||||
. {
|
. {
|
||||||
etcd skydns.local {
|
etcd skydns.local {
|
||||||
stubzones
|
|
||||||
path /skydns
|
path /skydns
|
||||||
endpoint http://localhost:2379
|
endpoint http://localhost:2379
|
||||||
upstream 8.8.8.8:53 8.8.4.4:53
|
upstream
|
||||||
}
|
}
|
||||||
prometheus
|
prometheus
|
||||||
cache 160 skydns.local
|
cache 160 skydns.local
|
||||||
|
@ -98,7 +91,7 @@ when resolving external pointing CNAMEs.
|
||||||
. {
|
. {
|
||||||
etcd skydns.local {
|
etcd skydns.local {
|
||||||
path /skydns
|
path /skydns
|
||||||
upstream /etc/resolv.conf
|
upstream
|
||||||
}
|
}
|
||||||
cache 160 skydns.local
|
cache 160 skydns.local
|
||||||
proxy . /etc/resolv.conf
|
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 {
|
etcd skydns.local 10.0.0.0/24 {
|
||||||
stubzones
|
|
||||||
...
|
...
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/etcd/msg"
|
"github.com/coredns/coredns/plugin/etcd/msg"
|
||||||
"github.com/coredns/coredns/plugin/pkg/fall"
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/request"
|
"github.com/coredns/coredns/request"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/upstream"
|
"github.com/coredns/coredns/plugin/pkg/upstream"
|
||||||
|
@ -35,10 +34,9 @@ type Etcd struct {
|
||||||
Fall fall.F
|
Fall fall.F
|
||||||
Zones []string
|
Zones []string
|
||||||
PathPrefix string
|
PathPrefix string
|
||||||
Upstream upstream.Upstream // Proxy for looking up names during the resolution process
|
Upstream *upstream.Upstream
|
||||||
Client *etcdcv3.Client
|
Client *etcdcv3.Client
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
Stubmap *map[string]proxy.Proxy // list of proxies for stub resolving.
|
|
||||||
|
|
||||||
endpoints []string // Stored here as well, to aid in testing.
|
endpoints []string // Stored here as well, to aid in testing.
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,20 +14,6 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
|
||||||
opt := plugin.Options{}
|
opt := plugin.Options{}
|
||||||
state := request.Request{W: w, Req: r, Context: ctx}
|
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())
|
zone := plugin.Zones(e.Zones).Matches(state.Name())
|
||||||
if zone == "" {
|
if zone == "" {
|
||||||
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)
|
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||||
"github.com/coredns/coredns/plugin/pkg/tls"
|
"github.com/coredns/coredns/plugin/pkg/tls"
|
||||||
"github.com/coredns/coredns/plugin/pkg/upstream"
|
"github.com/coredns/coredns/plugin/pkg/upstream"
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -283,9 +282,8 @@ func newEtcdPlugin() *Etcd {
|
||||||
tlsc, _ := tls.NewTLSConfigFromArgs()
|
tlsc, _ := tls.NewTLSConfigFromArgs()
|
||||||
client, _ := newEtcdClient(endpoints, tlsc)
|
client, _ := newEtcdClient(endpoints, tlsc)
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{"8.8.8.8:53"})
|
|
||||||
return &Etcd{
|
return &Etcd{
|
||||||
Upstream: upstream.Upstream{Forward: &p},
|
Upstream: upstream.New(),
|
||||||
PathPrefix: "skydns",
|
PathPrefix: "skydns",
|
||||||
Ctx: context.Background(),
|
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."},
|
Zones: []string{"skydns.test.", "skydns_extra.test.", "skydns_zonea.test.", "skydns_zoneb.test.", "skydns_zonec.test.", "skydns_zoned.test.", "in-addr.arpa."},
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||||
mwtls "github.com/coredns/coredns/plugin/pkg/tls"
|
mwtls "github.com/coredns/coredns/plugin/pkg/tls"
|
||||||
"github.com/coredns/coredns/plugin/pkg/upstream"
|
"github.com/coredns/coredns/plugin/pkg/upstream"
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
|
|
||||||
etcdcv3 "github.com/coreos/etcd/clientv3"
|
etcdcv3 "github.com/coreos/etcd/clientv3"
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
|
@ -25,18 +24,11 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
e, stubzones, err := etcdParse(c)
|
e, err := etcdParse(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return plugin.Error("etcd", err)
|
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 {
|
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
||||||
e.Next = next
|
e.Next = next
|
||||||
return e
|
return e
|
||||||
|
@ -45,20 +37,17 @@ func setup(c *caddy.Controller) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
|
func etcdParse(c *caddy.Controller) (*Etcd, error) {
|
||||||
stub := make(map[string]proxy.Proxy)
|
|
||||||
etc := Etcd{
|
etc := Etcd{
|
||||||
// Don't default to a proxy for lookups.
|
// Don't default to a proxy for lookups.
|
||||||
// Proxy: proxy.NewLookup([]string{"8.8.8.8:53", "8.8.4.4:53"}),
|
// Proxy: proxy.NewLookup([]string{"8.8.8.8:53", "8.8.4.4:53"}),
|
||||||
PathPrefix: "skydns",
|
PathPrefix: "skydns",
|
||||||
Ctx: context.Background(),
|
Ctx: context.Background(),
|
||||||
Stubmap: &stub,
|
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
err error
|
err error
|
||||||
endpoints = []string{defaultEndpoint}
|
endpoints = []string{defaultEndpoint}
|
||||||
stubzones = false
|
|
||||||
)
|
)
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
etc.Zones = c.RemainingArgs()
|
etc.Zones = c.RemainingArgs()
|
||||||
|
@ -74,38 +63,35 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
|
||||||
for {
|
for {
|
||||||
switch c.Val() {
|
switch c.Val() {
|
||||||
case "stubzones":
|
case "stubzones":
|
||||||
stubzones = true
|
// ignored, remove later.
|
||||||
case "fallthrough":
|
case "fallthrough":
|
||||||
etc.Fall.SetZonesFromArgs(c.RemainingArgs())
|
etc.Fall.SetZonesFromArgs(c.RemainingArgs())
|
||||||
case "debug":
|
case "debug":
|
||||||
/* it is a noop now */
|
/* it is a noop now */
|
||||||
case "path":
|
case "path":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return &Etcd{}, false, c.ArgErr()
|
return &Etcd{}, c.ArgErr()
|
||||||
}
|
}
|
||||||
etc.PathPrefix = c.Val()
|
etc.PathPrefix = c.Val()
|
||||||
case "endpoint":
|
case "endpoint":
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return &Etcd{}, false, c.ArgErr()
|
return &Etcd{}, c.ArgErr()
|
||||||
}
|
}
|
||||||
endpoints = args
|
endpoints = args
|
||||||
case "upstream":
|
case "upstream":
|
||||||
args := c.RemainingArgs()
|
// check args != 0 and error in the future
|
||||||
u, err := upstream.New(args)
|
c.RemainingArgs() // clear buffer
|
||||||
if err != nil {
|
etc.Upstream = upstream.New()
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
etc.Upstream = u
|
|
||||||
case "tls": // cert key cacertfile
|
case "tls": // cert key cacertfile
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
tlsConfig, err = mwtls.NewTLSConfigFromArgs(args...)
|
tlsConfig, err = mwtls.NewTLSConfigFromArgs(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Etcd{}, false, err
|
return &Etcd{}, err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if c.Val() != "}" {
|
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)
|
client, err := newEtcdClient(endpoints, tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Etcd{}, false, err
|
return &Etcd{}, err
|
||||||
}
|
}
|
||||||
etc.Client = client
|
etc.Client = client
|
||||||
etc.endpoints = endpoints
|
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) {
|
func newEtcdClient(endpoints []string, cc *tls.Config) (*etcdcv3.Client, error) {
|
||||||
|
|
|
@ -56,7 +56,7 @@ func TestSetupEtcd(t *testing.T) {
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
c := caddy.NewTestController("dns", test.input)
|
c := caddy.NewTestController("dns", test.input)
|
||||||
etcd, _ /*stubzones*/, err := etcdParse(c)
|
etcd, err := etcdParse(c)
|
||||||
|
|
||||||
if test.shouldErr && err == nil {
|
if test.shouldErr && err == nil {
|
||||||
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
|
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}()
|
|
|
@ -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")},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -17,16 +17,14 @@ Enabling *federation* without also having *kubernetes* is a noop.
|
||||||
~~~
|
~~~
|
||||||
federation [ZONES...] {
|
federation [ZONES...] {
|
||||||
NAME DOMAIN
|
NAME DOMAIN
|
||||||
upstream [ADDRESS...]
|
upstream
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* Each **NAME** and **DOMAIN** defines federation membership. One entry for each. A duplicate
|
* Each **NAME** and **DOMAIN** defines federation membership. One entry for each. A duplicate
|
||||||
**NAME** will silently overwrite any previous value.
|
**NAME** will silently overwrite any previous value.
|
||||||
* `upstream` [**ADDRESS**...] defines the upstream resolvers used for resolving the `CNAME` target
|
* `upstream` [**ADDRESS**...] resolve the `CNAME` target produced by this plugin. CoreDNS
|
||||||
produced by this plugin. If no **ADDRESS** is given, CoreDNS
|
will resolve External Services against itself.
|
||||||
will resolve External Services against itself. **ADDRESS** can be an IP, an IP:port, or a path
|
|
||||||
to a file structured like resolv.conf.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|
|
@ -64,12 +64,8 @@ func federationParse(c *caddy.Controller) (*Federation, error) {
|
||||||
x := c.Val()
|
x := c.Val()
|
||||||
switch x {
|
switch x {
|
||||||
case "upstream":
|
case "upstream":
|
||||||
args := c.RemainingArgs()
|
c.RemainingArgs()
|
||||||
u, err := upstream.New(args)
|
fed.Upstream = upstream.New()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fed.Upstream = &u
|
|
||||||
default:
|
default:
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
if x := len(args); x != 1 {
|
if x := len(args); x != 1 {
|
||||||
|
|
|
@ -29,7 +29,7 @@ file DBFILE [ZONES... ] {
|
||||||
transfer to ADDRESS...
|
transfer to ADDRESS...
|
||||||
reload DURATION
|
reload DURATION
|
||||||
no_reload
|
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
|
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.
|
and reloads the zone when serial changes.
|
||||||
* `no_reload` deprecated. Sets reload to 0.
|
* `no_reload` deprecated. Sets reload to 0.
|
||||||
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
|
* `upstream` resolve external names found (think CNAMEs) pointing to external names. This is only
|
||||||
pointing to external names. This is only really useful when CoreDNS is configured as a proxy; for
|
really useful when CoreDNS is configured as a proxy; for normal authoritative serving you don't
|
||||||
normal authoritative serving you don't need *or* want to use this. **ADDRESS** can be an IP
|
need *or* want to use this. CoreDNS will resolve CNAMEs against itself.
|
||||||
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.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|
|
@ -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.`
|
|
|
@ -1,5 +1,8 @@
|
||||||
package file
|
package file
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO(miek): move to test/ for full server testing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -294,3 +297,4 @@ ns.example.org. 1800 IN A 127.0.0.1
|
||||||
RXpMdvaE6ZDwalWldLjC3h8QDywDoFdndoRY
|
RXpMdvaE6ZDwalWldLjC3h8QDywDoFdndoRY
|
||||||
eHOsmTvvtWWqtO6Fa5A8gmHT5HA= )
|
eHOsmTvvtWWqtO6Fa5A8gmHT5HA= )
|
||||||
`
|
`
|
||||||
|
*/
|
||||||
|
|
|
@ -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 {
|
func (z *Zone) externalLookup(state request.Request, target string, qtype uint16) []dns.RR {
|
||||||
m, e := z.Upstream.Lookup(state, target, qtype)
|
m, e := z.Upstream.Lookup(state, target, qtype)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
// TODO(miek): Log, or return error here?
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if m == nil {
|
if m == nil {
|
||||||
|
|
|
@ -93,7 +93,7 @@ func fileParse(c *caddy.Controller) (Zones, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
reload := 1 * time.Minute
|
reload := 1 * time.Minute
|
||||||
upstr := upstream.Upstream{}
|
upstr := upstream.New()
|
||||||
t := []string{}
|
t := []string{}
|
||||||
var e error
|
var e error
|
||||||
|
|
||||||
|
@ -116,11 +116,8 @@ func fileParse(c *caddy.Controller) (Zones, error) {
|
||||||
reload = 0
|
reload = 0
|
||||||
|
|
||||||
case "upstream":
|
case "upstream":
|
||||||
args := c.RemainingArgs()
|
// ignore args, will be error later.
|
||||||
upstr, err = upstream.New(args)
|
c.RemainingArgs() // clear buffer
|
||||||
if err != nil {
|
|
||||||
return Zones{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Zones{}, c.Errf("unknown property '%s'", c.Val())
|
return Zones{}, c.Errf("unknown property '%s'", c.Val())
|
||||||
|
|
|
@ -57,8 +57,8 @@ func TestFileParse(t *testing.T) {
|
||||||
`file ` + zoneFileName1 + ` example.net. {
|
`file ` + zoneFileName1 + ` example.net. {
|
||||||
upstream a
|
upstream a
|
||||||
}`,
|
}`,
|
||||||
true,
|
false, // OK for now as we disregard any options for the `upstream`.
|
||||||
Zones{Names: []string{}},
|
Zones{Names: []string{"example.net."}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`file ` + zoneFileName1 + ` example.net. {
|
`file ` + zoneFileName1 + ` example.net. {
|
||||||
|
|
|
@ -32,7 +32,7 @@ type Zone struct {
|
||||||
LastReloaded time.Time
|
LastReloaded time.Time
|
||||||
reloadMu sync.RWMutex
|
reloadMu sync.RWMutex
|
||||||
reloadShutdown chan bool
|
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.
|
// Apex contains the apex records of a zone: SOA, NS and their potential signatures.
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,7 +32,7 @@ import (
|
||||||
type Kubernetes struct {
|
type Kubernetes struct {
|
||||||
Next plugin.Handler
|
Next plugin.Handler
|
||||||
Zones []string
|
Zones []string
|
||||||
Upstream upstream.Upstream
|
Upstream *upstream.Upstream
|
||||||
APIServerList []string
|
APIServerList []string
|
||||||
APIProxy *apiProxy
|
APIProxy *apiProxy
|
||||||
APICertAuth string
|
APICertAuth string
|
||||||
|
|
|
@ -241,12 +241,8 @@ func ParseStanza(c *caddy.Controller) (*Kubernetes, error) {
|
||||||
case "fallthrough":
|
case "fallthrough":
|
||||||
k8s.Fall.SetZonesFromArgs(c.RemainingArgs())
|
k8s.Fall.SetZonesFromArgs(c.RemainingArgs())
|
||||||
case "upstream":
|
case "upstream":
|
||||||
args := c.RemainingArgs()
|
c.RemainingArgs() // eat remaining args
|
||||||
u, err := upstream.New(args)
|
k8s.Upstream = upstream.New()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
k8s.Upstream = u
|
|
||||||
case "ttl":
|
case "ttl":
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/fall"
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
@ -23,7 +22,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
expectedLabelSelector string // expected label selector value
|
expectedLabelSelector string // expected label selector value
|
||||||
expectedPodMode string
|
expectedPodMode string
|
||||||
expectedFallthrough fall.F
|
expectedFallthrough fall.F
|
||||||
expectedUpstreams []string
|
|
||||||
}{
|
}{
|
||||||
// positive
|
// positive
|
||||||
{
|
{
|
||||||
|
@ -36,7 +34,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local test.local`,
|
`kubernetes coredns.local test.local`,
|
||||||
|
@ -48,7 +45,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -61,7 +57,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -75,7 +70,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -89,7 +83,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -103,7 +96,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -117,7 +109,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -131,7 +122,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -145,7 +135,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"environment=prod",
|
"environment=prod",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -159,7 +148,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"application=nginx,environment in (production,qa,staging)",
|
"application=nginx,environment in (production,qa,staging)",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local test.local {
|
`kubernetes coredns.local test.local {
|
||||||
|
@ -177,7 +165,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"application=nginx,environment in (production,qa,staging)",
|
"application=nginx,environment in (production,qa,staging)",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Root,
|
fall.Root,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
// negative
|
// negative
|
||||||
{
|
{
|
||||||
|
@ -192,7 +179,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -206,7 +192,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -220,7 +205,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -234,7 +218,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -248,7 +231,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -262,7 +244,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -276,7 +257,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
// pods disabled
|
// pods disabled
|
||||||
{
|
{
|
||||||
|
@ -291,7 +271,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
// pods insecure
|
// pods insecure
|
||||||
{
|
{
|
||||||
|
@ -306,7 +285,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeInsecure,
|
podModeInsecure,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
// pods verified
|
// pods verified
|
||||||
{
|
{
|
||||||
|
@ -321,7 +299,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeVerified,
|
podModeVerified,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
// pods invalid
|
// pods invalid
|
||||||
{
|
{
|
||||||
|
@ -336,7 +313,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeVerified,
|
podModeVerified,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
// fallthrough with zones
|
// fallthrough with zones
|
||||||
{
|
{
|
||||||
|
@ -351,12 +327,11 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.F{Zones: []string{"ip6.arpa.", "inaddr.arpa.", "foo.com."}},
|
fall.F{Zones: []string{"ip6.arpa.", "inaddr.arpa.", "foo.com."}},
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
// Valid upstream
|
// Valid upstream
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
upstream 13.14.15.16:53
|
upstream
|
||||||
}`,
|
}`,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
@ -366,22 +341,6 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
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
|
// More than one Kubernetes not allowed
|
||||||
{
|
{
|
||||||
|
@ -395,7 +354,6 @@ kubernetes cluster.local`,
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -409,7 +367,6 @@ kubernetes cluster.local`,
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -423,7 +380,6 @@ kubernetes cluster.local`,
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -437,7 +393,6 @@ kubernetes cluster.local`,
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
fall.Zero,
|
fall.Zero,
|
||||||
nil,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`kubernetes coredns.local {
|
`kubernetes coredns.local {
|
||||||
|
@ -519,31 +474,6 @@ kubernetes cluster.local`,
|
||||||
if !k8sController.Fall.Equal(test.expectedFallthrough) {
|
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)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,58 +1,35 @@
|
||||||
// Package upstream abstracts a upstream lookups so that plugins
|
// Package upstream abstracts a upstream lookups so that plugins can handle them in an unified way.
|
||||||
// can handle them in an unified way.
|
|
||||||
package upstream
|
package upstream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/plugin/pkg/nonwriter"
|
"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"
|
"github.com/coredns/coredns/request"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Upstream is used to resolve CNAME targets
|
// Upstream is used to resolve CNAME or other external targets via CoreDNS itself.
|
||||||
type Upstream struct {
|
type Upstream struct{}
|
||||||
self bool
|
|
||||||
Forward *proxy.Proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Upstream for given destination(s). If dests is empty it default to upstreaming to
|
// New creates a new Upstream to resolve names using the the coredns process.
|
||||||
// the coredns process.
|
func New() *Upstream { return &Upstream{} }
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup routes lookups to our selves or forward to a remote.
|
// 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) {
|
func (u *Upstream) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) {
|
||||||
if u.self {
|
server, ok := state.Context.Value(dnsserver.Key{}).(*dnsserver.Server)
|
||||||
req := new(dns.Msg)
|
if !ok {
|
||||||
req.SetQuestion(name, typ)
|
return nil, fmt.Errorf("no full server is running")
|
||||||
|
|
||||||
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 {
|
req := new(dns.Msg)
|
||||||
return u.Forward.Lookup(state, name, typ)
|
req.SetQuestion(name, typ)
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
nw := nonwriter.New(state.W)
|
||||||
|
|
||||||
|
server.ServeDNS(state.Context, nw, req)
|
||||||
|
|
||||||
|
return nw.Msg, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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++
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,12 +9,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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/mholt/caddy/caddyfile"
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStop(t *testing.T) {
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,8 +6,9 @@
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
The route53 plugin is useful for serving zones from resource record sets in AWS route53. This plugin
|
The route53 plugin is useful for serving zones from resource record
|
||||||
supports all Amazon Route 53 records (https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/ResourceRecordTypes.html).
|
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.
|
The route53 plugin can be used when coredns is deployed on AWS or elsewhere.
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
@ -15,33 +16,40 @@ The route53 plugin can be used when coredns is deployed on AWS or elsewhere.
|
||||||
~~~ txt
|
~~~ txt
|
||||||
route53 [ZONE:HOSTED_ZONE_ID...] {
|
route53 [ZONE:HOSTED_ZONE_ID...] {
|
||||||
[aws_access_key AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY]
|
[aws_access_key AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY]
|
||||||
upstream [ADDRESS...]
|
upstream
|
||||||
credentials PROFILE [FILENAME]
|
credentials PROFILE [FILENAME]
|
||||||
fallthrough [ZONES...]
|
fallthrough [ZONES...]
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* **ZONE** the name of the domain to be accessed. When there are multiple zones with overlapping domains
|
* **ZONE** the name of the domain to be accessed. When there are multiple zones with overlapping
|
||||||
(private vs. public hosted zone), CoreDNS does the lookup in the given order here. Therefore, for a
|
domains (private vs. public hosted zone), CoreDNS does the lookup in the given order here.
|
||||||
non-existing resource record, SOA response will be from the rightmost zone.
|
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
|
* **HOSTED*ZONE*ID** the ID of the hosted zone that contains the resource record sets to be
|
||||||
to be used when query AWS (optional). If they are not provided, then coredns tries to access
|
accessed.
|
||||||
AWS credentials the same way as AWS CLI, e.g., environmental variables, AWS credentials file,
|
|
||||||
instance profile credentials, etc.
|
* **AWS*ACCESS*KEY_ID** and **AWS*SECRET*ACCESS_KEY** the AWS access key ID and secret access key
|
||||||
* `upstream` [**ADDRESS**...] specifies upstream resolver(s) used for resolving services that point
|
to be used when query AWS (optional). If they are not provided, then coredns tries to access
|
||||||
to external hosts (eg. used to resolve CNAMEs). If no **ADDRESS** is given, CoreDNS will resolve
|
AWS credentials the same way as AWS CLI, e.g., environmental variables, AWS credentials file,
|
||||||
against itself. **ADDRESS** can be an IP, an IP:port or a path to a file structured like
|
instance profile credentials, etc.
|
||||||
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
|
||||||
* **PROFILE** AWS account profile name. Defaults to `default`.
|
CNAMEs). CoreDNS will resolve against itself.
|
||||||
* **FILENAME** AWS credentials filename. Defaults to `~/.aws/credentials`
|
|
||||||
are used.
|
* `credentials` used for reading the credential file and setting the profile name for a given
|
||||||
* `fallthrough` If zone matches and no record can be generated, pass request to the next plugin.
|
zone.
|
||||||
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
|
* **PROFILE** AWS account profile name. Defaults to `default`.
|
||||||
queries for those zones will be subject to fallthrough.
|
|
||||||
* **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block
|
* **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.
|
||||||
|
|
||||||
|
* **ZONES** zones it should be authoritative for. If empty, the zones from the configuration block
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|
|
@ -170,7 +170,7 @@ func (h *Route53) updateZones(ctx context.Context) error {
|
||||||
|
|
||||||
for i, hostedZone := range z {
|
for i, hostedZone := range z {
|
||||||
newZ := file.NewZone(zName, "")
|
newZ := file.NewZone(zName, "")
|
||||||
newZ.Upstream = *h.upstream
|
newZ.Upstream = h.upstream
|
||||||
in := &route53.ListResourceRecordSetsInput{
|
in := &route53.ListResourceRecordSetsInput{
|
||||||
HostedZoneId: aws.String(hostedZone.id),
|
HostedZoneId: aws.String(hostedZone.id),
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ func setup(c *caddy.Controller, f func(*credentials.Credentials) route53iface.Ro
|
||||||
var providers []credentials.Provider
|
var providers []credentials.Provider
|
||||||
var fall fall.F
|
var fall fall.F
|
||||||
|
|
||||||
up, _ := upstream.New(nil)
|
up := upstream.New()
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
|
@ -83,12 +83,7 @@ func setup(c *caddy.Controller, f func(*credentials.Credentials) route53iface.Ro
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
case "upstream":
|
case "upstream":
|
||||||
args := c.RemainingArgs()
|
c.RemainingArgs() // eats args
|
||||||
var err error
|
|
||||||
up, err = upstream.New(args)
|
|
||||||
if err != nil {
|
|
||||||
return c.Errf("invalid upstream: %v", err)
|
|
||||||
}
|
|
||||||
case "credentials":
|
case "credentials":
|
||||||
if c.NextArg() {
|
if c.NextArg() {
|
||||||
sharedProvider.Profile = c.Val()
|
sharedProvider.Profile = c.Val()
|
||||||
|
@ -109,7 +104,7 @@ func setup(c *caddy.Controller, f func(*credentials.Credentials) route53iface.Ro
|
||||||
|
|
||||||
client := f(credentials.NewChainCredentials(providers))
|
client := f(credentials.NewChainCredentials(providers))
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
h, err := New(ctx, client, keys, &up)
|
h, err := New(ctx, client, keys, up)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Errf("failed to create Route53 plugin: %v", err)
|
return c.Errf("failed to create Route53 plugin: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,18 +23,16 @@ A working syntax would be:
|
||||||
secondary [zones...] {
|
secondary [zones...] {
|
||||||
transfer from ADDRESS
|
transfer from ADDRESS
|
||||||
transfer to ADDRESS
|
transfer to ADDRESS
|
||||||
upstream [ADDRESS...]
|
upstream
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* `transfer from` specifies from which address to fetch the zone. It can be specified multiple times;
|
* `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.
|
if one does not work, another will be tried.
|
||||||
* `transfer to` can be enabled to allow this secondary zone to be transferred again.
|
* `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)
|
* `upstream` resolve external names found (think CNAMEs) pointing to external names. This is only
|
||||||
pointing to external names. This is only really useful when CoreDNS is configured as a proxy, for
|
really useful when CoreDNS is configured as a proxy; for normal authoritative serving you don't
|
||||||
normal authoritative serving you don't need *or* want to use this. **ADDRESS** can be an IP
|
need *or* want to use this. CoreDNS will resolve CNAMEs against itself.
|
||||||
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.
|
|
||||||
|
|
||||||
When a zone is due to be refreshed (Refresh timer fires) a random jitter of 5 seconds is
|
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
|
applied, before fetching. In the case of retry this will be 2 seconds. If there are any errors
|
||||||
|
|
|
@ -49,7 +49,7 @@ func setup(c *caddy.Controller) error {
|
||||||
func secondaryParse(c *caddy.Controller) (file.Zones, error) {
|
func secondaryParse(c *caddy.Controller) (file.Zones, error) {
|
||||||
z := make(map[string]*file.Zone)
|
z := make(map[string]*file.Zone)
|
||||||
names := []string{}
|
names := []string{}
|
||||||
upstr := upstream.Upstream{}
|
upstr := upstream.New()
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
|
|
||||||
if c.Val() == "secondary" {
|
if c.Val() == "secondary" {
|
||||||
|
@ -78,12 +78,7 @@ func secondaryParse(c *caddy.Controller) (file.Zones, error) {
|
||||||
return file.Zones{}, e
|
return file.Zones{}, e
|
||||||
}
|
}
|
||||||
case "upstream":
|
case "upstream":
|
||||||
args := c.RemainingArgs()
|
c.RemainingArgs() // eat args
|
||||||
var err error
|
|
||||||
upstr, err = upstream.New(args)
|
|
||||||
if err != nil {
|
|
||||||
return file.Zones{}, err
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return file.Zones{}, c.Errf("unknown property '%s'", c.Val())
|
return file.Zones{}, c.Errf("unknown property '%s'", c.Val())
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,13 @@ The *template* plugin allows you to dynamically respond to queries by just writi
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
template CLASS TYPE [ZONE...] {
|
template CLASS TYPE [ZONE...] {
|
||||||
[match REGEX...]
|
match REGEX...
|
||||||
[answer RR]
|
answer RR
|
||||||
[additional RR]
|
additional RR
|
||||||
[authority RR]
|
authority RR
|
||||||
[...]
|
rcode CODE
|
||||||
[rcode CODE]
|
upstream
|
||||||
[upstream [ADDRESS...]]
|
fallthrough [ZONE...]
|
||||||
[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
|
* `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.
|
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`.
|
* `rcode` **CODE** A response code (`NXDOMAIN, SERVFAIL, ...`). The default is `SUCCESS`.
|
||||||
* `upstream` [**ADDRESS**...] defines the upstream resolvers used for resolving CNAME.
|
* `upstream` defines the upstream resolvers used for resolving CNAMEs. CoreDNS will resolve CNAMEs against itself.
|
||||||
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.
|
|
||||||
* `fallthrough` Continue with the next plugin if the zone matched but no regex matched.
|
* `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
|
If specific zones are listed (for example `in-addr.arpa` and `ip6.arpa`), then only queries for
|
||||||
those zones will be subject to fallthrough.
|
those zones will be subject to fallthrough.
|
||||||
|
|
|
@ -144,12 +144,8 @@ func templateParse(c *caddy.Controller) (handler Handler, err error) {
|
||||||
t.fall.SetZonesFromArgs(c.RemainingArgs())
|
t.fall.SetZonesFromArgs(c.RemainingArgs())
|
||||||
|
|
||||||
case "upstream":
|
case "upstream":
|
||||||
args := c.RemainingArgs()
|
c.RemainingArgs() // eat remaining args
|
||||||
u, err := upstream.New(args)
|
t.upstream = upstream.New()
|
||||||
if err != nil {
|
|
||||||
return handler, err
|
|
||||||
}
|
|
||||||
t.upstream = &u
|
|
||||||
default:
|
default:
|
||||||
return handler, c.ArgErr()
|
return handler, c.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,13 +148,6 @@ func TestSetupParse(t *testing.T) {
|
||||||
}`,
|
}`,
|
||||||
false,
|
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 {
|
for i, test := range tests {
|
||||||
c := caddy.NewTestController("dns", test.inputFileRules)
|
c := caddy.NewTestController("dns", test.inputFileRules)
|
||||||
|
|
|
@ -7,10 +7,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,10 +30,9 @@ func TestAuto(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("www.example.org.", dns.TypeA)
|
||||||
|
resp, err := dns.Exchange(m, udp)
|
||||||
resp, err := p.Lookup(state, "www.example.org.", dns.TypeA)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
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
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
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"))
|
os.Remove(filepath.Join(tmpdir, "db.example.org"))
|
||||||
|
|
||||||
time.Sleep(1100 * time.Millisecond) // wait for it to be picked up
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
t.Fatal("Expected to receive reply, but didn't")
|
||||||
}
|
}
|
||||||
|
@ -99,10 +94,9 @@ func TestAutoNonExistentZone(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("example.org.", dns.TypeA)
|
||||||
|
resp, err := dns.Exchange(m, udp)
|
||||||
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
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
|
time.Sleep(1100 * time.Millisecond) // wait for it to be picked up
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetAxfr("example.org.")
|
m.SetAxfr("example.org.")
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: m}
|
resp, err := dns.Exchange(m, udp)
|
||||||
|
|
||||||
resp, err := p.Lookup(state, "example.org.", dns.TypeAXFR)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
t.Fatal("Expected to receive reply, but didn't")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,7 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -40,21 +38,20 @@ func TestLookupCache(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
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) {
|
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) {
|
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) {
|
func testCase(t *testing.T, name, addr string, expectAnsLen int, expectTTL uint32) {
|
||||||
resp, err := p.Lookup(state, name, dns.TypeA)
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion(name, dns.TypeA)
|
||||||
|
resp, err := dns.Exchange(m, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
t.Fatal("Expected to receive reply, but didn't")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,7 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
mtest "github.com/coredns/coredns/plugin/test"
|
mtest "github.com/coredns/coredns/plugin/test"
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -47,11 +45,10 @@ func TestLookupDS(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
|
||||||
state := request.Request{W: &mtest.ResponseWriter{}, Req: new(dns.Msg)}
|
|
||||||
|
|
||||||
for _, tc := range dsTestCases {
|
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 {
|
if err != nil || resp == nil {
|
||||||
t.Fatalf("Expected to receive reply, but didn't for %s %d", tc.Qname, tc.Qtype)
|
t.Fatalf("Expected to receive reply, but didn't for %s %d", tc.Qname, tc.Qtype)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/etcd/msg"
|
"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"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -38,16 +35,15 @@ func TestEtcdCache(t *testing.T) {
|
||||||
defer delete(ctx, t, etc, serv.Key)
|
defer delete(ctx, t, etc, serv.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("b.example.skydns.test.", dns.TypeA)
|
||||||
|
resp, err := dns.Exchange(m, udp)
|
||||||
resp, err := p.Lookup(state, "b.example.skydns.test.", dns.TypeA)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected to receive reply, but didn't: %s", err)
|
t.Errorf("Expected to receive reply, but didn't: %s", err)
|
||||||
}
|
}
|
||||||
checkResponse(t, resp)
|
checkResponse(t, resp)
|
||||||
|
|
||||||
resp, err = p.Lookup(state, "b.example.skydns.test.", dns.TypeA)
|
resp, err = dns.Exchange(m, udp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected to receive reply, but didn't: %s", err)
|
t.Errorf("Expected to receive reply, but didn't: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,6 @@ import (
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/etcd"
|
"github.com/coredns/coredns/plugin/etcd"
|
||||||
"github.com/coredns/coredns/plugin/etcd/msg"
|
"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"
|
etcdcv3 "github.com/coreos/etcd/clientv3"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -38,7 +35,7 @@ func TestEtcdStubAndProxyLookup(t *testing.T) {
|
||||||
stubzones
|
stubzones
|
||||||
path /skydns
|
path /skydns
|
||||||
endpoint http://localhost:2379
|
endpoint http://localhost:2379
|
||||||
upstream 8.8.8.8:53 8.8.4.4:53
|
upstream
|
||||||
fallthrough
|
fallthrough
|
||||||
}
|
}
|
||||||
proxy . 8.8.8.8:53
|
proxy . 8.8.8.8:53
|
||||||
|
@ -58,9 +55,9 @@ func TestEtcdStubAndProxyLookup(t *testing.T) {
|
||||||
defer delete(ctx, t, etc, serv.Key)
|
defer delete(ctx, t, etc, serv.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp}) // use udp port from the server
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("example.com.", dns.TypeA)
|
||||||
resp, err := p.Lookup(state, "example.com.", dns.TypeA)
|
resp, err := dns.Exchange(m, udp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected to receive reply, but didn't: %v", err)
|
t.Fatalf("Expected to receive reply, but didn't: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,5 @@ short.example.org. 1 IN A 127.0.0.3
|
||||||
*.w.example.org. IN TXT "Wildcard"
|
*.w.example.org. IN TXT "Wildcard"
|
||||||
a.b.c.w.example.org. IN TXT "Not a wildcard"
|
a.b.c.w.example.org. IN TXT "Not a wildcard"
|
||||||
cname.example.org. IN CNAME www.example.net.
|
cname.example.org. IN CNAME www.example.net.
|
||||||
|
|
||||||
service.example.org. IN SRV 8080 10 10 example.org.
|
service.example.org. IN SRV 8080 10 10 example.org.
|
||||||
`
|
`
|
||||||
|
|
|
@ -3,10 +3,6 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,10 +26,9 @@ func TestZoneExternalCNAMELookupWithoutProxy(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("cname.example.org.", dns.TypeA)
|
||||||
|
resp, err := dns.Exchange(m, udp)
|
||||||
resp, err := p.Lookup(state, "cname.example.org.", dns.TypeA)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected to receive reply, but didn't: %s", err)
|
t.Fatalf("Expected to receive reply, but didn't: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -52,11 +47,12 @@ func TestZoneExternalCNAMELookupWithProxy(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer rm()
|
defer rm()
|
||||||
|
|
||||||
// Corefile with for example without proxy section.
|
// Corefile with for example proxy section.
|
||||||
corefile := `example.org:0 {
|
corefile := `.:0 {
|
||||||
file ` + name + ` {
|
file ` + name + ` example.org {
|
||||||
upstream 8.8.8.8
|
upstream
|
||||||
}
|
}
|
||||||
|
proxy . 8.8.8.8 8.8.4.4
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
i, udp, _, err := CoreDNSServerAndPorts(corefile)
|
i, udp, _, err := CoreDNSServerAndPorts(corefile)
|
||||||
|
@ -65,10 +61,9 @@ func TestZoneExternalCNAMELookupWithProxy(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("cname.example.org.", dns.TypeA)
|
||||||
|
resp, err := dns.Exchange(m, udp)
|
||||||
resp, err := p.Lookup(state, "cname.example.org.", dns.TypeA)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected to receive reply, but didn't: %s", err)
|
t.Fatalf("Expected to receive reply, but didn't: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/file"
|
"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"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -39,10 +36,9 @@ example.net:0 {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("example.org.", dns.TypeA)
|
||||||
|
resp, err := dns.Exchange(m, udp)
|
||||||
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected to receive reply, but didn't: %s", err)
|
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
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
t.Fatal("Expected to receive reply, but didn't")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,6 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,10 +26,9 @@ func TestZoneSRVAdditional(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("service.example.org.", dns.TypeSRV)
|
||||||
|
resp, err := dns.Exchange(m, udp)
|
||||||
resp, err := p.Lookup(state, "service.example.org.", dns.TypeSRV)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Expected to receive reply, but didn't: %s", err)
|
t.Fatalf("Expected to receive reply, but didn't: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,6 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,10 +20,9 @@ func TestHostsInlineLookup(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("example.org.", dns.TypeA)
|
||||||
|
resp, err := dns.Exchange(m, udp)
|
||||||
resp, err := p.Lookup(state, "example.org.", dns.TypeA)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
t.Fatal("Expected to receive reply, but didn't")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,36 +3,9 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"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) {
|
func TestProxyThreeWay(t *testing.T) {
|
||||||
// Run 3 CoreDNS server, 2 upstream ones and a proxy. 1 Upstream is unhealthy after 1 query, but after
|
// 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.
|
// that we should still be able to send to the other one.
|
||||||
|
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,9 +3,7 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -29,46 +27,9 @@ func TestLookupProxy(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("example.org.", dns.TypeA)
|
||||||
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")
|
|
||||||
}
|
|
||||||
// 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
t.Fatal("Expected to receive reply, but didn't")
|
||||||
}
|
}
|
||||||
|
@ -108,13 +69,12 @@ func BenchmarkProxyLookup(b *testing.B) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("example.org.", dns.TypeA)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := p.Lookup(state, "example.org.", dns.TypeA)
|
if _, err := dns.Exchange(m, udp); err != nil {
|
||||||
if err != nil {
|
|
||||||
b.Fatal("Expected to receive reply, but didn't")
|
b.Fatal("Expected to receive reply, but didn't")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,6 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,9 +22,9 @@ func TestReverseCorefile(t *testing.T) {
|
||||||
t.Fatalf("Could not get UDP listening port")
|
t.Fatalf("Could not get UDP listening port")
|
||||||
}
|
}
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("17.0.0.10.in-addr.arpa.", dns.TypePTR)
|
||||||
resp, err := p.Lookup(state, "17.0.0.10.in-addr.arpa.", dns.TypePTR)
|
resp, err := dns.Exchange(m, udp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
t.Fatal("Expected to receive reply, but didn't")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,7 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -25,10 +23,9 @@ func TestEmptySecondaryZone(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
defer i.Stop()
|
||||||
|
|
||||||
p := proxy.NewLookup([]string{udp})
|
m := new(dns.Msg)
|
||||||
state := request.Request{W: &test.ResponseWriter{}, Req: new(dns.Msg)}
|
m.SetQuestion("www.example.org.", dns.TypeA)
|
||||||
|
resp, err := dns.Exchange(m, udp)
|
||||||
resp, err := p.Lookup(state, "www.example.org.", dns.TypeA)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't")
|
t.Fatal("Expected to receive reply, but didn't")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,7 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
|
||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
"github.com/coredns/coredns/request"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -29,11 +27,10 @@ func TestLookupWildcard(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer i.Stop()
|
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."} {
|
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 {
|
if err != nil || resp == nil {
|
||||||
t.Fatalf("Expected to receive reply, but didn't for %s", lookup)
|
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."} {
|
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 {
|
if err != nil || resp == nil {
|
||||||
t.Fatal("Expected to receive reply, but didn't", lookup)
|
t.Fatal("Expected to receive reply, but didn't", lookup)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue