From ab00797f89b598a391a428e24f2a05332ed109a3 Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Wed, 15 Jan 2025 02:07:19 +0000 Subject: [PATCH] FEATURE: Extend PTR magic handling to support RFC4183 names (#3364) --- pkg/transform/ptr.go | 68 ++++++++++++++++++++++++++++++++++++--- pkg/transform/ptr_test.go | 17 ++++++++++ 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/pkg/transform/ptr.go b/pkg/transform/ptr.go index 53b71d9bcb..4ec0c81cad 100644 --- a/pkg/transform/ptr.go +++ b/pkg/transform/ptr.go @@ -51,18 +51,22 @@ func ipv4magic(name, domain string) (string, error) { if strings.HasSuffix(rev, "."+domain) { return result, nil } - if ipMatchesClasslessDomain(ip, domain) { - return strings.SplitN(rev, ".", 2)[0], nil + octets := ipMatchesClasslessDomain(ip, domain) + if octets > 0 { + return strings.Join(strings.SplitN(rev, ".", octets+1)[0:octets], "."), nil } return "", fmt.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$`) +var isRfc4183Format1 = regexp.MustCompile(`(\d{1,3})-(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.in-addr\.arpa$`) +var isRfc4183Format2 = regexp.MustCompile(`(\d{1,3})-(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.in-addr\.arpa$`) +var isRfc4183Format3 = regexp.MustCompile(`(\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 { +func ipMatchesClasslessDomain(ip net.IP, domain string) int { // The unofficial but preferred format in RFC2317: m := isRfc2317Format1.FindStringSubmatch(domain) if m != nil { @@ -77,13 +81,67 @@ func ipMatchesClasslessDomain(ip net.IP, domain string) bool { 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 + return 1 + } + } + + // The format in RFC4183 for /25 to /32: + m = isRfc4183Format1.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 1 + } + } + + // The format in RFC4183 for /17 to /23: + m = isRfc4183Format2.FindStringSubmatch(domain) + if m != nil { + // IP: Domain: + // 172.20.18.27 128-27.20.172.in-addr.arpa + // A B C D F M Y Z + // The following should be true: + // A==Z, B==Y, + // If you mask ip by M, the second last octet should be F. + ii := ip.To4() + a, b, _, _ := ii[0], ii[1], ii[2], ii[3] + f, m, y, z := atob(m[1]), atob(m[2]), atob(m[3]), atob(m[4]) + masked := ip.Mask(net.CIDRMask(int(m), 32)) + if a == z && b == y && masked.Equal(net.IPv4(a, b, f, 0)) { + return 2 + } + } + + // The format in RFC4183 for /9 to /15: + m = isRfc4183Format3.FindStringSubmatch(domain) + if m != nil { + // IP: Domain: + // 172.20.18.27 128-27.172.in-addr.arpa + // A B C D F M Z + // The following should be true: + // A==Z, + // If you mask ip by M, the third last octet should be F. + ii := ip.To4() + a, _, _, _ := ii[0], ii[1], ii[2], ii[3] + f, m, z := atob(m[1]), atob(m[2]), atob(m[3]) + masked := ip.Mask(net.CIDRMask(int(m), 32)) + if a == z && masked.Equal(net.IPv4(a, f, 0, 0)) { + return 3 } } // To extend this to include other formats, add them here. - return false + return 0 } // atob converts a to a byte value or panics. diff --git a/pkg/transform/ptr_test.go b/pkg/transform/ptr_test.go index 93a1d3a916..dee42dc08f 100644 --- a/pkg/transform/ptr_test.go +++ b/pkg/transform/ptr_test.go @@ -48,6 +48,23 @@ func TestPtrMagic(t *testing.T) { {"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}, + // RFC4183 (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}, + // 172.20.160.0/19 is .160 - .191: + {"172.20.159.1", "160-19.20.172.in-addr.arpa", "", true}, + {"172.20.160.2", "160-19.20.172.in-addr.arpa", "2.160", false}, + {"172.20.191.3", "160-19.20.172.in-addr.arpa", "3.191", false}, + {"172.20.192.4", "160-19.20.172.in-addr.arpa", "", true}, + // 172.160.0.0/11 is .160 - .191: + {"172.159.1.5", "160-11.172.in-addr.arpa", "", true}, + {"172.160.2.6", "160-11.172.in-addr.arpa", "6.2.160", false}, + {"172.191.3.7", "160-11.172.in-addr.arpa", "7.3.191", false}, + {"172.192.4.8", "160-11.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},