package transform import ( "fmt" "net" "regexp" "strconv" "strings" "github.com/pkg/errors" ) func PtrNameMagic(name, domain string) (string, error) { // Implement the PTR name magic. If the name is a properly formed // IPv4 or IPv6 address, we replace it with the right string (i.e // reverse it and truncate it). // If the name is already in-addr.arpa or ipv6.arpa, // make sure the domain matches. if strings.HasSuffix(name, ".in-addr.arpa.") || strings.HasSuffix(name, ".ip6.arpa.") { if strings.HasSuffix(name, "."+domain+".") { return strings.TrimSuffix(name, "."+domain+"."), nil } else { return name, errors.Errorf("PTR record %v in wrong domain (%v)", name, domain) } } // If the domain is .arpa, we do magic. if strings.HasSuffix(domain, ".in-addr.arpa") { return ipv4magic(name, domain) } else if strings.HasSuffix(domain, ".ip6.arpa") { return ipv6magic(name, domain) } else { return name, nil } } func ipv4magic(name, domain string) (string, error) { // Not a valid IPv4 address. Leave it alone. ip := net.ParseIP(name) if ip == nil || ip.To4() == nil || !strings.Contains(name, ".") { return name, nil } // Reverse it. rev, err := ReverseDomainName(ip.String() + "/32") if err != nil { return name, err } result := strings.TrimSuffix(rev, "."+domain) // Are we in the right domain? if strings.HasSuffix(rev, "."+domain) { return result, nil } 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) { // Not a valid IPv6 address. Leave it alone. ip := net.ParseIP(name) if ip == nil || len(ip) != 16 || !strings.Contains(name, ":") { return name, nil } // Reverse it. rev, err := ReverseDomainName(ip.String() + "/128") if err != nil { return name, err } if !strings.HasSuffix(rev, "."+domain) { err = errors.Errorf("PTR record %v in wrong IPv6 domain (%v)", name, domain) } return strings.TrimSuffix(rev, "."+domain), err }