sigs.k8s.io/external-dns@v0.14.1/pkg/rfc2317/arpa.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package rfc2317
    18  
    19  import (
    20  	"fmt"
    21  	"net"
    22  	"strconv"
    23  	"strings"
    24  )
    25  
    26  // CidrToInAddr converts a CIDR block into its reverse lookup (in-addr) name.
    27  // Given "2001::/16" returns "1.0.0.2.ip6.arpa"
    28  // Given "10.20.30.0/24" returns "30.20.10.in-addr.arpa"
    29  // Given "10.20.30.0/25" returns "0/25.30.20.10.in-addr.arpa" (RFC2317)
    30  func CidrToInAddr(cidr string) (string, error) {
    31  	// If the user sent an IP instead of a CIDR (i.e. no "/"), turn it
    32  	// into a CIDR by adding /32 or /128 as appropriate.
    33  	ip := net.ParseIP(cidr)
    34  	if ip != nil {
    35  		if ip.To4() != nil {
    36  			cidr = ip.String() + "/32"
    37  			// Older code used `cidr + "/32"` but that didn't work with
    38  			// "IPv4 mapped IPv6 address". ip.String() returns the IPv4
    39  			// address for all IPv4 addresses no matter how they are
    40  			// expressed internally.
    41  		} else {
    42  			cidr = cidr + "/128"
    43  		}
    44  	}
    45  
    46  	a, c, err := net.ParseCIDR(cidr)
    47  	if err != nil {
    48  		return "", err
    49  	}
    50  	base, err := reverseaddr(a.String())
    51  	if err != nil {
    52  		return "", err
    53  	}
    54  	base = strings.TrimRight(base, ".")
    55  	if !a.Equal(c.IP) {
    56  		return "", fmt.Errorf("CIDR %v has 1 bits beyond the mask", cidr)
    57  	}
    58  
    59  	bits, total := c.Mask.Size()
    60  	var toTrim int
    61  	if bits == 0 {
    62  		return "", fmt.Errorf("cannot use /0 in reverse CIDR")
    63  	}
    64  
    65  	// Handle IPv4 "Classless in-addr.arpa delegation" RFC2317:
    66  	if total == 32 && bits >= 25 && bits < 32 {
    67  		// first address / netmask . Class-b-arpa.
    68  		fparts := strings.Split(c.IP.String(), ".")
    69  		first := fparts[3]
    70  		bparts := strings.SplitN(base, ".", 2)
    71  		return fmt.Sprintf("%s/%d.%s", first, bits, bparts[1]), nil
    72  	}
    73  
    74  	// Handle IPv4 Class-full and IPv6:
    75  	if total == 32 {
    76  		if bits%8 != 0 {
    77  			return "", fmt.Errorf("IPv4 mask must be multiple of 8 bits")
    78  		}
    79  		toTrim = (total - bits) / 8
    80  	} else if total == 128 {
    81  		if bits%4 != 0 {
    82  			return "", fmt.Errorf("IPv6 mask must be multiple of 4 bits")
    83  		}
    84  		toTrim = (total - bits) / 4
    85  	} else {
    86  		return "", fmt.Errorf("invalid address (not IPv4 or IPv6): %v", cidr)
    87  	}
    88  
    89  	parts := strings.SplitN(base, ".", toTrim+1)
    90  	return parts[len(parts)-1], nil
    91  }
    92  
    93  // copied from go source.
    94  // https://github.com/golang/go/blob/38b2c06e144c6ea7087c575c76c66e41265ae0b7/src/net/dnsclient.go#L26C1-L51C1
    95  // The go source does not export this function so we copy it here.
    96  
    97  // reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
    98  // address addr suitable for rDNS (PTR) record lookup or an error if it fails
    99  // to parse the IP address.
   100  func reverseaddr(addr string) (arpa string, err error) {
   101  	ip := net.ParseIP(addr)
   102  	if ip == nil {
   103  		return "", &net.DNSError{Err: "unrecognized address", Name: addr}
   104  	}
   105  	if ip.To4() != nil {
   106  		return Uitoa(uint(ip[15])) + "." + Uitoa(uint(ip[14])) + "." + Uitoa(uint(ip[13])) + "." + Uitoa(uint(ip[12])) + ".in-addr.arpa.", nil
   107  	}
   108  	// Must be IPv6
   109  	buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
   110  	// Add it, in reverse, to the buffer
   111  	for i := len(ip) - 1; i >= 0; i-- {
   112  		v := ip[i]
   113  		buf = append(buf, hexDigit[v&0xF],
   114  			'.',
   115  			hexDigit[v>>4],
   116  			'.')
   117  	}
   118  	// Append "ip6.arpa." and return (buf already has the final .)
   119  	buf = append(buf, "ip6.arpa."...)
   120  	return string(buf), nil
   121  }
   122  
   123  const hexDigit = "0123456789abcdef"
   124  
   125  func Uitoa(val uint) string {
   126  	return strconv.FormatInt(int64(val), 10)
   127  }