1
0
mirror of https://github.com/StackExchange/dnscontrol.git synced 2024-05-11 05:55:12 +00:00

PTR should handle "Classless in-addr.arpa delegation" RFC2317 (#149)

* Handle IPv4 "Classless in-addr.arpa delegation" RFC2317 (partial).
* Validate PTR name when in RFC2317 "Classless in-addr.arpa delegation" domains.
* Update docs
* Set CanUsePTR for Route53 and Google CloudDNS.
* BIND: Replace "/" with "_" in filenames.
This commit is contained in:
Tom Limoncelli
2017-07-10 19:24:55 -04:00
committed by GitHub
parent 9e66402e0b
commit e563c53658
9 changed files with 203 additions and 25 deletions

View File

@ -4,6 +4,8 @@ import (
"fmt"
"net"
"strings"
"github.com/pkg/errors"
)
func ReverseDomainName(cidr string) (string, error) {
@ -16,11 +18,26 @@ func ReverseDomainName(cidr string) (string, error) {
return "", err
}
base = strings.TrimRight(base, ".")
if !a.Equal(c.IP) {
return "", errors.Errorf("CIDR %v has 1 bits beyond the mask", cidr)
}
bits, total := c.Mask.Size()
var toTrim int
if bits == 0 {
return "", fmt.Errorf("Cannot use /0 in reverse cidr")
}
// Handle IPv4 "Classless in-addr.arpa delegation" RFC2317:
if total == 32 && bits >= 25 && bits < 32 {
// first address / netmask . Class-b-arpa.
fparts := strings.Split(c.IP.String(), ".")
first := fparts[3]
bparts := strings.SplitN(base, ".", 2)
return fmt.Sprintf("%s/%d.%s", first, bits, bparts[1]), nil
}
// Handle IPv4 Class-full and IPv6:
if total == 32 {
if bits%8 != 0 {
return "", fmt.Errorf("IPv4 mask must be multiple of 8 bits")

View File

@ -10,13 +10,15 @@ func TestReverse(t *testing.T) {
out string
}{
{"174.136.107.0/24", false, "107.136.174.in-addr.arpa"},
{"174.136.107.1/24", true, "107.136.174.in-addr.arpa"},
{"174.136.0.0/16", false, "136.174.in-addr.arpa"},
{"174.136.43.0/16", false, "136.174.in-addr.arpa"}, //do bits set inside the masked range matter? Should this be invalid? Is there a shorter way to specify this?
{"174.136.43.0/16", true, "136.174.in-addr.arpa"},
{"174.0.0.0/8", false, "174.in-addr.arpa"},
{"174.136.43.0/8", false, "174.in-addr.arpa"},
{"174.136.43.0/8", false, "174.in-addr.arpa"},
{"174.136.43.0/8", true, "174.in-addr.arpa"},
{"174.136.0.44/8", true, "174.in-addr.arpa"},
{"174.136.45.45/8", true, "174.in-addr.arpa"},
{"2001::/16", false, "1.0.0.2.ip6.arpa"},
{"2001:0db8:0123:4567:89ab:cdef:1234:5670/124", false, "7.6.5.4.3.2.1.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.8.b.d.0.1.0.0.2.ip6.arpa"},
@ -24,6 +26,51 @@ func TestReverse(t *testing.T) {
{"174.136.107.14/32", false, "14.107.136.174.in-addr.arpa"},
{"2001:0db8:0123:4567:89ab:cdef:1234:5678/128", false, "8.7.6.5.4.3.2.1.f.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.8.b.d.0.1.0.0.2.ip6.arpa"},
// IPv4 "Classless in-addr.arpa delegation" RFC2317.
// From examples in the RFC:
{"192.0.2.0/25", false, "0/25.2.0.192.in-addr.arpa"},
{"192.0.2.128/26", false, "128/26.2.0.192.in-addr.arpa"},
{"192.0.2.192/26", false, "192/26.2.0.192.in-addr.arpa"},
// All the base cases:
{"174.1.0.0/25", false, "0/25.0.1.174.in-addr.arpa"},
{"174.1.0.0/26", false, "0/26.0.1.174.in-addr.arpa"},
{"174.1.0.0/27", false, "0/27.0.1.174.in-addr.arpa"},
{"174.1.0.0/28", false, "0/28.0.1.174.in-addr.arpa"},
{"174.1.0.0/29", false, "0/29.0.1.174.in-addr.arpa"},
{"174.1.0.0/30", false, "0/30.0.1.174.in-addr.arpa"},
{"174.1.0.0/31", false, "0/31.0.1.174.in-addr.arpa"},
// /25 (all cases)
{"174.1.0.0/25", false, "0/25.0.1.174.in-addr.arpa"},
{"174.1.0.128/25", false, "128/25.0.1.174.in-addr.arpa"},
// /26 (all cases)
{"174.1.0.0/26", false, "0/26.0.1.174.in-addr.arpa"},
{"174.1.0.64/26", false, "64/26.0.1.174.in-addr.arpa"},
{"174.1.0.128/26", false, "128/26.0.1.174.in-addr.arpa"},
{"174.1.0.192/26", false, "192/26.0.1.174.in-addr.arpa"},
// /27 (all cases)
{"174.1.0.0/27", false, "0/27.0.1.174.in-addr.arpa"},
{"174.1.0.32/27", false, "32/27.0.1.174.in-addr.arpa"},
{"174.1.0.64/27", false, "64/27.0.1.174.in-addr.arpa"},
{"174.1.0.96/27", false, "96/27.0.1.174.in-addr.arpa"},
{"174.1.0.128/27", false, "128/27.0.1.174.in-addr.arpa"},
{"174.1.0.160/27", false, "160/27.0.1.174.in-addr.arpa"},
{"174.1.0.192/27", false, "192/27.0.1.174.in-addr.arpa"},
{"174.1.0.224/27", false, "224/27.0.1.174.in-addr.arpa"},
// /28 (first 2, last 2)
{"174.1.0.0/28", false, "0/28.0.1.174.in-addr.arpa"},
{"174.1.0.16/28", false, "16/28.0.1.174.in-addr.arpa"},
{"174.1.0.224/28", false, "224/28.0.1.174.in-addr.arpa"},
{"174.1.0.240/28", false, "240/28.0.1.174.in-addr.arpa"},
// /29 (first 2 cases)
{"174.1.0.0/29", false, "0/29.0.1.174.in-addr.arpa"},
{"174.1.0.8/29", false, "8/29.0.1.174.in-addr.arpa"},
// /30 (first 2 cases)
{"174.1.0.0/30", false, "0/30.0.1.174.in-addr.arpa"},
{"174.1.0.4/30", false, "4/30.0.1.174.in-addr.arpa"},
// /31 (first 2 cases)
{"174.1.0.0/31", false, "0/31.0.1.174.in-addr.arpa"},
{"174.1.0.2/31", false, "2/31.0.1.174.in-addr.arpa"},
//Errror Cases:
{"0.0.0.0/0", true, ""},
{"2001::/0", true, ""},
@ -37,7 +84,7 @@ func TestReverse(t *testing.T) {
t.Error("Should not have errored ", err)
} else if tst.isError && err == nil {
t.Errorf("Should have errored, but didn't. Got %s", d)
} else if d != tst.out {
} else if (!tst.isError) && d != tst.out {
t.Errorf("Expected '%s' but got '%s'", tst.out, d)
}
})

View File

@ -1,7 +1,10 @@
package transform
import (
"fmt"
"net"
"regexp"
"strconv"
"strings"
"github.com/pkg/errors"
@ -44,10 +47,56 @@ func ipv4magic(name, domain string) (string, error) {
if err != nil {
return name, err
}
if !strings.HasSuffix(rev, "."+domain) {
err = errors.Errorf("ERROR: PTR record %v in wrong IPv4 domain (%v)", name, domain)
result := strings.TrimSuffix(rev, "."+domain)
// Are we in the right domain?
if strings.HasSuffix(rev, "."+domain) {
return result, nil
}
return strings.TrimSuffix(rev, "."+domain), err
if ipMatchesClasslessDomain(ip, domain) {
return strings.SplitN(rev, ".", 2)[0], nil
}
return "", errors.Errorf("PTR record %v in wrong IPv4 domain (%v)", name, domain)
}
var isRfc2317Format1 = regexp.MustCompile(`(\d{1,3})/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.in-addr\.arpa$`)
// ipMatchesClasslessDomain returns true if ip is appropriate for domain.
// domain is a reverse DNS lookup zone (in-addr.arpa) as described in RFC2317.
func ipMatchesClasslessDomain(ip net.IP, domain string) bool {
// The unofficial but preferred format in RFC2317:
m := isRfc2317Format1.FindStringSubmatch(domain)
if m != nil {
// IP: Domain:
// 172.20.18.27 128/27.18.20.172.in-addr.arpa
// A B C D F M X Y Z
// The following should be true:
// A==Z, B==Y, C==X.
// If you mask ip by M, the last octet should be F.
ii := ip.To4()
a, b, c, _ := ii[0], ii[1], ii[2], ii[3]
f, m, x, y, z := atob(m[1]), atob(m[2]), atob(m[3]), atob(m[4]), atob(m[5])
masked := ip.Mask(net.CIDRMask(int(m), 32))
if a == z && b == y && c == x && masked.Equal(net.IPv4(a, b, c, f)) {
return true
}
}
// To extend this to include other formats, add them here.
return false
}
// atob converts a to a byte value or panics.
func atob(s string) byte {
if i, err := strconv.Atoi(s); err == nil {
if i < 256 {
return byte(i)
}
}
panic(fmt.Sprintf("(%v) matched \\d{1,3} but is not a byte", s))
}
func ipv6magic(name, domain string) (string, error) {
@ -63,7 +112,7 @@ func ipv6magic(name, domain string) (string, error) {
return name, err
}
if !strings.HasSuffix(rev, "."+domain) {
err = errors.Errorf("ERROR: PTR record %v in wrong IPv6 domain (%v)", name, domain)
err = errors.Errorf("PTR record %v in wrong IPv6 domain (%v)", name, domain)
}
return strings.TrimSuffix(rev, "."+domain), err
}

View File

@ -41,6 +41,13 @@ func TestPtrMagic(t *testing.T) {
{"1.0.0.0.0.0.0.0.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0.0.0.0.0.0.0", false},
{"1.0.0.0.0.0.0.0.0.0.0.0.0.0", "0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", "1.0.0.0.0.0.0.0.0.0.0.0.0.0", false},
// RFC2317 (Classless)
// 172.20.18.160/27 is .160 - .191:
{"172.20.18.159", "160/27.18.20.172.in-addr.arpa", "", true},
{"172.20.18.160", "160/27.18.20.172.in-addr.arpa", "160", false},
{"172.20.18.191", "160/27.18.20.172.in-addr.arpa", "191", false},
{"172.20.18.192", "160/27.18.20.172.in-addr.arpa", "", true},
// If it doesn't end in .arpa, the magic is disabled:
{"1.2.3.4", "example.com", "1.2.3.4", false},
{"1", "example.com", "1", false},