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  }