mirror of https://github.com/0xERR0R/blocky.git
feat: Support defining records by dns zone format (#1360)
* feat: Support zonefile configuration for custom dns mapping * docs: Update configuration.md * Rename var to ok * Linter fixes * Remove hashes in test describe description * Implement PR comments; zoneFileMapping -> zone, initialize with proper sizes * Remove custom CNAME parsing * Utilize TTL defined in zone file * Link to wikipedia's example file * Test to confirm that a relative zone entry without an $ORIGIN returns an error * Write a test covering the $INCLUDE directive * Write a test confirming that a dns zone can result in more than 1 RR * Linting * fix: Use proper matchers in CustomDNS Zone tests; Update configuration.md description * Pull in config directory to support relative $INCLUDE * Added tests to ensure the ability to use both bare filenames as well as relative filenames when using the $INCLUDE directive * Shorten test description (Linting error) * Move Assignment of z.RRs to the end of the UnmarshallYAML function * Moved tests for relative $INCLUDE zones to config_test. Added test case when config param passed to blocky is a directory * Corrected test case to _actually_ test againt bare file names
This commit is contained in:
parent
178dbb740e
commit
9f633f18d0
|
@ -455,21 +455,30 @@ func loadConfig(logger *logrus.Entry, path string, mandatory bool) (rCfg *Config
|
|||
return nil, fmt.Errorf("can't read config file(s): %w", err)
|
||||
}
|
||||
|
||||
var data []byte
|
||||
var (
|
||||
data []byte
|
||||
prettyPath string
|
||||
)
|
||||
|
||||
if fs.IsDir() {
|
||||
prettyPath = filepath.Join(path, "*")
|
||||
|
||||
data, err = readFromDir(path, data)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read config files: %w", err)
|
||||
}
|
||||
} else {
|
||||
prettyPath = path
|
||||
|
||||
data, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read config file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cfg.CustomDNS.Zone.configPath = prettyPath
|
||||
|
||||
err = unmarshalConfig(logger, data, &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -164,6 +164,94 @@ var _ = Describe("Config", func() {
|
|||
defaultTestFileConfig(c)
|
||||
})
|
||||
})
|
||||
When("Test config file contains a zone file with $INCLUDE", func() {
|
||||
When("The config path is set to the config file", func() {
|
||||
It("Should support the $INCLUDE directive with a bare filename", func() {
|
||||
folder := helpertest.NewTmpFolder("zones")
|
||||
folder.CreateStringFile("other.zone", "www 3600 A 1.2.3.4")
|
||||
cfgFile := writeConfigYmlWithLocalZoneFile(folder, "other.zone")
|
||||
|
||||
c, err = LoadConfig(cfgFile.Path, true)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c.CustomDNS.Zone.RRs).Should(HaveLen(1))
|
||||
|
||||
Expect(c.CustomDNS.Zone.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
helpertest.BeDNSRecord("www.example.com.", helpertest.A, "1.2.3.4"),
|
||||
helpertest.HaveTTL(BeNumerically("==", 3600)),
|
||||
)),
|
||||
))
|
||||
})
|
||||
It("Should support the $INCLUDE directive with a relative filename", func() {
|
||||
folder := helpertest.NewTmpFolder("zones")
|
||||
folder.CreateStringFile("other.zone", "www 3600 A 1.2.3.4")
|
||||
cfgFile := writeConfigYmlWithLocalZoneFile(folder, "./other.zone")
|
||||
|
||||
c, err = LoadConfig(cfgFile.Path, true)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c.CustomDNS.Zone.RRs).Should(HaveLen(1))
|
||||
|
||||
Expect(c.CustomDNS.Zone.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
helpertest.BeDNSRecord("www.example.com.", helpertest.A, "1.2.3.4"),
|
||||
helpertest.HaveTTL(BeNumerically("==", 3600)),
|
||||
)),
|
||||
))
|
||||
})
|
||||
})
|
||||
When("The config path is set to a directory", func() {
|
||||
It("Should support the $INCLUDE directive with a bare filename", func() {
|
||||
folder := helpertest.NewTmpFolder("zones")
|
||||
folder.CreateStringFile("other.zone", "www 3600 A 1.2.3.4")
|
||||
writeConfigYmlWithLocalZoneFile(folder, "other.zone")
|
||||
|
||||
c, err = LoadConfig(folder.Path, true)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c.CustomDNS.Zone.RRs).Should(HaveLen(1))
|
||||
|
||||
Expect(c.CustomDNS.Zone.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
helpertest.BeDNSRecord("www.example.com.", helpertest.A, "1.2.3.4"),
|
||||
helpertest.HaveTTL(BeNumerically("==", 3600)),
|
||||
)),
|
||||
))
|
||||
})
|
||||
It("Should support the $INCLUDE directive with a relative filename", func() {
|
||||
folder := helpertest.NewTmpFolder("zones")
|
||||
folder.CreateStringFile("other.zone", "www 3600 A 1.2.3.4")
|
||||
writeConfigYmlWithLocalZoneFile(folder, "./other.zone")
|
||||
|
||||
c, err = LoadConfig(folder.Path, true)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c.CustomDNS.Zone.RRs).Should(HaveLen(1))
|
||||
|
||||
Expect(c.CustomDNS.Zone.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
helpertest.BeDNSRecord("www.example.com.", helpertest.A, "1.2.3.4"),
|
||||
helpertest.HaveTTL(BeNumerically("==", 3600)),
|
||||
)),
|
||||
))
|
||||
})
|
||||
})
|
||||
})
|
||||
When("Test file does not exist", func() {
|
||||
It("should fail", func() {
|
||||
_, err := LoadConfig(tmpDir.JoinPath("config-does-not-exist.yaml"), true)
|
||||
|
@ -977,6 +1065,33 @@ func writeConfigYml(tmpDir *helpertest.TmpFolder) *helpertest.TmpFile {
|
|||
)
|
||||
}
|
||||
|
||||
func writeConfigYmlWithLocalZoneFile(tmpDir *helpertest.TmpFolder, includeStr string) *helpertest.TmpFile {
|
||||
return tmpDir.CreateStringFile("config.yml",
|
||||
"upstreams:",
|
||||
" userAgent: testBlocky",
|
||||
" init:",
|
||||
" strategy: failOnError",
|
||||
" groups:",
|
||||
" default:",
|
||||
" - tcp+udp:8.8.8.8",
|
||||
" - tcp+udp:8.8.4.4",
|
||||
" - 1.1.1.1",
|
||||
"customDNS:",
|
||||
" zone: |",
|
||||
" $ORIGIN example.com.",
|
||||
" $INCLUDE "+includeStr,
|
||||
"filtering:",
|
||||
" queryTypes:",
|
||||
" - AAAA",
|
||||
" - A",
|
||||
"fqdnOnly:",
|
||||
" enable: true",
|
||||
"port: 55553,:55554,[::1]:55555",
|
||||
"logLevel: debug",
|
||||
"minTlsServeVersion: 1.3",
|
||||
)
|
||||
}
|
||||
|
||||
func writeConfigDir(tmpDir *helpertest.TmpFolder) {
|
||||
tmpDir.CreateStringFile("config1.yaml",
|
||||
"upstreams:",
|
||||
|
|
|
@ -14,14 +14,57 @@ type CustomDNS struct {
|
|||
RewriterConfig `yaml:",inline"`
|
||||
CustomTTL Duration `yaml:"customTTL" default:"1h"`
|
||||
Mapping CustomDNSMapping `yaml:"mapping"`
|
||||
Zone ZoneFileDNS `yaml:"zone" default:""`
|
||||
FilterUnmappedTypes bool `yaml:"filterUnmappedTypes" default:"true"`
|
||||
}
|
||||
|
||||
type (
|
||||
CustomDNSMapping map[string]CustomDNSEntries
|
||||
CustomDNSEntries []dns.RR
|
||||
|
||||
ZoneFileDNS struct {
|
||||
RRs CustomDNSMapping
|
||||
configPath string
|
||||
}
|
||||
)
|
||||
|
||||
func (z *ZoneFileDNS) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var input string
|
||||
if err := unmarshal(&input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := make(CustomDNSMapping)
|
||||
|
||||
zoneParser := dns.NewZoneParser(strings.NewReader(input), "", z.configPath)
|
||||
zoneParser.SetIncludeAllowed(true)
|
||||
|
||||
for {
|
||||
zoneRR, ok := zoneParser.Next()
|
||||
|
||||
if !ok {
|
||||
if zoneParser.Err() != nil {
|
||||
return zoneParser.Err()
|
||||
}
|
||||
|
||||
// Done
|
||||
break
|
||||
}
|
||||
|
||||
domain := zoneRR.Header().Name
|
||||
|
||||
if _, ok := result[domain]; !ok {
|
||||
result[domain] = make(CustomDNSEntries, 0, 1)
|
||||
}
|
||||
|
||||
result[domain] = append(result[domain], zoneRR)
|
||||
}
|
||||
|
||||
z.RRs = result
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CustomDNSEntries) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var input string
|
||||
if err := unmarshal(&input); err != nil {
|
||||
|
@ -30,7 +73,6 @@ func (c *CustomDNSEntries) UnmarshalYAML(unmarshal func(interface{}) error) erro
|
|||
|
||||
parts := strings.Split(input, ",")
|
||||
result := make(CustomDNSEntries, len(parts))
|
||||
containsCNAME := false
|
||||
|
||||
for i, part := range parts {
|
||||
rr, err := configToRR(part)
|
||||
|
@ -38,16 +80,9 @@ func (c *CustomDNSEntries) UnmarshalYAML(unmarshal func(interface{}) error) erro
|
|||
return err
|
||||
}
|
||||
|
||||
_, isCNAME := rr.(*dns.CNAME)
|
||||
containsCNAME = containsCNAME || isCNAME
|
||||
|
||||
result[i] = rr
|
||||
}
|
||||
|
||||
if containsCNAME && len(result) > 1 {
|
||||
return fmt.Errorf("when a CNAME record is present, it must be the only record in the mapping")
|
||||
}
|
||||
|
||||
*c = result
|
||||
|
||||
return nil
|
||||
|
@ -70,47 +105,21 @@ func (c *CustomDNS) LogConfig(logger *logrus.Entry) {
|
|||
}
|
||||
}
|
||||
|
||||
func removePrefixSuffix(in, prefix string) string {
|
||||
in = strings.TrimPrefix(in, fmt.Sprintf("%s(", prefix))
|
||||
in = strings.TrimSuffix(in, ")")
|
||||
|
||||
return strings.TrimSpace(in)
|
||||
}
|
||||
|
||||
func configToRR(part string) (dns.RR, error) {
|
||||
if strings.HasPrefix(part, "CNAME(") {
|
||||
domain := removePrefixSuffix(part, "CNAME")
|
||||
domain = dns.Fqdn(domain)
|
||||
cname := &dns.CNAME{Target: domain}
|
||||
|
||||
return cname, nil
|
||||
func configToRR(ipStr string) (dns.RR, error) {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("invalid IP address '%s'", ipStr)
|
||||
}
|
||||
|
||||
// Fall back to A/AAAA records to maintain backwards compatibility in config.yml
|
||||
// We will still remove the A() or AAAA() if it exists
|
||||
if strings.Contains(part, ".") { // IPV4 address
|
||||
ipStr := removePrefixSuffix(part, "A")
|
||||
ip := net.ParseIP(ipStr)
|
||||
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("invalid IP address '%s'", part)
|
||||
}
|
||||
|
||||
if ip.To4() != nil {
|
||||
a := new(dns.A)
|
||||
a.A = ip
|
||||
|
||||
return a, nil
|
||||
} else { // IPV6 address
|
||||
ipStr := removePrefixSuffix(part, "AAAA")
|
||||
ip := net.ParseIP(ipStr)
|
||||
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("invalid IP address '%s'", part)
|
||||
}
|
||||
|
||||
aaaa := new(dns.AAAA)
|
||||
aaaa.AAAA = ip
|
||||
|
||||
return aaaa, nil
|
||||
}
|
||||
|
||||
aaaa := new(dns.AAAA)
|
||||
aaaa.AAAA = ip
|
||||
|
||||
return aaaa, nil
|
||||
}
|
||||
|
|
|
@ -2,8 +2,11 @@ package config
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
. "github.com/0xERR0R/blocky/helpertest"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
|
@ -25,7 +28,6 @@ var _ = Describe("CustomDNSConfig", func() {
|
|||
&dns.A{A: net.ParseIP("192.168.143.125")},
|
||||
&dns.AAAA{AAAA: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
||||
},
|
||||
"cname.domain": {&dns.CNAME{Target: "custom.domain"}},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -62,12 +64,11 @@ var _ = Describe("CustomDNSConfig", func() {
|
|||
ContainSubstring("custom.domain = "),
|
||||
ContainSubstring("ip6.domain = "),
|
||||
ContainSubstring("multiple.ips = "),
|
||||
ContainSubstring("cname.domain = "),
|
||||
))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("UnmarshalYAML", func() {
|
||||
Describe("CustomDNSEntries UnmarshalYAML", func() {
|
||||
It("Should parse config as map", func() {
|
||||
c := CustomDNSEntries{}
|
||||
err := c.UnmarshalYAML(func(i interface{}) error {
|
||||
|
@ -82,17 +83,6 @@ var _ = Describe("CustomDNSConfig", func() {
|
|||
Expect(aRecord.A).Should(Equal(net.ParseIP("1.2.3.4")))
|
||||
})
|
||||
|
||||
It("Should return an error if a CNAME is accomanied by any other record", func() {
|
||||
c := CustomDNSEntries{}
|
||||
err := c.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = "CNAME(example.com),A(1.2.3.4)"
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err).Should(MatchError("when a CNAME record is present, it must be the only record in the mapping"))
|
||||
})
|
||||
|
||||
It("should fail if wrong YAML format", func() {
|
||||
c := &CustomDNSEntries{}
|
||||
err := c.UnmarshalYAML(func(i interface{}) error {
|
||||
|
@ -102,4 +92,116 @@ var _ = Describe("CustomDNSConfig", func() {
|
|||
Expect(err).Should(MatchError("some err"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ZoneFileDNS UnmarshalYAML", func() {
|
||||
It("Should parse config as map", func() {
|
||||
z := ZoneFileDNS{}
|
||||
err := z.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = strings.TrimSpace(`
|
||||
$ORIGIN example.com.
|
||||
www 3600 A 1.2.3.4
|
||||
www 3600 AAAA 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
||||
www6 3600 AAAA 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
||||
cname 3600 CNAME www
|
||||
`)
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(z.RRs).Should(HaveLen(3))
|
||||
|
||||
Expect(z.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
HaveLen(2),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
BeDNSRecord("www.example.com.", A, "1.2.3.4"),
|
||||
HaveTTL(BeNumerically("==", 3600)),
|
||||
),
|
||||
SatisfyAll(
|
||||
BeDNSRecord("www.example.com.", AAAA, "2001:db8:85a3::8a2e:370:7334"),
|
||||
HaveTTL(BeNumerically("==", 3600)),
|
||||
))))
|
||||
|
||||
Expect(z.RRs["www6.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
BeDNSRecord("www6.example.com.", AAAA, "2001:db8:85a3::8a2e:370:7334"),
|
||||
HaveTTL(BeNumerically("==", 3600)),
|
||||
))))
|
||||
|
||||
Expect(z.RRs["cname.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
BeDNSRecord("cname.example.com.", CNAME, "www.example.com."),
|
||||
HaveTTL(BeNumerically("==", 3600)),
|
||||
))))
|
||||
})
|
||||
|
||||
It("Should support the $INCLUDE directive with an absolute path", func() {
|
||||
folder := NewTmpFolder("zones")
|
||||
file := folder.CreateStringFile("other.zone", "www 3600 A 1.2.3.4")
|
||||
|
||||
z := ZoneFileDNS{}
|
||||
err := z.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = strings.TrimSpace(`
|
||||
$ORIGIN example.com.
|
||||
$INCLUDE ` + file.Path)
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(z.RRs).Should(HaveLen(1))
|
||||
|
||||
Expect(z.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
BeDNSRecord("www.example.com.", A, "1.2.3.4"),
|
||||
HaveTTL(BeNumerically("==", 3600)),
|
||||
)),
|
||||
))
|
||||
})
|
||||
|
||||
It("Should return an error if the zone file is malformed", func() {
|
||||
z := ZoneFileDNS{}
|
||||
err := z.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = strings.TrimSpace(`
|
||||
$ORIGIN example.com.
|
||||
www A 1.2.3.4
|
||||
`)
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("dns: missing TTL with no previous value"))
|
||||
})
|
||||
It("Should return an error if a relative record is provided without an origin", func() {
|
||||
z := ZoneFileDNS{}
|
||||
err := z.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = strings.TrimSpace(`
|
||||
$TTL 3600
|
||||
www A 1.2.3.4
|
||||
`)
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("dns: bad owner name: \"www\""))
|
||||
})
|
||||
It("Should return an error if the unmarshall function returns an error", func() {
|
||||
z := ZoneFileDNS{}
|
||||
err := z.UnmarshalYAML(func(i interface{}) error {
|
||||
return fmt.Errorf("Failed to unmarshal")
|
||||
})
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err).Should(MatchError("Failed to unmarshal"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -47,7 +47,6 @@ customDNS:
|
|||
example.com: printer.lan
|
||||
mapping:
|
||||
printer.lan: 192.168.178.3,2001:0db8:85a3:08d3:1319:8a2e:0370:7344
|
||||
second-printer-address.lan: CNAME(printer.lan)
|
||||
|
||||
# optional: definition, which DNS resolver(s) should be used for queries to the domain (with all sub-domains). Multiple resolvers must be separated by a comma
|
||||
# Example: Query client.fritz.box will ask DNS server 192.168.178.1. This is necessary for local network, to resolve clients by host name
|
||||
|
|
|
@ -259,12 +259,13 @@ You can define your own domain name to IP mappings. For example, you can use a u
|
|||
or define a domain name for your local device on order to use the HTTPS certificate. Multiple IP addresses for one
|
||||
domain must be separated by a comma.
|
||||
|
||||
| Parameter | Type | Mandatory | Default value |
|
||||
| ------------------- | ------------------------------------------- | --------- | ------------- |
|
||||
| customTTL | duration (no unit is minutes) | no | 1h |
|
||||
| rewrite | string: string (domain: domain) | no | |
|
||||
| mapping | string: string (hostname: address or CNAME) | no | |
|
||||
| filterUnmappedTypes | boolean | no | true |
|
||||
| Parameter | Type | Mandatory | Default value |
|
||||
| ------------------- | ------------------------------------------------------ | --------- | ------------- |
|
||||
| customTTL | duration used for simple mappings (no unit is minutes) | no | 1h |
|
||||
| rewrite | string: string (domain: domain) | no | |
|
||||
| mapping | string: string (hostname: address or CNAME) | no | |
|
||||
| zone | string containing a DNS Zone | no | |
|
||||
| filterUnmappedTypes | boolean | no | true |
|
||||
|
||||
!!! example
|
||||
|
||||
|
@ -278,13 +279,22 @@ domain must be separated by a comma.
|
|||
mapping:
|
||||
printer.lan: 192.168.178.3
|
||||
otherdevice.lan: 192.168.178.15,2001:0db8:85a3:08d3:1319:8a2e:0370:7344
|
||||
anothername.lan: CNAME(otherdevice.lan)
|
||||
zone: |
|
||||
$ORIGIN example.com.
|
||||
www 3600 A 1.2.3.4
|
||||
@ 3600 CNAME www
|
||||
```
|
||||
|
||||
This configuration will also resolve any subdomain of the defined domain, recursively. For example querying any of
|
||||
`printer.lan`, `my.printer.lan` or `i.love.my.printer.lan` will return 192.168.178.3.
|
||||
|
||||
CNAME records are supported by setting the value of the mapping to `CNAME(target)`. Note that the target will be recursively resolved and will return an error if a loop is detected.
|
||||
CNAME records are supported by utilizing the `zone` parameter. The zone file is a multiline string containing a [DNS Zone File](https://en.wikipedia.org/wiki/Zone_file#Example_file).
|
||||
For records defined using the `zone` parameter, the `customTTL` parameter is unused. Instead, the TTL is defined in the zone directly.
|
||||
The following directives are supported in the zone file:
|
||||
* `$ORIGIN` - sets the origin for relative domain names
|
||||
* `$TTL` - sets the default TTL for records in the zone
|
||||
* `$INCLUDE` - includes another zone file relative to the blocky executable
|
||||
* `$GENERATE` - generates a range of records
|
||||
|
||||
With the optional parameter `rewrite` you can replace domain part of the query with the defined part **before** the
|
||||
resolver lookup is performed.
|
||||
|
|
|
@ -31,12 +31,25 @@ type CustomDNSResolver struct {
|
|||
|
||||
// NewCustomDNSResolver creates new resolver instance
|
||||
func NewCustomDNSResolver(cfg config.CustomDNS) *CustomDNSResolver {
|
||||
m := make(config.CustomDNSMapping, len(cfg.Mapping))
|
||||
reverse := make(map[string][]string, len(cfg.Mapping))
|
||||
dnsRecords := make(config.CustomDNSMapping, len(cfg.Mapping)+len(cfg.Zone.RRs))
|
||||
|
||||
for url, entries := range cfg.Mapping {
|
||||
m[strings.ToLower(url)] = entries
|
||||
url = util.ExtractDomainOnly(url)
|
||||
dnsRecords[url] = entries
|
||||
|
||||
for _, entry := range entries {
|
||||
entry.Header().Ttl = cfg.CustomTTL.SecondsU32()
|
||||
}
|
||||
}
|
||||
|
||||
for url, entries := range cfg.Zone.RRs {
|
||||
url = util.ExtractDomainOnly(url)
|
||||
dnsRecords[url] = entries
|
||||
}
|
||||
|
||||
reverse := make(map[string][]string, len(dnsRecords))
|
||||
|
||||
for url, entries := range dnsRecords {
|
||||
for _, entry := range entries {
|
||||
a, isA := entry.(*dns.A)
|
||||
|
||||
|
@ -59,7 +72,7 @@ func NewCustomDNSResolver(cfg config.CustomDNS) *CustomDNSResolver {
|
|||
typed: withType("custom_dns"),
|
||||
|
||||
createAnswerFromQuestion: util.CreateAnswerFromQuestion,
|
||||
mapping: m,
|
||||
mapping: dnsRecords,
|
||||
reverseAddresses: reverse,
|
||||
}
|
||||
}
|
||||
|
@ -175,11 +188,11 @@ func (r *CustomDNSResolver) processDNSEntry(
|
|||
) ([]dns.RR, error) {
|
||||
switch v := entry.(type) {
|
||||
case *dns.A:
|
||||
return r.processIP(v.A, question)
|
||||
return r.processIP(v.A, question, v.Header().Ttl)
|
||||
case *dns.AAAA:
|
||||
return r.processIP(v.AAAA, question)
|
||||
return r.processIP(v.AAAA, question, v.Header().Ttl)
|
||||
case *dns.CNAME:
|
||||
return r.processCNAME(ctx, request, *v, resolvedCnames, question)
|
||||
return r.processCNAME(ctx, request, *v, resolvedCnames, question, v.Header().Ttl)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported customDNS RR type %T", entry)
|
||||
|
@ -200,11 +213,11 @@ func (r *CustomDNSResolver) Resolve(ctx context.Context, request *model.Request)
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
func (r *CustomDNSResolver) processIP(ip net.IP, question dns.Question) (result []dns.RR, err error) {
|
||||
func (r *CustomDNSResolver) processIP(ip net.IP, question dns.Question, ttl uint32) (result []dns.RR, err error) {
|
||||
result = make([]dns.RR, 0)
|
||||
|
||||
if isSupportedType(ip, question) {
|
||||
rr, err := r.createAnswerFromQuestion(question, ip, r.cfg.CustomTTL.SecondsU32())
|
||||
rr, err := r.createAnswerFromQuestion(question, ip, ttl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -221,9 +234,9 @@ func (r *CustomDNSResolver) processCNAME(
|
|||
targetCname dns.CNAME,
|
||||
resolvedCnames []string,
|
||||
question dns.Question,
|
||||
ttl uint32,
|
||||
) (result []dns.RR, err error) {
|
||||
cname := new(dns.CNAME)
|
||||
ttl := r.cfg.CustomTTL.SecondsU32()
|
||||
cname.Hdr = dns.RR_Header{Class: dns.ClassINET, Ttl: ttl, Rrtype: dns.TypeCNAME, Name: question.Name}
|
||||
cname.Target = dns.Fqdn(targetCname.Target)
|
||||
result = append(result, cname)
|
||||
|
|
|
@ -18,7 +18,8 @@ import (
|
|||
|
||||
var _ = Describe("CustomDNSResolver", func() {
|
||||
var (
|
||||
TTL = uint32(time.Now().Second())
|
||||
TTL = uint32(time.Now().Second())
|
||||
zoneTTL = uint32(time.Now().Second() * 2)
|
||||
|
||||
sut *CustomDNSResolver
|
||||
m *mockResolver
|
||||
|
@ -38,6 +39,8 @@ var _ = Describe("CustomDNSResolver", func() {
|
|||
ctx, cancelFn = context.WithCancel(context.Background())
|
||||
DeferCleanup(cancelFn)
|
||||
|
||||
zoneHdr := dns.RR_Header{Ttl: zoneTTL}
|
||||
|
||||
cfg = config.CustomDNS{
|
||||
Mapping: config.CustomDNSMapping{
|
||||
"custom.domain": {&dns.A{A: net.ParseIP("192.168.143.123")}},
|
||||
|
@ -47,11 +50,16 @@ var _ = Describe("CustomDNSResolver", func() {
|
|||
&dns.A{A: net.ParseIP("192.168.143.125")},
|
||||
&dns.AAAA{AAAA: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
||||
},
|
||||
"cname.domain": {&dns.CNAME{Target: "custom.domain"}},
|
||||
"cname.ip6": {&dns.CNAME{Target: "ip6.domain"}},
|
||||
"cname.example": {&dns.CNAME{Target: "example.com"}},
|
||||
"cname.recursive": {&dns.CNAME{Target: "cname.recursive"}},
|
||||
"mx.domain": {&dns.MX{Mx: "mx.domain"}},
|
||||
},
|
||||
Zone: config.ZoneFileDNS{
|
||||
RRs: config.CustomDNSMapping{
|
||||
"example.zone.": {&dns.A{A: net.ParseIP("1.2.3.4"), Hdr: zoneHdr}},
|
||||
"cname.domain.": {&dns.CNAME{Target: "custom.domain", Hdr: zoneHdr}},
|
||||
"cname.ip6.": {&dns.CNAME{Target: "ip6.domain", Hdr: zoneHdr}},
|
||||
"cname.example.": {&dns.CNAME{Target: "example.com", Hdr: zoneHdr}},
|
||||
"cname.recursive.": {&dns.CNAME{Target: "cname.recursive", Hdr: zoneHdr}},
|
||||
"mx.domain.": {&dns.MX{Mx: "mx.domain", Hdr: zoneHdr}},
|
||||
},
|
||||
},
|
||||
CustomTTL: config.Duration(time.Duration(TTL) * time.Second),
|
||||
FilterUnmappedTypes: true,
|
||||
|
@ -136,6 +144,19 @@ var _ = Describe("CustomDNSResolver", func() {
|
|||
When("Ip 4 mapping is defined for custom domain and", func() {
|
||||
Context("filterUnmappedTypes is true", func() {
|
||||
BeforeEach(func() { cfg.FilterUnmappedTypes = true })
|
||||
It("defined ip4 query should be resolved from zone mappings and should use the TTL defined in the zone", func() {
|
||||
Expect(sut.Resolve(ctx, newRequest("example.zone.", A))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
BeDNSRecord("example.zone.", A, "1.2.3.4"),
|
||||
HaveTTL(BeNumerically("==", zoneTTL)),
|
||||
HaveResponseType(ResponseTypeCUSTOMDNS),
|
||||
HaveReason("CUSTOM DNS"),
|
||||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
// will not delegate to next resolver
|
||||
m.AssertNotCalled(GinkgoT(), "Resolve", mock.Anything)
|
||||
})
|
||||
It("defined ip4 query should be resolved", func() {
|
||||
Expect(sut.Resolve(ctx, newRequest("custom.domain.", A))).
|
||||
Should(
|
||||
|
|
Loading…
Reference in New Issue