Thanks for the replies! I've dug around in the net package and found the source of the issue I'm having. TL;DR: go 1.4 only tries a single IP address and 1.5 seems to favor the first in the returned list.
The root of the problem seems to be in net/ipsock.go in resolveInternetAddr(). I added logging to verify that multiple IPV4 addresses were being resolved (they were), but the last line of the func is `return firstFavoriteAddr(filter, ips, inetaddr)`.
firstFavoriteAddr() seems constructed to return either a single address or a pair consisting of one IPV4 address and one IPV6 address. Thus, as long as the local resolver returns addresses in the same order, the same address (addresses if a mix of IPV4 and IPV6 is encountered) is returned.
I patched together a new version of firstFavoriteAddr() to verify that it solves my problem:
@@ -69,8 +71,8 @@ func firstFavoriteAddr(filter func(IP) IP, ips []IP, inetaddr func(IP) netaddr)
return firstSupportedAddr(filter, ips, inetaddr)
}
var (
- ipv4, ipv6, swap bool
- list addrList
+ ip4Addrs, ip6Addrs addrList
+ result addrList
)
for _, ip := range ips {
// We'll take any IP address, but since the dialing
@@ -79,30 +81,27 @@ func firstFavoriteAddr(filter func(IP) IP, ips []IP, inetaddr func(IP) netaddr)
// possible. This is especially relevant if localhost
// resolves to [ipv6-localhost, ipv4-localhost]. Too
// much code assumes localhost == ipv4-localhost.
- if ip4 := ipv4only(ip); ip4 != nil && !ipv4 {
- list = append(list, inetaddr(ip4))
- ipv4 = true
- if ipv6 {
- swap = true
- }
- } else if ip6 := ipv6only(ip); ip6 != nil && !ipv6 {
- list = append(list, inetaddr(ip6))
- ipv6 = true
- }
- if ipv4 && ipv6 {
- if swap {
- list[0], list[1] = list[1], list[0]
- }
- break
+ if ip4 := ipv4only(ip); ip4 != nil {
+ ip4Addrs = append(ip4Addrs, inetaddr(ip4))
+ } else if ip6 := ipv6only(ip); ip6 != nil {
+ ip6Addrs = append(ip6Addrs, inetaddr(ip6))
}
}
- switch len(list) {
+
+ if len(ip4Addrs) > 0 {
+ result = append(result, ip4Addrs[rand.Intn(len(ip4Addrs))])
+ }
+ if len(ip6Addrs) > 0 {
+ result = append(result, ip6Addrs[rand.Intn(len(ip6Addrs))])
+ }
+
+ switch len(result) {
case 0:
return nil, errNoSuitableAddress
case 1:
- return list[0], nil
+ return result[0], nil
default:
- return list, nil
+ return result, nil
}
}
I'm not suggesting this as a code change, obviously. I just wanted to see if I had it right. My guess is that any code that tried to equalize the chosen IP A) would have to play nice with KeepAlive settings and B) shouldn't use rand as a dep.
This code seems to have changed quite a bit in in 1.5beta. firstFavoriteAddr() has been replaced with filterAddrList(), which does return all IPV4 addresses, but the first in the list continues to be favored. I haven't dug into why yet.
I will look more into implementing my own Dialer to achieve my goal. However, I can see that I will be circumventing quite a bit of well-vetted code, which I'm not enthusiastic about.
I guess my remaining question to anyone who has read this far down is: is the current behavior the best behavior? Should the net package do more to either A) spread requests across DNS lookup results and/or B) be more robust about trying multiple IPs in the event of a connection failure?
Cheers,
nate