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