Repository: go
Issue: net: Dialing IPv6 with timeout returns inconsistent error
Labels: NeedsInvestigation

<!--
Please answer these questions before submitting your issue. Thanks!
For questions please use one of our forums: https://github.com/golang/go/wiki/Questions
-->

### What version of Go are you using (`go version`)?

<pre>
$ go version
go version go1.16 linux/amd64
</pre>

### Does this issue reproduce with the latest release?

Yes.

### What operating system and processor architecture are you using (`go env`)?

Linux on amd64, `5.8.0-43-generic #49-Ubuntu`

### What did you do?

Set up a network namespace with an IPv6 prefix on lo.
```
unshare -Urn
ip link set dev lo up
ip addr add fd::/64 dev lo # NB: no local route
```

Run the following test:
```
func TestRuntimeDial(t *testing.T) {
	dialer := net.Dialer{
		Timeout: 1 * time.Second,
	}

	_, err := dialer.Dial("tcp6", "[fd::1]:1234")
	if !errors.Is(err, unix.ENETUNREACH) {
		t.Fatal("wrong error", err)
	}
}
```

Changing the timeout to `2 * time.Second` makes the test pass. Here is a trace for one second timeout:

```
[pid 93030] epoll_ctl(3, EPOLL_CTL_ADD, 4, {EPOLLIN, {u32=7312728, u64=7312728}}) = 0
[pid 93034] epoll_pwait(3, === RUN   TestRuntimeDial
[{EPOLLIN, {u32=7312728, u64=7312728}}], 128, 599999, NULL, 7) = 1
[pid 93034] epoll_pwait(3,  <unfinished ...>
[pid 93030] connect(6, {sa_family=AF_INET6, sin6_port=htons(1234), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "fd::1", &sin6_addr), sin6_scope_id=0}, 28) = -1 EINPROGRESS (Operation now in progress)
[pid 93030] epoll_ctl(3, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=2358718232, u64=140160026488600}}) = 0
[pid 93034] <... epoll_pwait resumed>[], 128, 999, NULL, 7) = 0
[pid 93034] epoll_pwait(3, [], 128, 0, NULL, 0) = 0
[pid 93034] epoll_pwait(3, [], 128, 1, NULL, 7) = 0
[pid 93034] epoll_pwait(3, [], 128, 0, NULL, 0) = 0
[pid 93034] epoll_ctl(3, EPOLL_CTL_DEL, 6, 0xc0000b1874 <unfinished ...>
[pid 93035] epoll_pwait(3,  <unfinished ...>
[pid 93034] <... epoll_ctl resumed>)    = 0
    netns_test.go:82: wrong error dial tcp6 [fd::1]:1234: i/o timeout
--- FAIL: TestRuntimeDial (1.00s)
FAIL
[pid 93035] <... epoll_pwait resumed> <unfinished ...>) = ?
[pid 93036] +++ exited with 1 +++
[pid 93035] +++ exited with 1 +++
[pid 93034] +++ exited with 1 +++
[pid 93033] +++ exited with 1 +++
[pid 93032] +++ exited with 1 +++
[pid 93031] +++ exited with 1 +++
+++ exited with 1 +++
```

And here one for two seconds:
```
[pid 93303] epoll_ctl(3, EPOLL_CTL_ADD, 4, {EPOLLIN, {u32=7312728, u64=7312728}}) = 0
[pid 93305] epoll_pwait(3, === RUN   TestRuntimeDial
[{EPOLLIN, {u32=7312728, u64=7312728}}], 128, 599999, NULL, 7) = 1
[pid 93305] epoll_pwait(3,  <unfinished ...>
[pid 93303] connect(6, {sa_family=AF_INET6, sin6_port=htons(1234), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "fd::1", &sin6_addr), sin6_scope_id=0}, 28) = -1 EINPROGRESS (Operation now in progress)
[pid 93303] epoll_ctl(3, EPOLL_CTL_ADD, 6, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=2103566104, u64=139773224279832}}) = 0
[pid 93305] <... epoll_pwait resumed>[{EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP|EPOLLRDHUP, {u32=2103566104, u64=139773224279832}}], 128, 1999, NULL, 7) = 1
[pid 93305] getsockopt(6, SOL_SOCKET, SO_ERROR, [ENETUNREACH], [4]) = 0
[pid 93305] epoll_ctl(3, EPOLL_CTL_DEL, 6, 0xc0000b1874) = 0
[pid 93307] epoll_pwait(3, [], 128, 0, NULL, 0) = 0
[pid 93307] epoll_pwait(3, --- PASS: TestRuntimeDial (1.01s)
PASS
 <unfinished ...>) = ?
[pid 93308] +++ exited with 0 +++
[pid 93306] +++ exited with 0 +++
[pid 93304] +++ exited with 0 +++
[pid 93307] +++ exited with 0 +++
[pid 93305] +++ exited with 0 +++
+++ exited with 0 +++
```

Note that the test finishes pretty much after one second, so maybe there is a race with an internal timeout?

### What did you expect to see?

Dial returns either a timeout or ENETUNREACH in both cases.

### What did you see instead?

Dial returns ENETUNREACH in one case, timeout error in the other one.