From 47dceabfc6465ba6c5d41472d6602d4ad5c9fb1b Mon Sep 17 00:00:00 2001 From: Vancl Date: Fri, 24 Mar 2023 20:52:44 +0800 Subject: [PATCH] plugin/k8s_extenral: Supports fallthrough option (#5959) * Add fallthrough option to k8s_external plugin to allow transitioning control to the next plugin if the domain is not found * Exit on start up if required plugin is not present. Signed-off-by: vanceli --------- Signed-off-by: vanceli Co-authored-by: vanceli --- plugin/k8s_external/README.md | 23 ++++++++++++++++++++-- plugin/k8s_external/external.go | 10 ++++++---- plugin/k8s_external/setup.go | 23 +++++++++++++++------- plugin/k8s_external/setup_test.go | 32 ++++++++++++++++++++++--------- 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/plugin/k8s_external/README.md b/plugin/k8s_external/README.md index 1cf5eca71..893a13158 100644 --- a/plugin/k8s_external/README.md +++ b/plugin/k8s_external/README.md @@ -10,8 +10,7 @@ This plugin allows an additional zone to resolve the external IP address(es) of service and headless services. This plugin is only useful if the *kubernetes* plugin is also loaded. The plugin uses an external zone to resolve in-cluster IP addresses. It only handles queries for A, -AAAA, SRV, and PTR records; all others result in NODATA responses. To make it a proper DNS zone, it handles -SOA and NS queries for the apex of the zone. +AAAA, SRV, and PTR records; To make it a proper DNS zone, it handles SOA and NS queries for the apex of the zone. By default the apex of the zone will look like the following (assuming the zone used is `example.org`): @@ -67,6 +66,14 @@ k8s_external [ZONE...] { * if there is a headless service with external IPs set, external IPs will be resolved +If the queried domain does not exist, you can fall through to next plugin by adding the `fallthrough` option. + +~~~ +k8s_external [ZONE...] { + fallthrough [ZONE...] +} +~~~ + ## Examples Enable names under `example.org` to be resolved to in-cluster DNS addresses. @@ -106,6 +113,18 @@ zone transfers. Notifies are not supported. } ~~~ +With the `fallthrough` option, if the queried domain does not exist, it will be passed to the next plugin that matches the zone. + +~~~ +. { + kubernetes cluster.local + k8s_external example.org { + fallthrough + } + forward . 8.8.8.8 +} +~~~ + # See Also For some background see [resolve external IP address](https://github.com/kubernetes/dns/issues/242). diff --git a/plugin/k8s_external/external.go b/plugin/k8s_external/external.go index 2cbf88555..e56784ad7 100644 --- a/plugin/k8s_external/external.go +++ b/plugin/k8s_external/external.go @@ -16,6 +16,7 @@ import ( "github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin/etcd/msg" + "github.com/coredns/coredns/plugin/pkg/fall" "github.com/coredns/coredns/plugin/pkg/upstream" "github.com/coredns/coredns/request" @@ -39,6 +40,7 @@ type Externaler interface { type External struct { Next plugin.Handler Zones []string + Fall fall.F hostmaster string apex string @@ -68,10 +70,6 @@ func (e *External) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r) } - if e.externalFunc == nil { - return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r) - } - state.Zone = zone for _, z := range e.Zones { // TODO(miek): save this in the External struct. @@ -93,6 +91,10 @@ func (e *External) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms m.Authoritative = true if len(svc) == 0 { + if e.Fall.Through(state.Name()) && rcode == dns.RcodeNameError { + return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r) + } + m.Rcode = rcode m.Ns = []dns.RR{e.soa(state)} w.WriteMsg(m) diff --git a/plugin/k8s_external/setup.go b/plugin/k8s_external/setup.go index dbb1372e5..f42f7de23 100644 --- a/plugin/k8s_external/setup.go +++ b/plugin/k8s_external/setup.go @@ -1,6 +1,7 @@ package external import ( + "errors" "strconv" "github.com/coredns/caddy" @@ -9,7 +10,9 @@ import ( "github.com/coredns/coredns/plugin/pkg/upstream" ) -func init() { plugin.Register("k8s_external", setup) } +const pluginName = "k8s_external" + +func init() { plugin.Register(pluginName, setup) } func setup(c *caddy.Controller) error { e, err := parse(c) @@ -21,14 +24,18 @@ func setup(c *caddy.Controller) error { c.OnStartup(func() error { m := dnsserver.GetConfig(c).Handler("kubernetes") if m == nil { - return nil + return plugin.Error(pluginName, errors.New("kubernetes plugin not loaded")) } - if x, ok := m.(Externaler); ok { - e.externalFunc = x.External - e.externalAddrFunc = x.ExternalAddress - e.externalServicesFunc = x.ExternalServices - e.externalSerialFunc = x.ExternalSerial + + x, ok := m.(Externaler) + if !ok { + return plugin.Error(pluginName, errors.New("kubernetes plugin does not implement the Externaler interface")) } + + e.externalFunc = x.External + e.externalAddrFunc = x.ExternalAddress + e.externalServicesFunc = x.ExternalServices + e.externalSerialFunc = x.ExternalSerial return nil }) @@ -70,6 +77,8 @@ func parse(c *caddy.Controller) (*External, error) { e.apex = args[0] case "headless": e.headless = true + case "fallthrough": + e.Fall.SetZonesFromArgs(c.RemainingArgs()) default: return nil, c.Errf("unknown property '%s'", c.Val()) } diff --git a/plugin/k8s_external/setup_test.go b/plugin/k8s_external/setup_test.go index 351b35a4f..8814554ef 100644 --- a/plugin/k8s_external/setup_test.go +++ b/plugin/k8s_external/setup_test.go @@ -4,24 +4,33 @@ import ( "testing" "github.com/coredns/caddy" + "github.com/coredns/coredns/plugin/pkg/fall" ) func TestSetup(t *testing.T) { tests := []struct { - input string - shouldErr bool - expectedZone string - expectedApex string - expectedHeadless bool + input string + shouldErr bool + expectedZone string + expectedApex string + expectedHeadless bool + expectedFallthrough fall.F }{ - {`k8s_external`, false, "", "dns", false}, - {`k8s_external example.org`, false, "example.org.", "dns", false}, + {`k8s_external`, false, "", "dns", false, fall.Zero}, + {`k8s_external example.org`, false, "example.org.", "dns", false, fall.Zero}, {`k8s_external example.org { apex testdns -}`, false, "example.org.", "testdns", false}, +}`, false, "example.org.", "testdns", false, fall.Zero}, {`k8s_external example.org { headless -}`, false, "example.org.", "dns", true}, +}`, false, "example.org.", "dns", true, fall.Zero}, + {`k8s_external example.org { + fallthrough +}`, false, "example.org.", "dns", false, fall.Root}, + {`k8s_external example.org { + fallthrough ip6.arpa inaddr.arpa foo.com +}`, false, "example.org.", "dns", false, + fall.F{Zones: []string{"ip6.arpa.", "inaddr.arpa.", "foo.com."}}}, } for i, test := range tests { @@ -53,5 +62,10 @@ func TestSetup(t *testing.T) { t.Errorf("Test %d, expected headless %q for input %s, got: %v", i, test.expectedApex, test.input, e.headless) } } + if !test.shouldErr { + if !e.Fall.Equal(test.expectedFallthrough) { + t.Errorf("Test %d, expected to be initialized with fallthrough %q for input %s, got: %v", i, test.expectedFallthrough, test.input, e.Fall) + } + } } }