1// Copyright 2009 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package net 6 7import ( 8 "cmp" 9 "internal/bytealg" 10 "internal/itoa" 11 "slices" 12 _ "unsafe" // for go:linkname 13 14 "golang.org/x/net/dns/dnsmessage" 15) 16 17// provided by runtime 18// 19//go:linkname runtime_rand runtime.rand 20func runtime_rand() uint64 21 22func randInt() int { 23 return int(uint(runtime_rand()) >> 1) // clear sign bit 24} 25 26func randIntn(n int) int { 27 return randInt() % n 28} 29 30// reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP 31// address addr suitable for rDNS (PTR) record lookup or an error if it fails 32// to parse the IP address. 33func reverseaddr(addr string) (arpa string, err error) { 34 ip := ParseIP(addr) 35 if ip == nil { 36 return "", &DNSError{Err: "unrecognized address", Name: addr} 37 } 38 if ip.To4() != nil { 39 return itoa.Uitoa(uint(ip[15])) + "." + itoa.Uitoa(uint(ip[14])) + "." + itoa.Uitoa(uint(ip[13])) + "." + itoa.Uitoa(uint(ip[12])) + ".in-addr.arpa.", nil 40 } 41 // Must be IPv6 42 buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) 43 // Add it, in reverse, to the buffer 44 for i := len(ip) - 1; i >= 0; i-- { 45 v := ip[i] 46 buf = append(buf, hexDigit[v&0xF], 47 '.', 48 hexDigit[v>>4], 49 '.') 50 } 51 // Append "ip6.arpa." and return (buf already has the final .) 52 buf = append(buf, "ip6.arpa."...) 53 return string(buf), nil 54} 55 56func equalASCIIName(x, y dnsmessage.Name) bool { 57 if x.Length != y.Length { 58 return false 59 } 60 for i := 0; i < int(x.Length); i++ { 61 a := x.Data[i] 62 b := y.Data[i] 63 if 'A' <= a && a <= 'Z' { 64 a += 0x20 65 } 66 if 'A' <= b && b <= 'Z' { 67 b += 0x20 68 } 69 if a != b { 70 return false 71 } 72 } 73 return true 74} 75 76// isDomainName checks if a string is a presentation-format domain name 77// (currently restricted to hostname-compatible "preferred name" LDH labels and 78// SRV-like "underscore labels"; see golang.org/issue/12421). 79// 80// isDomainName should be an internal detail, 81// but widely used packages access it using linkname. 82// Notable members of the hall of shame include: 83// - github.com/sagernet/sing 84// 85// Do not remove or change the type signature. 86// See go.dev/issue/67401. 87// 88//go:linkname isDomainName 89func isDomainName(s string) bool { 90 // The root domain name is valid. See golang.org/issue/45715. 91 if s == "." { 92 return true 93 } 94 95 // See RFC 1035, RFC 3696. 96 // Presentation format has dots before every label except the first, and the 97 // terminal empty label is optional here because we assume fully-qualified 98 // (absolute) input. We must therefore reserve space for the first and last 99 // labels' length octets in wire format, where they are necessary and the 100 // maximum total length is 255. 101 // So our _effective_ maximum is 253, but 254 is not rejected if the last 102 // character is a dot. 103 l := len(s) 104 if l == 0 || l > 254 || l == 254 && s[l-1] != '.' { 105 return false 106 } 107 108 last := byte('.') 109 nonNumeric := false // true once we've seen a letter or hyphen 110 partlen := 0 111 for i := 0; i < len(s); i++ { 112 c := s[i] 113 switch { 114 default: 115 return false 116 case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_': 117 nonNumeric = true 118 partlen++ 119 case '0' <= c && c <= '9': 120 // fine 121 partlen++ 122 case c == '-': 123 // Byte before dash cannot be dot. 124 if last == '.' { 125 return false 126 } 127 partlen++ 128 nonNumeric = true 129 case c == '.': 130 // Byte before dot cannot be dot, dash. 131 if last == '.' || last == '-' { 132 return false 133 } 134 if partlen > 63 || partlen == 0 { 135 return false 136 } 137 partlen = 0 138 } 139 last = c 140 } 141 if last == '-' || partlen > 63 { 142 return false 143 } 144 145 return nonNumeric 146} 147 148// absDomainName returns an absolute domain name which ends with a 149// trailing dot to match pure Go reverse resolver and all other lookup 150// routines. 151// See golang.org/issue/12189. 152// But we don't want to add dots for local names from /etc/hosts. 153// It's hard to tell so we settle on the heuristic that names without dots 154// (like "localhost" or "myhost") do not get trailing dots, but any other 155// names do. 156func absDomainName(s string) string { 157 if bytealg.IndexByteString(s, '.') != -1 && s[len(s)-1] != '.' { 158 s += "." 159 } 160 return s 161} 162 163// An SRV represents a single DNS SRV record. 164type SRV struct { 165 Target string 166 Port uint16 167 Priority uint16 168 Weight uint16 169} 170 171// byPriorityWeight sorts SRV records by ascending priority and weight. 172type byPriorityWeight []*SRV 173 174// shuffleByWeight shuffles SRV records by weight using the algorithm 175// described in RFC 2782. 176func (addrs byPriorityWeight) shuffleByWeight() { 177 sum := 0 178 for _, addr := range addrs { 179 sum += int(addr.Weight) 180 } 181 for sum > 0 && len(addrs) > 1 { 182 s := 0 183 n := randIntn(sum) 184 for i := range addrs { 185 s += int(addrs[i].Weight) 186 if s > n { 187 if i > 0 { 188 addrs[0], addrs[i] = addrs[i], addrs[0] 189 } 190 break 191 } 192 } 193 sum -= int(addrs[0].Weight) 194 addrs = addrs[1:] 195 } 196} 197 198// sort reorders SRV records as specified in RFC 2782. 199func (addrs byPriorityWeight) sort() { 200 slices.SortFunc(addrs, func(a, b *SRV) int { 201 if r := cmp.Compare(a.Priority, b.Priority); r != 0 { 202 return r 203 } 204 return cmp.Compare(a.Weight, b.Weight) 205 }) 206 i := 0 207 for j := 1; j < len(addrs); j++ { 208 if addrs[i].Priority != addrs[j].Priority { 209 addrs[i:j].shuffleByWeight() 210 i = j 211 } 212 } 213 addrs[i:].shuffleByWeight() 214} 215 216// An MX represents a single DNS MX record. 217type MX struct { 218 Host string 219 Pref uint16 220} 221 222// byPref sorts MX records by preference 223type byPref []*MX 224 225// sort reorders MX records as specified in RFC 5321. 226func (s byPref) sort() { 227 for i := range s { 228 j := randIntn(i + 1) 229 s[i], s[j] = s[j], s[i] 230 } 231 slices.SortFunc(s, func(a, b *MX) int { 232 return cmp.Compare(a.Pref, b.Pref) 233 }) 234} 235 236// An NS represents a single DNS NS record. 237type NS struct { 238 Host string 239} 240