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 ipv6 is available #68795

Open
rittneje opened this issue Aug 8, 2024 · 4 comments
Open

net: ipv4 chosen first when ipv6 is available #68795

rittneje opened this issue Aug 8, 2024 · 4 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@rittneje
Copy link

rittneje commented Aug 8, 2024

Go version

go version go1.22.5 darwin/amd64

Output of go env in your module/workspace:

GO111MODULE='auto'
GOARCH='amd64'
GOBIN=''
GOCACHE='/tmp/.gocache'
GOENV='/Users/rittneje/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/rittneje/test/pkg/mod'
GONOPROXY='[REDACTED]'
GONOSUMDB='[REDACTED]'
GOOS='darwin'
GOPATH='/Users/rittneje/test'
GOPRIVATE='[REDACTED]'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/rittneje/go1.22.5'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/Users/rittneje/go1.22.5/pkg/tool/darwin_amd64'
GOVCS='[REDACTED]'
GOVERSION='go1.22.5'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/Users/rittneje/test/src/dialtest/go.mod'
GOWORK='/Users/rittneje/test/go.work'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/kf/kr7_s3xx0l12zbj3jrn082hmzy5gvy/T/go-build666976387=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

package main

import (
	"context"
	"log"
	"net"
	"net/http/httptrace"
)

func main() {
	d := &net.Dialer{}

	ctx := httptrace.WithClientTrace(context.Background(), &httptrace.ClientTrace{
		DNSStart: func(info httptrace.DNSStartInfo) {
			log.Printf("DNSStart: %q", info.Host)
		},
		DNSDone: func(info httptrace.DNSDoneInfo) {
			log.Printf("DNSDone: %s", info.Addrs)
		},
		ConnectStart: func(network, addr string) {
			log.Printf("ConnectStart: %s %s", network, addr)
		},
		ConnectDone: func(network, addr string, err error) {
			log.Printf("ConnectDone: %s %s %v", network, addr, err)
		},
	})

	c, err := d.DialContext(ctx, "tcp", "www.google.com:443")
	if err != nil {
		panic(err)
	}
	defer c.Close()

	log.Printf("RemoteAddr: %s", c.RemoteAddr())
}

What did you see happen?

On a network where IPv4 is blocked:

2024/08/08 16:03:47 DNSStart: "www.google.com"
2024/08/08 16:03:47 DNSDone: [{142.250.72.100 } {2607:f8b0:4006:809::2004 }]
2024/08/08 16:03:47 ConnectStart: tcp 142.250.72.100:443
2024/08/08 16:03:48 ConnectStart: tcp [2607:f8b0:4006:809::2004]:443
2024/08/08 16:03:48 ConnectDone: tcp [2607:f8b0:4006:809::2004]:443
2024/08/08 16:03:48 RemoteAddr: [2607:f8b0:4006:809::2004]:443

This shows that it incorrectly tried IPv4 first, and then fell back to IPv6.

(The above output is from running on Linux ARM with the netgo build tag, not on macos.)

What did you expect to see?

As per the documentation in net, it should follow "happy eyeballs" and dial IPv6 first, and only fall back to IPv4 if that doesn't work.

If the current behavior is intentional, then the documentation ought to be corrected.

This was previously reported in #54928 but was not fixed.

@rittneje rittneje changed the title net: ipv4 chosen first when both ipv4 and ipv6 are available net: ipv4 chosen first when ipv6 is available Aug 8, 2024
@cagedmantis cagedmantis added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Aug 13, 2024
@cagedmantis cagedmantis added this to the Backlog milestone Aug 13, 2024
@cagedmantis
Copy link
Contributor

cc @ianlancetaylor @neild

@ianlancetaylor
Copy link
Contributor

It looks like we prefer IPv4 since https://go.dev/cl/8360. That does seem to contradict RFC 6555, which says that we should try IPv6 first. That RFC is obsoleted by RFC 8305. The latter requires that we use whichever DNS answer we get first, but we don't seem to do that at all; we collect all the DNS answers before we try opening any connection.

Changing this might be as simple as changing isIPv4 to isNotIPv4 at https://go.googlesource.com/go/+/refs/heads/master/src/net/dial.go#522.

@neild
Copy link
Contributor

neild commented Aug 22, 2024

Our dial algorithm doesn't match the behavior in RFC 8305 at all. We dial each address within a family serially, only trying a fallback address after the previous one has failed. RFC 8305 recommends sorting all addresses into a single list with IPv4 and IPv6 interleaved, and adding racing dials over time.

I can't tell if we take into account whether the local system has any IPv4/IPv6 connectivity. I think we don't, which means we'll try to dial addresses for families where we have no local address. These dials should fail quickly, so I don't know how much of a problem that is.

Any changes here should take errors into account. When a dial fails, we currently return the error for the first dial attempt made. If our first attempt fails because the system has no connectivity for the address family, and a subsequent attempt fails for some other reason (ECONNREFUSED, say), returning the first error is confusing and masks useful information. This is #18183 and a long standing problem, but we should be careful not to make matters worse.

I'm concerned that changing Dial to prefer IPv6 over IPv4 will cause breakage. What's the fallback for users who encounter problems? Maybe there should be a net.Dialer option to set the preferred address family?

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

No branches or pull requests

5 participants