summaryrefslogtreecommitdiffstats
path: root/src/net/dial_test.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/net/dial_test.go
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/net/dial_test.go')
-rw-r--r--src/net/dial_test.go1088
1 files changed, 1088 insertions, 0 deletions
diff --git a/src/net/dial_test.go b/src/net/dial_test.go
new file mode 100644
index 0000000..ca9f0da
--- /dev/null
+++ b/src/net/dial_test.go
@@ -0,0 +1,1088 @@
+// 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.
+
+//go:build !js && !wasip1
+
+package net
+
+import (
+ "bufio"
+ "context"
+ "errors"
+ "fmt"
+ "internal/testenv"
+ "io"
+ "os"
+ "runtime"
+ "strings"
+ "sync"
+ "syscall"
+ "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 := newLocalListener(t, "tcp")
+ 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 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)
+ case "openbsd":
+ testenv.SkipFlaky(t, 15157)
+ }
+ if !supportsIPv4() || !supportsIPv6() {
+ t.Skip("both IPv4 and IPv6 are required")
+ }
+
+ 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: 5 * time.Second}
+ 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, network string, laddr, raddr *TCPAddr) (*TCPConn, error) {
+ sd := &sysDialer{network: network, address: raddr.String()}
+ c, err := sd.doDialTCP(ctx, 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(t *testing.T) (dialLatency time.Duration) {
+ // On most platforms, dialing a closed port should be nearly instantaneous —
+ // less than a few hundred milliseconds. However, on some platforms it may be
+ // much slower: on Windows and OpenBSD, it has been observed to take up to a
+ // few seconds.
+
+ l, err := Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("dialClosedPort: Listen failed: %v", err)
+ }
+ addr := l.Addr().String()
+ l.Close()
+
+ startTime := time.Now()
+ c, err := Dial("tcp", addr)
+ if err == nil {
+ c.Close()
+ }
+ elapsed := time.Since(startTime)
+ t.Logf("dialClosedPort: measured delay %v", elapsed)
+ return elapsed
+}
+
+func TestDialParallel(t *testing.T) {
+ const instant time.Duration = 0
+ const fallbackDelay = 200 * time.Millisecond
+
+ 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, instant},
+ {[]string{"::1", "127.0.0.1"}, []string{}, "tcp6", true, instant},
+ // Skip a "connection refused" in the fallback thread.
+ {[]string{slowDst4, slowDst6}, []string{"::1", "127.0.0.1"}, "tcp6", true, fallbackDelay},
+ // Primary refused, fallback without delay.
+ {[]string{"127.0.0.1"}, []string{"::1"}, "tcp4", true, instant},
+ {[]string{"::1"}, []string{"127.0.0.1"}, "tcp6", true, instant},
+ // Everything is refused.
+ {[]string{"127.0.0.1"}, []string{}, "tcp4", false, instant},
+ // 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},
+ }
+
+ // 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 {
+ i, tt := i, tt
+ t.Run(fmt.Sprint(i), func(t *testing.T) {
+ dialTCP := func(ctx context.Context, network string, laddr, raddr *TCPAddr) (*TCPConn, error) {
+ n := "tcp6"
+ if raddr.IP.To4() != nil {
+ n = "tcp4"
+ }
+ if n == tt.teardownNetwork {
+ return nil, errors.New("unreachable")
+ }
+ if r := raddr.IP.String(); r == slowDst4 || r == slowDst6 {
+ <-ctx.Done()
+ return nil, ctx.Err()
+ }
+ return &TCPConn{}, nil
+ }
+
+ primaries := makeAddrs(tt.primaries, "80")
+ fallbacks := makeAddrs(tt.fallbacks, "80")
+ d := Dialer{
+ FallbackDelay: fallbackDelay,
+ }
+ const forever = 60 * time.Minute
+ if tt.expectElapsed == instant {
+ d.FallbackDelay = forever
+ }
+ startTime := time.Now()
+ sd := &sysDialer{
+ Dialer: d,
+ network: "tcp",
+ address: "?",
+ testHookDialTCP: dialTCP,
+ }
+ c, err := sd.dialParallel(context.Background(), 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)
+ }
+
+ if elapsed < tt.expectElapsed || elapsed >= forever {
+ t.Errorf("#%d: got %v; want >= %v, < forever", i, elapsed, tt.expectElapsed)
+ }
+
+ // Repeat each case, ensuring that it can be canceled.
+ ctx, cancel := context.WithCancel(context.Background())
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ time.Sleep(5 * time.Millisecond)
+ cancel()
+ wg.Done()
+ }()
+ // Ignore errors, since all we care about is that the
+ // call can be canceled.
+ c, _ = sd.dialParallel(ctx, primaries, fallbacks)
+ if c != nil {
+ c.Close()
+ }
+ wg.Wait()
+ })
+ }
+}
+
+func lookupSlowFast(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, 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, network, 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.Since(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 readDeadline time.Time
+ if td, ok := t.Deadline(); ok {
+ const arbitraryCleanupMargin = 1 * time.Second
+ readDeadline = td.Add(-arbitraryCleanupMargin)
+ } else {
+ readDeadline = time.Now().Add(5 * time.Second)
+ }
+
+ var closed sync.WaitGroup
+ closed.Add(2)
+ handler := func(dss *dualStackServer, ln Listener) {
+ // Accept one connection per address.
+ c, err := ln.Accept()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Workaround for https://go.dev/issue/37795.
+ // On arm64 macOS (current as of macOS 12.4),
+ // reading from a socket at the same time as the client
+ // is closing it occasionally hangs for 60 seconds before
+ // returning ECONNRESET. Sleep for a bit to give the
+ // socket time to close before trying to read from it.
+ if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
+ time.Sleep(10 * time.Millisecond)
+ }
+
+ // The client should close itself, without sending data.
+ c.SetReadDeadline(readDeadline)
+ var b [1]byte
+ if _, err := c.Read(b[:]); err != io.EOF {
+ t.Errorf("got %v; want %v", err, io.EOF)
+ }
+ c.Close()
+ closed.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
+
+ var dialing sync.WaitGroup
+ dialing.Add(2)
+ origTestHookDialTCP := testHookDialTCP
+ defer func() { testHookDialTCP = origTestHookDialTCP }()
+ testHookDialTCP = func(ctx context.Context, net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
+ // Wait until Happy Eyeballs kicks in and both connections are dialing,
+ // and inhibit cancellation.
+ // This forces dialParallel to juggle two successful connections.
+ dialing.Done()
+ dialing.Wait()
+
+ // 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:
+ sd := &sysDialer{network: net, address: raddr.String()}
+ return sd.doDialTCP(context.Background(), laddr, raddr)
+ }
+
+ d := Dialer{
+ FallbackDelay: fallbackDelay,
+ }
+ sd := &sysDialer{
+ 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 := sd.dialParallel(context.Background(), makeAddr("127.0.0.1"), makeAddr("::1"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.Close()
+
+ // The server should've seen both connections.
+ closed.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.Equal(tt.expectDeadline) {
+ t.Errorf("#%d: got %v; want %v", i, deadline, tt.expectDeadline)
+ }
+ }
+}
+
+// isEADDRINUSE reports whether err is syscall.EADDRINUSE.
+var isEADDRINUSE = func(err error) bool { return false }
+
+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"}},
+ }
+
+ issue34264Index := -1
+ if supportsIPv4map() {
+ issue34264Index = len(tests)
+ 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 lss [2]*localServer
+ for i, network := range []string{"tcp4", "tcp6"} {
+ lss[i] = newLocalServer(t, network)
+ defer lss[i].teardown()
+ if err := lss[i].buildup(handler); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ for i, 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 {
+ if i == issue34264Index && runtime.GOOS == "freebsd" && isEADDRINUSE(err) {
+ // https://golang.org/issue/34264: FreeBSD through at least version 12.2
+ // has been observed to fail with EADDRINUSE when dialing from an IPv6
+ // local address to an IPv4 remote address.
+ t.Logf("%s %v->%s: got %v; want %v", tt.network, tt.laddr, tt.raddr, err, tt.error)
+ t.Logf("(spurious EADDRINUSE ignored on freebsd: see https://golang.org/issue/34264)")
+ } else {
+ 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) {
+ testenv.SkipFlaky(t, 13324)
+
+ if !supportsIPv4() || !supportsIPv6() {
+ t.Skip("both IPv4 and IPv6 are required")
+ }
+
+ closedPortDelay := dialClosedPort(t)
+
+ 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 := newLocalServer(t, "tcp")
+ defer ls.teardown()
+ if err := ls.buildup(handler); err != nil {
+ t.Fatal(err)
+ }
+ defer func() { testHookSetKeepAlive = func(time.Duration) {} }()
+
+ tests := []struct {
+ ka time.Duration
+ expected time.Duration
+ }{
+ {-1, -1},
+ {0, 15 * time.Second},
+ {5 * time.Second, 5 * time.Second},
+ {30 * time.Second, 30 * time.Second},
+ }
+
+ for _, test := range tests {
+ var got time.Duration = -1
+ testHookSetKeepAlive = func(d time.Duration) { got = d }
+ d := Dialer{KeepAlive: test.ka}
+ c, err := d.Dial("tcp", ls.Listener.Addr().String())
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.Close()
+ if got != test.expected {
+ t.Errorf("Dialer.KeepAlive = %v: SetKeepAlive set to %v, want %v", d.KeepAlive, got, test.expected)
+ }
+ }
+}
+
+func TestDialCancel(t *testing.T) {
+ mustHaveExternalNetwork(t)
+
+ 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 {
+ // Using strings.Contains is ugly but
+ // may work on plan9 and windows.
+ ignorable := []string{
+ "connection refused",
+ "unreachable",
+ "no route to host",
+ }
+ e := err.Error()
+ for _, ignore := range ignorable {
+ if strings.Contains(e, ignore) {
+ t.Skipf("connection to %v failed fast with %v", blackholeIPPort, err)
+ }
+ }
+
+ 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 := newLocalListener(t, "tcp")
+
+ 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 cancellation 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()
+ }
+}
+
+func TestDialClosedPortFailFast(t *testing.T) {
+ if runtime.GOOS != "windows" {
+ // Reported by go.dev/issues/23366.
+ t.Skip("skipping windows only test")
+ }
+ for _, network := range []string{"tcp", "tcp4", "tcp6"} {
+ t.Run(network, func(t *testing.T) {
+ if !testableNetwork(network) {
+ t.Skipf("skipping: can't listen on %s", network)
+ }
+ // Reserve a local port till the end of the
+ // test by opening a listener and connecting to
+ // it using Dial.
+ ln := newLocalListener(t, network)
+ addr := ln.Addr().String()
+ conn1, err := Dial(network, addr)
+ if err != nil {
+ ln.Close()
+ t.Fatal(err)
+ }
+ defer conn1.Close()
+ // Now close the listener so the next Dial fails
+ // keeping conn1 alive so the port is not made
+ // available.
+ ln.Close()
+
+ maxElapsed := time.Second
+ // The host can be heavy-loaded and take
+ // longer than configured. Retry until
+ // Dial takes less than maxElapsed or
+ // the test times out.
+ for {
+ startTime := time.Now()
+ conn2, err := Dial(network, addr)
+ if err == nil {
+ conn2.Close()
+ t.Fatal("error expected")
+ }
+ elapsed := time.Since(startTime)
+ if elapsed < maxElapsed {
+ break
+ }
+ t.Logf("got %v; want < %v", elapsed, maxElapsed)
+ }
+ })
+ }
+}
+
+// Issue 18806: it should always be possible to net.Dial a
+// net.Listener().Addr().String when the listen address was ":n", even
+// if the machine has halfway configured IPv6 such that it can bind on
+// "::" not connect back to that same address.
+func TestDialListenerAddr(t *testing.T) {
+ if !testableNetwork("tcp4") {
+ t.Skipf("skipping: can't listen on tcp4")
+ }
+
+ // The original issue report was for listening on just ":0" on a system that
+ // supports both tcp4 and tcp6 for external traffic but only tcp4 for loopback
+ // traffic. However, the port opened by ":0" is externally-accessible, and may
+ // trigger firewall alerts or otherwise be mistaken for malicious activity
+ // (see https://go.dev/issue/59497). Moreover, it often does not reproduce
+ // the scenario in the issue, in which the port *cannot* be dialed as tcp6.
+ //
+ // To address both of those problems, we open a tcp4-only localhost port, but
+ // then dial the address string that the listener would have reported for a
+ // dual-stack port.
+ ln, err := Listen("tcp4", "localhost:0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ln.Close()
+
+ t.Logf("listening on %q", ln.Addr())
+ _, port, err := SplitHostPort(ln.Addr().String())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // If we had opened a dual-stack port without an explicit "localhost" address,
+ // the Listener would arbitrarily report an empty tcp6 address in its Addr
+ // string.
+ //
+ // The documentation for Dial says ‘if the host is empty or a literal
+ // unspecified IP address, as in ":80", "0.0.0.0:80" or "[::]:80" for TCP and
+ // UDP, "", "0.0.0.0" or "::" for IP, the local system is assumed.’
+ // In #18806, it was decided that that should include the local tcp4 host
+ // even if the string is in the tcp6 format.
+ dialAddr := "[::]:" + port
+ c, err := Dial("tcp4", dialAddr)
+ if err != nil {
+ t.Fatalf(`Dial("tcp4", %q): %v`, dialAddr, err)
+ }
+ c.Close()
+ t.Logf(`Dial("tcp4", %q) succeeded`, dialAddr)
+}
+
+func TestDialerControl(t *testing.T) {
+ switch runtime.GOOS {
+ case "plan9":
+ t.Skipf("not supported on %s", runtime.GOOS)
+ }
+
+ t.Run("StreamDial", func(t *testing.T) {
+ for _, network := range []string{"tcp", "tcp4", "tcp6", "unix", "unixpacket"} {
+ if !testableNetwork(network) {
+ continue
+ }
+ ln := newLocalListener(t, network)
+ defer ln.Close()
+ d := Dialer{Control: controlOnConnSetup}
+ c, err := d.Dial(network, ln.Addr().String())
+ if err != nil {
+ t.Error(err)
+ continue
+ }
+ c.Close()
+ }
+ })
+ t.Run("PacketDial", func(t *testing.T) {
+ for _, network := range []string{"udp", "udp4", "udp6", "unixgram"} {
+ if !testableNetwork(network) {
+ continue
+ }
+ c1 := newLocalPacketListener(t, network)
+ if network == "unixgram" {
+ defer os.Remove(c1.LocalAddr().String())
+ }
+ defer c1.Close()
+ d := Dialer{Control: controlOnConnSetup}
+ c2, err := d.Dial(network, c1.LocalAddr().String())
+ if err != nil {
+ t.Error(err)
+ continue
+ }
+ c2.Close()
+ }
+ })
+}
+
+func TestDialerControlContext(t *testing.T) {
+ switch runtime.GOOS {
+ case "plan9":
+ t.Skipf("%s does not have full support of socktest", runtime.GOOS)
+ }
+ t.Run("StreamDial", func(t *testing.T) {
+ for i, network := range []string{"tcp", "tcp4", "tcp6", "unix", "unixpacket"} {
+ if !testableNetwork(network) {
+ continue
+ }
+ ln := newLocalListener(t, network)
+ defer ln.Close()
+ var id int
+ d := Dialer{ControlContext: func(ctx context.Context, network string, address string, c syscall.RawConn) error {
+ id = ctx.Value("id").(int)
+ return controlOnConnSetup(network, address, c)
+ }}
+ c, err := d.DialContext(context.WithValue(context.Background(), "id", i+1), network, ln.Addr().String())
+ if err != nil {
+ t.Error(err)
+ continue
+ }
+ if id != i+1 {
+ t.Errorf("got id %d, want %d", id, i+1)
+ }
+ c.Close()
+ }
+ })
+}
+
+// mustHaveExternalNetwork is like testenv.MustHaveExternalNetwork
+// except that it won't skip testing on non-mobile builders.
+func mustHaveExternalNetwork(t *testing.T) {
+ t.Helper()
+ mobile := runtime.GOOS == "android" || runtime.GOOS == "ios"
+ if testenv.Builder() == "" || mobile {
+ testenv.MustHaveExternalNetwork(t)
+ }
+}
+
+type contextWithNonZeroDeadline struct {
+ context.Context
+}
+
+func (contextWithNonZeroDeadline) Deadline() (time.Time, bool) {
+ // Return non-zero time.Time value with false indicating that no deadline is set.
+ return time.Unix(0, 0), false
+}
+
+func TestDialWithNonZeroDeadline(t *testing.T) {
+ ln := newLocalListener(t, "tcp")
+ defer ln.Close()
+ _, port, err := SplitHostPort(ln.Addr().String())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ctx := contextWithNonZeroDeadline{Context: context.Background()}
+ var dialer Dialer
+ c, err := dialer.DialContext(ctx, "tcp", JoinHostPort("", port))
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.Close()
+}