// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package dnsutil import ( "errors" "net" "regexp" "slices" "strings" "github.com/miekg/dns" ) type TranslateAddressAccept int // MaxLabelLength is the maximum length for a name that can be used in DNS. const ( MaxLabelLength = 63 arpaLabel = "arpa" arpaIPV4Label = "in-addr" arpaIPV6Label = "ip6" TranslateAddressAcceptDomain TranslateAddressAccept = 1 << iota TranslateAddressAcceptIPv4 TranslateAddressAcceptIPv6 TranslateAddressAcceptAny TranslateAddressAccept = ^0 ) // InvalidNameRe is a regex that matches characters which can not be included in // a DNS name. var InvalidNameRe = regexp.MustCompile(`[^A-Za-z0-9\\-]+`) // matches valid DNS labels according to RFC 1123, should be at most 63 // characters according to the RFC var validLabel = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?$`) // IsValidLabel returns true if the string given is a valid DNS label (RFC 1123). // Note: the only difference between RFC 1035 and RFC 1123 labels is that in // RFC 1123 labels can begin with a number. func IsValidLabel(name string) bool { return validLabel.MatchString(name) } // ValidateLabel is similar to IsValidLabel except it returns an error // instead of false when name is not a valid DNS label. The error will contain // reference to what constitutes a valid DNS label. func ValidateLabel(name string) error { if !IsValidLabel(name) { return errors.New("a valid DNS label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character") } return nil } // IPFromARPA returns the net.IP address from a fully-qualified ARPA PTR domain name. // If the address is an invalid format, it returns nil. func IPFromARPA(arpa string) net.IP { labels := dns.SplitDomainName(arpa) if len(labels) != 6 && len(labels) != 34 { return nil } // The last two labels should be "in-addr" or "ip6" and "arpa" if labels[len(labels)-1] != arpaLabel { return nil } var ip net.IP switch labels[len(labels)-2] { case arpaIPV4Label: parts := labels[:len(labels)-2] slices.Reverse(parts) ip = net.ParseIP(strings.Join(parts, ".")) case arpaIPV6Label: parts := labels[:len(labels)-2] slices.Reverse(parts) // Condense the different words of the address address := strings.Join(parts[0:4], "") for i := 4; i <= len(parts)-4; i = i + 4 { word := parts[i : i+4] address = address + ":" + strings.Join(word, "") } ip = net.ParseIP(address) // default: fallthrough } return ip }