github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/pkg/transform/arpa.go (about)

     1  package transform
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"strings"
     7  )
     8  
     9  // ReverseDomainName turns a CIDR block into a reversed (in-addr) name.
    10  func ReverseDomainName(cidr string) (string, error) {
    11  	a, c, err := net.ParseCIDR(cidr)
    12  	if err != nil {
    13  		return "", err
    14  	}
    15  	base, err := reverseaddr(a.String())
    16  	if err != nil {
    17  		return "", err
    18  	}
    19  	base = strings.TrimRight(base, ".")
    20  	if !a.Equal(c.IP) {
    21  		return "", fmt.Errorf("CIDR %v has 1 bits beyond the mask", cidr)
    22  	}
    23  
    24  	bits, total := c.Mask.Size()
    25  	var toTrim int
    26  	if bits == 0 {
    27  		return "", fmt.Errorf("Cannot use /0 in reverse cidr")
    28  	}
    29  
    30  	// Handle IPv4 "Classless in-addr.arpa delegation" RFC2317:
    31  	if total == 32 && bits >= 25 && bits < 32 {
    32  		// first address / netmask . Class-b-arpa.
    33  		fparts := strings.Split(c.IP.String(), ".")
    34  		first := fparts[3]
    35  		bparts := strings.SplitN(base, ".", 2)
    36  		return fmt.Sprintf("%s/%d.%s", first, bits, bparts[1]), nil
    37  	}
    38  
    39  	// Handle IPv4 Class-full and IPv6:
    40  	if total == 32 {
    41  		if bits%8 != 0 {
    42  			return "", fmt.Errorf("IPv4 mask must be multiple of 8 bits")
    43  		}
    44  		toTrim = (total - bits) / 8
    45  	} else if total == 128 {
    46  		if bits%4 != 0 {
    47  			return "", fmt.Errorf("IPv6 mask must be multiple of 4 bits")
    48  		}
    49  		toTrim = (total - bits) / 4
    50  	} else {
    51  		return "", fmt.Errorf("Address is not IPv4 or IPv6: %v", cidr)
    52  	}
    53  
    54  	parts := strings.SplitN(base, ".", toTrim+1)
    55  	return parts[len(parts)-1], nil
    56  }
    57  
    58  // copied from go source.
    59  // https://github.com/golang/go/blob/bfc164c64d33edfaf774b5c29b9bf5648a6447fb/src/net/dnsclient.go#L15
    60  
    61  // reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
    62  // address addr suitable for rDNS (PTR) record lookup or an error if it fails
    63  // to parse the IP address.
    64  func reverseaddr(addr string) (arpa string, err error) {
    65  	ip := net.ParseIP(addr)
    66  	if ip == nil {
    67  		return "", &net.DNSError{Err: "unrecognized address", Name: addr}
    68  	}
    69  	if ip.To4() != nil {
    70  		return uitoa(uint(ip[15])) + "." + uitoa(uint(ip[14])) + "." + uitoa(uint(ip[13])) + "." + uitoa(uint(ip[12])) + ".in-addr.arpa.", nil
    71  	}
    72  	// Must be IPv6
    73  	buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
    74  	// Add it, in reverse, to the buffer
    75  	for i := len(ip) - 1; i >= 0; i-- {
    76  		v := ip[i]
    77  		buf = append(buf, hexDigit[v&0xF])
    78  		buf = append(buf, '.')
    79  		buf = append(buf, hexDigit[v>>4])
    80  		buf = append(buf, '.')
    81  	}
    82  	// Append "ip6.arpa." and return (buf already has the final .)
    83  	buf = append(buf, "ip6.arpa."...)
    84  	return string(buf), nil
    85  }
    86  
    87  // Convert unsigned integer to decimal string.
    88  func uitoa(val uint) string {
    89  	if val == 0 { // avoid string allocation
    90  		return "0"
    91  	}
    92  	var buf [20]byte // big enough for 64bit value base 10
    93  	i := len(buf) - 1
    94  	for val >= 10 {
    95  		q := val / 10
    96  		buf[i] = byte('0' + val - q*10)
    97  		i--
    98  		val = q
    99  	}
   100  	// val < 10
   101  	buf[i] = byte('0' + val)
   102  	return string(buf[i:])
   103  }
   104  
   105  const hexDigit = "0123456789abcdef"