Add pkg/fall for Fallthrough (#1355)
* Add pkg/fall for Fallthrough Move this into it's own package to facilitate tests. Important bug was fixed: make the names fully qualified. Add fall package to hosts, reverse, etcd, and fix kubernetes and any tests. The k8s tests are still as-is, might need a future cleanup.
This commit is contained in:
parent
84ebbbc722
commit
c6febe6250
22 changed files with 217 additions and 110 deletions
|
@ -41,15 +41,15 @@ We use the Unix manual page style:
|
||||||
|
|
||||||
### Example Domain Names
|
### Example Domain Names
|
||||||
|
|
||||||
Please be sure to use `example.org` or `example.net` in any examples you provide. These are the
|
Please be sure to use `example.org` or `example.net` in any examples and tests you provide. These
|
||||||
standard domain names created for this purpose.
|
are the standard domain names created for this purpose.
|
||||||
|
|
||||||
## Fallthrough
|
## Fallthrough
|
||||||
|
|
||||||
In a perfect world the following would be true for plugin: "Either you are responsible for a zone or
|
In a perfect world the following would be true for plugin: "Either you are responsible for a zone or
|
||||||
not". If the answer is "not", the plugin should call the next plugin in the chain. If "yes" it
|
not". If the answer is "not", the plugin should call the next plugin in the chain. If "yes" it
|
||||||
should handle *all* names that fall in this zone and the names below - i.e. it should handle the
|
should handle *all* names that fall in this zone and the names below - i.e. it should handle the
|
||||||
entire domain.
|
entire domain and all sub domains.
|
||||||
|
|
||||||
~~~ txt
|
~~~ txt
|
||||||
. {
|
. {
|
||||||
|
@ -61,7 +61,7 @@ In this example the *file* plugin is handling all names below (and including) `e
|
||||||
a query comes in that is not a subdomain (or equal to) `example.org` the next plugin is called.
|
a query comes in that is not a subdomain (or equal to) `example.org` the next plugin is called.
|
||||||
|
|
||||||
Now, the world isn't perfect, and there are good reasons to "fallthrough" to the next middlware,
|
Now, the world isn't perfect, and there are good reasons to "fallthrough" to the next middlware,
|
||||||
meaning a plugin is only responsible for a subset of names within the zone. The first of these
|
meaning a plugin is only responsible for a *subset* of names within the zone. The first of these
|
||||||
to appear was the *reverse* plugin that synthesis PTR and A/AAAA responses (useful with IPv6).
|
to appear was the *reverse* plugin that synthesis PTR and A/AAAA responses (useful with IPv6).
|
||||||
|
|
||||||
The nature of the *reverse* plugin is such that it only deals with A,AAAA and PTR and then only
|
The nature of the *reverse* plugin is such that it only deals with A,AAAA and PTR and then only
|
||||||
|
|
|
@ -29,7 +29,7 @@ If you want to `round robin` A and AAAA responses look at the `loadbalance` plug
|
||||||
~~~
|
~~~
|
||||||
etcd [ZONES...] {
|
etcd [ZONES...] {
|
||||||
stubzones
|
stubzones
|
||||||
fallthrough
|
fallthrough [ZONES...]
|
||||||
path PATH
|
path PATH
|
||||||
endpoint ENDPOINT...
|
endpoint ENDPOINT...
|
||||||
upstream ADDRESS...
|
upstream ADDRESS...
|
||||||
|
@ -40,6 +40,9 @@ etcd [ZONES...] {
|
||||||
* `stubzones` enables the stub zones feature. The stubzone is *only* done in the etcd tree located
|
* `stubzones` enables the stub zones feature. The stubzone is *only* done in the etcd tree located
|
||||||
under the *first* zone specified.
|
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
|
||||||
|
is authoritative. If specific zones are listed (for example `in-addr.arpa` and `ip6.arpa`), then only
|
||||||
|
queries for those zones will be subject to fallthrough.
|
||||||
* **PATH** the path inside etcd. Defaults to "/skydns".
|
* **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` upstream resolvers to be used resolve external names found in etcd (think CNAMEs)
|
||||||
|
|
|
@ -9,6 +9,7 @@ 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/proxy"
|
"github.com/coredns/coredns/plugin/proxy"
|
||||||
"github.com/coredns/coredns/request"
|
"github.com/coredns/coredns/request"
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ import (
|
||||||
// Etcd is a plugin talks to an etcd cluster.
|
// Etcd is a plugin talks to an etcd cluster.
|
||||||
type Etcd struct {
|
type Etcd struct {
|
||||||
Next plugin.Handler
|
Next plugin.Handler
|
||||||
Fallthrough bool
|
Fall *fall.F
|
||||||
Zones []string
|
Zones []string
|
||||||
PathPrefix string
|
PathPrefix string
|
||||||
Proxy proxy.Proxy // Proxy for looking up names during the resolution process
|
Proxy proxy.Proxy // Proxy for looking up names during the resolution process
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.IsNameError(err) {
|
if e.IsNameError(err) {
|
||||||
if e.Fallthrough {
|
if e.Fall.Through(state.Name()) {
|
||||||
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)
|
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)
|
||||||
}
|
}
|
||||||
// Make err nil when returning here, so we don't log spam for NXDOMAIN.
|
// Make err nil when returning here, so we don't log spam for NXDOMAIN.
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/etcd/msg"
|
"github.com/coredns/coredns/plugin/etcd/msg"
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
func TestMultiLookup(t *testing.T) {
|
func TestMultiLookup(t *testing.T) {
|
||||||
etc := newEtcdPlugin()
|
etc := newEtcdPlugin()
|
||||||
etc.Zones = []string{"skydns.test.", "miek.nl."}
|
etc.Zones = []string{"skydns.test.", "miek.nl."}
|
||||||
etc.Fallthrough = true
|
etc.Fall = fall.New()
|
||||||
etc.Next = test.ErrorHandler()
|
etc.Next = test.ErrorHandler()
|
||||||
|
|
||||||
for _, serv := range servicesMulti {
|
for _, serv := range servicesMulti {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
mwtls "github.com/coredns/coredns/plugin/pkg/tls"
|
mwtls "github.com/coredns/coredns/plugin/pkg/tls"
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
"github.com/coredns/coredns/plugin/proxy"
|
||||||
|
|
||||||
|
@ -73,7 +74,8 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
|
||||||
case "stubzones":
|
case "stubzones":
|
||||||
stubzones = true
|
stubzones = true
|
||||||
case "fallthrough":
|
case "fallthrough":
|
||||||
etc.Fallthrough = true
|
etc.Fall = fall.New()
|
||||||
|
etc.Fall.SetZones(c.RemainingArgs())
|
||||||
case "debug":
|
case "debug":
|
||||||
/* it is a noop now */
|
/* it is a noop now */
|
||||||
case "path":
|
case "path":
|
||||||
|
|
|
@ -16,7 +16,7 @@ available hosts files that block access to advertising servers.
|
||||||
~~~
|
~~~
|
||||||
hosts [FILE [ZONES...]] {
|
hosts [FILE [ZONES...]] {
|
||||||
[INLINE]
|
[INLINE]
|
||||||
fallthrough
|
fallthrough [ZONES...]
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
@ -29,6 +29,9 @@ hosts [FILE [ZONES...]] {
|
||||||
then all of them will be treated as the additional content for hosts file. The specified hosts
|
then all of them will be treated as the additional content for hosts file. The specified hosts
|
||||||
file path will still be read but entries will be overrided.
|
file path will still be read but entries will be overrided.
|
||||||
* `fallthrough` If zone matches and no record can be generated, pass request to the next plugin.
|
* `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.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
"github.com/coredns/coredns/request"
|
"github.com/coredns/coredns/request"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -16,7 +17,7 @@ type Hosts struct {
|
||||||
Next plugin.Handler
|
Next plugin.Handler
|
||||||
*Hostsfile
|
*Hostsfile
|
||||||
|
|
||||||
Fallthrough bool
|
Fall *fall.F
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeDNS implements the plugin.Handle interface.
|
// ServeDNS implements the plugin.Handle interface.
|
||||||
|
@ -52,7 +53,7 @@ func (h Hosts) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(answers) == 0 {
|
if len(answers) == 0 {
|
||||||
if h.Fallthrough {
|
if h.Fall.Through(qname) {
|
||||||
return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r)
|
return plugin.NextOrFailure(h.Name(), h.Next, ctx, w, r)
|
||||||
}
|
}
|
||||||
if !h.otherRecordsExist(state.QType(), qname) {
|
if !h.otherRecordsExist(state.QType(), qname) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
)
|
)
|
||||||
|
@ -105,14 +106,10 @@ func hostsParse(c *caddy.Controller) (Hosts, error) {
|
||||||
for c.NextBlock() {
|
for c.NextBlock() {
|
||||||
switch c.Val() {
|
switch c.Val() {
|
||||||
case "fallthrough":
|
case "fallthrough":
|
||||||
args := c.RemainingArgs()
|
h.Fall = fall.New()
|
||||||
if len(args) == 0 {
|
h.Fall.SetZones(c.RemainingArgs())
|
||||||
h.Fallthrough = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return h, c.ArgErr()
|
|
||||||
default:
|
default:
|
||||||
if !h.Fallthrough {
|
if h.Fall.IsNil() {
|
||||||
line := strings.Join(append([]string{c.Val()}, c.RemainingArgs()...), " ")
|
line := strings.Join(append([]string{c.Val()}, c.RemainingArgs()...), " ")
|
||||||
inline = append(inline, line)
|
inline = append(inline, line)
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -3,6 +3,8 @@ package hosts
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,48 +14,48 @@ func TestHostsParse(t *testing.T) {
|
||||||
shouldErr bool
|
shouldErr bool
|
||||||
expectedPath string
|
expectedPath string
|
||||||
expectedOrigins []string
|
expectedOrigins []string
|
||||||
expectedFallthrough bool
|
expectedFallthrough *fall.F
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
`hosts
|
`hosts
|
||||||
`,
|
`,
|
||||||
false, "/etc/hosts", nil, false,
|
false, "/etc/hosts", nil, nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`hosts /tmp`,
|
`hosts /tmp`,
|
||||||
false, "/tmp", nil, false,
|
false, "/tmp", nil, nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`hosts /etc/hosts miek.nl.`,
|
`hosts /etc/hosts miek.nl.`,
|
||||||
false, "/etc/hosts", []string{"miek.nl."}, false,
|
false, "/etc/hosts", []string{"miek.nl."}, nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`hosts /etc/hosts miek.nl. pun.gent.`,
|
`hosts /etc/hosts miek.nl. pun.gent.`,
|
||||||
false, "/etc/hosts", []string{"miek.nl.", "pun.gent."}, false,
|
false, "/etc/hosts", []string{"miek.nl.", "pun.gent."}, nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`hosts {
|
`hosts {
|
||||||
fallthrough
|
fallthrough
|
||||||
}`,
|
}`,
|
||||||
false, "/etc/hosts", nil, true,
|
false, "/etc/hosts", nil, fall.Zero(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`hosts /tmp {
|
`hosts /tmp {
|
||||||
fallthrough
|
fallthrough
|
||||||
}`,
|
}`,
|
||||||
false, "/tmp", nil, true,
|
false, "/tmp", nil, fall.Zero(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`hosts /etc/hosts miek.nl. {
|
`hosts /etc/hosts miek.nl. {
|
||||||
fallthrough
|
fallthrough
|
||||||
}`,
|
}`,
|
||||||
false, "/etc/hosts", []string{"miek.nl."}, true,
|
false, "/etc/hosts", []string{"miek.nl."}, fall.Zero(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`hosts /etc/hosts miek.nl 10.0.0.9/8 {
|
`hosts /etc/hosts miek.nl 10.0.0.9/8 {
|
||||||
fallthrough
|
fallthrough
|
||||||
}`,
|
}`,
|
||||||
false, "/etc/hosts", []string{"miek.nl.", "10.in-addr.arpa."}, true,
|
false, "/etc/hosts", []string{"miek.nl.", "10.in-addr.arpa."}, fall.Zero(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,8 +72,8 @@ func TestHostsParse(t *testing.T) {
|
||||||
t.Fatalf("Test %d expected %v, got %v", i, test.expectedPath, h.path)
|
t.Fatalf("Test %d expected %v, got %v", i, test.expectedPath, h.path)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if h.Fallthrough != test.expectedFallthrough {
|
if !h.Fall.Equal(test.expectedFallthrough) {
|
||||||
t.Fatalf("Test %d expected fallthrough of %v, got %v", i, test.expectedFallthrough, h.Fallthrough)
|
t.Fatalf("Test %d expected fallthrough of %v, got %v", i, test.expectedFallthrough, h.Fall)
|
||||||
}
|
}
|
||||||
if len(h.Origins) != len(test.expectedOrigins) {
|
if len(h.Origins) != len(test.expectedOrigins) {
|
||||||
t.Fatalf("Test %d expected %v, got %v", i, test.expectedOrigins, h.Origins)
|
t.Fatalf("Test %d expected %v, got %v", i, test.expectedOrigins, h.Origins)
|
||||||
|
@ -90,7 +92,7 @@ func TestHostsInlineParse(t *testing.T) {
|
||||||
inputFileRules string
|
inputFileRules string
|
||||||
shouldErr bool
|
shouldErr bool
|
||||||
expectedbyAddr map[string][]string
|
expectedbyAddr map[string][]string
|
||||||
expectedFallthrough bool
|
expectedFallthrough *fall.F
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
`hosts highly_unlikely_to_exist_hosts_file example.org {
|
`hosts highly_unlikely_to_exist_hosts_file example.org {
|
||||||
|
@ -103,7 +105,7 @@ func TestHostsInlineParse(t *testing.T) {
|
||||||
`example.org.`,
|
`example.org.`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
true,
|
fall.Zero(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`hosts highly_unlikely_to_exist_hosts_file example.org {
|
`hosts highly_unlikely_to_exist_hosts_file example.org {
|
||||||
|
@ -115,7 +117,7 @@ func TestHostsInlineParse(t *testing.T) {
|
||||||
`example.org.`,
|
`example.org.`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
false,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`hosts highly_unlikely_to_exist_hosts_file example.org {
|
`hosts highly_unlikely_to_exist_hosts_file example.org {
|
||||||
|
@ -124,7 +126,7 @@ func TestHostsInlineParse(t *testing.T) {
|
||||||
}`,
|
}`,
|
||||||
true,
|
true,
|
||||||
map[string][]string{},
|
map[string][]string{},
|
||||||
true,
|
fall.Zero(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,8 +139,8 @@ func TestHostsInlineParse(t *testing.T) {
|
||||||
} else if err != nil && !test.shouldErr {
|
} else if err != nil && !test.shouldErr {
|
||||||
t.Fatalf("Test %d expected no errors, but got '%v'", i, err)
|
t.Fatalf("Test %d expected no errors, but got '%v'", i, err)
|
||||||
} else if !test.shouldErr {
|
} else if !test.shouldErr {
|
||||||
if h.Fallthrough != test.expectedFallthrough {
|
if !h.Fall.Equal(test.expectedFallthrough) {
|
||||||
t.Fatalf("Test %d expected fallthrough of %v, got %v", i, test.expectedFallthrough, h.Fallthrough)
|
t.Fatalf("Test %d expected fallthrough of %v, got %v", i, test.expectedFallthrough, h.Fall)
|
||||||
}
|
}
|
||||||
for k, expectedVal := range test.expectedbyAddr {
|
for k, expectedVal := range test.expectedbyAddr {
|
||||||
if val, ok := h.hmap.byAddr[k]; !ok {
|
if val, ok := h.hmap.byAddr[k]; !ok {
|
||||||
|
|
|
@ -59,7 +59,7 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
|
||||||
}
|
}
|
||||||
|
|
||||||
if k.IsNameError(err) {
|
if k.IsNameError(err) {
|
||||||
if plugin.Fallthrough(k.Fallthrough, state.Name()) {
|
if k.Fall.Through(state.Name()) {
|
||||||
return plugin.NextOrFailure(k.Name(), k.Next, ctx, w, r)
|
return plugin.NextOrFailure(k.Name(), k.Next, ctx, w, r)
|
||||||
}
|
}
|
||||||
return plugin.BackendError(&k, zone, dns.RcodeNameError, state, nil /* err */, plugin.Options{})
|
return plugin.BackendError(&k, zone, dns.RcodeNameError, state, nil /* err */, plugin.Options{})
|
||||||
|
|
|
@ -12,6 +12,7 @@ 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/dnsutil"
|
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
"github.com/coredns/coredns/plugin/pkg/healthcheck"
|
"github.com/coredns/coredns/plugin/pkg/healthcheck"
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
"github.com/coredns/coredns/plugin/proxy"
|
||||||
"github.com/coredns/coredns/request"
|
"github.com/coredns/coredns/request"
|
||||||
|
@ -40,7 +41,7 @@ type Kubernetes struct {
|
||||||
Namespaces map[string]bool
|
Namespaces map[string]bool
|
||||||
podMode string
|
podMode string
|
||||||
endpointNameMode bool
|
endpointNameMode bool
|
||||||
Fallthrough *[]string // nil = disabled, empty = all zones, o/w zones
|
Fall *fall.F
|
||||||
ttl uint32
|
ttl uint32
|
||||||
|
|
||||||
primaryZoneIndex int
|
primaryZoneIndex int
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
"github.com/coredns/coredns/plugin/proxy"
|
"github.com/coredns/coredns/plugin/proxy"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
|
@ -172,8 +173,8 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, dnsControlOpts, error) {
|
||||||
}
|
}
|
||||||
return nil, opts, c.ArgErr()
|
return nil, opts, c.ArgErr()
|
||||||
case "fallthrough":
|
case "fallthrough":
|
||||||
zones := c.RemainingArgs()
|
k8s.Fall = fall.New()
|
||||||
k8s.Fallthrough = &zones
|
k8s.Fall.SetZones(c.RemainingArgs())
|
||||||
case "upstream":
|
case "upstream":
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
|
|
|
@ -347,7 +347,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
defaultResyncPeriod,
|
defaultResyncPeriod,
|
||||||
"",
|
"",
|
||||||
podModeDisabled,
|
podModeDisabled,
|
||||||
&[]string{"ip6.arpa", "inaddr.arpa", "foo.com"},
|
&[]string{"ip6.arpa.", "inaddr.arpa.", "foo.com."},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
// Valid upstream
|
// Valid upstream
|
||||||
|
@ -443,7 +443,7 @@ func TestKubernetesParse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallthrough
|
// fallthrough
|
||||||
foundFallthrough := k8sController.Fallthrough
|
foundFallthrough := k8sController.Fall
|
||||||
if foundFallthrough != nil {
|
if foundFallthrough != nil {
|
||||||
failed := false
|
failed := false
|
||||||
if test.expectedFallthrough == nil {
|
if test.expectedFallthrough == nil {
|
||||||
|
|
77
plugin/pkg/fall/fall.go
Normal file
77
plugin/pkg/fall/fall.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// Package fall handles the fallthrough logic used in plugins that support it.
|
||||||
|
package fall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/coredns/coredns/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// F can be nil to allow for no fallthrough, empty allow all zones to fallthrough or
|
||||||
|
// contain a zone list that is checked.
|
||||||
|
type F []string
|
||||||
|
|
||||||
|
// New returns a new F.
|
||||||
|
func New() *F { return new(F) }
|
||||||
|
|
||||||
|
// Through will check if we should fallthrough for qname. Note that we've named the
|
||||||
|
// variable in each plugin "Fall", so this then reads Fall.Through().
|
||||||
|
func (f *F) Through(qname string) bool {
|
||||||
|
if f == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(*f) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
zone := plugin.Zones(*f).Matches(qname)
|
||||||
|
return zone != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetZones will set zones in f.
|
||||||
|
func (f *F) SetZones(zones []string) {
|
||||||
|
for i := range zones {
|
||||||
|
zones[i] = plugin.Host(zones[i]).Normalize()
|
||||||
|
}
|
||||||
|
*f = zones
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example returns an F with example.org. as the zone name.
|
||||||
|
var Example = func() *F {
|
||||||
|
f := F([]string{"example.org."})
|
||||||
|
return &f
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Zero returns a zero valued F.
|
||||||
|
var Zero = func() *F {
|
||||||
|
f := F([]string{})
|
||||||
|
return &f
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil returns true is f is nil.
|
||||||
|
func (f *F) IsNil() bool { return f == nil }
|
||||||
|
|
||||||
|
// IsZero returns true is f is zero (and not nil).
|
||||||
|
func (f *F) IsZero() bool {
|
||||||
|
if f == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(*f) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if f and g are equal. Only useful in tests, The (possible) zones
|
||||||
|
// are *not* checked.
|
||||||
|
func (f *F) Equal(g *F) bool {
|
||||||
|
if f.IsNil() {
|
||||||
|
if g.IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if f.IsZero() {
|
||||||
|
if g.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(*f) != len(*g) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
48
plugin/pkg/fall/fall_test.go
Normal file
48
plugin/pkg/fall/fall_test.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package fall
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIsNil(t *testing.T) {
|
||||||
|
var f *F
|
||||||
|
if !f.IsNil() {
|
||||||
|
t.Errorf("F should be nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsZero(t *testing.T) {
|
||||||
|
f := New()
|
||||||
|
if !f.IsZero() {
|
||||||
|
t.Errorf("F should be zero")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFallThroughExample(t *testing.T) {
|
||||||
|
if !Example.Through("example.org.") {
|
||||||
|
t.Errorf("example.org. should fall through")
|
||||||
|
}
|
||||||
|
if Example.Through("example.net.") {
|
||||||
|
t.Errorf("example.net. should not fall through")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFallthrough(t *testing.T) {
|
||||||
|
var fall *F
|
||||||
|
if fall.Through("foo.com.") {
|
||||||
|
t.Errorf("Expected false, got true for nil fallthrough")
|
||||||
|
}
|
||||||
|
|
||||||
|
fall = New()
|
||||||
|
if !fall.Through("foo.net.") {
|
||||||
|
t.Errorf("Expected true, got false for all zone fallthrough")
|
||||||
|
}
|
||||||
|
|
||||||
|
fall.SetZones([]string{"foo.com", "bar.com"})
|
||||||
|
|
||||||
|
if fall.Through("foo.net.") {
|
||||||
|
t.Errorf("Expected false, got true for non-matching fallthrough zone")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fall.Through("bar.com.") {
|
||||||
|
t.Errorf("Expected true, got false for matching fallthrough zone")
|
||||||
|
}
|
||||||
|
}
|
|
@ -83,21 +83,6 @@ func NextOrFailure(name string, next Handler, ctx context.Context, w dns.Respons
|
||||||
return dns.RcodeServerFailure, Error(name, errors.New("no next plugin found"))
|
return dns.RcodeServerFailure, Error(name, errors.New("no next plugin found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallthrough handles the fallthrough logic used in plugins that support it
|
|
||||||
func Fallthrough(ftzones *[]string, qname string) bool {
|
|
||||||
if ftzones == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(*ftzones) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
zone := Zones(*ftzones).Matches(qname)
|
|
||||||
if zone != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientWrite returns true if the response has been written to the client.
|
// ClientWrite returns true if the response has been written to the client.
|
||||||
// Each plugin to adhire to this protocol.
|
// Each plugin to adhire to this protocol.
|
||||||
func ClientWrite(rcode int) bool {
|
func ClientWrite(rcode int) bool {
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
package plugin
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestFallthrough(t *testing.T) {
|
|
||||||
if Fallthrough(nil, "foo.com.") {
|
|
||||||
t.Errorf("Expected false, got true for nil fallthrough")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !Fallthrough(&[]string{}, "foo.net.") {
|
|
||||||
t.Errorf("Expected true, got false for all zone fallthrough")
|
|
||||||
}
|
|
||||||
|
|
||||||
if Fallthrough(&[]string{"foo.com", "bar.com"}, "foo.net") {
|
|
||||||
t.Errorf("Expected false, got true for non-matching fallthrough zone")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !Fallthrough(&[]string{"foo.com.", "bar.com."}, "bar.com.") {
|
|
||||||
t.Errorf("Expected true, got false for matching fallthrough zone")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,7 @@ response. This is only done for "address" records (PTR, A and AAAA).
|
||||||
reverse NETWORK... {
|
reverse NETWORK... {
|
||||||
hostname TEMPLATE
|
hostname TEMPLATE
|
||||||
[ttl TTL]
|
[ttl TTL]
|
||||||
[fallthrough]
|
[fallthrough [ZONES...]]
|
||||||
[wildcard]
|
[wildcard]
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ reverse NETWORK... {
|
||||||
* `hostname` injects the IP and zone to a template for the hostname. Defaults to "ip-{IP}.{zone[1]}". See below for template.
|
* `hostname` injects the IP and zone to a template for the hostname. Defaults to "ip-{IP}.{zone[1]}". See below for template.
|
||||||
* `ttl` defaults to 60
|
* `ttl` defaults to 60
|
||||||
* `fallthrough` if zone matches and no record can be generated, pass request to the next plugin.
|
* `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.
|
||||||
* `wildcard` allows matches to catch all subdomains as well.
|
* `wildcard` allows matches to catch all subdomains as well.
|
||||||
|
|
||||||
### Template Syntax
|
### Template Syntax
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
"github.com/coredns/coredns/request"
|
"github.com/coredns/coredns/request"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -15,7 +16,8 @@ import (
|
||||||
type Reverse struct {
|
type Reverse struct {
|
||||||
Next plugin.Handler
|
Next plugin.Handler
|
||||||
Networks networks
|
Networks networks
|
||||||
Fallthrough bool
|
|
||||||
|
Fall *fall.F
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeDNS implements the plugin.Handler interface.
|
// ServeDNS implements the plugin.Handler interface.
|
||||||
|
@ -97,7 +99,7 @@ func (re Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
|
||||||
return dns.RcodeSuccess, nil
|
return dns.RcodeSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if re.Fallthrough {
|
if re.Fall.Through(state.Name()) {
|
||||||
return plugin.NextOrFailure(re.Name(), re.Next, ctx, w, r)
|
return plugin.NextOrFailure(re.Name(), re.Next, ctx, w, r)
|
||||||
}
|
}
|
||||||
return dns.RcodeServerFailure, nil
|
return dns.RcodeServerFailure, nil
|
||||||
|
|
|
@ -24,7 +24,6 @@ func TestReverse(t *testing.T) {
|
||||||
Template: "ip-{ip}.example.org.",
|
Template: "ip-{ip}.example.org.",
|
||||||
RegexMatchIP: regexIP4,
|
RegexMatchIP: regexIP4,
|
||||||
}},
|
}},
|
||||||
Fallthrough: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
)
|
)
|
||||||
|
@ -21,19 +22,19 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupReverse(c *caddy.Controller) error {
|
func setupReverse(c *caddy.Controller) error {
|
||||||
networks, fallThrough, err := reverseParse(c)
|
networks, fall, err := reverseParse(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return plugin.Error("reverse", err)
|
return plugin.Error("reverse", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
||||||
return Reverse{Next: next, Networks: networks, Fallthrough: fallThrough}
|
return Reverse{Next: next, Networks: networks, Fall: fall}
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) {
|
func reverseParse(c *caddy.Controller) (nets networks, f *fall.F, err error) {
|
||||||
zones := make([]string, len(c.ServerBlockKeys))
|
zones := make([]string, len(c.ServerBlockKeys))
|
||||||
wildcard := false
|
wildcard := false
|
||||||
|
|
||||||
|
@ -52,12 +53,12 @@ func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) {
|
||||||
}
|
}
|
||||||
_, ipnet, err := net.ParseCIDR(cidr)
|
_, ipnet, err := net.ParseCIDR(cidr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, c.Errf("network needs to be CIDR formatted: %q\n", cidr)
|
return nil, f, c.Errf("network needs to be CIDR formatted: %q\n", cidr)
|
||||||
}
|
}
|
||||||
cidrs = append(cidrs, ipnet)
|
cidrs = append(cidrs, ipnet)
|
||||||
}
|
}
|
||||||
if len(cidrs) == 0 {
|
if len(cidrs) == 0 {
|
||||||
return nil, false, c.ArgErr()
|
return nil, f, c.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
// set defaults
|
// set defaults
|
||||||
|
@ -69,27 +70,28 @@ func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) {
|
||||||
switch c.Val() {
|
switch c.Val() {
|
||||||
case "hostname":
|
case "hostname":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, false, c.ArgErr()
|
return nil, f, c.ArgErr()
|
||||||
}
|
}
|
||||||
template = c.Val()
|
template = c.Val()
|
||||||
|
|
||||||
case "ttl":
|
case "ttl":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, false, c.ArgErr()
|
return nil, f, c.ArgErr()
|
||||||
}
|
}
|
||||||
ttl, err = strconv.Atoi(c.Val())
|
ttl, err = strconv.Atoi(c.Val())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
case "wildcard":
|
case "wildcard":
|
||||||
wildcard = true
|
wildcard = true
|
||||||
|
|
||||||
case "fallthrough":
|
case "fallthrough":
|
||||||
fall = true
|
f = fall.New()
|
||||||
|
f.SetZones(c.RemainingArgs())
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, false, c.ArgErr()
|
return nil, f, c.ArgErr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +109,7 @@ func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) {
|
||||||
// extract zone from template
|
// extract zone from template
|
||||||
templateZone := strings.SplitAfterN(template, ".", 2)
|
templateZone := strings.SplitAfterN(template, ".", 2)
|
||||||
if len(templateZone) != 2 || templateZone[1] == "" {
|
if len(templateZone) != 2 || templateZone[1] == "" {
|
||||||
return nil, false, c.Errf("cannot find domain in template '%v'", template)
|
return nil, f, c.Errf("cannot find domain in template '%v'", template)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create for each configured network in this stanza
|
// Create for each configured network in this stanza
|
||||||
|
@ -128,7 +130,7 @@ func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) {
|
||||||
regexIP,
|
regexIP,
|
||||||
1) + "$")
|
1) + "$")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
nets = append(nets, network{
|
nets = append(nets, network{
|
||||||
|
@ -143,5 +145,5 @@ func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) {
|
||||||
|
|
||||||
// sort by cidr
|
// sort by cidr
|
||||||
sort.Sort(nets)
|
sort.Sort(nets)
|
||||||
return nets, fall, nil
|
return nets, f, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue