mirror of
https://github.com/coredns/coredns.git
synced 2025-11-02 02:03:13 -05:00
Remove the word middleware (#1067)
* Rename middleware to plugin first pass; mostly used 'sed', few spots where I manually changed text. This still builds a coredns binary. * fmt error * Rename AddMiddleware to AddPlugin * Readd AddMiddleware to remain backwards compat
This commit is contained in:
86
plugin/reverse/README.md
Normal file
86
plugin/reverse/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# reverse
|
||||
|
||||
The *reverse* plugin allows CoreDNS to respond dynamically to a PTR request and the related A/AAAA request.
|
||||
|
||||
## Syntax
|
||||
|
||||
~~~
|
||||
reverse NETWORK... {
|
||||
hostname TEMPLATE
|
||||
[ttl TTL]
|
||||
[fallthrough]
|
||||
[wildcard]
|
||||
~~~
|
||||
|
||||
* **NETWORK** one or more CIDR formatted networks to respond on.
|
||||
* `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
|
||||
* `fallthrough` if zone matches and no record can be generated, pass request to the next plugin.
|
||||
* `wildcard` allows matches to catch all subdomains as well.
|
||||
|
||||
### Template Syntax
|
||||
|
||||
The template for the hostname is used for generating the PTR for a reverse lookup and matching the
|
||||
forward lookup back to an IP.
|
||||
|
||||
#### `{ip}`
|
||||
|
||||
The `{ip}` symbol is **required** to make reverse work.
|
||||
For IPv4 lookups the IP is directly extracted
|
||||
With IPv6 lookups the ":" is removed, and any zero ranged are expanded, e.g.,
|
||||
"ffff::ffff" results in "ffff000000000000000000000000ffff"
|
||||
|
||||
#### `{zone[i]}`
|
||||
|
||||
The `{zone[i]}` symbol is **optional** and can be replaced by a fixed (zone) string.
|
||||
The zone will be matched by the zones listed in *this* configuration stanza.
|
||||
`i` needs to be replaced with the index of the configured listener zones, starting with 1.
|
||||
|
||||
## Examples
|
||||
|
||||
~~~ txt
|
||||
arpa compute.internal {
|
||||
# proxy unmatched requests
|
||||
proxy . 8.8.8.8
|
||||
|
||||
# answer requests for IPs in this network
|
||||
# PTR 1.0.32.10.in-addr.arpa. 3600 ip-10.0.32.1.compute.internal.
|
||||
# A ip-10.0.32.1.compute.internal. 3600 10.0.32.1
|
||||
# v6 is also possible
|
||||
# PTR 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.d.f.ip6.arpa. 3600 ip-fd010000000000000000000000000001.compute.internal.
|
||||
# AAAA ip-fd010000000000000000000000000001.compute.internal. 3600 fd01::1
|
||||
reverse 10.32.0.0/16 fd01::/16 {
|
||||
# template of the ip injection to hostname, zone resolved to compute.internal.
|
||||
hostname ip-{ip}.{zone[2]}
|
||||
|
||||
ttl 3600
|
||||
|
||||
# Forward unanswered or unmatched requests to proxy
|
||||
# without this flag, requesting A/AAAA records on compute.internal. will end here.
|
||||
fallthrough
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
|
||||
~~~ txt
|
||||
32.10.in-addr.arpa.arpa arpa.company.org {
|
||||
|
||||
reverse 10.32.0.0/16 {
|
||||
# template of the ip injection to hostname, zone resolved to arpa.company.org.
|
||||
hostname "ip-{ip}.v4.{zone[2]}"
|
||||
|
||||
ttl 3600
|
||||
|
||||
# fallthrough is not required, v4.arpa.company.org. will be only answered here
|
||||
}
|
||||
|
||||
# cidr closer to the ip wins, so we can overwrite the "default"
|
||||
reverse 10.32.2.0/24 {
|
||||
# its also possible to set fix domain suffix
|
||||
hostname ip-{ip}.fix.arpa.company.org.
|
||||
|
||||
ttl 3600
|
||||
}
|
||||
}
|
||||
~~~
|
||||
87
plugin/reverse/network.go
Normal file
87
plugin/reverse/network.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package reverse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type network struct {
|
||||
IPnet *net.IPNet
|
||||
Zone string // forward lookup zone
|
||||
Template string
|
||||
TTL uint32
|
||||
RegexMatchIP *regexp.Regexp
|
||||
}
|
||||
|
||||
// TODO: we might want to get rid of these regexes.
|
||||
const hexDigit = "0123456789abcdef"
|
||||
const templateNameIP = "{ip}"
|
||||
const regexMatchV4 = "((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))"
|
||||
const regexMatchV6 = "([0-9a-fA-F]{32})"
|
||||
|
||||
// hostnameToIP converts the hostname back to an ip, based on the template
|
||||
// returns nil if there is no IP found.
|
||||
func (network *network) hostnameToIP(rname string) net.IP {
|
||||
var matchedIP net.IP
|
||||
|
||||
match := network.RegexMatchIP.FindStringSubmatch(rname)
|
||||
if len(match) != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if network.IPnet.IP.To4() != nil {
|
||||
matchedIP = net.ParseIP(match[1])
|
||||
} else {
|
||||
// TODO: can probably just allocate a []byte and use that.
|
||||
var buf bytes.Buffer
|
||||
// convert back to an valid ipv6 string with colons
|
||||
for i := 0; i < 8*4; i += 4 {
|
||||
buf.WriteString(match[1][i : i+4])
|
||||
if i < 28 {
|
||||
buf.WriteString(":")
|
||||
}
|
||||
}
|
||||
matchedIP = net.ParseIP(buf.String())
|
||||
}
|
||||
|
||||
// No valid ip or it does not belong to this network
|
||||
if matchedIP == nil || !network.IPnet.Contains(matchedIP) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return matchedIP
|
||||
}
|
||||
|
||||
// ipToHostname converts an IP to an DNS compatible hostname and injects it into the template.domain.
|
||||
func (network *network) ipToHostname(ip net.IP) (name string) {
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
// replace . to -
|
||||
name = ipv4.String()
|
||||
} else {
|
||||
// assume v6
|
||||
// ensure zeros are present in string
|
||||
buf := make([]byte, 0, len(ip)*4)
|
||||
for i := 0; i < len(ip); i++ {
|
||||
v := ip[i]
|
||||
buf = append(buf, hexDigit[v>>4])
|
||||
buf = append(buf, hexDigit[v&0xF])
|
||||
}
|
||||
name = string(buf)
|
||||
}
|
||||
// inject the converted ip into the fqdn template
|
||||
return strings.Replace(network.Template, templateNameIP, name, 1)
|
||||
}
|
||||
|
||||
type networks []network
|
||||
|
||||
func (n networks) Len() int { return len(n) }
|
||||
func (n networks) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
|
||||
|
||||
// cidr closer to the ip wins (by netmask)
|
||||
func (n networks) Less(i, j int) bool {
|
||||
isize, _ := n[i].IPnet.Mask.Size()
|
||||
jsize, _ := n[j].IPnet.Mask.Size()
|
||||
return isize > jsize
|
||||
}
|
||||
135
plugin/reverse/network_test.go
Normal file
135
plugin/reverse/network_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package reverse
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test converting from hostname to IP and back again to hostname
|
||||
func TestNetworkConversion(t *testing.T) {
|
||||
|
||||
_, net4, _ := net.ParseCIDR("10.1.1.0/24")
|
||||
_, net6, _ := net.ParseCIDR("fd01::/64")
|
||||
|
||||
regexIP4, _ := regexp.Compile("^dns-" + regexMatchV4 + "\\.domain\\.internal\\.$")
|
||||
regexIP6, _ := regexp.Compile("^dns-" + regexMatchV6 + "\\.domain\\.internal\\.$")
|
||||
|
||||
tests := []struct {
|
||||
network network
|
||||
resultHost string
|
||||
resultIP net.IP
|
||||
}{
|
||||
{
|
||||
network{
|
||||
IPnet: net4,
|
||||
Template: "dns-{ip}.domain.internal.",
|
||||
RegexMatchIP: regexIP4,
|
||||
},
|
||||
"dns-10.1.1.23.domain.internal.",
|
||||
net.ParseIP("10.1.1.23"),
|
||||
},
|
||||
{
|
||||
network{
|
||||
IPnet: net6,
|
||||
Template: "dns-{ip}.domain.internal.",
|
||||
RegexMatchIP: regexIP6,
|
||||
},
|
||||
"dns-fd01000000000000000000000000a32f.domain.internal.",
|
||||
net.ParseIP("fd01::a32f"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
resultIP := test.network.hostnameToIP(test.resultHost)
|
||||
if !reflect.DeepEqual(test.resultIP, resultIP) {
|
||||
t.Fatalf("Test %d expected %v, got %v", i, test.resultIP, resultIP)
|
||||
}
|
||||
|
||||
resultHost := test.network.ipToHostname(test.resultIP)
|
||||
if !reflect.DeepEqual(test.resultHost, resultHost) {
|
||||
t.Fatalf("Test %d expected %v, got %v", i, test.resultHost, resultHost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkHostnameToIP(t *testing.T) {
|
||||
|
||||
_, net4, _ := net.ParseCIDR("10.1.1.0/24")
|
||||
_, net6, _ := net.ParseCIDR("fd01::/64")
|
||||
|
||||
regexIP4, _ := regexp.Compile("^dns-" + regexMatchV4 + "\\.domain\\.internal\\.$")
|
||||
regexIP6, _ := regexp.Compile("^dns-" + regexMatchV6 + "\\.domain\\.internal\\.$")
|
||||
|
||||
// Test regex does NOT match
|
||||
// All this test should return nil
|
||||
testsNil := []struct {
|
||||
network network
|
||||
hostname string
|
||||
}{
|
||||
{
|
||||
network{
|
||||
IPnet: net4,
|
||||
RegexMatchIP: regexIP4,
|
||||
},
|
||||
// domain does not match
|
||||
"dns-10.1.1.23.domain.internals.",
|
||||
},
|
||||
{
|
||||
network{
|
||||
IPnet: net4,
|
||||
RegexMatchIP: regexIP4,
|
||||
},
|
||||
// IP does match / contain in subnet
|
||||
"dns-200.1.1.23.domain.internals.",
|
||||
},
|
||||
{
|
||||
network{
|
||||
IPnet: net4,
|
||||
RegexMatchIP: regexIP4,
|
||||
},
|
||||
// template does not match
|
||||
"dns-10.1.1.23-x.domain.internal.",
|
||||
},
|
||||
{
|
||||
network{
|
||||
IPnet: net4,
|
||||
RegexMatchIP: regexIP4,
|
||||
},
|
||||
// template does not match
|
||||
"IP-dns-10.1.1.23.domain.internal.",
|
||||
},
|
||||
{
|
||||
network{
|
||||
IPnet: net6,
|
||||
RegexMatchIP: regexIP6,
|
||||
},
|
||||
// template does not match
|
||||
"dnx-fd01000000000000000000000000a32f.domain.internal.",
|
||||
},
|
||||
{
|
||||
network{
|
||||
IPnet: net6,
|
||||
RegexMatchIP: regexIP6,
|
||||
},
|
||||
// no valid v6 (missing one 0, only 31 chars)
|
||||
"dns-fd0100000000000000000000000a32f.domain.internal.",
|
||||
},
|
||||
{
|
||||
network{
|
||||
IPnet: net6,
|
||||
RegexMatchIP: regexIP6,
|
||||
},
|
||||
// IP does match / contain in subnet
|
||||
"dns-ed01000000000000000000000000a32f.domain.internal.",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range testsNil {
|
||||
resultIP := test.network.hostnameToIP(test.hostname)
|
||||
if resultIP != nil {
|
||||
t.Fatalf("Test %d expected nil, got %v", i, resultIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
107
plugin/reverse/reverse.go
Normal file
107
plugin/reverse/reverse.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package reverse
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
||||
"github.com/coredns/coredns/request"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Reverse provides dynamic reverse DNS and the related forward RR.
|
||||
type Reverse struct {
|
||||
Next plugin.Handler
|
||||
Networks networks
|
||||
Fallthrough bool
|
||||
}
|
||||
|
||||
// ServeDNS implements the plugin.Handler interface.
|
||||
func (re Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
var rr dns.RR
|
||||
|
||||
state := request.Request{W: w, Req: r}
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true
|
||||
|
||||
switch state.QType() {
|
||||
case dns.TypePTR:
|
||||
address := dnsutil.ExtractAddressFromReverse(state.Name())
|
||||
|
||||
if address == "" {
|
||||
// Not an reverse lookup, but can still be an pointer for an domain
|
||||
break
|
||||
}
|
||||
|
||||
ip := net.ParseIP(address)
|
||||
// loop through the configured networks
|
||||
for _, n := range re.Networks {
|
||||
if n.IPnet.Contains(ip) {
|
||||
rr = &dns.PTR{
|
||||
Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: n.TTL},
|
||||
Ptr: n.ipToHostname(ip),
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case dns.TypeA:
|
||||
for _, n := range re.Networks {
|
||||
if dns.IsSubDomain(n.Zone, state.Name()) {
|
||||
|
||||
// skip if requesting an v4 address and network is not v4
|
||||
if n.IPnet.IP.To4() == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
result := n.hostnameToIP(state.Name())
|
||||
if result != nil {
|
||||
rr = &dns.A{
|
||||
Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: n.TTL},
|
||||
A: result,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case dns.TypeAAAA:
|
||||
for _, n := range re.Networks {
|
||||
if dns.IsSubDomain(n.Zone, state.Name()) {
|
||||
|
||||
// Do not use To16 which tries to make v4 in v6
|
||||
if n.IPnet.IP.To4() != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
result := n.hostnameToIP(state.Name())
|
||||
if result != nil {
|
||||
rr = &dns.AAAA{
|
||||
Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: n.TTL},
|
||||
AAAA: result,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if rr != nil {
|
||||
m.Answer = append(m.Answer, rr)
|
||||
state.SizeAndDo(m)
|
||||
w.WriteMsg(m)
|
||||
return dns.RcodeSuccess, nil
|
||||
}
|
||||
|
||||
if re.Fallthrough {
|
||||
return plugin.NextOrFailure(re.Name(), re.Next, ctx, w, r)
|
||||
}
|
||||
return dns.RcodeServerFailure, nil
|
||||
}
|
||||
|
||||
// Name implements the Handler interface.
|
||||
func (re Reverse) Name() string { return "reverse" }
|
||||
71
plugin/reverse/reverse_test.go
Normal file
71
plugin/reverse/reverse_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package reverse
|
||||
|
||||
import (
|
||||
"net"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/coredns/coredns/plugin/pkg/dnsrecorder"
|
||||
"github.com/coredns/coredns/plugin/test"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
_, net4, _ := net.ParseCIDR("10.1.1.0/24")
|
||||
regexIP4, _ := regexp.Compile("^.*ip-" + regexMatchV4 + "\\.example\\.org\\.$")
|
||||
|
||||
em := Reverse{
|
||||
Networks: networks{network{
|
||||
IPnet: net4,
|
||||
Zone: "example.org",
|
||||
Template: "ip-{ip}.example.org.",
|
||||
RegexMatchIP: regexIP4,
|
||||
}},
|
||||
Fallthrough: false,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
next plugin.Handler
|
||||
qname string
|
||||
qtype uint16
|
||||
expectedCode int
|
||||
expectedReply string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
next: test.NextHandler(dns.RcodeSuccess, nil),
|
||||
qname: "test.ip-10.1.1.2.example.org",
|
||||
expectedCode: dns.RcodeSuccess,
|
||||
expectedReply: "10.1.1.2",
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
for i, tr := range tests {
|
||||
req := new(dns.Msg)
|
||||
|
||||
tr.qtype = dns.TypeA
|
||||
req.SetQuestion(dns.Fqdn(tr.qname), tr.qtype)
|
||||
|
||||
rec := dnsrecorder.New(&test.ResponseWriter{})
|
||||
code, err := em.ServeDNS(ctx, rec, req)
|
||||
|
||||
if err != tr.expectedErr {
|
||||
t.Errorf("Test %d: Expected error %v, but got %v", i, tr.expectedErr, err)
|
||||
}
|
||||
if code != int(tr.expectedCode) {
|
||||
t.Errorf("Test %d: Expected status code %d, but got %d", i, tr.expectedCode, code)
|
||||
}
|
||||
if tr.expectedReply != "" {
|
||||
answer := rec.Msg.Answer[0].(*dns.A).A.String()
|
||||
if answer != tr.expectedReply {
|
||||
t.Errorf("Test %d: Expected answer %s, but got %s", i, tr.expectedReply, answer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
147
plugin/reverse/setup.go
Normal file
147
plugin/reverse/setup.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package reverse
|
||||
|
||||
import (
|
||||
"net"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/coredns/coredns/core/dnsserver"
|
||||
"github.com/coredns/coredns/plugin"
|
||||
|
||||
"github.com/mholt/caddy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterPlugin("reverse", caddy.Plugin{
|
||||
ServerType: "dns",
|
||||
Action: setupReverse,
|
||||
})
|
||||
}
|
||||
|
||||
func setupReverse(c *caddy.Controller) error {
|
||||
networks, fallThrough, err := reverseParse(c)
|
||||
if err != nil {
|
||||
return plugin.Error("reverse", err)
|
||||
}
|
||||
|
||||
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
||||
return Reverse{Next: next, Networks: networks, Fallthrough: fallThrough}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) {
|
||||
zones := make([]string, len(c.ServerBlockKeys))
|
||||
wildcard := false
|
||||
|
||||
// We copy from the serverblock, these contains Hosts.
|
||||
for i, str := range c.ServerBlockKeys {
|
||||
zones[i] = plugin.Host(str).Normalize()
|
||||
}
|
||||
|
||||
for c.Next() {
|
||||
var cidrs []*net.IPNet
|
||||
|
||||
// parse all networks
|
||||
for _, cidr := range c.RemainingArgs() {
|
||||
if cidr == "{" {
|
||||
break
|
||||
}
|
||||
_, ipnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return nil, false, c.Errf("network needs to be CIDR formatted: %q\n", cidr)
|
||||
}
|
||||
cidrs = append(cidrs, ipnet)
|
||||
}
|
||||
if len(cidrs) == 0 {
|
||||
return nil, false, c.ArgErr()
|
||||
}
|
||||
|
||||
// set defaults
|
||||
var (
|
||||
template = "ip-" + templateNameIP + ".{zone[1]}"
|
||||
ttl = 60
|
||||
)
|
||||
for c.NextBlock() {
|
||||
switch c.Val() {
|
||||
case "hostname":
|
||||
if !c.NextArg() {
|
||||
return nil, false, c.ArgErr()
|
||||
}
|
||||
template = c.Val()
|
||||
|
||||
case "ttl":
|
||||
if !c.NextArg() {
|
||||
return nil, false, c.ArgErr()
|
||||
}
|
||||
ttl, err = strconv.Atoi(c.Val())
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
case "wildcard":
|
||||
wildcard = true
|
||||
|
||||
case "fallthrough":
|
||||
fall = true
|
||||
|
||||
default:
|
||||
return nil, false, c.ArgErr()
|
||||
}
|
||||
}
|
||||
|
||||
// prepare template
|
||||
// replace {zone[index]} by the listen zone/domain of this config block
|
||||
for i, zone := range zones {
|
||||
// TODO: we should be smarter about actually replacing this. This works, but silently allows "zone[-1]"
|
||||
// for instance.
|
||||
template = strings.Replace(template, "{zone["+strconv.Itoa(i+1)+"]}", zone, 1)
|
||||
}
|
||||
if !strings.HasSuffix(template, ".") {
|
||||
template += "."
|
||||
}
|
||||
|
||||
// extract zone from template
|
||||
templateZone := strings.SplitAfterN(template, ".", 2)
|
||||
if len(templateZone) != 2 || templateZone[1] == "" {
|
||||
return nil, false, c.Errf("cannot find domain in template '%v'", template)
|
||||
}
|
||||
|
||||
// Create for each configured network in this stanza
|
||||
for _, ipnet := range cidrs {
|
||||
// precompile regex for hostname to ip matching
|
||||
regexIP := regexMatchV4
|
||||
if ipnet.IP.To4() == nil {
|
||||
regexIP = regexMatchV6
|
||||
}
|
||||
prefix := "^"
|
||||
if wildcard {
|
||||
prefix += ".*"
|
||||
}
|
||||
regex, err := regexp.Compile(
|
||||
prefix + strings.Replace( // inject ip regex into template
|
||||
regexp.QuoteMeta(template), // escape dots
|
||||
regexp.QuoteMeta(templateNameIP),
|
||||
regexIP,
|
||||
1) + "$")
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
nets = append(nets, network{
|
||||
IPnet: ipnet,
|
||||
Zone: templateZone[1],
|
||||
Template: template,
|
||||
RegexMatchIP: regex,
|
||||
TTL: uint32(ttl),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// sort by cidr
|
||||
sort.Sort(nets)
|
||||
return nets, fall, nil
|
||||
}
|
||||
195
plugin/reverse/setup_test.go
Normal file
195
plugin/reverse/setup_test.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package reverse
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/mholt/caddy"
|
||||
)
|
||||
|
||||
func TestSetupParse(t *testing.T) {
|
||||
|
||||
_, net4, _ := net.ParseCIDR("10.1.1.0/24")
|
||||
_, net6, _ := net.ParseCIDR("fd01::/64")
|
||||
|
||||
regexIP4wildcard, _ := regexp.Compile("^.*ip-" + regexMatchV4 + "\\.domain\\.com\\.$")
|
||||
regexIP6, _ := regexp.Compile("^ip-" + regexMatchV6 + "\\.domain\\.com\\.$")
|
||||
regexIpv4dynamic, _ := regexp.Compile("^dynamic-" + regexMatchV4 + "-intern\\.dynamic\\.domain\\.com\\.$")
|
||||
regexIpv6dynamic, _ := regexp.Compile("^dynamic-" + regexMatchV6 + "-intern\\.dynamic\\.domain\\.com\\.$")
|
||||
regexIpv4vpndynamic, _ := regexp.Compile("^dynamic-" + regexMatchV4 + "-vpn\\.dynamic\\.domain\\.com\\.$")
|
||||
|
||||
serverBlockKeys := []string{"domain.com.:8053", "dynamic.domain.com.:8053"}
|
||||
|
||||
tests := []struct {
|
||||
inputFileRules string
|
||||
shouldErr bool
|
||||
networks networks
|
||||
}{
|
||||
{
|
||||
// with defaults
|
||||
`reverse fd01::/64`,
|
||||
false,
|
||||
networks{network{
|
||||
IPnet: net6,
|
||||
Template: "ip-{ip}.domain.com.",
|
||||
Zone: "domain.com.",
|
||||
TTL: 60,
|
||||
RegexMatchIP: regexIP6,
|
||||
}},
|
||||
},
|
||||
{
|
||||
`reverse`,
|
||||
true,
|
||||
networks{},
|
||||
},
|
||||
{
|
||||
//no cidr
|
||||
`reverse 10.1.1.1`,
|
||||
true,
|
||||
networks{},
|
||||
},
|
||||
{
|
||||
//no cidr
|
||||
`reverse 10.1.1.0/16 fd00::`,
|
||||
true,
|
||||
networks{},
|
||||
},
|
||||
{
|
||||
// invalid key
|
||||
`reverse 10.1.1.0/24 {
|
||||
notavailable
|
||||
}`,
|
||||
true,
|
||||
networks{},
|
||||
},
|
||||
{
|
||||
// no domain suffix
|
||||
`reverse 10.1.1.0/24 {
|
||||
hostname ip-{ip}.
|
||||
}`,
|
||||
true,
|
||||
networks{},
|
||||
},
|
||||
{
|
||||
// hostname requires an second arg
|
||||
`reverse 10.1.1.0/24 {
|
||||
hostname
|
||||
}`,
|
||||
true,
|
||||
networks{},
|
||||
},
|
||||
{
|
||||
// template breaks regex compile
|
||||
`reverse 10.1.1.0/24 {
|
||||
hostname ip-{[-x
|
||||
}`,
|
||||
true,
|
||||
networks{},
|
||||
},
|
||||
{
|
||||
// ttl requires an (u)int
|
||||
`reverse 10.1.1.0/24 {
|
||||
ttl string
|
||||
}`,
|
||||
true,
|
||||
networks{},
|
||||
},
|
||||
{
|
||||
`reverse fd01::/64 {
|
||||
hostname dynamic-{ip}-intern.{zone[2]}
|
||||
ttl 50
|
||||
}
|
||||
reverse 10.1.1.0/24 {
|
||||
hostname dynamic-{ip}-vpn.{zone[2]}
|
||||
fallthrough
|
||||
}`,
|
||||
false,
|
||||
networks{network{
|
||||
IPnet: net6,
|
||||
Template: "dynamic-{ip}-intern.dynamic.domain.com.",
|
||||
Zone: "dynamic.domain.com.",
|
||||
TTL: 50,
|
||||
RegexMatchIP: regexIpv6dynamic,
|
||||
}, network{
|
||||
IPnet: net4,
|
||||
Template: "dynamic-{ip}-vpn.dynamic.domain.com.",
|
||||
Zone: "dynamic.domain.com.",
|
||||
TTL: 60,
|
||||
RegexMatchIP: regexIpv4vpndynamic,
|
||||
}},
|
||||
},
|
||||
{
|
||||
// multiple networks in one stanza
|
||||
`reverse fd01::/64 10.1.1.0/24 {
|
||||
hostname dynamic-{ip}-intern.{zone[2]}
|
||||
ttl 50
|
||||
fallthrough
|
||||
}`,
|
||||
false,
|
||||
networks{network{
|
||||
IPnet: net6,
|
||||
Template: "dynamic-{ip}-intern.dynamic.domain.com.",
|
||||
Zone: "dynamic.domain.com.",
|
||||
TTL: 50,
|
||||
RegexMatchIP: regexIpv6dynamic,
|
||||
}, network{
|
||||
IPnet: net4,
|
||||
Template: "dynamic-{ip}-intern.dynamic.domain.com.",
|
||||
Zone: "dynamic.domain.com.",
|
||||
TTL: 50,
|
||||
RegexMatchIP: regexIpv4dynamic,
|
||||
}},
|
||||
},
|
||||
{
|
||||
// fix domain in template
|
||||
`reverse fd01::/64 {
|
||||
hostname dynamic-{ip}-intern.dynamic.domain.com
|
||||
ttl 300
|
||||
fallthrough
|
||||
}`,
|
||||
false,
|
||||
networks{network{
|
||||
IPnet: net6,
|
||||
Template: "dynamic-{ip}-intern.dynamic.domain.com.",
|
||||
Zone: "dynamic.domain.com.",
|
||||
TTL: 300,
|
||||
RegexMatchIP: regexIpv6dynamic,
|
||||
}},
|
||||
},
|
||||
{
|
||||
`reverse 10.1.1.0/24 {
|
||||
hostname ip-{ip}.{zone[1]}
|
||||
ttl 50
|
||||
wildcard
|
||||
fallthrough
|
||||
}`,
|
||||
false,
|
||||
networks{network{
|
||||
IPnet: net4,
|
||||
Template: "ip-{ip}.domain.com.",
|
||||
Zone: "domain.com.",
|
||||
TTL: 50,
|
||||
RegexMatchIP: regexIP4wildcard,
|
||||
}},
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
c := caddy.NewTestController("dns", test.inputFileRules)
|
||||
c.ServerBlockKeys = serverBlockKeys
|
||||
networks, _, err := reverseParse(c)
|
||||
|
||||
if err == nil && test.shouldErr {
|
||||
t.Fatalf("Test %d expected errors, but got no error", i)
|
||||
} else if err != nil && !test.shouldErr {
|
||||
t.Fatalf("Test %d expected no errors, but got '%v'", i, err)
|
||||
}
|
||||
for j, n := range networks {
|
||||
reflect.DeepEqual(test.networks[j], n)
|
||||
if !reflect.DeepEqual(test.networks[j], n) {
|
||||
t.Fatalf("Test %d/%d expected %v, got %v", i, j, test.networks[j], n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user