mirror of
https://github.com/golang/go
synced 2024-11-15 04:50:31 -07:00
1a0b1cca4c
Windows sockets allow bind to 0.0.0.0:80 but not connect to it. To make Listen(":80") / Dial(":80") work as documented on Windows, connect to 127.0.0.1 or ::1 (depending on network) in place of 0.0.0.0. Fixes #6290. Change-Id: Ia27537067276871648546678fbe0f1b8478329fe Reviewed-on: https://go-review.googlesource.com/32101 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Mikio Hara <mikioh.mikioh@gmail.com>
955 lines
25 KiB
Go
955 lines
25 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package net
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"internal/testenv"
|
|
"io"
|
|
"net/internal/socktest"
|
|
"runtime"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var prohibitionaryDialArgTests = []struct {
|
|
network string
|
|
address string
|
|
}{
|
|
{"tcp6", "127.0.0.1"},
|
|
{"tcp6", "::ffff:127.0.0.1"},
|
|
}
|
|
|
|
func TestProhibitionaryDialArg(t *testing.T) {
|
|
testenv.MustHaveExternalNetwork(t)
|
|
|
|
switch runtime.GOOS {
|
|
case "plan9":
|
|
t.Skipf("not supported on %s", runtime.GOOS)
|
|
}
|
|
if !supportsIPv4map {
|
|
t.Skip("mapping ipv4 address inside ipv6 address not supported")
|
|
}
|
|
|
|
ln, err := Listen("tcp", "[::]:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ln.Close()
|
|
|
|
_, port, err := SplitHostPort(ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for i, tt := range prohibitionaryDialArgTests {
|
|
c, err := Dial(tt.network, JoinHostPort(tt.address, port))
|
|
if err == nil {
|
|
c.Close()
|
|
t.Errorf("#%d: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDialLocal(t *testing.T) {
|
|
ln, err := newLocalListener("tcp")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ln.Close()
|
|
_, port, err := SplitHostPort(ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c, err := Dial("tcp", JoinHostPort("", port))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c.Close()
|
|
}
|
|
|
|
func TestDialTimeoutFDLeak(t *testing.T) {
|
|
switch runtime.GOOS {
|
|
case "plan9":
|
|
t.Skipf("%s does not have full support of socktest", runtime.GOOS)
|
|
case "openbsd":
|
|
testenv.SkipFlaky(t, 15157)
|
|
}
|
|
|
|
const T = 100 * time.Millisecond
|
|
|
|
switch runtime.GOOS {
|
|
case "plan9", "windows":
|
|
origTestHookDialChannel := testHookDialChannel
|
|
testHookDialChannel = func() { time.Sleep(2 * T) }
|
|
defer func() { testHookDialChannel = origTestHookDialChannel }()
|
|
if runtime.GOOS == "plan9" {
|
|
break
|
|
}
|
|
fallthrough
|
|
default:
|
|
sw.Set(socktest.FilterConnect, func(so *socktest.Status) (socktest.AfterFilter, error) {
|
|
time.Sleep(2 * T)
|
|
return nil, errTimeout
|
|
})
|
|
defer sw.Set(socktest.FilterConnect, nil)
|
|
}
|
|
|
|
// Avoid tracking open-close jitterbugs between netFD and
|
|
// socket that leads to confusion of information inside
|
|
// socktest.Switch.
|
|
// It may happen when the Dial call bumps against TCP
|
|
// simultaneous open. See selfConnect in tcpsock_posix.go.
|
|
defer func() { sw.Set(socktest.FilterClose, nil) }()
|
|
var mu sync.Mutex
|
|
var attempts int
|
|
sw.Set(socktest.FilterClose, func(so *socktest.Status) (socktest.AfterFilter, error) {
|
|
mu.Lock()
|
|
attempts++
|
|
mu.Unlock()
|
|
return nil, nil
|
|
})
|
|
|
|
const N = 100
|
|
var wg sync.WaitGroup
|
|
wg.Add(N)
|
|
for i := 0; i < N; i++ {
|
|
go func() {
|
|
defer wg.Done()
|
|
// This dial never starts to send any SYN
|
|
// segment because of above socket filter and
|
|
// test hook.
|
|
c, err := DialTimeout("tcp", "127.0.0.1:0", T)
|
|
if err == nil {
|
|
t.Errorf("unexpectedly established: tcp:%s->%s", c.LocalAddr(), c.RemoteAddr())
|
|
c.Close()
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
if attempts < N {
|
|
t.Errorf("got %d; want >= %d", attempts, N)
|
|
}
|
|
}
|
|
|
|
func TestDialerDualStackFDLeak(t *testing.T) {
|
|
switch runtime.GOOS {
|
|
case "plan9":
|
|
t.Skipf("%s does not have full support of socktest", runtime.GOOS)
|
|
case "windows":
|
|
t.Skipf("not implemented a way to cancel dial racers in TCP SYN-SENT state on %s", runtime.GOOS)
|
|
}
|
|
if !supportsIPv4 || !supportsIPv6 {
|
|
t.Skip("both IPv4 and IPv6 are required")
|
|
}
|
|
|
|
closedPortDelay, expectClosedPortDelay := dialClosedPort()
|
|
if closedPortDelay > expectClosedPortDelay {
|
|
t.Errorf("got %v; want <= %v", closedPortDelay, expectClosedPortDelay)
|
|
}
|
|
|
|
before := sw.Sockets()
|
|
origTestHookLookupIP := testHookLookupIP
|
|
defer func() { testHookLookupIP = origTestHookLookupIP }()
|
|
testHookLookupIP = lookupLocalhost
|
|
handler := func(dss *dualStackServer, ln Listener) {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
dss, err := newDualStackServer()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := dss.buildup(handler); err != nil {
|
|
dss.teardown()
|
|
t.Fatal(err)
|
|
}
|
|
|
|
const N = 10
|
|
var wg sync.WaitGroup
|
|
wg.Add(N)
|
|
d := &Dialer{DualStack: true, Timeout: 100*time.Millisecond + closedPortDelay}
|
|
for i := 0; i < N; i++ {
|
|
go func() {
|
|
defer wg.Done()
|
|
c, err := d.Dial("tcp", JoinHostPort("localhost", dss.port))
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
c.Close()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
dss.teardown()
|
|
after := sw.Sockets()
|
|
if len(after) != len(before) {
|
|
t.Errorf("got %d; want %d", len(after), len(before))
|
|
}
|
|
}
|
|
|
|
// Define a pair of blackholed (IPv4, IPv6) addresses, for which dialTCP is
|
|
// expected to hang until the timeout elapses. These addresses are reserved
|
|
// for benchmarking by RFC 6890.
|
|
const (
|
|
slowDst4 = "198.18.0.254"
|
|
slowDst6 = "2001:2::254"
|
|
)
|
|
|
|
// In some environments, the slow IPs may be explicitly unreachable, and fail
|
|
// more quickly than expected. This test hook prevents dialTCP from returning
|
|
// before the deadline.
|
|
func slowDialTCP(ctx context.Context, net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
|
|
c, err := doDialTCP(ctx, net, laddr, raddr)
|
|
if ParseIP(slowDst4).Equal(raddr.IP) || ParseIP(slowDst6).Equal(raddr.IP) {
|
|
// Wait for the deadline, or indefinitely if none exists.
|
|
<-ctx.Done()
|
|
}
|
|
return c, err
|
|
}
|
|
|
|
func dialClosedPort() (actual, expected time.Duration) {
|
|
// Estimate the expected time for this platform.
|
|
// On Windows, dialing a closed port takes roughly 1 second,
|
|
// but other platforms should be instantaneous.
|
|
if runtime.GOOS == "windows" {
|
|
expected = 1500 * time.Millisecond
|
|
} else {
|
|
expected = 95 * time.Millisecond
|
|
}
|
|
|
|
l, err := Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
return 999 * time.Hour, expected
|
|
}
|
|
addr := l.Addr().String()
|
|
l.Close()
|
|
// On OpenBSD, interference from TestSelfConnect is mysteriously
|
|
// causing the first attempt to hang for a few seconds, so we throw
|
|
// away the first result and keep the second.
|
|
for i := 1; ; i++ {
|
|
startTime := time.Now()
|
|
c, err := Dial("tcp", addr)
|
|
if err == nil {
|
|
c.Close()
|
|
}
|
|
elapsed := time.Now().Sub(startTime)
|
|
if i == 2 {
|
|
return elapsed, expected
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDialParallel(t *testing.T) {
|
|
testenv.MustHaveExternalNetwork(t)
|
|
|
|
if !supportsIPv4 || !supportsIPv6 {
|
|
t.Skip("both IPv4 and IPv6 are required")
|
|
}
|
|
|
|
closedPortDelay, expectClosedPortDelay := dialClosedPort()
|
|
if closedPortDelay > expectClosedPortDelay {
|
|
t.Errorf("got %v; want <= %v", closedPortDelay, expectClosedPortDelay)
|
|
}
|
|
|
|
const instant time.Duration = 0
|
|
const fallbackDelay = 200 * time.Millisecond
|
|
|
|
// Some cases will run quickly when "connection refused" is fast,
|
|
// or trigger the fallbackDelay on Windows. This value holds the
|
|
// lesser of the two delays.
|
|
var closedPortOrFallbackDelay time.Duration
|
|
if closedPortDelay < fallbackDelay {
|
|
closedPortOrFallbackDelay = closedPortDelay
|
|
} else {
|
|
closedPortOrFallbackDelay = fallbackDelay
|
|
}
|
|
|
|
origTestHookDialTCP := testHookDialTCP
|
|
defer func() { testHookDialTCP = origTestHookDialTCP }()
|
|
testHookDialTCP = slowDialTCP
|
|
|
|
nCopies := func(s string, n int) []string {
|
|
out := make([]string, n)
|
|
for i := 0; i < n; i++ {
|
|
out[i] = s
|
|
}
|
|
return out
|
|
}
|
|
|
|
var testCases = []struct {
|
|
primaries []string
|
|
fallbacks []string
|
|
teardownNetwork string
|
|
expectOk bool
|
|
expectElapsed time.Duration
|
|
}{
|
|
// These should just work on the first try.
|
|
{[]string{"127.0.0.1"}, []string{}, "", true, instant},
|
|
{[]string{"::1"}, []string{}, "", true, instant},
|
|
{[]string{"127.0.0.1", "::1"}, []string{slowDst6}, "tcp6", true, instant},
|
|
{[]string{"::1", "127.0.0.1"}, []string{slowDst4}, "tcp4", true, instant},
|
|
// Primary is slow; fallback should kick in.
|
|
{[]string{slowDst4}, []string{"::1"}, "", true, fallbackDelay},
|
|
// Skip a "connection refused" in the primary thread.
|
|
{[]string{"127.0.0.1", "::1"}, []string{}, "tcp4", true, closedPortDelay},
|
|
{[]string{"::1", "127.0.0.1"}, []string{}, "tcp6", true, closedPortDelay},
|
|
// Skip a "connection refused" in the fallback thread.
|
|
{[]string{slowDst4, slowDst6}, []string{"::1", "127.0.0.1"}, "tcp6", true, fallbackDelay + closedPortDelay},
|
|
// Primary refused, fallback without delay.
|
|
{[]string{"127.0.0.1"}, []string{"::1"}, "tcp4", true, closedPortOrFallbackDelay},
|
|
{[]string{"::1"}, []string{"127.0.0.1"}, "tcp6", true, closedPortOrFallbackDelay},
|
|
// Everything is refused.
|
|
{[]string{"127.0.0.1"}, []string{}, "tcp4", false, closedPortDelay},
|
|
// Nothing to do; fail instantly.
|
|
{[]string{}, []string{}, "", false, instant},
|
|
// Connecting to tons of addresses should not trip the deadline.
|
|
{nCopies("::1", 1000), []string{}, "", true, instant},
|
|
}
|
|
|
|
handler := func(dss *dualStackServer, ln Listener) {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
|
|
// Convert a list of IP strings into TCPAddrs.
|
|
makeAddrs := func(ips []string, port string) addrList {
|
|
var out addrList
|
|
for _, ip := range ips {
|
|
addr, err := ResolveTCPAddr("tcp", JoinHostPort(ip, port))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out = append(out, addr)
|
|
}
|
|
return out
|
|
}
|
|
|
|
for i, tt := range testCases {
|
|
dss, err := newDualStackServer()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer dss.teardown()
|
|
if err := dss.buildup(handler); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tt.teardownNetwork != "" {
|
|
// Destroy one of the listening sockets, creating an unreachable port.
|
|
dss.teardownNetwork(tt.teardownNetwork)
|
|
}
|
|
|
|
primaries := makeAddrs(tt.primaries, dss.port)
|
|
fallbacks := makeAddrs(tt.fallbacks, dss.port)
|
|
d := Dialer{
|
|
FallbackDelay: fallbackDelay,
|
|
}
|
|
startTime := time.Now()
|
|
dp := &dialParam{
|
|
Dialer: d,
|
|
network: "tcp",
|
|
address: "?",
|
|
}
|
|
c, err := dialParallel(context.Background(), dp, primaries, fallbacks)
|
|
elapsed := time.Since(startTime)
|
|
|
|
if c != nil {
|
|
c.Close()
|
|
}
|
|
|
|
if tt.expectOk && err != nil {
|
|
t.Errorf("#%d: got %v; want nil", i, err)
|
|
} else if !tt.expectOk && err == nil {
|
|
t.Errorf("#%d: got nil; want non-nil", i)
|
|
}
|
|
|
|
expectElapsedMin := tt.expectElapsed - 95*time.Millisecond
|
|
expectElapsedMax := tt.expectElapsed + 95*time.Millisecond
|
|
if !(elapsed >= expectElapsedMin) {
|
|
t.Errorf("#%d: got %v; want >= %v", i, elapsed, expectElapsedMin)
|
|
} else if !(elapsed <= expectElapsedMax) {
|
|
t.Errorf("#%d: got %v; want <= %v", i, elapsed, expectElapsedMax)
|
|
}
|
|
|
|
// Repeat each case, ensuring that it can be canceled quickly.
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
time.Sleep(5 * time.Millisecond)
|
|
cancel()
|
|
wg.Done()
|
|
}()
|
|
startTime = time.Now()
|
|
c, err = dialParallel(ctx, dp, primaries, fallbacks)
|
|
if c != nil {
|
|
c.Close()
|
|
}
|
|
elapsed = time.Now().Sub(startTime)
|
|
if elapsed > 100*time.Millisecond {
|
|
t.Errorf("#%d (cancel): got %v; want <= 100ms", i, elapsed)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
}
|
|
|
|
func lookupSlowFast(ctx context.Context, fn func(context.Context, string) ([]IPAddr, error), host string) ([]IPAddr, error) {
|
|
switch host {
|
|
case "slow6loopback4":
|
|
// Returns a slow IPv6 address, and a local IPv4 address.
|
|
return []IPAddr{
|
|
{IP: ParseIP(slowDst6)},
|
|
{IP: ParseIP("127.0.0.1")},
|
|
}, nil
|
|
default:
|
|
return fn(ctx, host)
|
|
}
|
|
}
|
|
|
|
func TestDialerFallbackDelay(t *testing.T) {
|
|
testenv.MustHaveExternalNetwork(t)
|
|
|
|
if !supportsIPv4 || !supportsIPv6 {
|
|
t.Skip("both IPv4 and IPv6 are required")
|
|
}
|
|
|
|
origTestHookLookupIP := testHookLookupIP
|
|
defer func() { testHookLookupIP = origTestHookLookupIP }()
|
|
testHookLookupIP = lookupSlowFast
|
|
|
|
origTestHookDialTCP := testHookDialTCP
|
|
defer func() { testHookDialTCP = origTestHookDialTCP }()
|
|
testHookDialTCP = slowDialTCP
|
|
|
|
var testCases = []struct {
|
|
dualstack bool
|
|
delay time.Duration
|
|
expectElapsed time.Duration
|
|
}{
|
|
// Use a very brief delay, which should fallback immediately.
|
|
{true, 1 * time.Nanosecond, 0},
|
|
// Use a 200ms explicit timeout.
|
|
{true, 200 * time.Millisecond, 200 * time.Millisecond},
|
|
// The default is 300ms.
|
|
{true, 0, 300 * time.Millisecond},
|
|
}
|
|
|
|
handler := func(dss *dualStackServer, ln Listener) {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
dss, err := newDualStackServer()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer dss.teardown()
|
|
if err := dss.buildup(handler); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for i, tt := range testCases {
|
|
d := &Dialer{DualStack: tt.dualstack, FallbackDelay: tt.delay}
|
|
|
|
startTime := time.Now()
|
|
c, err := d.Dial("tcp", JoinHostPort("slow6loopback4", dss.port))
|
|
elapsed := time.Now().Sub(startTime)
|
|
if err == nil {
|
|
c.Close()
|
|
} else if tt.dualstack {
|
|
t.Error(err)
|
|
}
|
|
expectMin := tt.expectElapsed - 1*time.Millisecond
|
|
expectMax := tt.expectElapsed + 95*time.Millisecond
|
|
if !(elapsed >= expectMin) {
|
|
t.Errorf("#%d: got %v; want >= %v", i, elapsed, expectMin)
|
|
}
|
|
if !(elapsed <= expectMax) {
|
|
t.Errorf("#%d: got %v; want <= %v", i, elapsed, expectMax)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDialParallelSpuriousConnection(t *testing.T) {
|
|
if !supportsIPv4 || !supportsIPv6 {
|
|
t.Skip("both IPv4 and IPv6 are required")
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
handler := func(dss *dualStackServer, ln Listener) {
|
|
// Accept one connection per address.
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The client should close itself, without sending data.
|
|
c.SetReadDeadline(time.Now().Add(1 * time.Second))
|
|
var b [1]byte
|
|
if _, err := c.Read(b[:]); err != io.EOF {
|
|
t.Errorf("got %v; want %v", err, io.EOF)
|
|
}
|
|
c.Close()
|
|
wg.Done()
|
|
}
|
|
dss, err := newDualStackServer()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer dss.teardown()
|
|
if err := dss.buildup(handler); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
const fallbackDelay = 100 * time.Millisecond
|
|
|
|
origTestHookDialTCP := testHookDialTCP
|
|
defer func() { testHookDialTCP = origTestHookDialTCP }()
|
|
testHookDialTCP = func(ctx context.Context, net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
|
|
// Sleep long enough for Happy Eyeballs to kick in, and inhibit cancelation.
|
|
// This forces dialParallel to juggle two successful connections.
|
|
time.Sleep(fallbackDelay * 2)
|
|
|
|
// Now ignore the provided context (which will be canceled) and use a
|
|
// different one to make sure this completes with a valid connection,
|
|
// which we hope to be closed below:
|
|
return doDialTCP(context.Background(), net, laddr, raddr)
|
|
}
|
|
|
|
d := Dialer{
|
|
FallbackDelay: fallbackDelay,
|
|
}
|
|
dp := &dialParam{
|
|
Dialer: d,
|
|
network: "tcp",
|
|
address: "?",
|
|
}
|
|
|
|
makeAddr := func(ip string) addrList {
|
|
addr, err := ResolveTCPAddr("tcp", JoinHostPort(ip, dss.port))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return addrList{addr}
|
|
}
|
|
|
|
// dialParallel returns one connection (and closes the other.)
|
|
c, err := dialParallel(context.Background(), dp, makeAddr("127.0.0.1"), makeAddr("::1"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c.Close()
|
|
|
|
// The server should've seen both connections.
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestDialerPartialDeadline(t *testing.T) {
|
|
now := time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
|
|
var testCases = []struct {
|
|
now time.Time
|
|
deadline time.Time
|
|
addrs int
|
|
expectDeadline time.Time
|
|
expectErr error
|
|
}{
|
|
// Regular division.
|
|
{now, now.Add(12 * time.Second), 1, now.Add(12 * time.Second), nil},
|
|
{now, now.Add(12 * time.Second), 2, now.Add(6 * time.Second), nil},
|
|
{now, now.Add(12 * time.Second), 3, now.Add(4 * time.Second), nil},
|
|
// Bump against the 2-second sane minimum.
|
|
{now, now.Add(12 * time.Second), 999, now.Add(2 * time.Second), nil},
|
|
// Total available is now below the sane minimum.
|
|
{now, now.Add(1900 * time.Millisecond), 999, now.Add(1900 * time.Millisecond), nil},
|
|
// Null deadline.
|
|
{now, noDeadline, 1, noDeadline, nil},
|
|
// Step the clock forward and cross the deadline.
|
|
{now.Add(-1 * time.Millisecond), now, 1, now, nil},
|
|
{now.Add(0 * time.Millisecond), now, 1, noDeadline, errTimeout},
|
|
{now.Add(1 * time.Millisecond), now, 1, noDeadline, errTimeout},
|
|
}
|
|
for i, tt := range testCases {
|
|
deadline, err := partialDeadline(tt.now, tt.deadline, tt.addrs)
|
|
if err != tt.expectErr {
|
|
t.Errorf("#%d: got %v; want %v", i, err, tt.expectErr)
|
|
}
|
|
if deadline != tt.expectDeadline {
|
|
t.Errorf("#%d: got %v; want %v", i, deadline, tt.expectDeadline)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDialerLocalAddr(t *testing.T) {
|
|
if !supportsIPv4 || !supportsIPv6 {
|
|
t.Skip("both IPv4 and IPv6 are required")
|
|
}
|
|
|
|
type test struct {
|
|
network, raddr string
|
|
laddr Addr
|
|
error
|
|
}
|
|
var tests = []test{
|
|
{"tcp4", "127.0.0.1", nil, nil},
|
|
{"tcp4", "127.0.0.1", &TCPAddr{}, nil},
|
|
{"tcp4", "127.0.0.1", &TCPAddr{IP: ParseIP("0.0.0.0")}, nil},
|
|
{"tcp4", "127.0.0.1", &TCPAddr{IP: ParseIP("0.0.0.0").To4()}, nil},
|
|
{"tcp4", "127.0.0.1", &TCPAddr{IP: ParseIP("::")}, &AddrError{Err: "some error"}},
|
|
{"tcp4", "127.0.0.1", &TCPAddr{IP: ParseIP("127.0.0.1").To4()}, nil},
|
|
{"tcp4", "127.0.0.1", &TCPAddr{IP: ParseIP("127.0.0.1").To16()}, nil},
|
|
{"tcp4", "127.0.0.1", &TCPAddr{IP: IPv6loopback}, errNoSuitableAddress},
|
|
{"tcp4", "127.0.0.1", &UDPAddr{}, &AddrError{Err: "some error"}},
|
|
{"tcp4", "127.0.0.1", &UnixAddr{}, &AddrError{Err: "some error"}},
|
|
|
|
{"tcp6", "::1", nil, nil},
|
|
{"tcp6", "::1", &TCPAddr{}, nil},
|
|
{"tcp6", "::1", &TCPAddr{IP: ParseIP("0.0.0.0")}, nil},
|
|
{"tcp6", "::1", &TCPAddr{IP: ParseIP("0.0.0.0").To4()}, nil},
|
|
{"tcp6", "::1", &TCPAddr{IP: ParseIP("::")}, nil},
|
|
{"tcp6", "::1", &TCPAddr{IP: ParseIP("127.0.0.1").To4()}, errNoSuitableAddress},
|
|
{"tcp6", "::1", &TCPAddr{IP: ParseIP("127.0.0.1").To16()}, errNoSuitableAddress},
|
|
{"tcp6", "::1", &TCPAddr{IP: IPv6loopback}, nil},
|
|
{"tcp6", "::1", &UDPAddr{}, &AddrError{Err: "some error"}},
|
|
{"tcp6", "::1", &UnixAddr{}, &AddrError{Err: "some error"}},
|
|
|
|
{"tcp", "127.0.0.1", nil, nil},
|
|
{"tcp", "127.0.0.1", &TCPAddr{}, nil},
|
|
{"tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("0.0.0.0")}, nil},
|
|
{"tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("0.0.0.0").To4()}, nil},
|
|
{"tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("127.0.0.1").To4()}, nil},
|
|
{"tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("127.0.0.1").To16()}, nil},
|
|
{"tcp", "127.0.0.1", &TCPAddr{IP: IPv6loopback}, errNoSuitableAddress},
|
|
{"tcp", "127.0.0.1", &UDPAddr{}, &AddrError{Err: "some error"}},
|
|
{"tcp", "127.0.0.1", &UnixAddr{}, &AddrError{Err: "some error"}},
|
|
|
|
{"tcp", "::1", nil, nil},
|
|
{"tcp", "::1", &TCPAddr{}, nil},
|
|
{"tcp", "::1", &TCPAddr{IP: ParseIP("0.0.0.0")}, nil},
|
|
{"tcp", "::1", &TCPAddr{IP: ParseIP("0.0.0.0").To4()}, nil},
|
|
{"tcp", "::1", &TCPAddr{IP: ParseIP("::")}, nil},
|
|
{"tcp", "::1", &TCPAddr{IP: ParseIP("127.0.0.1").To4()}, errNoSuitableAddress},
|
|
{"tcp", "::1", &TCPAddr{IP: ParseIP("127.0.0.1").To16()}, errNoSuitableAddress},
|
|
{"tcp", "::1", &TCPAddr{IP: IPv6loopback}, nil},
|
|
{"tcp", "::1", &UDPAddr{}, &AddrError{Err: "some error"}},
|
|
{"tcp", "::1", &UnixAddr{}, &AddrError{Err: "some error"}},
|
|
}
|
|
|
|
if supportsIPv4map {
|
|
tests = append(tests, test{
|
|
"tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("::")}, nil,
|
|
})
|
|
} else {
|
|
tests = append(tests, test{
|
|
"tcp", "127.0.0.1", &TCPAddr{IP: ParseIP("::")}, &AddrError{Err: "some error"},
|
|
})
|
|
}
|
|
|
|
origTestHookLookupIP := testHookLookupIP
|
|
defer func() { testHookLookupIP = origTestHookLookupIP }()
|
|
testHookLookupIP = lookupLocalhost
|
|
handler := func(ls *localServer, ln Listener) {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
var err error
|
|
var lss [2]*localServer
|
|
for i, network := range []string{"tcp4", "tcp6"} {
|
|
lss[i], err = newLocalServer(network)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer lss[i].teardown()
|
|
if err := lss[i].buildup(handler); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
d := &Dialer{LocalAddr: tt.laddr}
|
|
var addr string
|
|
ip := ParseIP(tt.raddr)
|
|
if ip.To4() != nil {
|
|
addr = lss[0].Listener.Addr().String()
|
|
}
|
|
if ip.To16() != nil && ip.To4() == nil {
|
|
addr = lss[1].Listener.Addr().String()
|
|
}
|
|
c, err := d.Dial(tt.network, addr)
|
|
if err == nil && tt.error != nil || err != nil && tt.error == nil {
|
|
t.Errorf("%s %v->%s: got %v; want %v", tt.network, tt.laddr, tt.raddr, err, tt.error)
|
|
}
|
|
if err != nil {
|
|
if perr := parseDialError(err); perr != nil {
|
|
t.Error(perr)
|
|
}
|
|
continue
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
|
|
func TestDialerDualStack(t *testing.T) {
|
|
// This test is known to be flaky. Don't frighten regular
|
|
// users about it; only fail on the build dashboard.
|
|
if testenv.Builder() == "" {
|
|
testenv.SkipFlaky(t, 13324)
|
|
}
|
|
if !supportsIPv4 || !supportsIPv6 {
|
|
t.Skip("both IPv4 and IPv6 are required")
|
|
}
|
|
|
|
closedPortDelay, expectClosedPortDelay := dialClosedPort()
|
|
if closedPortDelay > expectClosedPortDelay {
|
|
t.Errorf("got %v; want <= %v", closedPortDelay, expectClosedPortDelay)
|
|
}
|
|
|
|
origTestHookLookupIP := testHookLookupIP
|
|
defer func() { testHookLookupIP = origTestHookLookupIP }()
|
|
testHookLookupIP = lookupLocalhost
|
|
handler := func(dss *dualStackServer, ln Listener) {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
|
|
var timeout = 150*time.Millisecond + closedPortDelay
|
|
for _, dualstack := range []bool{false, true} {
|
|
dss, err := newDualStackServer()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer dss.teardown()
|
|
if err := dss.buildup(handler); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d := &Dialer{DualStack: dualstack, Timeout: timeout}
|
|
for range dss.lns {
|
|
c, err := d.Dial("tcp", JoinHostPort("localhost", dss.port))
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
switch addr := c.LocalAddr().(*TCPAddr); {
|
|
case addr.IP.To4() != nil:
|
|
dss.teardownNetwork("tcp4")
|
|
case addr.IP.To16() != nil && addr.IP.To4() == nil:
|
|
dss.teardownNetwork("tcp6")
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDialerKeepAlive(t *testing.T) {
|
|
handler := func(ls *localServer, ln Listener) {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
ls, err := newLocalServer("tcp")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ls.teardown()
|
|
if err := ls.buildup(handler); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { testHookSetKeepAlive = func() {} }()
|
|
|
|
for _, keepAlive := range []bool{false, true} {
|
|
got := false
|
|
testHookSetKeepAlive = func() { got = true }
|
|
var d Dialer
|
|
if keepAlive {
|
|
d.KeepAlive = 30 * time.Second
|
|
}
|
|
c, err := d.Dial("tcp", ls.Listener.Addr().String())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c.Close()
|
|
if got != keepAlive {
|
|
t.Errorf("Dialer.KeepAlive = %v: SetKeepAlive called = %v, want %v", d.KeepAlive, got, !got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDialCancel(t *testing.T) {
|
|
switch testenv.Builder() {
|
|
case "linux-arm64-buildlet":
|
|
t.Skip("skipping on linux-arm64-buildlet; incompatible network config? issue 15191")
|
|
case "":
|
|
testenv.MustHaveExternalNetwork(t)
|
|
}
|
|
|
|
if runtime.GOOS == "nacl" {
|
|
// nacl doesn't have external network access.
|
|
t.Skipf("skipping on %s", runtime.GOOS)
|
|
}
|
|
|
|
blackholeIPPort := JoinHostPort(slowDst4, "1234")
|
|
if !supportsIPv4 {
|
|
blackholeIPPort = JoinHostPort(slowDst6, "1234")
|
|
}
|
|
|
|
ticker := time.NewTicker(10 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
const cancelTick = 5 // the timer tick we cancel the dial at
|
|
const timeoutTick = 100
|
|
|
|
var d Dialer
|
|
cancel := make(chan struct{})
|
|
d.Cancel = cancel
|
|
errc := make(chan error, 1)
|
|
connc := make(chan Conn, 1)
|
|
go func() {
|
|
if c, err := d.Dial("tcp", blackholeIPPort); err != nil {
|
|
errc <- err
|
|
} else {
|
|
connc <- c
|
|
}
|
|
}()
|
|
ticks := 0
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
ticks++
|
|
if ticks == cancelTick {
|
|
close(cancel)
|
|
}
|
|
if ticks == timeoutTick {
|
|
t.Fatal("timeout waiting for dial to fail")
|
|
}
|
|
case c := <-connc:
|
|
c.Close()
|
|
t.Fatal("unexpected successful connection")
|
|
case err := <-errc:
|
|
if perr := parseDialError(err); perr != nil {
|
|
t.Error(perr)
|
|
}
|
|
if ticks < cancelTick {
|
|
t.Fatalf("dial error after %d ticks (%d before cancel sent): %v",
|
|
ticks, cancelTick-ticks, err)
|
|
}
|
|
if oe, ok := err.(*OpError); !ok || oe.Err != errCanceled {
|
|
t.Fatalf("dial error = %v (%T); want OpError with Err == errCanceled", err, err)
|
|
}
|
|
return // success.
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCancelAfterDial(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("avoiding time.Sleep")
|
|
}
|
|
|
|
ln, err := newLocalListener("tcp")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
defer func() {
|
|
ln.Close()
|
|
wg.Wait()
|
|
}()
|
|
|
|
// Echo back the first line of each incoming connection.
|
|
go func() {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
break
|
|
}
|
|
rb := bufio.NewReader(c)
|
|
line, err := rb.ReadString('\n')
|
|
if err != nil {
|
|
t.Error(err)
|
|
c.Close()
|
|
continue
|
|
}
|
|
if _, err := c.Write([]byte(line)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
c.Close()
|
|
}
|
|
wg.Done()
|
|
}()
|
|
|
|
try := func() {
|
|
cancel := make(chan struct{})
|
|
d := &Dialer{Cancel: cancel}
|
|
c, err := d.Dial("tcp", ln.Addr().String())
|
|
|
|
// Immediately after dialing, request cancelation and sleep.
|
|
// Before Issue 15078 was fixed, this would cause subsequent operations
|
|
// to fail with an i/o timeout roughly 50% of the time.
|
|
close(cancel)
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
// Send some data to confirm that the connection is still alive.
|
|
const message = "echo!\n"
|
|
if _, err := c.Write([]byte(message)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// The server should echo the line, and close the connection.
|
|
rb := bufio.NewReader(c)
|
|
line, err := rb.ReadString('\n')
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if line != message {
|
|
t.Errorf("got %q; want %q", line, message)
|
|
}
|
|
if _, err := rb.ReadByte(); err != io.EOF {
|
|
t.Errorf("got %v; want %v", err, io.EOF)
|
|
}
|
|
}
|
|
|
|
// This bug manifested about 50% of the time, so try it a few times.
|
|
for i := 0; i < 10; i++ {
|
|
try()
|
|
}
|
|
}
|