middleware/reverse: random updates (#516)

* middleware/reverse: random updates

Make the documentation somewhat shorter (and hopefully clearer in the
process). Also to be on-par with the *auto* middleware, start counting
the referenced zones from 1 (instead of 0).
Some variable cleanups and use the NextOrFailure in the ServeDNS
function.

* More TODOs
This commit is contained in:
Miek Gieben 2017-02-10 12:48:51 +00:00 committed by GitHub
parent 87a39a6353
commit 3e196a6d57
5 changed files with 123 additions and 154 deletions

View file

@ -1,42 +1,43 @@
# reverse # reverse
The *reverse* middleware allows CoreDNS to respond dynamic to an PTR request and the related A/AAAA request. The *reverse* middleware allows CoreDNS to respond dynamicly to an PTR request and the related A/AAAA request.
## Syntax ## Syntax
~~~ ~~~
reverse NETWORK.. { reverse NETWORK... {
hostname TEMPLATE hostname TEMPLATE
[ttl TTL] [ttl TTL]
[fallthrough] [fallthrough]
~~~ ~~~
* **NETWORK** one or more CIDR formatted networks to respond on. * **NETWORK** one or more CIDR formatted networks to respond on.
* `hostname` inject the ip and zone to an template for the hostname. Defaults to "ip-{ip}.{zone[0]}". See below for template. * `hostname` inject the IP and zone to an template for the hostname. Defaults to "ip-{IP}.{zone[1]}". See below for template.
* `ttl` defaults to 60 * `ttl` defaults to 60
* `fallthrough` If zone matches and no record can be generated, pass request to the next middleware. * `fallthrough` If zone matches and no record can be generated, pass request to the next middleware.
### Template Syntax ### Template Syntax
The template for the hostname is used for generating the PTR for an reverse lookup and matching the forward lookup back to an ip.
The template for the hostname is used for generating the PTR for an reverse lookup and matching the
forward lookup back to an IP.
#### `{ip}` #### `{ip}`
This symbol is **required** to work.
V4 network replaces the "." with an "-". 10.1.1.1 results in "10-1-1-1" The `{ip}` symbol is **required** to make reverse work.
V6 network removes the ":" and fills the zeros. "ffff::ffff" results in "ffff000000000000000000000000ffff" For IPv4 lookups the "." is replaced with an "-", i.e.: 10.1.1.1 results in "10-1-1-1"
With IPv6 lookups the ":" is removed, and any zero ranged are expanded, i.e.:
"ffff::ffff" results in "ffff000000000000000000000000ffff"
#### `{zone[i]}` #### `{zone[i]}`
This symbol is **optional** to use and can be replaced by a fix zone string.
The zone will be matched by the configured listener on the server block key.
`i` needs to be replaced to the index of the configured listener zones, starting with 0.
`arpa.:53 domain.com.:8053` will resolve `zone{0}` to `arpa.` and `zone{1}` to `domain.com.` 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 to the index of the configured listener zones, starting with 1.
## Examples ## Examples
~~~ ~~~ txt
# Serve on port 53 arpa compute.internal {
# match arpa. and compute.internal. to resolv reverse and forward lookup
.arpa.:53 compute.internal.:53 {
# proxy unmatched requests # proxy unmatched requests
proxy . 8.8.8.8 proxy . 8.8.8.8
@ -48,32 +49,25 @@ The zone will be matched by the configured listener on the server block key.
# AAAA ip-fd010000000000000000000000000001.compute.internal. 3600 fd01::1 # AAAA ip-fd010000000000000000000000000001.compute.internal. 3600 fd01::1
reverse 10.32.0.0/16 fd01::/16 { reverse 10.32.0.0/16 fd01::/16 {
# template of the ip injection to hostname, zone resolved to compute.internal. # template of the ip injection to hostname, zone resolved to compute.internal.
hostname ip-{ip}.{zone[1]} hostname ip-{ip}.{zone[2]}
# set time-to-live of the RR
ttl 3600 ttl 3600
# forward unanswered or unmatched requests to proxy # Forward unanswered or unmatched requests to proxy # without this flag, requesting A/AAAA
# without this flag, requesting A/AAAA records on compute.internal. will end here records on compute.internal. will end here.
fallthrough fallthrough
} }
# cache with ttl timeout
cache
} }
~~~ ~~~
~~~ ~~~ txt
# Serve on port 53 32.10.in-addr.arpa.arpa arpa.company.org {
# listen only on the specific network
32.10.in-addr.arpa.arpa.:53 arpa.company.org.:53 {
reverse 10.32.0.0/16 { reverse 10.32.0.0/16 {
# template of the ip injection to hostname, zone resolved to arpa.company.org. # template of the ip injection to hostname, zone resolved to arpa.company.org.
hostname "ip-{ip}.v4.{zone[1]}" hostname "ip-{ip}.v4.{zone[2]}"
# set time-to-live of the RR
ttl 3600 ttl 3600
# fallthrough is not required, v4.arpa.company.org. will be only answered here # fallthrough is not required, v4.arpa.company.org. will be only answered here
@ -84,14 +78,7 @@ The zone will be matched by the configured listener on the server block key.
# its also possible to set fix domain suffix # its also possible to set fix domain suffix
hostname ip-{ip}.fix.arpa.company.org. hostname ip-{ip}.fix.arpa.company.org.
# set time-to-live of the RR
ttl 3600 ttl 3600
} }
# cache with ttl timeout
cache
} }
~~~ ~~~

View file

@ -1,9 +1,9 @@
package reverse package reverse
import ( import (
"bytes"
"net" "net"
"regexp" "regexp"
"bytes"
"strings" "strings"
) )
@ -16,32 +16,31 @@ type network struct {
Fallthrough bool Fallthrough bool
} }
// TODO: we might want to get rid of these regexes.
const hexDigit = "0123456789abcdef" const hexDigit = "0123456789abcdef"
const templateNameIP = "{ip}" 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 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})" const regexMatchV6 = "([0-9a-fA-F]{32})"
// For forward lookup // hostnameToIP converts the hostname back to an ip, based on the template
// converts the hostname back to an ip, based on the template // returns nil if there is no IP found.
// returns nil if there is no ip found
func (network *network) hostnameToIP(rname string) net.IP { func (network *network) hostnameToIP(rname string) net.IP {
var matchedIP net.IP var matchedIP net.IP
// use precompiled regex by setup
match := network.RegexMatchIP.FindStringSubmatch(rname) match := network.RegexMatchIP.FindStringSubmatch(rname)
// regex did not matched if len(match) != 2 {
if (len(match) != 2) {
return nil return nil
} }
if network.IPnet.IP.To4() != nil { if network.IPnet.IP.To4() != nil {
matchedIP = net.ParseIP(strings.Replace(match[1], "-", ".", 4)) matchedIP = net.ParseIP(strings.Replace(match[1], "-", ".", 4))
} else { } else {
// TODO: can probably just allocate a []byte and use that.
var buf bytes.Buffer var buf bytes.Buffer
// convert back to an valid ipv6 string with colons // convert back to an valid ipv6 string with colons
for i := 0; i < 8*4; i += 4 { for i := 0; i < 8*4; i += 4 {
buf.WriteString(match[1][i : i+4]) buf.WriteString(match[1][i : i+4])
if (i < 28) { if i < 28 {
buf.WriteString(":") buf.WriteString(":")
} }
} }
@ -56,13 +55,9 @@ func (network *network) hostnameToIP(rname string) net.IP {
return matchedIP return matchedIP
} }
// For reverse lookup // ipToHostname converts an IP to an DNS compatible hostname and injects it into the template.domain.
// Converts an Ip to an dns compatible hostname and injects it into the template.domain func (network *network) ipToHostname(ip net.IP) (name string) {
func (network *network) ipToHostname(ip net.IP) string { if ipv4 := ip.To4(); ipv4 != nil {
var name string
ipv4 := ip.To4()
if ipv4 != nil {
// replace . to - // replace . to -
name = uitoa(ipv4[0]) + "-" + name = uitoa(ipv4[0]) + "-" +
uitoa(ipv4[1]) + "-" + uitoa(ipv4[1]) + "-" +
@ -104,20 +99,12 @@ func uitoa(val uint8) string {
type networks []network type networks []network
// implements the sort interface func (n networks) Len() int { return len(n) }
func (slice networks) Len() int { func (n networks) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
return len(slice)
}
// implements the sort interface
// cidr closer to the ip wins (by netmask) // cidr closer to the ip wins (by netmask)
func (slice networks) Less(i, j int) bool { func (n networks) Less(i, j int) bool {
isize, _ := slice[i].IPnet.Mask.Size() isize, _ := n[i].IPnet.Mask.Size()
jsize, _ := slice[j].IPnet.Mask.Size() jsize, _ := n[j].IPnet.Mask.Size()
return isize > jsize return isize > jsize
} }
// implements the sort interface
func (slice networks) Swap(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
}

View file

@ -3,24 +3,26 @@ package reverse
import ( import (
"net" "net"
"github.com/miekg/coredns/request"
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/pkg/dnsutil" "github.com/miekg/coredns/middleware/pkg/dnsutil"
"github.com/miekg/coredns/request"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// Reverse provides dynamic reverse dns and the related forward rr // Reverse provides dynamic reverse DNS and the related forward RR.
type Reverse struct { type Reverse struct {
Next middleware.Handler Next middleware.Handler
Networks networks Networks networks
} }
// ServeDNS implements the middleware.Handler interface. // ServeDNS implements the middleware.Handler interface.
func (reverse Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { func (re Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
var rr dns.RR var (
nextHandler := true rr dns.RR
fallThrough bool
)
state := request.Request{W: w, Req: r} state := request.Request{W: w, Req: r}
m := new(dns.Msg) m := new(dns.Msg)
@ -38,9 +40,9 @@ func (reverse Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dn
ip := net.ParseIP(address) ip := net.ParseIP(address)
// loop through the configured networks // loop through the configured networks
for _, n := range reverse.Networks { for _, n := range re.Networks {
if (n.IPnet.Contains(ip)) { if n.IPnet.Contains(ip) {
nextHandler = n.Fallthrough fallThrough = n.Fallthrough
rr = &dns.PTR{ rr = &dns.PTR{
Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: n.TTL}, Hdr: dns.RR_Header{Name: state.QName(), Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: n.TTL},
Ptr: n.ipToHostname(ip), Ptr: n.ipToHostname(ip),
@ -50,9 +52,9 @@ func (reverse Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dn
} }
case dns.TypeA: case dns.TypeA:
for _, n := range reverse.Networks { for _, n := range re.Networks {
if dns.IsSubDomain(n.Zone, state.Name()) { if dns.IsSubDomain(n.Zone, state.Name()) {
nextHandler = n.Fallthrough fallThrough = n.Fallthrough
// skip if requesting an v4 address and network is not v4 // skip if requesting an v4 address and network is not v4
if n.IPnet.IP.To4() == nil { if n.IPnet.IP.To4() == nil {
@ -71,9 +73,9 @@ func (reverse Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dn
} }
case dns.TypeAAAA: case dns.TypeAAAA:
for _, n := range reverse.Networks { for _, n := range re.Networks {
if dns.IsSubDomain(n.Zone, state.Name()) { if dns.IsSubDomain(n.Zone, state.Name()) {
nextHandler = n.Fallthrough fallThrough = n.Fallthrough
// Do not use To16 which tries to make v4 in v6 // Do not use To16 which tries to make v4 in v6
if n.IPnet.IP.To4() != nil { if n.IPnet.IP.To4() != nil {
@ -93,13 +95,8 @@ func (reverse Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dn
} }
if rr == nil { if rr == nil && !fallThrough {
if reverse.Next == nil || !nextHandler { return middleware.NextOrFailure(re.Name(), re.Next, ctx, w, r)
// could not resolv
w.WriteMsg(m)
return dns.RcodeNameError, nil
}
return reverse.Next.ServeDNS(ctx, w, r)
} }
m.Answer = append(m.Answer, rr) m.Answer = append(m.Answer, rr)
@ -109,6 +106,4 @@ func (reverse Reverse) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dn
} }
// Name implements the Handler interface. // Name implements the Handler interface.
func (reverse Reverse) Name() string { func (re Reverse) Name() string { return "reverse" }
return "reverse"
}

View file

@ -2,10 +2,10 @@ package reverse
import ( import (
"net" "net"
"sort"
"strings"
"strconv"
"regexp" "regexp"
"sort"
"strconv"
"strings"
"github.com/miekg/coredns/core/dnsserver" "github.com/miekg/coredns/core/dnsserver"
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
@ -56,7 +56,7 @@ func reverseParse(c *caddy.Controller) (networks, error) {
} }
_, ipnet, err := net.ParseCIDR(cidr) _, ipnet, err := net.ParseCIDR(cidr)
if err != nil { if err != nil {
return nil, c.Errf("%v needs to be an CIDR formatted Network\n", cidr) return nil, c.Errf("network needs to be CIDR formatted: %q\n", cidr)
} }
cidrs = append(cidrs, ipnet) cidrs = append(cidrs, ipnet)
} }
@ -66,7 +66,7 @@ func reverseParse(c *caddy.Controller) (networks, error) {
// set defaults // set defaults
var ( var (
template = "ip-" + templateNameIP + ".{zone[0]}" template = "ip-" + templateNameIP + ".{zone[1]}"
ttl = 60 ttl = 60
fall = false fall = false
) )
@ -98,7 +98,9 @@ func reverseParse(c *caddy.Controller) (networks, error) {
// prepare template // prepare template
// replace {zone[index]} by the listen zone/domain of this config block // replace {zone[index]} by the listen zone/domain of this config block
for i, zone := range zones { for i, zone := range zones {
template = strings.Replace(template, "{zone[" + string(i + 48) + "]}", zone, 1) // 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, ".") { if !strings.HasSuffix(template, ".") {
template += "." template += "."
@ -107,7 +109,7 @@ func reverseParse(c *caddy.Controller) (networks, error) {
// extract zone from template // extract zone from template
templateZone := strings.SplitAfterN(template, ".", 2) templateZone := strings.SplitAfterN(template, ".", 2)
if len(templateZone) != 2 || templateZone[1] == "" { if len(templateZone) != 2 || templateZone[1] == "" {
return nil, c.Errf("Cannot find domain in template '%v'", template) return nil, c.Errf("cannot find domain in template '%v'", template)
} }
// Create for each configured network in this stanza // Create for each configured network in this stanza
@ -122,9 +124,8 @@ func reverseParse(c *caddy.Controller) (networks, error) {
regexp.QuoteMeta(template), // escape dots regexp.QuoteMeta(template), // escape dots
regexp.QuoteMeta(templateNameIP), regexp.QuoteMeta(templateNameIP),
regexIP, regexIP,
1, ) + "$") 1) + "$")
if err != nil { if err != nil {
// invalid regex
return nil, err return nil, err
} }

View file

@ -1,10 +1,10 @@
package reverse package reverse
import ( import (
"testing"
"net" "net"
"reflect" "reflect"
"regexp" "regexp"
"testing"
"github.com/mholt/caddy" "github.com/mholt/caddy"
) )
@ -98,11 +98,11 @@ func TestSetupParse(t *testing.T) {
}, },
{ {
`reverse fd01::/64 { `reverse fd01::/64 {
hostname dynamic-{ip}-intern.{zone[1]} hostname dynamic-{ip}-intern.{zone[2]}
ttl 50 ttl 50
} }
reverse 10.1.1.0/24 { reverse 10.1.1.0/24 {
hostname dynamic-{ip}-vpn.{zone[1]} hostname dynamic-{ip}-vpn.{zone[2]}
fallthrough fallthrough
}`, }`,
false, false,
@ -125,7 +125,7 @@ func TestSetupParse(t *testing.T) {
{ {
// multiple networks in one stanza // multiple networks in one stanza
`reverse fd01::/64 10.1.1.0/24 { `reverse fd01::/64 10.1.1.0/24 {
hostname dynamic-{ip}-intern.{zone[1]} hostname dynamic-{ip}-intern.{zone[2]}
ttl 50 ttl 50
fallthrough fallthrough
}`, }`,
@ -163,7 +163,6 @@ func TestSetupParse(t *testing.T) {
Fallthrough: true, Fallthrough: true,
}}, }},
}, },
} }
for i, test := range tests { for i, test := range tests {
c := caddy.NewTestController("dns", test.inputFileRules) c := caddy.NewTestController("dns", test.inputFileRules)