mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 01:34:21 -04:00 
			
		
		
		
	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:
		| @@ -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 | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user