Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 100 additions & 100 deletions src/net/addrselect.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,51 @@
package net

import (
"math/rand/v2"
"net/netip"
"slices"
)

func sortByRFC6724(addrs []IPAddr) {
// sortByRFC6724 sort ip by RFC6724
// dnsLoadBalance true for dial usage with shuffle before sort
// dnsLoadBalance flase for simple DNS with stable sort
func sortByRFC6724(addrs []IPAddr, dnsLoadbalance bool) {
if len(addrs) < 2 {
return
}
sortByRFC6724withSrcs(addrs, srcAddrs(addrs))
sortByRFC6724withSrcs(addrs, srcAddrs(addrs), dnsLoadbalance)
}

func sortByRFC6724withSrcs(addrs []IPAddr, srcs []netip.Addr) {
// sortByRFC6724withSrcs sort by RFC6724 with srcs
// shuffle addrInfos before sorting if dnsLoadbalance is true
// section 6. Rule9 and Rule10 superseded for DNS load-balance indirectly
func sortByRFC6724withSrcs(addrs []IPAddr, srcs []netip.Addr, dnsLoadbalance bool) {
if len(addrs) != len(srcs) {
panic("internal error")
}
addrInfos := make([]byRFC6724Info, len(addrs))
for i, v := range addrs {
addrAttrIP, _ := netip.AddrFromSlice(v.IP)
addrAttr := ipAttrOf(addrAttrIP)
srcAttr := ipAttrOf(srcs[i])
addrInfos[i] = byRFC6724Info{
addr: addrs[i],
addrAttr: ipAttrOf(addrAttrIP),
addrAttr: addrAttr,
src: srcs[i],
srcAttr: ipAttrOf(srcs[i]),
srcAttr: srcAttr,
score: scoreByRFC6724(addrs[i].IP, srcs[i], addrAttr, srcAttr, dnsLoadbalance),
}
}

// shuffle addrInfos before sorting if dnsLoadbalance is true
// section 6. Rule9 and Rule10 superseded for DNS load-balance indirectly
if dnsLoadbalance {
rand.Shuffle(len(addrInfos), func(i, j int) {
addrInfos[i], addrInfos[j] = addrInfos[j], addrInfos[i]
})
}
slices.SortStableFunc(addrInfos, compareByRFC6724)

for i := range addrInfos {
addrs[i] = addrInfos[i].addr
}
Expand Down Expand Up @@ -81,130 +100,111 @@ type byRFC6724Info struct {
addrAttr ipAttr
src netip.Addr
srcAttr ipAttr
// score use to sort to implement RFC 6724 section 6.
score int
}

// compareByRFC6724 compares two byRFC6724Info records and returns an integer
// indicating the order. It follows the algorithm and variable names from
// RFC 6724 section 6. Returns -1 if a is preferred, 1 if b is preferred,
// and 0 if they are equal.
func compareByRFC6724(a, b byRFC6724Info) int {
DA := a.addr.IP
DB := b.addr.IP
SourceDA := a.src
SourceDB := b.src
attrDA := &a.addrAttr
attrDB := &b.addrAttr
attrSourceDA := &a.srcAttr
attrSourceDB := &b.srcAttr

const preferDA = -1
const preferDB = 1
return b.score - a.score
}

// Rule 1: Avoid unusable destinations.
// If DB is known to be unreachable or if Source(DB) is undefined, then
// prefer DA. Similarly, if DA is known to be unreachable or if
// Source(DA) is undefined, then prefer DB.
if !SourceDA.IsValid() && !SourceDB.IsValid() {
return 0 // "equal"
}
if !SourceDB.IsValid() {
return preferDA
}
if !SourceDA.IsValid() {
return preferDB
// scoreByRFC6724 Calculate score with bit in reverse for sort
// Rules 9 and 10 ignored if ignoreRule9and10
func scoreByRFC6724(ip IP, source netip.Addr, attr, attrSource ipAttr, ignoreRule9and10 bool) int {
var score int
bitIndex := 1

// RFC6724 section 6.
// 6. Destination Address Selection
// Rule 9: Use longest matching prefix.
// When DA and DB belong to the same address family (both are IPv6 or
// both are IPv4): If CommonPrefixLen(Source(DA), DA) >
// CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if
// CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB),
// then prefer DB.
// Rule 10: Otherwise, leave the order unchanged.
// If DA preceded DB in the original list, prefer DA. Otherwise, prefer
// DB.
// Rules 9 and 10 MAY be superseded if the implementation has other
// means of sorting destination addresses. For example, if the
// implementation somehow knows which destination addresses will result
// in the "best" communications performance.
if !ignoreRule9and10 {
// However, applying this rule to IPv4 addresses causes
// problems (see issues 13283 and 18518), so limit to IPv6.
// TODO compare between ipv4 and ipv6 ignoreed insignificant ?
if ip.To4() == nil {
score += commonPrefixLen(source, ip)
}
}
bitIndex += 7

// Rule 2: Prefer matching scope.
// If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)),
// then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and
// Scope(DB) = Scope(Source(DB)), then prefer DB.
if attrDA.Scope == attrSourceDA.Scope && attrDB.Scope != attrSourceDB.Scope {
return preferDA
}
if attrDA.Scope != attrSourceDA.Scope && attrDB.Scope == attrSourceDB.Scope {
return preferDB
}
// Rule 8: Prefer smaller scope.
// If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) >
// Scope(DB), then prefer DB.
score += ((1 << 8) - int(attr.Scope)) << bitIndex
bitIndex += 8

// Rule 3: Avoid deprecated addresses.
// If Source(DA) is deprecated and Source(DB) is not, then prefer DB.
// Similarly, if Source(DA) is not deprecated and Source(DB) is
// deprecated, then prefer DA.
// Rule 7: Prefer native transport.
// If DA is reached via an encapsulating transition mechanism (e.g.,
// IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is
// reached via encapsulation and DA is not, then prefer DA.

// TODO(bradfitz): implement? low priority for now.

// Rule 4: Prefer home addresses.
// If Source(DA) is simultaneously a home address and care-of address
// and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is
// simultaneously a home address and care-of address and Source(DA) is
// not, then prefer DB.

// TODO(bradfitz): implement? low priority for now.
// Rule 6: Prefer higher precedence.
// If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if
// Precedence(DA) < Precedence(DB), then prefer DB.
score += int(attr.Precedence) << bitIndex
bitIndex += 8

// Rule 5: Prefer matching label.
// If Label(Source(DA)) = Label(DA) and Label(Source(DB)) <> Label(DB),
// then prefer DA. Similarly, if Label(Source(DA)) <> Label(DA) and
// Label(Source(DB)) = Label(DB), then prefer DB.
if attrSourceDA.Label == attrDA.Label &&
attrSourceDB.Label != attrDB.Label {
return preferDA
}
if attrSourceDA.Label != attrDA.Label &&
attrSourceDB.Label == attrDB.Label {
return preferDB
if attrSource.Label == attr.Label {
score += 1 << bitIndex
}
bitIndex++

// Rule 6: Prefer higher precedence.
// If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if
// Precedence(DA) < Precedence(DB), then prefer DB.
if attrDA.Precedence > attrDB.Precedence {
return preferDA
}
if attrDA.Precedence < attrDB.Precedence {
return preferDB
}
// Rule 4: Prefer home addresses.
// If Source(DA) is simultaneously a home address and care-of address
// and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is
// simultaneously a home address and care-of address and Source(DA) is
// not, then prefer DB.

// Rule 7: Prefer native transport.
// If DA is reached via an encapsulating transition mechanism (e.g.,
// IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is
// reached via encapsulation and DA is not, then prefer DA.
// TODO(bradfitz): implement? low priority for now.

// Rule 3: Avoid deprecated addresses.
// If Source(DA) is deprecated and Source(DB) is not, then prefer DB.
// Similarly, if Source(DA) is not deprecated and Source(DB) is
// deprecated, then prefer DA.

// TODO(bradfitz): implement? low priority for now.

// Rule 8: Prefer smaller scope.
// If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) >
// Scope(DB), then prefer DB.
if attrDA.Scope < attrDB.Scope {
return preferDA
}
if attrDA.Scope > attrDB.Scope {
return preferDB
// Rule 2: Prefer matching scope.
// If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)),
// then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and
// Scope(DB) = Scope(Source(DB)), then prefer DB.
if attr.Scope == attrSource.Scope {
score += 1 << bitIndex
}
bitIndex++

// Rule 9: Use the longest matching prefix.
// When DA and DB belong to the same address family (both are IPv6 or
// both are IPv4 [but see below]): If CommonPrefixLen(Source(DA), DA) >
// CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if
// CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB),
// then prefer DB.
//
// However, applying this rule to IPv4 addresses causes
// problems (see issues 13283 and 18518), so limit to IPv6.
if DA.To4() == nil && DB.To4() == nil {
commonA := commonPrefixLen(SourceDA, DA)
commonB := commonPrefixLen(SourceDB, DB)

if commonA > commonB {
return preferDA
}
if commonA < commonB {
return preferDB
}
// Rule 1: Avoid unusable destinations.
// If DB is known to be unreachable or if Source(DB) is undefined, then
// prefer DA. Similarly, if DA is known to be unreachable or if
// Source(DA) is undefined, then prefer DB.
if source.IsValid() {
score += 1 << bitIndex
}
bitIndex++

// Rule 10: Otherwise, leave the order unchanged.
// If DA preceded DB in the original list, prefer DA.
// Otherwise, prefer DB.
return 0 // "equal"
return score
}

type policyTableEntry struct {
Expand Down
6 changes: 2 additions & 4 deletions src/net/addrselect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func TestSortByRFC6724(t *testing.T) {
copy(inCopy, tt.in)
srcCopy := make([]netip.Addr, len(tt.in))
copy(srcCopy, tt.srcs)
sortByRFC6724withSrcs(inCopy, srcCopy)
sortByRFC6724withSrcs(inCopy, srcCopy, false)
if !reflect.DeepEqual(inCopy, tt.want) {
t.Errorf("test %d:\nin = %s\ngot: %s\nwant: %s\n", i, tt.in, inCopy, tt.want)
}
Expand All @@ -136,14 +136,13 @@ func TestSortByRFC6724(t *testing.T) {
inCopy[j], inCopy[k] = inCopy[k], inCopy[j]
srcCopy[j], srcCopy[k] = srcCopy[k], srcCopy[j]
}
sortByRFC6724withSrcs(inCopy, srcCopy)
sortByRFC6724withSrcs(inCopy, srcCopy, false)
if !reflect.DeepEqual(inCopy, tt.want) {
t.Errorf("test %d, starting backwards:\nin = %s\ngot: %s\nwant: %s\n", i, tt.in, inCopy, tt.want)
}
}

}

}

func TestRFC6724PolicyTableOrder(t *testing.T) {
Expand Down Expand Up @@ -308,5 +307,4 @@ func TestRFC6724CommonPrefixLength(t *testing.T) {
t.Errorf("%d. commonPrefixLen(%s, %s) = %d; want %d", i, tt.a, tt.b, got, tt.want)
}
}

}
16 changes: 11 additions & 5 deletions src/net/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ func parseNetwork(ctx context.Context, network string, needsProto bool) (afnet s
// resolveAddrList resolves addr using hint and returns a list of
// addresses. The result contains at least one address when error is
// nil.
func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string, hint Addr) (addrList, error) {
func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string, hint Addr, dnsLoadBalance bool) (addrList, error) {
afnet, _, err := parseNetwork(ctx, network, true)
if err != nil {
return nil, err
Expand All @@ -351,7 +351,7 @@ func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string
}
return addrList{addr}, nil
}
addrs, err := r.internetAddrList(ctx, afnet, addr)
addrs, err := r.internetAddrList(ctx, afnet, addr, dnsLoadBalance)
if err != nil || op != "dial" || hint == nil {
return addrs, err
}
Expand Down Expand Up @@ -536,7 +536,13 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn
resolveCtx = context.WithValue(resolveCtx, nettrace.TraceKey{}, &shadow)
}

addrs, err := d.resolver().resolveAddrList(resolveCtx, "dial", network, address, d.LocalAddr)
// Multiple hosts implements load-balancing for DNS by randomly sorts the list of A or AAAA records.
// See https://github.com/golang/go/issues/34511
// See https://github.com/grafana/loki/issues/3301
// See https://github.com/grafana/loki/issues/9239
// See https://github.com/restic/restic/issues/4444
// See https://github.com/golang/go/issues/31698
addrs, err := d.resolver().resolveAddrList(resolveCtx, "dial", network, address, d.LocalAddr, true)
if err != nil {
return nil, &OpError{Op: "dial", Net: network, Source: nil, Addr: nil, Err: err}
}
Expand Down Expand Up @@ -872,7 +878,7 @@ func (lc *ListenConfig) SetMultipathTCP(use bool) {
// The ctx argument is used while resolving the address on which to listen;
// it does not affect the returned Listener.
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil)
addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil, false)
if err != nil {
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
}
Expand Down Expand Up @@ -909,7 +915,7 @@ func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Li
// The ctx argument is used while resolving the address on which to listen;
// it does not affect the returned PacketConn.
func (lc *ListenConfig) ListenPacket(ctx context.Context, network, address string) (PacketConn, error) {
addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil)
addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil, false)
if err != nil {
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
}
Expand Down
4 changes: 2 additions & 2 deletions src/net/dnsclient_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ func goLookupIPFiles(name string) (addrs []IPAddr, canonical string) {
addrs = append(addrs, addr)
}
}
sortByRFC6724(addrs)
sortByRFC6724(addrs, false)
return addrs, canonical
}

Expand Down Expand Up @@ -796,7 +796,7 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, network, name strin
// just one is misleading. See also golang.org/issue/6324.
lastErr.Name = name
}
sortByRFC6724(addrs)
sortByRFC6724(addrs, false)
if len(addrs) == 0 && !(network == "CNAME" && cname.Length > 0) {
if order == hostLookupDNSFiles {
var canonical string
Expand Down
2 changes: 1 addition & 1 deletion src/net/iprawsock.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func ResolveIPAddr(network, address string) (*IPAddr, error) {
default:
return nil, UnknownNetworkError(network)
}
addrs, err := DefaultResolver.internetAddrList(context.Background(), afnet, address)
addrs, err := DefaultResolver.internetAddrList(context.Background(), afnet, address, false)
if err != nil {
return nil, err
}
Expand Down
6 changes: 5 additions & 1 deletion src/net/ipsock.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ func JoinHostPort(host, port string) string {
// address or a DNS name, and returns a list of internet protocol
// family addresses. The result contains at least one address when
// error is nil.
func (r *Resolver) internetAddrList(ctx context.Context, net, addr string) (addrList, error) {
func (r *Resolver) internetAddrList(ctx context.Context, net, addr string, dnsLoadBalance bool) (addrList, error) {
var (
err error
host, port string
Expand Down Expand Up @@ -290,6 +290,10 @@ func (r *Resolver) internetAddrList(ctx context.Context, net, addr string) (addr
if err != nil {
return nil, err
}
if dnsLoadBalance {
sortByRFC6724(ips, true)
}

// Issue 18806: if the machine has halfway configured
// IPv6 such that it can bind on "::" (IPv6unspecified)
// but not connect back to that same address, fall
Expand Down
Loading