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  }