summaryrefslogtreecommitdiffstats
path: root/llgo/third_party/gofrontend/libgo/go/net/dial_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'llgo/third_party/gofrontend/libgo/go/net/dial_test.go')
-rw-r--r--llgo/third_party/gofrontend/libgo/go/net/dial_test.go867
1 files changed, 517 insertions, 350 deletions
diff --git a/llgo/third_party/gofrontend/libgo/go/net/dial_test.go b/llgo/third_party/gofrontend/libgo/go/net/dial_test.go
index 42898d669f7..ed6d7cc42f1 100644
--- a/llgo/third_party/gofrontend/libgo/go/net/dial_test.go
+++ b/llgo/third_party/gofrontend/libgo/go/net/dial_test.go
@@ -5,111 +5,50 @@
package net
import (
- "bytes"
- "flag"
- "fmt"
"io"
- "os"
- "os/exec"
- "reflect"
- "regexp"
+ "net/internal/socktest"
"runtime"
- "strconv"
"sync"
"testing"
"time"
)
-func newLocalListener(t *testing.T) Listener {
- ln, err := Listen("tcp", "127.0.0.1:0")
- if err != nil {
- ln, err = Listen("tcp6", "[::1]:0")
+var prohibitionaryDialArgTests = []struct {
+ network string
+ address string
+}{
+ {"tcp6", "127.0.0.1"},
+ {"tcp6", "::ffff:127.0.0.1"},
+}
+
+func TestProhibitionaryDialArg(t *testing.T) {
+ switch runtime.GOOS {
+ case "plan9":
+ t.Skipf("not supported on %s", runtime.GOOS)
+ }
+ if testing.Short() || !*testExternal {
+ t.Skip("avoid external network")
}
+ if !supportsIPv4map {
+ t.Skip("mapping ipv4 address inside ipv6 address not supported")
+ }
+
+ ln, err := Listen("tcp", "[::]:0")
if err != nil {
t.Fatal(err)
}
- return ln
-}
-
-func TestDialTimeout(t *testing.T) {
- origBacklog := listenerBacklog
- defer func() {
- listenerBacklog = origBacklog
- }()
- listenerBacklog = 1
-
- ln := newLocalListener(t)
defer ln.Close()
- errc := make(chan error)
-
- numConns := listenerBacklog + 100
+ _, port, err := SplitHostPort(ln.Addr().String())
+ if err != nil {
+ t.Fatal(err)
+ }
- // TODO(bradfitz): It's hard to test this in a portable
- // way. This is unfortunate, but works for now.
- switch runtime.GOOS {
- case "linux":
- // The kernel will start accepting TCP connections before userspace
- // gets a chance to not accept them, so fire off a bunch to fill up
- // the kernel's backlog. Then we test we get a failure after that.
- for i := 0; i < numConns; i++ {
- go func() {
- _, err := DialTimeout("tcp", ln.Addr().String(), 200*time.Millisecond)
- errc <- err
- }()
- }
- case "darwin", "plan9", "windows":
- // At least OS X 10.7 seems to accept any number of
- // connections, ignoring listen's backlog, so resort
- // to connecting to a hopefully-dead 127/8 address.
- // Same for windows.
- //
- // Use an IANA reserved port (49151) instead of 80, because
- // on our 386 builder, this Dial succeeds, connecting
- // to an IIS web server somewhere. The data center
- // or VM or firewall must be stealing the TCP connection.
- //
- // IANA Service Name and Transport Protocol Port Number Registry
- // <http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml>
- go func() {
- c, err := DialTimeout("tcp", "127.0.71.111:49151", 200*time.Millisecond)
- if err == nil {
- err = fmt.Errorf("unexpected: connected to %s!", c.RemoteAddr())
- c.Close()
- }
- errc <- err
- }()
- default:
- // TODO(bradfitz):
- // OpenBSD may have a reject route to 127/8 except 127.0.0.1/32
- // by default. FreeBSD likely works, but is untested.
- // TODO(rsc):
- // The timeout never happens on Windows. Why? Issue 3016.
- t.Skipf("skipping test on %q; untested.", runtime.GOOS)
- }
-
- connected := 0
- for {
- select {
- case <-time.After(15 * time.Second):
- t.Fatal("too slow")
- case err := <-errc:
- if err == nil {
- connected++
- if connected == numConns {
- t.Fatal("all connections connected; expected some to time out")
- }
- } else {
- terr, ok := err.(timeout)
- if !ok {
- t.Fatalf("got error %q; want error with timeout interface", err)
- }
- if !terr.Timeout() {
- t.Fatalf("got error %q; not a timeout", err)
- }
- // Pass. We saw a timeout error.
- return
- }
+ 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)
}
}
}
@@ -117,7 +56,7 @@ func TestDialTimeout(t *testing.T) {
func TestSelfConnect(t *testing.T) {
if runtime.GOOS == "windows" {
// TODO(brainman): do not know why it hangs.
- t.Skip("skipping known-broken test on windows")
+ t.Skip("known-broken test on windows")
}
// Test that Dial does not honor self-connects.
@@ -160,303 +99,518 @@ func TestSelfConnect(t *testing.T) {
}
}
-var runErrorTest = flag.Bool("run_error_test", false, "let TestDialError check for dns errors")
+func TestDialTimeoutFDLeak(t *testing.T) {
+ switch runtime.GOOS {
+ case "plan9":
+ t.Skipf("%s does not have full support of socktest", runtime.GOOS)
+ }
-type DialErrorTest struct {
- Net string
- Raddr string
- Pattern string
-}
+ const T = 100 * time.Millisecond
-var dialErrorTests = []DialErrorTest{
- {
- "datakit", "mh/astro/r70",
- "dial datakit mh/astro/r70: unknown network datakit",
- },
- {
- "tcp", "127.0.0.1:☺",
- "dial tcp 127.0.0.1:☺: unknown port tcp/☺",
- },
- {
- "tcp", "no-such-name.google.com.:80",
- "dial tcp no-such-name.google.com.:80: lookup no-such-name.google.com.( on .*)?: no (.*)",
- },
- {
- "tcp", "no-such-name.no-such-top-level-domain.:80",
- "dial tcp no-such-name.no-such-top-level-domain.:80: lookup no-such-name.no-such-top-level-domain.( on .*)?: no (.*)",
- },
- {
- "tcp", "no-such-name:80",
- `dial tcp no-such-name:80: lookup no-such-name\.(.*\.)?( on .*)?: no (.*)`,
- },
- {
- "tcp", "mh/astro/r70:http",
- "dial tcp mh/astro/r70:http: lookup mh/astro/r70: invalid domain name",
- },
- {
- "unix", "/etc/file-not-found",
- "dial unix /etc/file-not-found: no such file or directory",
- },
- {
- "unix", "/etc/",
- "dial unix /etc/: (permission denied|socket operation on non-socket|connection refused)",
- },
- {
- "unixpacket", "/etc/file-not-found",
- "dial unixpacket /etc/file-not-found: no such file or directory",
- },
- {
- "unixpacket", "/etc/",
- "dial unixpacket /etc/: (permission denied|socket operation on non-socket|connection refused)",
- },
-}
+ 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)
+ forceCloseSockets()
+ }()
+ 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, errTimedout
+ })
-var duplicateErrorPattern = `dial (.*) dial (.*)`
+ 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 TestDialError(t *testing.T) {
- if !*runErrorTest {
- t.Logf("test disabled; use -run_error_test to enable")
- return
+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)
}
- for i, tt := range dialErrorTests {
- c, err := Dial(tt.Net, tt.Raddr)
- if c != nil {
+ if !supportsIPv4 || !supportsIPv6 {
+ t.Skip("both IPv4 and IPv6 are required")
+ }
+
+ 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()
}
- if err == nil {
- t.Errorf("#%d: nil error, want match for %#q", i, tt.Pattern)
- continue
- }
- s := err.Error()
- match, _ := regexp.MatchString(tt.Pattern, s)
- if !match {
- t.Errorf("#%d: %q, want match for %#q", i, s, tt.Pattern)
- }
- match, _ = regexp.MatchString(duplicateErrorPattern, s)
- if match {
- t.Errorf("#%d: %q, duplicate error return from Dial", i, s)
- }
+ }
+ dss, err := newDualStackServer([]streamListener{
+ {network: "tcp4", address: "127.0.0.1"},
+ {network: "tcp6", address: "::1"},
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer dss.teardown()
+ if err := dss.buildup(handler); err != nil {
+ t.Fatal(err)
+ }
+
+ before := sw.Sockets()
+ const T = 100 * time.Millisecond
+ const N = 10
+ var wg sync.WaitGroup
+ wg.Add(N)
+ d := &Dialer{DualStack: true, Timeout: T}
+ 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()
+ time.Sleep(2 * T) // wait for the dial racers to stop
+ after := sw.Sockets()
+ if len(after) != len(before) {
+ t.Errorf("got %d; want %d", len(after), len(before))
}
}
-var invalidDialAndListenArgTests = []struct {
- net string
- addr string
- err error
-}{
- {"foo", "bar", &OpError{Op: "dial", Net: "foo", Addr: nil, Err: UnknownNetworkError("foo")}},
- {"baz", "", &OpError{Op: "listen", Net: "baz", Addr: nil, Err: UnknownNetworkError("baz")}},
- {"tcp", "", &OpError{Op: "dial", Net: "tcp", Addr: nil, Err: errMissingAddress}},
+// 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 = "192.18.0.254"
+ slowDst6 = "2001:2::254"
+ slowTimeout = 1 * time.Second
+)
+
+// 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(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) {
+ c, err := dialTCP(net, laddr, raddr, deadline)
+ if ParseIP(slowDst4).Equal(raddr.IP) || ParseIP(slowDst6).Equal(raddr.IP) {
+ time.Sleep(deadline.Sub(time.Now()))
+ }
+ return c, err
}
-func TestInvalidDialAndListenArgs(t *testing.T) {
- for _, tt := range invalidDialAndListenArgTests {
- var err error
- switch tt.err.(*OpError).Op {
- case "dial":
- _, err = Dial(tt.net, tt.addr)
- case "listen":
- _, err = Listen(tt.net, tt.addr)
+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()
}
- if !reflect.DeepEqual(tt.err, err) {
- t.Fatalf("got %#v; expected %#v", err, tt.err)
+ elapsed := time.Now().Sub(startTime)
+ if i == 2 {
+ return elapsed, expected
}
}
}
-func TestDialTimeoutFDLeak(t *testing.T) {
- if runtime.GOOS != "linux" {
- // TODO(bradfitz): test on other platforms
- t.Skipf("skipping test on %q", runtime.GOOS)
+func TestDialParallel(t *testing.T) {
+ if testing.Short() || !*testExternal {
+ t.Skip("avoid external network")
+ }
+ if !supportsIPv4 || !supportsIPv6 {
+ t.Skip("both IPv4 and IPv6 are required")
}
- ln := newLocalListener(t)
- defer ln.Close()
-
- type connErr struct {
- conn Conn
- err error
+ closedPortDelay, expectClosedPortDelay := dialClosedPort()
+ if closedPortDelay > expectClosedPortDelay {
+ t.Errorf("got %v; want <= %v", closedPortDelay, expectClosedPortDelay)
}
- dials := listenerBacklog + 100
- // used to be listenerBacklog + 5, but was found to be unreliable, issue 4384.
- maxGoodConnect := listenerBacklog + runtime.NumCPU()*10
- resc := make(chan connErr)
- for i := 0; i < dials; i++ {
- go func() {
- conn, err := DialTimeout("tcp", ln.Addr().String(), 500*time.Millisecond)
- resc <- connErr{conn, err}
- }()
+
+ 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
}
- var firstErr string
- var ngood int
- var toClose []io.Closer
- for i := 0; i < dials; i++ {
- ce := <-resc
- if ce.err == nil {
- ngood++
- if ngood > maxGoodConnect {
- t.Errorf("%d good connects; expected at most %d", ngood, maxGoodConnect)
- }
- toClose = append(toClose, ce.conn)
- continue
+ 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
}
- err := ce.err
- if firstErr == "" {
- firstErr = err.Error()
- } else if err.Error() != firstErr {
- t.Fatalf("inconsistent error messages: first was %q, then later %q", firstErr, err)
+ 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()
}
}
- for _, c := range toClose {
- c.Close()
- }
- for i := 0; i < 100; i++ {
- if got := numFD(); got < dials {
- // Test passes.
- return
+
+ // 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)
}
- time.Sleep(10 * time.Millisecond)
+ return out
}
- if got := numFD(); got >= dials {
- t.Errorf("num fds after %d timeouts = %d; want <%d", dials, got, dials)
+
+ for i, tt := range testCases {
+ dss, err := newDualStackServer([]streamListener{
+ {network: "tcp4", address: "127.0.0.1"},
+ {network: "tcp6", address: "::1"},
+ })
+ 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,
+ Timeout: slowTimeout,
+ }
+ ctx := &dialContext{
+ Dialer: d,
+ network: "tcp",
+ address: "?",
+ finalDeadline: d.deadline(time.Now()),
+ }
+ startTime := time.Now()
+ c, err := dialParallel(ctx, primaries, fallbacks)
+ elapsed := time.Now().Sub(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)
+ }
}
+ // Wait for any slowDst4/slowDst6 connections to timeout.
+ time.Sleep(slowTimeout * 3 / 2)
}
-func numTCP() (ntcp, nopen, nclose int, err error) {
- lsof, err := exec.Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output()
- if err != nil {
- return 0, 0, 0, err
+func lookupSlowFast(fn func(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(host)
}
- ntcp += bytes.Count(lsof, []byte("TCP"))
- for _, state := range []string{"LISTEN", "SYN_SENT", "SYN_RECEIVED", "ESTABLISHED"} {
- nopen += bytes.Count(lsof, []byte(state))
+}
+
+func TestDialerFallbackDelay(t *testing.T) {
+ if testing.Short() || !*testExternal {
+ t.Skip("avoid external network")
}
- for _, state := range []string{"CLOSED", "CLOSE_WAIT", "LAST_ACK", "FIN_WAIT_1", "FIN_WAIT_2", "CLOSING", "TIME_WAIT"} {
- nclose += bytes.Count(lsof, []byte(state))
+ if !supportsIPv4 || !supportsIPv6 {
+ t.Skip("both IPv4 and IPv6 are required")
}
- return ntcp, nopen, nclose, nil
-}
-func TestDialMultiFDLeak(t *testing.T) {
- t.Skip("flaky test - golang.org/issue/8764")
+ origTestHookLookupIP := testHookLookupIP
+ defer func() { testHookLookupIP = origTestHookLookupIP }()
+ testHookLookupIP = lookupSlowFast
- if !supportsIPv4 || !supportsIPv6 {
- t.Skip("neither ipv4 nor ipv6 is supported")
+ 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},
+ // This case is last, in order to wait for hanging slowDst6 connections.
+ {false, 0, slowTimeout},
}
- halfDeadServer := func(dss *dualStackServer, ln Listener) {
+ handler := func(dss *dualStackServer, ln Listener) {
for {
- if c, err := ln.Accept(); err != nil {
+ c, err := ln.Accept()
+ if err != nil {
return
- } else {
- // It just keeps established
- // connections like a half-dead server
- // does.
- dss.putConn(c)
}
+ c.Close()
}
}
dss, err := newDualStackServer([]streamListener{
- {net: "tcp4", addr: "127.0.0.1"},
- {net: "tcp6", addr: "[::1]"},
+ {network: "tcp", address: "127.0.0.1"},
})
if err != nil {
- t.Fatalf("newDualStackServer failed: %v", err)
+ t.Fatal(err)
}
defer dss.teardown()
- if err := dss.buildup(halfDeadServer); err != nil {
- t.Fatalf("dualStackServer.buildup failed: %v", err)
+ if err := dss.buildup(handler); err != nil {
+ t.Fatal(err)
}
- _, before, _, err := numTCP()
+ for i, tt := range testCases {
+ d := &Dialer{DualStack: tt.dualstack, FallbackDelay: tt.delay, Timeout: slowTimeout}
+
+ 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 TestDialSerialAsyncSpuriousConnection(t *testing.T) {
+ ln, err := newLocalListener("tcp")
if err != nil {
- t.Skipf("skipping test; error finding or running lsof: %v", err)
+ t.Fatal(err)
}
+ defer ln.Close()
- var wg sync.WaitGroup
- portnum, _, _ := dtoi(dss.port, 0)
- ras := addrList{
- // Losers that will fail to connect, see RFC 6890.
- &TCPAddr{IP: IPv4(198, 18, 0, 254), Port: portnum},
- &TCPAddr{IP: ParseIP("2001:2::254"), Port: portnum},
-
- // Winner candidates of this race.
- &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: portnum},
- &TCPAddr{IP: IPv6loopback, Port: portnum},
-
- // Losers that will have established connections.
- &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: portnum},
- &TCPAddr{IP: IPv6loopback, Port: portnum},
- }
- const T1 = 10 * time.Millisecond
- const T2 = 2 * T1
- const N = 10
- for i := 0; i < N; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- if c, err := dialMulti("tcp", "fast failover test", nil, ras, time.Now().Add(T1)); err == nil {
- c.Close()
- }
- }()
+ d := Dialer{}
+ ctx := &dialContext{
+ Dialer: d,
+ network: "tcp",
+ address: "?",
+ finalDeadline: d.deadline(time.Now()),
}
- wg.Wait()
- time.Sleep(T2)
- ntcp, after, nclose, err := numTCP()
+ results := make(chan dialResult)
+ cancel := make(chan struct{})
+
+ // Spawn a connection in the background.
+ go dialSerialAsync(ctx, addrList{ln.Addr()}, nil, cancel, results)
+
+ // Receive it at the server.
+ c, err := ln.Accept()
if err != nil {
- t.Skipf("skipping test; error finding or running lsof: %v", err)
+ t.Fatal(err)
}
- t.Logf("tcp sessions: %v, open sessions: %v, closing sessions: %v", ntcp, after, nclose)
+ defer c.Close()
- if after != before {
- t.Fatalf("got %v open sessions; expected %v", after, before)
+ // Tell dialSerialAsync that someone else won the race.
+ close(cancel)
+
+ // The connection 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)
}
}
-func numFD() int {
- if runtime.GOOS == "linux" {
- f, err := os.Open("/proc/self/fd")
- if err != nil {
- panic(err)
+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)
}
- defer f.Close()
- names, err := f.Readdirnames(0)
- if err != nil {
- panic(err)
+ if deadline != tt.expectDeadline {
+ t.Errorf("#%d: got %v; want %v", i, deadline, tt.expectDeadline)
}
- return len(names)
}
- // All tests using this should be skipped anyway, but:
- panic("numFDs not implemented on " + runtime.GOOS)
}
-func TestDialer(t *testing.T) {
- ln, err := Listen("tcp4", "127.0.0.1:0")
- if err != nil {
- t.Fatalf("Listen failed: %v", err)
- }
- defer ln.Close()
+func TestDialerLocalAddr(t *testing.T) {
ch := make(chan error, 1)
- go func() {
+ handler := func(ls *localServer, ln Listener) {
c, err := ln.Accept()
if err != nil {
- ch <- fmt.Errorf("Accept failed: %v", err)
+ ch <- err
return
}
defer c.Close()
ch <- nil
- }()
+ }
+ ls, err := newLocalServer("tcp")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ls.teardown()
+ if err := ls.buildup(handler); err != nil {
+ t.Fatal(err)
+ }
- laddr, err := ResolveTCPAddr("tcp4", "127.0.0.1:0")
+ laddr, err := ResolveTCPAddr(ls.Listener.Addr().Network(), ls.Listener.Addr().String())
if err != nil {
- t.Fatalf("ResolveTCPAddr failed: %v", err)
+ t.Fatal(err)
}
+ laddr.Port = 0
d := &Dialer{LocalAddr: laddr}
- c, err := d.Dial("tcp4", ln.Addr().String())
+ c, err := d.Dial(ls.Listener.Addr().Network(), ls.Addr().String())
if err != nil {
- t.Fatalf("Dial failed: %v", err)
+ t.Fatal(err)
}
defer c.Close()
c.Read(make([]byte, 1))
@@ -466,61 +620,64 @@ func TestDialer(t *testing.T) {
}
}
-func TestDialDualStackLocalhost(t *testing.T) {
- switch runtime.GOOS {
- case "nacl":
- t.Skipf("skipping test on %q", runtime.GOOS)
+func TestDialerDualStack(t *testing.T) {
+ if !supportsIPv4 || !supportsIPv6 {
+ t.Skip("both IPv4 and IPv6 are required")
}
- if ips, err := LookupIP("localhost"); err != nil {
- t.Fatalf("LookupIP failed: %v", err)
- } else if len(ips) < 2 || !supportsIPv4 || !supportsIPv6 {
- t.Skip("localhost doesn't have a pair of different address family IP addresses")
+ closedPortDelay, expectClosedPortDelay := dialClosedPort()
+ if closedPortDelay > expectClosedPortDelay {
+ t.Errorf("got %v; want <= %v", closedPortDelay, expectClosedPortDelay)
}
- touchAndByeServer := func(dss *dualStackServer, ln Listener) {
+ origTestHookLookupIP := testHookLookupIP
+ defer func() { testHookLookupIP = origTestHookLookupIP }()
+ testHookLookupIP = lookupLocalhost
+ handler := func(dss *dualStackServer, ln Listener) {
for {
- if c, err := ln.Accept(); err != nil {
+ c, err := ln.Accept()
+ if err != nil {
return
- } else {
- c.Close()
}
+ c.Close()
}
}
- dss, err := newDualStackServer([]streamListener{
- {net: "tcp4", addr: "127.0.0.1"},
- {net: "tcp6", addr: "[::1]"},
- })
- if err != nil {
- t.Fatalf("newDualStackServer failed: %v", err)
- }
- defer dss.teardown()
- if err := dss.buildup(touchAndByeServer); err != nil {
- t.Fatalf("dualStackServer.buildup failed: %v", err)
- }
- d := &Dialer{DualStack: true}
- for range dss.lns {
- if c, err := d.Dial("tcp", "localhost:"+dss.port); err != nil {
- t.Errorf("Dial failed: %v", err)
- } else {
- if addr := c.LocalAddr().(*TCPAddr); addr.IP.To4() != nil {
+ var timeout = 100*time.Millisecond + closedPortDelay
+ for _, dualstack := range []bool{false, true} {
+ dss, err := newDualStackServer([]streamListener{
+ {network: "tcp4", address: "127.0.0.1"},
+ {network: "tcp6", address: "::1"},
+ })
+ 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")
- } else if addr.IP.To16() != nil && addr.IP.To4() == nil {
+ case addr.IP.To16() != nil && addr.IP.To4() == nil:
dss.teardownNetwork("tcp6")
}
c.Close()
}
}
+ time.Sleep(timeout * 3 / 2) // wait for the dial racers to stop
}
func TestDialerKeepAlive(t *testing.T) {
- ln := newLocalListener(t)
- defer ln.Close()
- defer func() {
- testHookSetKeepAlive = func() {}
- }()
- go func() {
+ handler := func(ls *localServer, ln Listener) {
for {
c, err := ln.Accept()
if err != nil {
@@ -528,7 +685,17 @@ func TestDialerKeepAlive(t *testing.T) {
}
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 }
@@ -536,7 +703,7 @@ func TestDialerKeepAlive(t *testing.T) {
if keepAlive {
d.KeepAlive = 30 * time.Second
}
- c, err := d.Dial("tcp", ln.Addr().String())
+ c, err := d.Dial("tcp", ls.Listener.Addr().String())
if err != nil {
t.Fatal(err)
}
OpenPOWER on IntegriCloud