Skip to content

Commit

Permalink
Merge pull request #37 from cubic3d/feat-root-zone-handling
Browse files Browse the repository at this point in the history
Allow root zone queries and fall through
  • Loading branch information
networkop authored Jul 23, 2021
2 parents 1caa37f + f17f8cd commit 3f596a2
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 66 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ coredns
tilt_modules
.helm
.vscode
.idea
28 changes: 0 additions & 28 deletions apex.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,6 @@ import (
"github.com/miekg/dns"
)

// serveApex serves request that hit the zone' apex. A reply is written back to the client.
func (gw *Gateway) serveApex(state request.Request) (int, error) {
m := new(dns.Msg)
m.SetReply(state.Req)
switch state.QType() {
case dns.TypeSOA:
// Force to true to fix broken behaviour of legacy glibc `getaddrinfo`.
// See https://github.com/coredns/coredns/pull/3573
m.Authoritative = true
m.Answer = []dns.RR{gw.soa(state)}
case dns.TypeNS:
m.Answer = gw.nameservers(state)

addr := gw.ExternalAddrFunc(state)
for _, rr := range addr {
rr.Header().Ttl = gw.ttlSOA
m.Extra = append(m.Extra, rr)
}
default:
m.Ns = []dns.RR{gw.soa(state)}
}

if err := state.W.WriteMsg(m); err != nil {
log.Errorf("Failed to send a response: %s", err)
}
return 0, nil
}

// serveSubApex serves requests that hit the zones fake 'dns' subdomain where our nameservers live.
func (gw *Gateway) serveSubApex(state request.Request) (int, error) {
base, _ := dnsutil.TrimZone(state.Name(), state.Zone)
Expand Down
14 changes: 13 additions & 1 deletion apex_dual_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gateway

import (
"context"
"net"
"testing"

"github.com/coredns/coredns/plugin/pkg/dnstest"
Expand All @@ -11,6 +12,16 @@ import (
"github.com/miekg/dns"
)

func setupEmptyLookupFuncs() {

if resource := lookupResource("Ingress"); resource != nil {
resource.lookup = func(_ []string) []net.IP { return []net.IP{} }
}
if resource := lookupResource("Service"); resource != nil {
resource.lookup = func(_ []string) []net.IP { return []net.IP{} }
}
}

func TestDualNS(t *testing.T) {

ctrl := &KubeController{hasSynced: true}
Expand All @@ -20,6 +31,7 @@ func TestDualNS(t *testing.T) {
gw.Controller = ctrl
gw.ExternalAddrFunc = selfDualAddressTest
gw.secondNS = "dns2.kube-system"
setupEmptyLookupFuncs()

ctx := context.TODO()
for i, tc := range testsDualNS {
Expand Down Expand Up @@ -49,7 +61,7 @@ var testsDualNS = []test.Case{
{
Qname: "example.com.", Qtype: dns.TypeSOA,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
Ns: []dns.RR{
test.SOA("example.com. 60 IN SOA dns1.kube-system.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
Expand Down
3 changes: 2 additions & 1 deletion apex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func TestApex(t *testing.T) {
gw.Next = test.NextHandler(dns.RcodeSuccess, nil)
gw.Controller = ctrl
gw.ExternalAddrFunc = selfAddressTest
setupEmptyLookupFuncs()

ctx := context.TODO()
for i, tc := range testsApex {
Expand Down Expand Up @@ -48,7 +49,7 @@ var testsApex = []test.Case{
{
Qname: "example.com.", Qtype: dns.TypeSOA,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
Ns: []dns.RR{
test.SOA("example.com. 60 IN SOA dns1.kube-system.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
Expand Down
61 changes: 39 additions & 22 deletions gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,11 @@ func (gw *Gateway) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
return dns.RcodeServerFailure, plugin.Error(thisPlugin, fmt.Errorf("Could not sync required resources"))
}

var isRootZoneQuery bool
for _, z := range gw.Zones {
if state.Name() == z { // apex query
ret, err := gw.serveApex(state)
return ret, err
isRootZoneQuery = true
break
}
if dns.IsSubDomain(gw.apex+"."+z, state.Name()) {
// dns subdomain test for ns. and dns. queries
Expand All @@ -142,39 +143,55 @@ func (gw *Gateway) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
}
log.Debugf("Computed response addresses %v", addrs)

// Fall through if no host matches
if len(addrs) == 0 && gw.Fall.Through(qname) {
return plugin.NextOrFailure(gw.Name(), gw.Next, ctx, w, r)
}

m := new(dns.Msg)
m.SetReply(state.Req)

// If there's no match, fall through or return NXDOMAIN
if len(addrs) == 0 {
if gw.Fall.Through(qname) {
return plugin.NextOrFailure(gw.Name(), gw.Next, ctx, w, r)
}
switch state.QType() {
case dns.TypeA:

m.Rcode = dns.RcodeNameError
m.Ns = []dns.RR{gw.soa(state)}
if err := w.WriteMsg(m); err != nil {
log.Errorf("Failed to send a response: %s", err)
if len(addrs) == 0 {

if !isRootZoneQuery {
// No match, return NXDOMAIN
m.Rcode = dns.RcodeNameError
}

m.Ns = []dns.RR{gw.soa(state)}

} else {

m.Answer = gw.A(state.Name(), addrs)
// Force to true to fix broken behaviour of legacy glibc `getaddrinfo`.
// See https://github.com/coredns/coredns/pull/3573
m.Authoritative = true
}
return 0, nil
}
case dns.TypeSOA:

switch state.QType() {
case dns.TypeA:
m.Answer = gw.A(state.Name(), addrs)
// Force to true to fix broken behaviour of legacy glibc `getaddrinfo`.
// See https://github.com/coredns/coredns/pull/3573
m.Authoritative = true
default:
m.Ns = []dns.RR{gw.soa(state)}
}

// If there's no match, fall through or return the SOA
if len(m.Answer) == 0 {
if gw.Fall.Through(qname) {
return plugin.NextOrFailure(gw.Name(), gw.Next, ctx, w, r)
case dns.TypeNS:

if isRootZoneQuery {
m.Answer = gw.nameservers(state)

addr := gw.ExternalAddrFunc(state)
for _, rr := range addr {
rr.Header().Ttl = gw.ttlSOA
m.Extra = append(m.Extra, rr)
}
} else {
m.Ns = []dns.RR{gw.soa(state)}
}

default:
m.Ns = []dns.RR{gw.soa(state)}
}

Expand Down
96 changes: 82 additions & 14 deletions gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,29 @@ package gateway

import (
"context"
"errors"
"net"
"strings"
"testing"

"github.com/coredns/coredns/plugin/pkg/fall"

"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test"

"github.com/miekg/dns"
)

type FallthroughCase struct {
test.Case
FallthroughZones []string
FallthroughExpected bool
}

type Fallen struct {
error
}

func TestLookup(t *testing.T) {
real := []string{"Ingress", "Service"}
fake := []string{"Gateway", "Pod"}
Expand Down Expand Up @@ -64,78 +77,105 @@ func TestGateway(t *testing.T) {
}
}

func TestGatewayFallthrough(t *testing.T) {

ctrl := &KubeController{hasSynced: true}
gw := newGateway()
gw.Zones = []string{"example.com."}
gw.Next = test.NextHandler(dns.RcodeSuccess, Fallen{})
gw.ExternalAddrFunc = selfAddressTest
gw.Controller = ctrl
setupTestLookupFuncs()

ctx := context.TODO()
for i, tc := range testsFallthrough {
r := tc.Msg()
w := dnstest.NewRecorder(&test.ResponseWriter{})

gw.Fall = fall.F{Zones: tc.FallthroughZones}
_, err := gw.ServeDNS(ctx, w, r)

if errors.As(err, &Fallen{}) && !tc.FallthroughExpected {
t.Fatalf("Test %d query resulted unexpectidly in a fall through instead of a response", i)
}
if err == nil && tc.FallthroughExpected {
t.Fatalf("Test %d query resulted unexpectidly in a response instead of a fall through", i)
}
}
}

var tests = []test.Case{
// Existing Service
// Existing Service | Test 0
{
Qname: "svc1.ns1.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.A("svc1.ns1.example.com. 60 IN A 192.0.1.1"),
},
},
// Existing Ingress
// Existing Ingress | Test 1
{
Qname: "domain.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.A("domain.example.com. 60 IN A 192.0.0.1"),
},
},
// Ingress takes precedence over services
// Ingress takes precedence over services | Test 2
{
Qname: "svc2.ns1.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.A("svc2.ns1.example.com. 60 IN A 192.0.0.2"),
},
},
// Non-existing Service
// Non-existing Service | Test 3
{
Qname: "svcX.ns1.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("example.com. 60 IN SOA dns1.kube-system.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
// Non-existing Ingress
// Non-existing Ingress | Test 4
{
Qname: "d0main.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("example.com. 60 IN SOA dns1.kube-system.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
// SOA for the existing domain
// SOA for the existing domain | Test 5
{
Qname: "domain.example.com.", Qtype: dns.TypeSOA, Rcode: dns.RcodeSuccess,
Ns: []dns.RR{
test.SOA("example.com. 60 IN SOA dns1.kube-system.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
// Service with no public addresses
// Service with no public addresses | Test 6
{
Qname: "svc3.ns1.example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeNameError,
Ns: []dns.RR{
test.SOA("example.com. 60 IN SOA dns1.kube-system.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
// Real service, wrong query type
// Real service, wrong query type | Test 7
{
Qname: "svc3.ns1.example.com.", Qtype: dns.TypeAAAA, Rcode: dns.RcodeNameError,
Qname: "svc3.ns1.example.com.", Qtype: dns.TypeCNAME, Rcode: dns.RcodeSuccess,
Ns: []dns.RR{
test.SOA("example.com. 60 IN SOA dns1.kube-system.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
},
},
// Ingress FQDN == zone
// Ingress FQDN == zone | Test 8
{
Qname: "example.com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
Ns: []dns.RR{
test.SOA("example.com. 60 IN SOA dns1.kube-system.example.com. hostmaster.example.com. 1499347823 7200 1800 86400 5"),
Answer: []dns.RR{
test.A("example.com. 60 IN A 192.0.0.3"),
},
},
// Existing Ingress with a mix of lower and upper case letters
// Existing Ingress with a mix of lower and upper case letters | Test 9
{
Qname: "dOmAiN.eXamPLe.cOm.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.A("domain.example.com. 60 IN A 192.0.0.1"),
},
},
// Existing Service with a mix of lower and upper case letters
// Existing Service with a mix of lower and upper case letters | Test 10
{
Qname: "svC1.Ns1.exAmplE.Com.", Qtype: dns.TypeA, Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
Expand All @@ -144,6 +184,34 @@ var tests = []test.Case{
},
}

var testsFallthrough = []FallthroughCase{
// Match found, fallthrough enabled | Test 0
{
Case: test.Case{Qname: "example.com.", Qtype: dns.TypeA},
FallthroughZones: []string{"."}, FallthroughExpected: false,
},
// No match found, fallthrough enabled | Test 1
{
Case: test.Case{Qname: "non-existent.example.com.", Qtype: dns.TypeA},
FallthroughZones: []string{"."}, FallthroughExpected: true,
},
// Match found, fallthrough for different zone | Test 2
{
Case: test.Case{Qname: "example.com.", Qtype: dns.TypeA},
FallthroughZones: []string{"not-example.com."}, FallthroughExpected: false,
},
// No match found, fallthrough for different zone | Test 3
{
Case: test.Case{Qname: "non-existent.example.com.", Qtype: dns.TypeA},
FallthroughZones: []string{"not-example.com."}, FallthroughExpected: false,
},
// No fallthrough on gw apex | Test 4
{
Case: test.Case{Qname: "dns1.kube-system.example.com.", Qtype: dns.TypeA},
FallthroughZones: []string{"."}, FallthroughExpected: false,
},
}

var testServiceIndexes = map[string][]net.IP{
"svc1.ns1": {net.ParseIP("192.0.1.1")},
"svc2.ns1": {net.ParseIP("192.0.1.2")},
Expand Down

0 comments on commit 3f596a2

Please sign in to comment.