Skip to content

Commit 18d218d

Browse files
authored
pickfirst: Interleave IPv6 and IPv4 addresses for happy eyeballs (#7742)
1 parent e9ac44c commit 18d218d

File tree

3 files changed

+257
-26
lines changed

3 files changed

+257
-26
lines changed

balancer/pickfirst/pickfirstleaf/pickfirstleaf.go

+74-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"encoding/json"
3030
"errors"
3131
"fmt"
32+
"net"
3233
"sync"
3334

3435
"google.golang.org/grpc/balancer"
@@ -61,6 +62,16 @@ var (
6162
// TODO: change to pick-first when this becomes the default pick_first policy.
6263
const logPrefix = "[pick-first-leaf-lb %p] "
6364

65+
type ipAddrFamily int
66+
67+
const (
68+
// ipAddrFamilyUnknown represents strings that can't be parsed as an IP
69+
// address.
70+
ipAddrFamilyUnknown ipAddrFamily = iota
71+
ipAddrFamilyV4
72+
ipAddrFamilyV6
73+
)
74+
6475
type pickfirstBuilder struct{}
6576

6677
func (pickfirstBuilder) Build(cc balancer.ClientConn, _ balancer.BuildOptions) balancer.Balancer {
@@ -206,9 +217,6 @@ func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState
206217
// "Flatten the list by concatenating the ordered list of addresses for
207218
// each of the endpoints, in order." - A61
208219
for _, endpoint := range endpoints {
209-
// "In the flattened list, interleave addresses from the two address
210-
// families, as per RFC-8305 section 4." - A61
211-
// TODO: support the above language.
212220
newAddrs = append(newAddrs, endpoint.Addresses...)
213221
}
214222
} else {
@@ -232,6 +240,8 @@ func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState
232240
// SubConn multiple times in the same pass. We don't want this.
233241
newAddrs = deDupAddresses(newAddrs)
234242

243+
newAddrs = interleaveAddresses(newAddrs)
244+
235245
// Since we have a new set of addresses, we are again at first pass.
236246
b.firstPass = true
237247

@@ -314,6 +324,67 @@ func deDupAddresses(addrs []resolver.Address) []resolver.Address {
314324
return retAddrs
315325
}
316326

327+
// interleaveAddresses interleaves addresses of both families (IPv4 and IPv6)
328+
// as per RFC-8305 section 4.
329+
// Whichever address family is first in the list is followed by an address of
330+
// the other address family; that is, if the first address in the list is IPv6,
331+
// then the first IPv4 address should be moved up in the list to be second in
332+
// the list. It doesn't support configuring "First Address Family Count", i.e.
333+
// there will always be a single member of the first address family at the
334+
// beginning of the interleaved list.
335+
// Addresses that are neither IPv4 nor IPv6 are treated as part of a third
336+
// "unknown" family for interleaving.
337+
// See: https://datatracker.ietf.org/doc/html/rfc8305#autoid-6
338+
func interleaveAddresses(addrs []resolver.Address) []resolver.Address {
339+
familyAddrsMap := map[ipAddrFamily][]resolver.Address{}
340+
interleavingOrder := []ipAddrFamily{}
341+
for _, addr := range addrs {
342+
family := addressFamily(addr.Addr)
343+
if _, found := familyAddrsMap[family]; !found {
344+
interleavingOrder = append(interleavingOrder, family)
345+
}
346+
familyAddrsMap[family] = append(familyAddrsMap[family], addr)
347+
}
348+
349+
interleavedAddrs := make([]resolver.Address, 0, len(addrs))
350+
351+
for curFamilyIdx := 0; len(interleavedAddrs) < len(addrs); curFamilyIdx = (curFamilyIdx + 1) % len(interleavingOrder) {
352+
// Some IP types may have fewer addresses than others, so we look for
353+
// the next type that has a remaining member to add to the interleaved
354+
// list.
355+
family := interleavingOrder[curFamilyIdx]
356+
remainingMembers := familyAddrsMap[family]
357+
if len(remainingMembers) > 0 {
358+
interleavedAddrs = append(interleavedAddrs, remainingMembers[0])
359+
familyAddrsMap[family] = remainingMembers[1:]
360+
}
361+
}
362+
363+
return interleavedAddrs
364+
}
365+
366+
// addressFamily returns the ipAddrFamily after parsing the address string.
367+
// If the address isn't of the format "ip-address:port", it returns
368+
// ipAddrFamilyUnknown. The address may be valid even if it's not an IP when
369+
// using a resolver like passthrough where the address may be a hostname in
370+
// some format that the dialer can resolve.
371+
func addressFamily(address string) ipAddrFamily {
372+
// Parse the IP after removing the port.
373+
host, _, err := net.SplitHostPort(address)
374+
if err != nil {
375+
return ipAddrFamilyUnknown
376+
}
377+
ip := net.ParseIP(host)
378+
switch {
379+
case ip.To4() != nil:
380+
return ipAddrFamilyV4
381+
case ip.To16() != nil:
382+
return ipAddrFamilyV6
383+
default:
384+
return ipAddrFamilyUnknown
385+
}
386+
}
387+
317388
// reconcileSubConnsLocked updates the active subchannels based on a new address
318389
// list from the resolver. It does this by:
319390
// - closing subchannels: any existing subchannels associated with addresses

0 commit comments

Comments
 (0)