diff --git a/net/dnscache/dnscache.go b/net/dnscache/dnscache.go index c4fa28bd1..bcec3ca38 100644 --- a/net/dnscache/dnscache.go +++ b/net/dnscache/dnscache.go @@ -523,6 +523,21 @@ type res struct { return nil, errors.New("no IPs") } + // Partition candidate list and then merge such that an IPv6 address is + // in the first spot if present, and then addresses are interleaved. + // This ensures that we're trying an IPv6 address first, then + // alternating between v4 and v6 in case one of the two networks is + // broken. + var iv4, iv6 []netip.Addr + for _, ip := range ips { + if ip.Is6() { + iv6 = append(iv6, ip) + } else { + iv4 = append(iv4, ip) + } + } + ips = interleaveSlices(iv6, iv4) + go func() { for i, ip := range ips { if i != 0 { @@ -580,6 +595,21 @@ type res struct { } } +// interleaveSlices combines two slices of the form [a, b, c] and [x, y, z] +// into a slice with elements interleaved; i.e. [a, x, b, y, c, z]. +func interleaveSlices[T any](a, b []T) []T { + var ( + i int + ret = make([]T, 0, len(a)+len(b)) + ) + for i = 0; i < len(a) && i < len(b); i++ { + ret = append(ret, a[i], b[i]) + } + ret = append(ret, a[i:]...) + ret = append(ret, b[i:]...) + return ret +} + func v4addrs(aa []net.IPAddr) (ret []netip.Addr) { for _, a := range aa { ip, ok := netip.AddrFromSlice(a.IP) diff --git a/net/dnscache/dnscache_test.go b/net/dnscache/dnscache_test.go index 29fcd8e39..ef09c112f 100644 --- a/net/dnscache/dnscache_test.go +++ b/net/dnscache/dnscache_test.go @@ -140,3 +140,27 @@ func TestResolverAllHostStaticResult(t *testing.T) { t.Errorf("bad dial error got %q; want %q", got, want) } } + +func TestInterleaveSlices(t *testing.T) { + testCases := []struct { + name string + a, b []int + want []int + }{ + {name: "equal", a: []int{1, 3, 5}, b: []int{2, 4, 6}, want: []int{1, 2, 3, 4, 5, 6}}, + {name: "short_b", a: []int{1, 3, 5}, b: []int{2, 4}, want: []int{1, 2, 3, 4, 5}}, + {name: "short_a", a: []int{1, 3}, b: []int{2, 4, 6}, want: []int{1, 2, 3, 4, 6}}, + {name: "len_1", a: []int{1}, b: []int{2, 4, 6}, want: []int{1, 2, 4, 6}}, + {name: "nil_a", a: nil, b: []int{2, 4, 6}, want: []int{2, 4, 6}}, + {name: "nil_all", a: nil, b: nil, want: []int{}}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + merged := interleaveSlices(tc.a, tc.b) + if !reflect.DeepEqual(merged, tc.want) { + t.Errorf("got %v; want %v", merged, tc.want) + } + }) + } +}