Commit 0919216d authored by Miek Gieben's avatar Miek Gieben

middleware/{file, auto}: resolve external CNAMEs

Do the same thing as in etcd and give the option of externally resolving
CNAME. This is needed when CoreDNS is a proxy as well is serving zones.
parent a8287bb0
...@@ -13,6 +13,8 @@ zonefile. New zones or changed zone are automatically picked up from disk. ...@@ -13,6 +13,8 @@ zonefile. New zones or changed zone are automatically picked up from disk.
~~~ ~~~
auto [ZONES...] { auto [ZONES...] {
directory DIR [REGEXP ORIGIN_TEMPLATE [TIMEOUT]] directory DIR [REGEXP ORIGIN_TEMPLATE [TIMEOUT]]
no_reload
upstream ADDRESS...
} }
~~~ ~~~
...@@ -26,6 +28,10 @@ are used. ...@@ -26,6 +28,10 @@ are used.
name `db.example.com`, the extracted origin will be `example.com`. **TIMEOUT** specifies how often name `db.example.com`, the extracted origin will be `example.com`. **TIMEOUT** specifies how often
CoreDNS should scan the directory, the default is every 60 seconds. This value is in seconds. CoreDNS should scan the directory, the default is every 60 seconds. This value is in seconds.
The minimum value is 1 second. The minimum value is 1 second.
* `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the
file. This option disables that behavior.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names.
All directives from the *file* middleware are supported. Note that *auto* will load all zones found, All directives from the *file* middleware 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:
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/file" "github.com/miekg/coredns/middleware/file"
"github.com/miekg/coredns/middleware/metrics" "github.com/miekg/coredns/middleware/metrics"
"github.com/miekg/coredns/middleware/proxy"
"github.com/miekg/coredns/request" "github.com/miekg/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
...@@ -33,6 +34,7 @@ type ( ...@@ -33,6 +34,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
noReload bool noReload bool
Proxy proxy.Proxy // Proxy for looking up names during the resolution process
duration time.Duration duration time.Duration
} }
......
...@@ -2,6 +2,7 @@ package auto ...@@ -2,6 +2,7 @@ package auto
import ( import (
"log" "log"
"net"
"os" "os"
"path" "path"
"regexp" "regexp"
...@@ -12,6 +13,7 @@ import ( ...@@ -12,6 +13,7 @@ import (
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/file" "github.com/miekg/coredns/middleware/file"
"github.com/miekg/coredns/middleware/metrics" "github.com/miekg/coredns/middleware/metrics"
"github.com/miekg/coredns/middleware/proxy"
"github.com/mholt/caddy" "github.com/mholt/caddy"
) )
...@@ -142,6 +144,19 @@ func autoParse(c *caddy.Controller) (Auto, error) { ...@@ -142,6 +144,19 @@ func autoParse(c *caddy.Controller) (Auto, error) {
case "no_reload": case "no_reload":
a.loader.noReload = true a.loader.noReload = true
case "upstream":
args := c.RemainingArgs()
if len(args) == 0 {
return a, false, c.ArgErr()
}
for i := 0; i < len(args); i++ {
h, p, e := net.SplitHostPort(args[i])
if e != nil && p == "" {
args[i] = h + ":53"
}
}
a.loader.Proxy = proxy.New(args)
default: default:
t, _, e := file.TransferParse(c, false) t, _, e := file.TransferParse(c, false)
if e != nil { if e != nil {
......
...@@ -35,8 +35,8 @@ etcd [ZONES...] { ...@@ -35,8 +35,8 @@ etcd [ZONES...] {
under the *first* zone specified. under the *first* zone specified.
* **PATH** the path inside etcd. Defaults to "/skydns". * **PATH** the path inside etcd. Defaults to "/skydns".
* **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2397". * **ENDPOINT** the etcd endpoints. Defaults to "http://localhost:2397".
* `upstream` upstream resolvers to be used resolve external names found in etcd (think CNAMEs) * `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names. If you want CoreDNS to act as a proxy for clients, you'll need to add pointing to external names. If you want CoreDNS also to act as a proxy for clients, you'll need to add
the proxy middleware. the proxy middleware.
* `tls` followed the cert, key and the CA's cert filenames. * `tls` followed the cert, key and the CA's cert filenames.
* `debug` allows for debug queries. Prefix the name with `o-o.debug.` to retrieve extra information in the * `debug` allows for debug queries. Prefix the name with `o-o.debug.` to retrieve extra information in the
......
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/mholt/caddy"
"github.com/miekg/coredns/middleware/etcd/msg" "github.com/miekg/coredns/middleware/etcd/msg"
"github.com/miekg/coredns/middleware/pkg/dnsrecorder" "github.com/miekg/coredns/middleware/pkg/dnsrecorder"
"github.com/miekg/coredns/middleware/pkg/singleflight" "github.com/miekg/coredns/middleware/pkg/singleflight"
...@@ -17,6 +16,7 @@ import ( ...@@ -17,6 +16,7 @@ import (
"github.com/miekg/coredns/middleware/test" "github.com/miekg/coredns/middleware/test"
etcdc "github.com/coreos/etcd/client" etcdc "github.com/coreos/etcd/client"
"github.com/mholt/caddy"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
......
...@@ -27,6 +27,7 @@ TSIG key information, something like `transfer out [ADDRESS...] key [NAME[:ALG]] ...@@ -27,6 +27,7 @@ TSIG key information, something like `transfer out [ADDRESS...] key [NAME[:ALG]]
file DBFILE [ZONES... ] { file DBFILE [ZONES... ] {
transfer to ADDRESS... transfer to ADDRESS...
no_reload no_reload
upstream ADDRESS...
} }
~~~ ~~~
...@@ -36,6 +37,8 @@ file DBFILE [ZONES... ] { ...@@ -36,6 +37,8 @@ file DBFILE [ZONES... ] {
When an address is specified a notify message will be send whenever the zone is reloaded. When an address is specified a notify message will be send whenever the zone is reloaded.
* `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the * `no_reload` by default CoreDNS will reload a zone from disk whenever it detects a change to the
file. This option disables that behavior. file. This option disables that behavior.
* `upstream` defines upstream resolvers to be used resolve external names found (think CNAMEs)
pointing to external names.
## Examples ## Examples
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/miekg/coredns/middleware/pkg/dnsrecorder" "github.com/miekg/coredns/middleware/pkg/dnsrecorder"
"github.com/miekg/coredns/middleware/proxy"
"github.com/miekg/coredns/middleware/test" "github.com/miekg/coredns/middleware/test"
"github.com/miekg/dns" "github.com/miekg/dns"
...@@ -68,6 +69,12 @@ var cnameTestCases = []test.Case{ ...@@ -68,6 +69,12 @@ var cnameTestCases = []test.Case{
test.CNAME("www3.example.org. 1800 IN CNAME www2.example.org."), 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, Qname: "www3.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{ Answer: []dns.RR{
...@@ -80,6 +87,59 @@ var cnameTestCases = []test.Case{ ...@@ -80,6 +87,59 @@ var cnameTestCases = []test.Case{
}, },
} }
func TestLookupCNAMEExternal(t *testing.T) {
name := "example.org."
zone, err := Parse(strings.NewReader(dbExampleCNAME), name, "stdin")
if err != nil {
t.Fatalf("Expected no error when reading zone, got %q", err)
}
zone.Proxy = proxy.New([]string{"8.8.8.8:53"}) // TODO(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 := dnsrecorder.New(&test.ResponseWriter{})
_, err := fm.ServeDNS(ctx, rec, m)
if err != nil {
t.Errorf("Expected no error, got %v\n", err)
return
}
resp := rec.Msg
sort.Sort(test.RRSet(resp.Answer))
sort.Sort(test.RRSet(resp.Ns))
sort.Sort(test.RRSet(resp.Extra))
if !test.Header(t, tc, resp) {
t.Logf("%v\n", resp)
continue
}
if !test.Section(t, tc, test.Answer, resp.Answer) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Ns, resp.Ns) {
t.Logf("%v\n", resp)
}
if !test.Section(t, tc, test.Extra, resp.Extra) {
t.Logf("%v\n", resp)
}
}
}
var exernalTestCases = []test.Case{
{
Qname: "external.example.org.", Qtype: dns.TypeA,
Answer: []dns.RR{
test.CNAME("external.example.org. 1800 CNAME www.example.net."),
},
},
}
const dbExampleCNAME = ` const dbExampleCNAME = `
$TTL 30M $TTL 30M
$ORIGIN example.org. $ORIGIN example.org.
...@@ -95,4 +155,5 @@ www3 IN CNAME www2 ...@@ -95,4 +155,5 @@ www3 IN CNAME www2
www2 IN CNAME www1 www2 IN CNAME www1
www1 IN CNAME www www1 IN CNAME www
www IN CNAME a www IN CNAME a
dangling IN CNAME foo` dangling IN CNAME foo
external IN CNAME www.example.net.`
...@@ -2,6 +2,7 @@ package file ...@@ -2,6 +2,7 @@ package file
import ( import (
"github.com/miekg/coredns/middleware/file/tree" "github.com/miekg/coredns/middleware/file/tree"
"github.com/miekg/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
...@@ -118,7 +119,7 @@ func (z *Zone) Lookup(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR, ...@@ -118,7 +119,7 @@ func (z *Zone) Lookup(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR,
// Found entire name. // Found entire name.
if found && shot { if found && shot {
// DNAME... // DNAME...?
if rrs := elem.Types(dns.TypeCNAME); len(rrs) > 0 && qtype != dns.TypeCNAME { if rrs := elem.Types(dns.TypeCNAME); len(rrs) > 0 && qtype != dns.TypeCNAME {
return z.searchCNAME(elem, rrs, qtype, do) return z.searchCNAME(elem, rrs, qtype, do)
} }
...@@ -260,8 +261,16 @@ func (z *Zone) searchCNAME(elem *tree.Elem, rrs []dns.RR, qtype uint16, do bool) ...@@ -260,8 +261,16 @@ func (z *Zone) searchCNAME(elem *tree.Elem, rrs []dns.RR, qtype uint16, do bool)
} }
} }
elem, _ = z.Tree.Search(rrs[0].(*dns.CNAME).Target) targetName := rrs[0].(*dns.CNAME).Target
elem, _ = z.Tree.Search(targetName)
println(targetName)
if elem == nil { if elem == nil {
if !dns.IsSubDomain(z.origin, targetName) {
println(targetName, "is not a child of", z.origin)
}
st := request.Request{}
z.Proxy.Lookup(st, targetName, qtype)
return rrs, nil, nil, Success return rrs, nil, nil, Success
} }
...@@ -279,8 +288,12 @@ Redo: ...@@ -279,8 +288,12 @@ Redo:
rrs = append(rrs, sigs...) rrs = append(rrs, sigs...)
} }
} }
elem, _ = z.Tree.Search(cname[0].(*dns.CNAME).Target) targetName := cname[0].(*dns.CNAME).Target
elem, _ = z.Tree.Search(targetName)
if elem == nil { if elem == nil {
if !dns.IsSubDomain(z.origin, targetName) {
println(targetName, "is not a child of", z.origin)
}
return rrs, nil, nil, Success return rrs, nil, nil, Success
} }
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/miekg/coredns/core/dnsserver" "github.com/miekg/coredns/core/dnsserver"
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/proxy"
"github.com/mholt/caddy" "github.com/mholt/caddy"
) )
...@@ -90,6 +91,7 @@ func fileParse(c *caddy.Controller) (Zones, error) { ...@@ -90,6 +91,7 @@ func fileParse(c *caddy.Controller) (Zones, error) {
} }
noReload := false noReload := false
prxy := proxy.Proxy{}
for c.NextBlock() { for c.NextBlock() {
t, _, e := TransferParse(c, false) t, _, e := TransferParse(c, false)
if e != nil { if e != nil {
...@@ -98,6 +100,19 @@ func fileParse(c *caddy.Controller) (Zones, error) { ...@@ -98,6 +100,19 @@ func fileParse(c *caddy.Controller) (Zones, error) {
switch c.Val() { switch c.Val() {
case "no_reload": case "no_reload":
noReload = true noReload = true
case "upstream":
args := c.RemainingArgs()
if len(args) == 0 {
return Zones{}, c.ArgErr()
}
for i := 0; i < len(args); i++ {
h, p, e := net.SplitHostPort(args[i])
if e != nil && p == "" {
args[i] = h + ":53"
}
}
prxy = proxy.New(args)
} }
for _, origin := range origins { for _, origin := range origins {
...@@ -105,6 +120,7 @@ func fileParse(c *caddy.Controller) (Zones, error) { ...@@ -105,6 +120,7 @@ func fileParse(c *caddy.Controller) (Zones, error) {
z[origin].TransferTo = append(z[origin].TransferTo, t...) z[origin].TransferTo = append(z[origin].TransferTo, t...)
} }
z[origin].NoReload = noReload z[origin].NoReload = noReload
z[origin].Proxy = prxy
} }
} }
} }
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"sync" "sync"
"github.com/miekg/coredns/middleware/file/tree" "github.com/miekg/coredns/middleware/file/tree"
"github.com/miekg/coredns/middleware/proxy"
"github.com/miekg/coredns/request" "github.com/miekg/coredns/request"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
...@@ -31,6 +32,7 @@ type Zone struct { ...@@ -31,6 +32,7 @@ type Zone struct {
NoReload bool NoReload bool
reloadMu sync.RWMutex reloadMu sync.RWMutex
ReloadShutdown chan bool ReloadShutdown chan bool
Proxy proxy.Proxy // Proxy for looking up 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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment