github.com/StackExchange/DNSControl@v0.2.8/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  }