github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/pkg/transform/ptr.go (about) 1 package transform 2 3 import ( 4 "fmt" 5 "net" 6 "regexp" 7 "strconv" 8 "strings" 9 ) 10 11 // PtrNameMagic implements the PTR magic. 12 func PtrNameMagic(name, domain string) (string, error) { 13 // Implement the PTR name magic. If the name is a properly formed 14 // IPv4 or IPv6 address, we replace it with the right string (i.e 15 // reverse it and truncate it). 16 17 // If the name is already in-addr.arpa or ipv6.arpa, 18 // make sure the domain matches. 19 if strings.HasSuffix(name, ".in-addr.arpa.") || strings.HasSuffix(name, ".ip6.arpa.") { 20 if strings.HasSuffix(name, "."+domain+".") { 21 return strings.TrimSuffix(name, "."+domain+"."), nil 22 } 23 return name, fmt.Errorf("PTR record %v in wrong domain (%v)", name, domain) 24 } 25 26 // If the domain is .arpa, we do magic. 27 if strings.HasSuffix(domain, ".in-addr.arpa") { 28 return ipv4magic(name, domain) 29 } else if strings.HasSuffix(domain, ".ip6.arpa") { 30 return ipv6magic(name, domain) 31 } else { 32 return name, nil 33 } 34 } 35 36 func ipv4magic(name, domain string) (string, error) { 37 // Not a valid IPv4 address. Leave it alone. 38 ip := net.ParseIP(name) 39 if ip == nil || ip.To4() == nil || !strings.Contains(name, ".") { 40 return name, nil 41 } 42 43 // Reverse it. 44 rev, err := ReverseDomainName(ip.String() + "/32") 45 if err != nil { 46 return name, err 47 } 48 result := strings.TrimSuffix(rev, "."+domain) 49 50 // Are we in the right domain? 51 if strings.HasSuffix(rev, "."+domain) { 52 return result, nil 53 } 54 if ipMatchesClasslessDomain(ip, domain) { 55 return strings.SplitN(rev, ".", 2)[0], nil 56 } 57 58 return "", fmt.Errorf("PTR record %v in wrong IPv4 domain (%v)", name, domain) 59 } 60 61 var isRfc2317Format1 = regexp.MustCompile(`(\d{1,3})/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.in-addr\.arpa$`) 62 63 // ipMatchesClasslessDomain returns true if ip is appropriate for domain. 64 // domain is a reverse DNS lookup zone (in-addr.arpa) as described in RFC2317. 65 func ipMatchesClasslessDomain(ip net.IP, domain string) bool { 66 67 // The unofficial but preferred format in RFC2317: 68 m := isRfc2317Format1.FindStringSubmatch(domain) 69 if m != nil { 70 // IP: Domain: 71 // 172.20.18.27 128/27.18.20.172.in-addr.arpa 72 // A B C D F M X Y Z 73 // The following should be true: 74 // A==Z, B==Y, C==X. 75 // If you mask ip by M, the last octet should be F. 76 ii := ip.To4() 77 a, b, c, _ := ii[0], ii[1], ii[2], ii[3] 78 f, m, x, y, z := atob(m[1]), atob(m[2]), atob(m[3]), atob(m[4]), atob(m[5]) 79 masked := ip.Mask(net.CIDRMask(int(m), 32)) 80 if a == z && b == y && c == x && masked.Equal(net.IPv4(a, b, c, f)) { 81 return true 82 } 83 } 84 85 // To extend this to include other formats, add them here. 86 87 return false 88 } 89 90 // atob converts a to a byte value or panics. 91 func atob(s string) byte { 92 if i, err := strconv.Atoi(s); err == nil { 93 if i < 256 { 94 return byte(i) 95 } 96 } 97 panic(fmt.Sprintf("(%v) matched \\d{1,3} but is not a byte", s)) 98 } 99 100 func ipv6magic(name, domain string) (string, error) { 101 // Not a valid IPv6 address. Leave it alone. 102 ip := net.ParseIP(name) 103 if ip == nil || len(ip) != 16 || !strings.Contains(name, ":") { 104 return name, nil 105 } 106 107 // Reverse it. 108 rev, err := ReverseDomainName(ip.String() + "/128") 109 if err != nil { 110 return name, err 111 } 112 if !strings.HasSuffix(rev, "."+domain) { 113 err = fmt.Errorf("PTR record %v in wrong IPv6 domain (%v)", name, domain) 114 } 115 return strings.TrimSuffix(rev, "."+domain), err 116 }