Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: ipv4 chosen first when both ipv4 and ipv6 are available #54928

Closed
edevil opened this issue Sep 7, 2022 · 10 comments
Closed

net: ipv4 chosen first when both ipv4 and ipv6 are available #54928

edevil opened this issue Sep 7, 2022 · 10 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@edevil
Copy link

edevil commented Sep 7, 2022

What version of Go are you using (go version)?

$ go version
go version go1.19 darwin/amd64

Does this issue reproduce with the latest release?

Yes.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/acruz/Library/Caches/go-build"
GOENV="/Users/acruz/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/acruz/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/acruz/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go/1.19/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.19/libexec/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.19"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/50/p3qbm6c112v0sdym26_t_bq80000gn/T/go-build466847973=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

Connect to a host that resolves both to ipv4 and ipv6 addresses.

What did you expect to see?

According to https://github.com/golang/go/blob/master/src/net/dial.go#L71, with the default FallbackDelay of 300ms, ipv6 should be tried first and only after the configured delay ipv4 should be attempted.

What did you see instead?

ipv4 was chosen first.

The partition function uses the label of the first result, which is the DNS result that resolved first (A vs AAAA). If the A response was received first, then the primary address(es) will be ipv4, which contradicts the purpose of the Happy Eyeballs RFC.

@edevil edevil changed the title affected/package: net (ipv4 chosen first when both ipv4 and ipv6 are available) net: (ipv4 chosen first when both ipv4 and ipv6 are available) Sep 7, 2022
@edevil edevil changed the title net: (ipv4 chosen first when both ipv4 and ipv6 are available) net: ipv4 chosen first when both ipv4 and ipv6 are available Sep 7, 2022
edevil added a commit to edevil/go that referenced this issue Sep 7, 2022
When connecting to an endpoint that resolves to ipv4 and ipv6
addresses, ensure that ipv6 addresses are tried first, as per
happy eyeballs RFC.

Currently whether ipv4 or ipv6 addresses are tried first depends on
the dns resolution speed. If the A resolution returned first, it
would be first in the address list, and partition() uses the label
of the first result to build the primaries category.

Fixes golang#54928
@gopherbot
Copy link

Change https://go.dev/cl/429075 mentions this issue: net: ensure ipv6 endpoints are tried first

@mknyszek
Copy link
Contributor

mknyszek commented Sep 7, 2022

CC @neild

@mknyszek mknyszek added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Sep 7, 2022
@mknyszek mknyszek added this to the Backlog milestone Sep 7, 2022
@jhenstridge
Copy link

If the A response was received first, then the primary address(es) will be ipv4, which contradicts the purpose of the Happy Eyeballs RFC.

You are correct that the behaviour is different to the first Happy Eyeballs RFC (RFC 6555), but it does match Happy Eyeballs v2 (RFC 8305) which removes the preference for IPv6.

The important thing is to try both address families in parallel. If the winner happens to be IPv4, then so be it. Maybe the Dialer.FallbackDelay documentation should be considered the bug.

@edevil
Copy link
Author

edevil commented Sep 8, 2022

Ah, interesting. Didn't realize there were two versions. In that case, it seems compliant.

It is unfortunate however that we can't seem to influence the sorting order of these addresses/families. DNS resolution time does not always correlate to the best addresses to try first.

Should I just update the documentation?

@database64128
Copy link
Contributor

You are correct that the behaviour is different to the first Happy Eyeballs RFC (RFC 6555), but it does match Happy Eyeballs v2 (RFC 8305) which removes the preference for IPv6.

The important thing is to try both address families in parallel. If the winner happens to be IPv4, then so be it. Maybe the Dialer.FallbackDelay documentation should be considered the bug.

This is incorrect. RFC 8305 section 3 says:

   The algorithm proceeds as follows: if a positive AAAA response (a
   response with at least one valid AAAA record) is received first, the
   first IPv6 connection attempt is immediately started.  If a positive
   A response is received first due to reordering, the client SHOULD
   wait a short time for the AAAA response to ensure that preference is
   given to IPv6 (it is common for the AAAA response to follow the A
   response by a few milliseconds).  This delay will be referred to as
   the "Resolution Delay".  The recommended value for the Resolution
   Delay is 50 milliseconds.  If a positive AAAA response is received
   within the Resolution Delay period, the client immediately starts the
   IPv6 connection attempt.

Go currently implements Happy Eyeballs v1, not v2.

which is the DNS result that resolved first (A vs AAAA)

This is probably only true for the Go resolver. Not sure how it's like on macOS, but on Linux and macOS, without explicitly disabling CGO, Happy Eyeballs works as expected, because the system's name resolution API takes care of sorting the addresses for you. Both getaddrinfo on Linux and GetAddrInfoW on Windows place IPv6 addresses before IPv4 by default when the system has public IPv6 addresses.

To make Happy Eyeballs work with the Go resolver, the resolver would need a new API to stream results back to the caller.

@edevil
Copy link
Author

edevil commented Sep 10, 2022

It seems that in some environments getaddrinfo doesn't always produce those results:

Debian Linux host:

$ python3 -c "import socket; print(socket.getaddrinfo('google.com', 0))"
[(<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('2607:f8b0:4005:810::200e', 0, 0, 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('2607:f8b0:4005:810::200e', 0, 0, 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_RAW: 3>, 0, '', ('2607:f8b0:4005:810::200e', 0, 0, 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('172.217.164.110', 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('172.217.164.110', 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_RAW: 3>, 0, '', ('172.217.164.110', 0))]

debian image running in docker container in the linux host above:

root@cdbbe91e6d71:/# python3 -c "import socket; print(socket.getaddrinfo('google.com', 0))"
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('142.250.217.110', 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('142.250.217.110', 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_RAW: 3>, 0, '', ('142.250.217.110', 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('2607:f8b0:4005:810::200e', 0, 0, 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('2607:f8b0:4005:810::200e', 0, 0, 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_RAW: 3>, 0, '', ('2607:f8b0:4005:810::200e', 0, 0, 0))]

MacOS host:

❯ python3 -c "import socket; print(socket.getaddrinfo('google.com', 0))"
[(<AddressFamily.AF_INET6: 30>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('2a00:1450:4003:801::200e', 0, 0, 0)), (<AddressFamily.AF_INET6: 30>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('2a00:1450:4003:801::200e', 0, 0, 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('142.250.185.14', 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('142.250.185.14', 0))]

Debian linux container running on Docker in MacOS host:

root@c7f8e648bbd7:/# python3 -c "import socket; print(socket.getaddrinfo('google.com', 0))"
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('142.250.185.14', 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('142.250.185.14', 0)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_RAW: 3>, 0, '', ('142.250.185.14', 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('2a00:1450:4003:801::200e', 0, 0, 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('2a00:1450:4003:801::200e', 0, 0, 0)), (<AddressFamily.AF_INET6: 10>, <SocketKind.SOCK_RAW: 3>, 0, '', ('2a00:1450:4003:801::200e', 0, 0, 0))]

At least in container environments ipv4 addresses seem to be returned first?

@database64128
Copy link
Contributor

Did you run your container with --net host? By default getaddrinfo only returns IPv6 addresses first if you have public IPv6 addresses. The behavior can be configured in /etc/gai.conf.

@edevil
Copy link
Author

edevil commented Sep 10, 2022

It seems you are indeed correct, using the host network ipv6 addresses are returned first. Do you know, by any chance, how to configure gai.conf so as to make ipv6 results appear first even when it seems ipv6 is not configured?

The configured precedences already prioritize ipv6...

Thanks.

@edevil
Copy link
Author

edevil commented Sep 12, 2022

Closing since Go seems to be working as expected after all.

@edevil edevil closed this as completed Sep 12, 2022
@JcobCN
Copy link

JcobCN commented Jun 1, 2023

Closing since Go seems to be working as expected after all.

For me, that is not working as expection, still handle ipv4 first.

go version go1.20.4 linux/amd64

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants