github.com/noisysockets/noisysockets@v0.21.2-0.20240515114641-7f467e651c90/internal/dns/addrselect/addrselect.go (about) 1 // SPDX-License-Identifier: MPL-2.0 2 /* 3 * Copyright (C) 2024 The Noisy Sockets Authors. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * Portions of this file are based on code originally from the Go project, 10 * 11 * Copyright (c) 2015 The Go Authors. All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions are 15 * met: 16 * 17 * * Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * * Redistributions in binary form must reproduce the above 20 * copyright notice, this list of conditions and the following disclaimer 21 * in the documentation and/or other materials provided with the 22 * distribution. 23 * * Neither the name of Google Inc. nor the names of its 24 * contributors may be used to endorse or promote products derived from 25 * this software without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 28 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 29 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 30 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 31 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 32 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 33 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 34 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 35 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 36 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38 */ 39 40 // Package addrselect implements RFC 6724, which describes the 41 // preferred source and destination address selection algorithm for 42 // Internet Protocol version 6 (IPv6) and Internet Protocol version 4 43 // (IPv4). 44 package addrselect 45 46 import ( 47 stdnet "net" 48 "net/netip" 49 "sort" 50 51 "github.com/noisysockets/noisysockets/network" 52 ) 53 54 func SortByRFC6724(net network.Network, addrs []netip.Addr) { 55 if len(addrs) < 2 { 56 return 57 } 58 SortByRFC6724withSrcs(net, addrs, srcAddrs(net, addrs)) 59 } 60 61 func SortByRFC6724withSrcs(net network.Network, addrs []netip.Addr, srcs []netip.Addr) { 62 if len(addrs) != len(srcs) { 63 panic("internal error") 64 } 65 addrAttr := make([]ipAttr, len(addrs)) 66 srcAttr := make([]ipAttr, len(srcs)) 67 for i, v := range addrs { 68 addrAttrIP, _ := netip.AddrFromSlice(v.AsSlice()) 69 addrAttr[i] = ipAttrOf(addrAttrIP) 70 srcAttr[i] = ipAttrOf(srcs[i]) 71 } 72 sort.Stable(&byRFC6724{ 73 addrs: addrs, 74 addrAttr: addrAttr, 75 srcs: srcs, 76 srcAttr: srcAttr, 77 }) 78 } 79 80 // srcAddrs tries to UDP-connect to each address to see if it has a 81 // route. (This doesn't send any packets). The destination port 82 // number is irrelevant. 83 func srcAddrs(net network.Network, addrs []netip.Addr) []netip.Addr { 84 srcs := make([]netip.Addr, len(addrs)) 85 for i := range addrs { 86 c, err := net.Dial("udp", stdnet.JoinHostPort(addrs[i].String(), "9")) 87 if err == nil { 88 if src, ok := c.LocalAddr().(*stdnet.UDPAddr); ok { 89 srcs[i], _ = netip.AddrFromSlice(src.IP) 90 } 91 _ = c.Close() 92 } 93 } 94 return srcs 95 } 96 97 type ipAttr struct { 98 Scope scope 99 Precedence uint8 100 Label uint8 101 } 102 103 func ipAttrOf(ip netip.Addr) ipAttr { 104 if !ip.IsValid() { 105 return ipAttr{} 106 } 107 match := rfc6724policyTable.Classify(ip) 108 return ipAttr{ 109 Scope: classifyScope(ip), 110 Precedence: match.Precedence, 111 Label: match.Label, 112 } 113 } 114 115 type byRFC6724 struct { 116 addrs []netip.Addr // addrs to sort 117 addrAttr []ipAttr 118 srcs []netip.Addr // or not valid addr if unreachable 119 srcAttr []ipAttr 120 } 121 122 func (s *byRFC6724) Len() int { return len(s.addrs) } 123 124 func (s *byRFC6724) Swap(i, j int) { 125 s.addrs[i], s.addrs[j] = s.addrs[j], s.addrs[i] 126 s.srcs[i], s.srcs[j] = s.srcs[j], s.srcs[i] 127 s.addrAttr[i], s.addrAttr[j] = s.addrAttr[j], s.addrAttr[i] 128 s.srcAttr[i], s.srcAttr[j] = s.srcAttr[j], s.srcAttr[i] 129 } 130 131 // Less reports whether i is a better destination address for this 132 // host than j. 133 // 134 // The algorithm and variable names comes from RFC 6724 section 6. 135 func (s *byRFC6724) Less(i, j int) bool { 136 DA := s.addrs[i] 137 DB := s.addrs[j] 138 SourceDA := s.srcs[i] 139 SourceDB := s.srcs[j] 140 attrDA := &s.addrAttr[i] 141 attrDB := &s.addrAttr[j] 142 attrSourceDA := &s.srcAttr[i] 143 attrSourceDB := &s.srcAttr[j] 144 145 const preferDA = true 146 const preferDB = false 147 148 // Rule 1: Avoid unusable destinations. 149 // If DB is known to be unreachable or if Source(DB) is undefined, then 150 // prefer DA. Similarly, if DA is known to be unreachable or if 151 // Source(DA) is undefined, then prefer DB. 152 if !SourceDA.IsValid() && !SourceDB.IsValid() { 153 return false // "equal" 154 } 155 if !SourceDB.IsValid() { 156 return preferDA 157 } 158 if !SourceDA.IsValid() { 159 return preferDB 160 } 161 162 // Rule 2: Prefer matching scope. 163 // If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)), 164 // then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and 165 // Scope(DB) = Scope(Source(DB)), then prefer DB. 166 if attrDA.Scope == attrSourceDA.Scope && attrDB.Scope != attrSourceDB.Scope { 167 return preferDA 168 } 169 if attrDA.Scope != attrSourceDA.Scope && attrDB.Scope == attrSourceDB.Scope { 170 return preferDB 171 } 172 173 // Rule 3: Avoid deprecated addresses. 174 // If Source(DA) is deprecated and Source(DB) is not, then prefer DB. 175 // Similarly, if Source(DA) is not deprecated and Source(DB) is 176 // deprecated, then prefer DA. 177 178 // TODO(bradfitz): implement? low priority for now. 179 180 // Rule 4: Prefer home addresses. 181 // If Source(DA) is simultaneously a home address and care-of address 182 // and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is 183 // simultaneously a home address and care-of address and Source(DA) is 184 // not, then prefer DB. 185 186 // TODO(bradfitz): implement? low priority for now. 187 188 // Rule 5: Prefer matching label. 189 // If Label(Source(DA)) = Label(DA) and Label(Source(DB)) <> Label(DB), 190 // then prefer DA. Similarly, if Label(Source(DA)) <> Label(DA) and 191 // Label(Source(DB)) = Label(DB), then prefer DB. 192 if attrSourceDA.Label == attrDA.Label && 193 attrSourceDB.Label != attrDB.Label { 194 return preferDA 195 } 196 if attrSourceDA.Label != attrDA.Label && 197 attrSourceDB.Label == attrDB.Label { 198 return preferDB 199 } 200 201 // Rule 6: Prefer higher precedence. 202 // If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if 203 // Precedence(DA) < Precedence(DB), then prefer DB. 204 if attrDA.Precedence > attrDB.Precedence { 205 return preferDA 206 } 207 if attrDA.Precedence < attrDB.Precedence { 208 return preferDB 209 } 210 211 // Rule 7: Prefer native transport. 212 // If DA is reached via an encapsulating transition mechanism (e.g., 213 // IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is 214 // reached via encapsulation and DA is not, then prefer DA. 215 216 // TODO(bradfitz): implement? low priority for now. 217 218 // Rule 8: Prefer smaller scope. 219 // If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) > 220 // Scope(DB), then prefer DB. 221 if attrDA.Scope < attrDB.Scope { 222 return preferDA 223 } 224 if attrDA.Scope > attrDB.Scope { 225 return preferDB 226 } 227 228 // Rule 9: Use the longest matching prefix. 229 // When DA and DB belong to the same address family (both are IPv6 or 230 // both are IPv4 [but see below]): If CommonPrefixLen(Source(DA), DA) > 231 // CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if 232 // CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB), 233 // then prefer DB. 234 // 235 // However, applying this rule to IPv4 addresses causes 236 // problems (see issues 13283 and 18518), so limit to IPv6. 237 if DA.Is6() && DB.Is6() { 238 commonA := commonPrefixLen(SourceDA, DA) 239 commonB := commonPrefixLen(SourceDB, DB) 240 241 if commonA > commonB { 242 return preferDA 243 } 244 if commonA < commonB { 245 return preferDB 246 } 247 } 248 249 // Rule 10: Otherwise, leave the order unchanged. 250 // If DA preceded DB in the original list, prefer DA. 251 // Otherwise, prefer DB. 252 return false // "equal" 253 } 254 255 type policyTableEntry struct { 256 Prefix netip.Prefix 257 Precedence uint8 258 Label uint8 259 } 260 261 type policyTable []policyTableEntry 262 263 // RFC 6724 section 2.1. 264 // Items are sorted by the size of their Prefix.Mask.Size, 265 var rfc6724policyTable = policyTable{ 266 { 267 // "::1/128" 268 Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}), 128), 269 Precedence: 50, 270 Label: 0, 271 }, 272 { 273 // "::ffff:0:0/96" 274 // IPv4-compatible, etc. 275 Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}), 96), 276 Precedence: 35, 277 Label: 4, 278 }, 279 { 280 // "::/96" 281 Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 96), 282 Precedence: 1, 283 Label: 3, 284 }, 285 { 286 // "2001::/32" 287 // Teredo 288 Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01}), 32), 289 Precedence: 5, 290 Label: 5, 291 }, 292 { 293 // "2002::/16" 294 // 6to4 295 Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x02}), 16), 296 Precedence: 30, 297 Label: 2, 298 }, 299 { 300 // "3ffe::/16" 301 Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x3f, 0xfe}), 16), 302 Precedence: 1, 303 Label: 12, 304 }, 305 { 306 // "fec0::/10" 307 Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfe, 0xc0}), 10), 308 Precedence: 1, 309 Label: 11, 310 }, 311 { 312 // "fc00::/7" 313 Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfc}), 7), 314 Precedence: 3, 315 Label: 13, 316 }, 317 { 318 // "::/0" 319 Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0), 320 Precedence: 40, 321 Label: 1, 322 }, 323 } 324 325 // Classify returns the policyTableEntry of the entry with the longest 326 // matching prefix that contains ip. 327 // The table t must be sorted from largest mask size to smallest. 328 func (t policyTable) Classify(ip netip.Addr) policyTableEntry { 329 // Prefix.Contains() will not match an IPv6 prefix for an IPv4 address. 330 if ip.Is4() { 331 ip = netip.AddrFrom16(ip.As16()) 332 } 333 for _, ent := range t { 334 if ent.Prefix.Contains(ip) { 335 return ent 336 } 337 } 338 return policyTableEntry{} 339 } 340 341 // RFC 6724 section 3.1. 342 type scope uint8 343 344 const ( 345 scopeInterfaceLocal scope = 0x1 346 scopeLinkLocal scope = 0x2 347 scopeAdminLocal scope = 0x4 348 scopeSiteLocal scope = 0x5 349 scopeOrgLocal scope = 0x8 350 scopeGlobal scope = 0xe 351 ) 352 353 func classifyScope(ip netip.Addr) scope { 354 if ip.IsLoopback() || ip.IsLinkLocalUnicast() { 355 return scopeLinkLocal 356 } 357 ipv6 := ip.Is6() && !ip.Is4In6() 358 ipv6AsBytes := ip.As16() 359 if ipv6 && ip.IsMulticast() { 360 return scope(ipv6AsBytes[1] & 0xf) 361 } 362 // Site-local addresses are defined in RFC 3513 section 2.5.6 363 // (and deprecated in RFC 3879). 364 if ipv6 && ipv6AsBytes[0] == 0xfe && ipv6AsBytes[1]&0xc0 == 0xc0 { 365 return scopeSiteLocal 366 } 367 return scopeGlobal 368 } 369 370 // commonPrefixLen reports the length of the longest prefix (looking 371 // at the most significant, or leftmost, bits) that the 372 // two addresses have in common, up to the length of a's prefix (i.e., 373 // the portion of the address not including the interface ID). 374 // 375 // If a or b is an IPv4 address as an IPv6 address, the IPv4 addresses 376 // are compared (with max common prefix length of 32). 377 // If a and b are different IP versions, 0 is returned. 378 // 379 // See https://tools.ietf.org/html/rfc6724#section-2.2 380 func commonPrefixLen(a, b netip.Addr) (cpl int) { 381 // Normalize IPv4-mapped IPv6 addresses to their IPv4 representations 382 if a.Is4In6() { 383 a = a.Unmap() 384 } 385 if b.Is4In6() { 386 b = b.Unmap() 387 } 388 389 // If a and b are of different IP versions, return 0 390 if a.Is4() != b.Is4() { 391 return 0 392 } 393 394 aAsSlice := a.AsSlice() 395 bAsSlice := b.AsSlice() 396 397 // If IPv6, only up to the prefix (first 64 bits) 398 if len(aAsSlice) > 8 { 399 aAsSlice = aAsSlice[:8] 400 bAsSlice = bAsSlice[:8] 401 } 402 403 for len(aAsSlice) > 0 { 404 if aAsSlice[0] == bAsSlice[0] { 405 cpl += 8 406 aAsSlice = aAsSlice[1:] 407 bAsSlice = bAsSlice[1:] 408 continue 409 } 410 bits := 8 411 ab, bb := aAsSlice[0], bAsSlice[0] 412 for { 413 ab >>= 1 414 bb >>= 1 415 bits-- 416 if ab == bb { 417 cpl += bits 418 return 419 } 420 } 421 } 422 return 423 }