diff --git a/config/config.go b/config/config.go index d6d5af725..852364938 100644 --- a/config/config.go +++ b/config/config.go @@ -208,6 +208,7 @@ type Config struct { FqdnOnly FqdnOnlyConfig `yaml:"fqdnOnly"` Filtering FilteringConfig `yaml:"filtering"` Ede EdeConfig `yaml:"ede"` + SUDN SUDNConfig `yaml:"specialUseDomains"` // Deprecated options Deprecated struct { diff --git a/config/sudn.go b/config/sudn.go new file mode 100644 index 000000000..bf18a05c4 --- /dev/null +++ b/config/sudn.go @@ -0,0 +1,25 @@ +package config + +import ( + "github.com/sirupsen/logrus" +) + +// SUDNConfig configuration for Special Use Domain Names +type SUDNConfig struct { + // These are "recommended for private use" but not mandatory. + // If a user wishes to use one, it will most likely be via conditional + // upstream or custom DNS, which come before SUDN in the resolver chain. + // Thus defaulting to `true` and returning NXDOMAIN here should not conflict. + RFC6762AppendixG bool `yaml:"rfc6762-appendixG" default:"true"` +} + +// IsEnabled implements `config.Configurable`. +func (c *SUDNConfig) IsEnabled() bool { + // The Special Use RFCs are always active + return true +} + +// LogConfig implements `config.Configurable`. +func (c *SUDNConfig) LogConfig(logger *logrus.Entry) { + logger.Debugf("rfc6762-appendixG = %v", c.RFC6762AppendixG) +} diff --git a/config/sudn_test.go b/config/sudn_test.go new file mode 100644 index 000000000..350efa12f --- /dev/null +++ b/config/sudn_test.go @@ -0,0 +1,34 @@ +package config + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("SUDNConfig", func() { + var cfg SUDNConfig + + suiteBeforeEach() + + BeforeEach(func() { + var err error + + cfg, err = WithDefaults[SUDNConfig]() + Expect(err).Should(Succeed()) + }) + + Describe("IsEnabled", func() { + It("is true", func() { + Expect(cfg.IsEnabled()).Should(BeTrue()) + }) + }) + + Describe("LogConfig", func() { + It("should log configuration", func() { + cfg.LogConfig(logger) + + Expect(hook.Calls).ShouldNot(BeEmpty()) + Expect(hook.Messages).Should(ContainElement(ContainSubstring("rfc6762-appendixG = true"))) + }) + }) +}) diff --git a/docs/config.yml b/docs/config.yml index 4d290d5a4..98a21c2d6 100644 --- a/docs/config.yml +++ b/docs/config.yml @@ -319,3 +319,9 @@ log: ede: # enabled if true, Default: false enable: true + +# optional: configure optional Special Use Domain Names (SUDN) +specialUseDomains: + # optional: block recomended private TLDs + # default: true + rfc6762-appendixG: true diff --git a/docs/configuration.md b/docs/configuration.md index fc33bc523..3cf357718 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -671,6 +671,24 @@ Configuration parameters: enable: true ``` +## Special Use Domain Names + +SUDN (Special Use Domain Names) are always enabled as they are required by various RFCs. +Some RFCs have optional recommendations, which are configurable as described below. + +Configuration parameters: + +| Parameter | Type | Mandatory | Default value | Description | +|-------------------------------------|------|-----------|---------------|-----------------------------------------------------------------------------------------------| +| specialUseDomains.rfc6762-appendixG | bool | no | true | Block TLDs listed in [RFC 6762 Appendix G](https://www.rfc-editor.org/rfc/rfc6762#appendix-G) | + +!!! example + + ```yaml + specialUseDomains: + rfc6762-appendixG: true + ``` + ## SSL certificate configuration (DoH / TLS listener) See [Wiki - Configuration of HTTPS](https://github.com/0xERR0R/blocky/wiki/Configuration-of-HTTPS-for-DoH-and-Rest-API) diff --git a/helpertest/helper.go b/helpertest/helper.go index ce614ad37..6ea513f02 100644 --- a/helpertest/helper.go +++ b/helpertest/helper.go @@ -12,6 +12,7 @@ import ( "github.com/miekg/dns" "github.com/onsi/gomega" + "github.com/onsi/gomega/gcustom" "github.com/onsi/gomega/types" ) @@ -22,6 +23,7 @@ const ( MX = dns.Type(dns.TypeMX) PTR = dns.Type(dns.TypePTR) TXT = dns.Type(dns.TypeTXT) + DS = dns.Type(dns.TypeDS) ) // TempFile creates temp file with passed data @@ -76,21 +78,30 @@ func HaveNoAnswer() types.GomegaMatcher { } func HaveReason(reason string) types.GomegaMatcher { - return gomega.WithTransform(func(m *model.Response) string { - return m.Reason - }, gomega.Equal(reason)) + return gcustom.MakeMatcher(func(m *model.Response) (bool, error) { + return m.Reason == reason, nil + }).WithTemplate( + "Expected:\n{{.Actual}}\n{{.To}} have reason:\n{{format .Data 1}}", + reason, + ) } func HaveResponseType(c model.ResponseType) types.GomegaMatcher { - return gomega.WithTransform(func(m *model.Response) model.ResponseType { - return m.RType - }, gomega.Equal(c)) + return gcustom.MakeMatcher(func(m *model.Response) (bool, error) { + return m.RType == c, nil + }).WithTemplate( + "Expected:\n{{.Actual}}\n{{.To}} have ResponseType:\n{{format .Data 1}}", + c.String(), + ) } func HaveReturnCode(code int) types.GomegaMatcher { - return gomega.WithTransform(func(m *model.Response) int { - return m.Res.Rcode - }, gomega.Equal(code)) + return gcustom.MakeMatcher(func(m *model.Response) (bool, error) { + return m.Res.Rcode == code, nil + }).WithTemplate( + "Expected:\n{{.Actual}}\n{{.To}} have RCode:\n{{format .Data 1}}", + fmt.Sprintf("%d (%s)", code, dns.RcodeToString[code]), + ) } func toFirstRR(actual interface{}) (dns.RR, error) { diff --git a/log/mock_entry.go b/log/mock_entry.go index 0415a5ef3..b79ba4e76 100644 --- a/log/mock_entry.go +++ b/log/mock_entry.go @@ -10,6 +10,7 @@ import ( func NewMockEntry() (*logrus.Entry, *MockLoggerHook) { logger := logrus.New() logger.Out = io.Discard + logger.Level = logrus.TraceLevel entry := logrus.Entry{Logger: logger} hook := MockLoggerHook{} diff --git a/resolver/resolver.go b/resolver/resolver.go index e1e9c83eb..9b6e1c7e5 100644 --- a/resolver/resolver.go +++ b/resolver/resolver.go @@ -39,21 +39,17 @@ func newRequestWithClient(question string, rType dns.Type, ip string, clientName } } -// newResponseMsg creates a new dns.Msg as response for a request -func newResponseMsg(request *model.Request) *dns.Msg { +// newResponse creates a response to the given request +func newResponse(request *model.Request, rcode int, rtype model.ResponseType, reason string) *model.Response { response := new(dns.Msg) response.SetReply(request.Req) + response.Rcode = rcode - return response -} - -// returnResponseModel wrapps a dns.Msg into a model.Response -func returnResponseModel(response *dns.Msg, rtype model.ResponseType, reason string) (*model.Response, error) { return &model.Response{ Res: response, RType: rtype, Reason: reason, - }, nil + } } func newRequestWithClientID(question string, rType dns.Type, ip, requestClientID string) *model.Request { diff --git a/resolver/sudn_resolver.go b/resolver/sudn_resolver.go index 772f81a17..44508a2da 100644 --- a/resolver/sudn_resolver.go +++ b/resolver/sudn_resolver.go @@ -1,167 +1,183 @@ package resolver import ( - "fmt" "net" "strings" + "github.com/0xERR0R/blocky/config" "github.com/0xERR0R/blocky/model" "github.com/miekg/dns" - "github.com/sirupsen/logrus" ) -const ( - sudnTest = "test." - sudnInvalid = "invalid." - sudnLocalhost = "localhost." - mdnsLocal = "local." -) - -func sudnArpaSlice() []string { - return []string{ - "10.in-addr.arpa.", - "21.172.in-addr.arpa.", - "26.172.in-addr.arpa.", - "16.172.in-addr.arpa.", - "22.172.in-addr.arpa.", - "27.172.in-addr.arpa.", - "17.172.in-addr.arpa.", - "30.172.in-addr.arpa.", - "28.172.in-addr.arpa.", - "18.172.in-addr.arpa.", - "23.172.in-addr.arpa.", - "29.172.in-addr.arpa.", - "19.172.in-addr.arpa.", - "24.172.in-addr.arpa.", - "31.172.in-addr.arpa.", - "20.172.in-addr.arpa.", - "25.172.in-addr.arpa.", - "168.192.in-addr.arpa.", +type sudnHandler = func(request *model.Request, cfg *config.SUDNConfig) *model.Response + +//nolint:gochecknoglobals +var ( + loopbackV4 = net.ParseIP("127.0.0.1") + loopbackV6 = net.IPv6loopback + + // See Wikipedia for an up-to-date reference: + // https://en.wikipedia.org/wiki/Special-use_domain_name + sudnHandlers = map[string]sudnHandler{ + // RFC 6761 + // https://www.rfc-editor.org/rfc/rfc6761 + // + // Section 6.1 + "10.in-addr.arpa.": sudnNXDomain, + "21.172.in-addr.arpa.": sudnNXDomain, + "26.172.in-addr.arpa.": sudnNXDomain, + "16.172.in-addr.arpa.": sudnNXDomain, + "22.172.in-addr.arpa.": sudnNXDomain, + "27.172.in-addr.arpa.": sudnNXDomain, + "17.172.in-addr.arpa.": sudnNXDomain, + "30.172.in-addr.arpa.": sudnNXDomain, + "28.172.in-addr.arpa.": sudnNXDomain, + "18.172.in-addr.arpa.": sudnNXDomain, + "23.172.in-addr.arpa.": sudnNXDomain, + "29.172.in-addr.arpa.": sudnNXDomain, + "19.172.in-addr.arpa.": sudnNXDomain, + "24.172.in-addr.arpa.": sudnNXDomain, + "31.172.in-addr.arpa.": sudnNXDomain, + "20.172.in-addr.arpa.": sudnNXDomain, + "25.172.in-addr.arpa.": sudnNXDomain, + "168.192.in-addr.arpa.": sudnNXDomain, + // Section 6.2 + "test.": sudnNXDomain, + // Section 6.3 + "localhost.": sudnLocalhost, + // Section 6.4 + "invalid.": sudnNXDomain, + // Section 6.5 + "example.": nil, + "example.com.": nil, + "example.net.": nil, + "example.org.": nil, + + // RFC 6762 + // https://www.rfc-editor.org/rfc/rfc6762 + // + // mDNS is not implemented, so just return NXDOMAIN + // + // Section 3 + "local.": sudnNXDomain, + // Section 12 + "254.169.in-addr.arpa.": sudnNXDomain, // also section 4 + "8.e.f.ip6.arpa.": sudnNXDomain, + "9.e.f.ip6.arpa.": sudnNXDomain, + "a.e.f.ip6.arpa.": sudnNXDomain, + "b.e.f.ip6.arpa.": sudnNXDomain, + // Appendix G + "intranet.": sudnRFC6762AppendixG, + "internal.": sudnRFC6762AppendixG, + "private.": sudnRFC6762AppendixG, + "corp.": sudnRFC6762AppendixG, + "home.": sudnRFC6762AppendixG, + "lan.": sudnRFC6762AppendixG, + + // RFC 7686 + // https://www.rfc-editor.org/rfc/rfc7686 + "onion.": sudnNXDomain, + + // RFC 8375 + // https://www.rfc-editor.org/rfc/rfc8375 + // + // Section 4 + "home.arpa.": sudnHomeArpa, } -} - -type defaultIPs struct { - loopbackV4 net.IP - loopbackV6 net.IP -} +) type SpecialUseDomainNamesResolver struct { NextResolver typed - - defaults *defaultIPs + configurable[*config.SUDNConfig] } -func NewSpecialUseDomainNamesResolver() ChainedResolver { +func NewSpecialUseDomainNamesResolver(cfg config.SUDNConfig) ChainedResolver { return &SpecialUseDomainNamesResolver{ - typed: withType("special_use_domains"), - - defaults: &defaultIPs{ - loopbackV4: net.ParseIP("127.0.0.1"), - loopbackV6: net.IPv6loopback, - }, + typed: withType("special_use_domains"), + configurable: withConfig(&cfg), } } -// IsEnabled implements `config.Configurable`. -func (r *SpecialUseDomainNamesResolver) IsEnabled() bool { - // RFC 6761 & 6762 are always active - return true -} - -// LogConfig implements `config.Configurable`. -func (r *SpecialUseDomainNamesResolver) LogConfig(logger *logrus.Entry) { - logger.Info("enabled") -} - func (r *SpecialUseDomainNamesResolver) Resolve(request *model.Request) (*model.Response, error) { - // RFC 6761 - negative - if r.isSpecial(request, sudnArpaSlice()...) || - r.isSpecial(request, sudnInvalid) || - r.isSpecial(request, sudnTest) { - return r.negativeResponse(request) - } - // RFC 6761 - switched - if r.isSpecial(request, sudnLocalhost) { - return r.responseSwitch(request, sudnLocalhost, r.defaults.loopbackV4, r.defaults.loopbackV6) - } - - // RFC 6762 - negative - if r.isSpecial(request, mdnsLocal) { - return r.negativeResponse(request) + handler := r.handler(request) + if handler != nil { + resp := handler(request, r.cfg) + if resp != nil { + return resp, nil + } } return r.next.Resolve(request) } -func (r *SpecialUseDomainNamesResolver) isSpecial(request *model.Request, names ...string) bool { - domainFromQuestion := request.Req.Question[0].Name - for _, n := range names { - if domainFromQuestion == n || - strings.HasSuffix(domainFromQuestion, fmt.Sprintf(".%s", n)) { - return true +func (r *SpecialUseDomainNamesResolver) handler(request *model.Request) sudnHandler { + q := request.Req.Question[0] + domain := q.Name + + for { + handler, ok := sudnHandlers[domain] + if ok { + return handler + } + + _, after, ok := strings.Cut(domain, ".") + if !ok { + return nil } + + domain = after } +} - return false +func newSUDNResponse(response *model.Request, rcode int) *model.Response { + return newResponse(response, rcode, model.ResponseTypeSPECIAL, "Special-Use Domain Name") } -func (r *SpecialUseDomainNamesResolver) responseSwitch(request *model.Request, - name string, ipV4, ipV6 net.IP, -) (*model.Response, error) { - qtype := request.Req.Question[0].Qtype - switch qtype { +func sudnNXDomain(request *model.Request, _ *config.SUDNConfig) *model.Response { + return newSUDNResponse(request, dns.RcodeNameError) +} + +func sudnLocalhost(request *model.Request, cfg *config.SUDNConfig) *model.Response { + q := request.Req.Question[0] + + var rr dns.RR + + switch q.Qtype { case dns.TypeA: - return r.positiveResponse(request, name, dns.TypeA, ipV4) + rr = &dns.A{A: loopbackV4} case dns.TypeAAAA: - return r.positiveResponse(request, name, dns.TypeAAAA, ipV6) + rr = &dns.AAAA{AAAA: loopbackV6} default: - return r.negativeResponse(request) + return sudnNXDomain(request, cfg) } -} -func (r *SpecialUseDomainNamesResolver) positiveResponse(request *model.Request, - name string, rtype uint16, ip net.IP, -) (*model.Response, error) { - response := newResponseMsg(request) - response.Rcode = dns.RcodeSuccess - - hdr := dns.RR_Header{ - Name: name, - Rrtype: rtype, + *rr.Header() = dns.RR_Header{ + Name: q.Name, + Rrtype: q.Qtype, Class: dns.ClassINET, Ttl: 0, } - if rtype != dns.TypeA && rtype != dns.TypeAAAA { - return nil, fmt.Errorf("invalid response type") - } + response := newSUDNResponse(request, dns.RcodeSuccess) + response.Res.Answer = []dns.RR{rr} - var rr dns.RR - if rtype == dns.TypeA { - rr = &dns.A{ - A: ip, - Hdr: hdr, - } - } else { - rr = &dns.AAAA{ - AAAA: ip, - Hdr: hdr, - } - } - - response.Answer = []dns.RR{rr} - - return r.returnResponseModel(response) + return response } -func (r *SpecialUseDomainNamesResolver) negativeResponse(request *model.Request) (*model.Response, error) { - response := newResponseMsg(request) - response.Rcode = dns.RcodeNameError +func sudnRFC6762AppendixG(request *model.Request, cfg *config.SUDNConfig) *model.Response { + if !cfg.RFC6762AppendixG { + return nil + } - return r.returnResponseModel(response) + return sudnNXDomain(request, cfg) } -func (r *SpecialUseDomainNamesResolver) returnResponseModel(response *dns.Msg) (*model.Response, error) { - return returnResponseModel(response, model.ResponseTypeSPECIAL, "Special-Use Domain Name") +func sudnHomeArpa(request *model.Request, cfg *config.SUDNConfig) *model.Response { + if request.Req.Question[0].Qtype == dns.TypeDS { + // DS queries must be forwarded + return nil + } + + return sudnNXDomain(request, cfg) } diff --git a/resolver/sudn_resolver_test.go b/resolver/sudn_resolver_test.go index ff5af9bf9..5cf9ed006 100644 --- a/resolver/sudn_resolver_test.go +++ b/resolver/sudn_resolver_test.go @@ -1,20 +1,24 @@ package resolver import ( + "fmt" + + "github.com/0xERR0R/blocky/config" . "github.com/0xERR0R/blocky/helpertest" - "github.com/0xERR0R/blocky/log" . "github.com/0xERR0R/blocky/model" "github.com/0xERR0R/blocky/util" "github.com/miekg/dns" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/onsi/gomega/types" "github.com/stretchr/testify/mock" ) var _ = Describe("SudnResolver", Label("sudnResolver"), func() { var ( - sut *SpecialUseDomainNamesResolver - m *mockResolver + sut *SpecialUseDomainNamesResolver + sutConfig config.SUDNConfig + m *mockResolver ) Describe("Type", func() { @@ -24,136 +28,154 @@ var _ = Describe("SudnResolver", Label("sudnResolver"), func() { }) BeforeEach(func() { + var err error + + sutConfig, err = config.WithDefaults[config.SUDNConfig]() + Expect(err).Should(Succeed()) + }) + + JustBeforeEach(func() { mockAnswer, err := util.NewMsgWithAnswer("example.com.", 300, A, "123.145.123.145") Expect(err).Should(Succeed()) m = &mockResolver{} m.On("Resolve", mock.Anything).Return(&Response{Res: mockAnswer}, nil) - sut = NewSpecialUseDomainNamesResolver().(*SpecialUseDomainNamesResolver) + sut = NewSpecialUseDomainNamesResolver(sutConfig).(*SpecialUseDomainNamesResolver) sut.Next(m) }) - Describe("IsEnabled", func() { - It("is true", func() { - Expect(sut.IsEnabled()).Should(BeTrue()) - }) - }) + Describe("handlers", func() { + It("should have correct response type", func() { + for domain, handler := range sudnHandlers { + resp, err := sut.Resolve(newRequest(domain, A)) + Expect(err).Should(Succeed()) - Describe("LogConfig", func() { - It("should not log anything", func() { - logger, hook := log.NewMockEntry() + if handler == nil { + Expect(resp).ShouldNot(HaveResponseType(ResponseTypeSPECIAL)) - sut.LogConfig(logger) + continue + } - Expect(hook.Calls).ShouldNot(BeEmpty()) - }) - }) - - Describe("Blocking special names", func() { - It("should block arpa", func() { - for _, arpa := range sudnArpaSlice() { - Expect(sut.Resolve(newRequest(arpa, A))). + Expect(resp). Should( SatisfyAll( - HaveNoAnswer(), HaveResponseType(ResponseTypeSPECIAL), - HaveReturnCode(dns.RcodeNameError), HaveReason("Special-Use Domain Name"), )) } }) - - It("should block test", func() { - Expect(sut.Resolve(newRequest(sudnTest, A))). - Should( - SatisfyAll( - HaveNoAnswer(), - HaveResponseType(ResponseTypeSPECIAL), - HaveReturnCode(dns.RcodeNameError), - HaveReason("Special-Use Domain Name"), - )) - }) - - It("should block invalid", func() { - Expect(sut.Resolve(newRequest(sudnInvalid, A))). - Should( - SatisfyAll( - HaveNoAnswer(), - HaveResponseType(ResponseTypeSPECIAL), - HaveReturnCode(dns.RcodeNameError), - HaveReason("Special-Use Domain Name"), - )) - }) - - It("should block localhost none A", func() { - Expect(sut.Resolve(newRequest(sudnLocalhost, HTTPS))). - Should( - SatisfyAll( - HaveNoAnswer(), - HaveResponseType(ResponseTypeSPECIAL), - HaveReturnCode(dns.RcodeNameError), - HaveReason("Special-Use Domain Name"), - )) - }) - - It("should block local", func() { - Expect(sut.Resolve(newRequest(mdnsLocal, A))). - Should( - SatisfyAll( - HaveNoAnswer(), - HaveResponseType(ResponseTypeSPECIAL), - HaveReturnCode(dns.RcodeNameError), - HaveReason("Special-Use Domain Name"), - )) - }) - - It("should block localhost none A", func() { - Expect(sut.Resolve(newRequest(mdnsLocal, HTTPS))). - Should( - SatisfyAll( - HaveNoAnswer(), - HaveResponseType(ResponseTypeSPECIAL), - HaveReturnCode(dns.RcodeNameError), - HaveReason("Special-Use Domain Name"), - )) - }) }) - Describe("Resolve localhost", func() { - It("should resolve IPv4 loopback", func() { - Expect(sut.Resolve(newRequest(sudnLocalhost, A))). - Should( - SatisfyAll( - BeDNSRecord(sudnLocalhost, A, sut.defaults.loopbackV4.String()), - HaveTTL(BeNumerically("==", 0)), - HaveResponseType(ResponseTypeSPECIAL), - HaveReturnCode(dns.RcodeSuccess), - )) + Describe("Resolve", func() { + //nolint:unparam // linter thinks `qName` is always `A` because of "RFC 6762 Appendix G" table + entry := func(qType dns.Type, qName string, expectedRCode int, extraMatchers ...any) TableEntry { + GinkgoHelper() + + var verb string + switch expectedRCode { + case dns.RcodeSuccess: + verb = "resolve" + case dns.RcodeNameError: + verb = "block" + } + + description := fmt.Sprintf("should %s %s IN %s", verb, qName, qType) + + args := []any{qType, qName, expectedRCode} + args = append(args, extraMatchers...) + + return Entry(description, args...) + } + + DescribeTable("handled domains", + func(qType dns.Type, qName string, expectedRCode int, extraMatchers ...types.GomegaMatcher) { + resp, err := sut.Resolve(newRequest(qName, qType)) + Expect(err).Should(Succeed()) + Expect(resp).Should(SatisfyAll( + HaveResponseType(ResponseTypeSPECIAL), + HaveReason("Special-Use Domain Name"), + HaveReturnCode(expectedRCode), + )) + + switch expectedRCode { + case dns.RcodeSuccess: + Expect(resp).Should(HaveTTL(BeNumerically("==", 0))) + case dns.RcodeNameError: + Expect(resp).Should(HaveNoAnswer()) + } + + Expect(resp).Should(SatisfyAll(extraMatchers...)) + }, + + entry(A, "1.0.0.10.in-addr.arpa.", dns.RcodeNameError), + entry(A, "something.test.", dns.RcodeNameError), + entry(A, "something.localhost.", dns.RcodeSuccess, BeDNSRecord("something.localhost.", A, loopbackV4.String())), + entry(AAAA, "thing.localhost.", dns.RcodeSuccess, BeDNSRecord("thing.localhost.", AAAA, loopbackV6.String())), + entry(HTTPS, "something.localhost.", dns.RcodeNameError), + entry(A, "something.invalid.", dns.RcodeNameError), + entry(A, "something.local.", dns.RcodeNameError), + entry(HTTPS, "something.local.", dns.RcodeNameError), + entry(A, "1.0.254.169.in-addr.arpa.", dns.RcodeNameError), + entry(A, "something.intranet.", dns.RcodeNameError), + entry(A, "something.internal.", dns.RcodeNameError), + entry(A, "something.private.", dns.RcodeNameError), + entry(A, "something.corp.", dns.RcodeNameError), + entry(A, "something.home.", dns.RcodeNameError), + entry(A, "something.lan.", dns.RcodeNameError), + entry(A, "something.onion.", dns.RcodeNameError), + ) + + When("RFC 6762 Appendix G is disabled", func() { + BeforeEach(func() { + sutConfig.RFC6762AppendixG = false + }) + + DescribeTable("", + func(qType dns.Type, qName string, expectedRCode int) { + resp, err := sut.Resolve(newRequest(qName, qType)) + Expect(err).Should(Succeed()) + Expect(resp).Should(HaveReturnCode(expectedRCode)) + Expect(resp).ShouldNot(HaveResponseType(ResponseTypeSPECIAL)) + }, + + entry(A, "something.intranet.", dns.RcodeSuccess), + entry(A, "something.intranet.", dns.RcodeSuccess), + entry(A, "something.internal.", dns.RcodeSuccess), + entry(A, "something.private.", dns.RcodeSuccess), + entry(A, "something.corp.", dns.RcodeSuccess), + entry(A, "something.home.", dns.RcodeSuccess), + entry(A, "something.lan.", dns.RcodeSuccess), + ) }) - It("should resolve IPv6 loopback", func() { - Expect(sut.Resolve(newRequest(sudnLocalhost, AAAA))). + It("should forward example.com", func() { + Expect(sut.Resolve(newRequest("example.com", A))). Should( SatisfyAll( - BeDNSRecord(sudnLocalhost, AAAA, sut.defaults.loopbackV6.String()), - HaveTTL(BeNumerically("==", 0)), - HaveResponseType(ResponseTypeSPECIAL), + BeDNSRecord("example.com.", A, "123.145.123.145"), + HaveTTL(BeNumerically("==", 300)), + HaveResponseType(ResponseTypeRESOLVED), HaveReturnCode(dns.RcodeSuccess), )) }) - }) - Describe("Forward other", func() { - It("should forward example.com", func() { - Expect(sut.Resolve(newRequest("example.com", A))). + It("should forward home.arpa. IN DS", func() { + Expect(sut.Resolve(newRequest("something.home.arpa.", DS))). Should( SatisfyAll( + // setup code doesn't care about the question BeDNSRecord("example.com.", A, "123.145.123.145"), HaveTTL(BeNumerically("==", 300)), HaveResponseType(ResponseTypeRESOLVED), HaveReturnCode(dns.RcodeSuccess), )) }) + + It("should forward non special use domains", func() { + resp, err := sut.Resolve(newRequest("something.not-special.", AAAA)) + Expect(err).Should(Succeed()) + Expect(resp).ShouldNot(HaveResponseType(ResponseTypeSPECIAL)) + }) }) }) diff --git a/server/server.go b/server/server.go index 8f949b7f5..20e5a9ea9 100644 --- a/server/server.go +++ b/server/server.go @@ -425,7 +425,7 @@ func createQueryResolver( blocking, resolver.NewCachingResolver(cfg.Caching, redisClient), resolver.NewRewriterResolver(cfg.Conditional.RewriterConfig, condUpstream), - resolver.NewSpecialUseDomainNamesResolver(), + resolver.NewSpecialUseDomainNamesResolver(cfg.SUDN), parallel, )