afc8adc88f
PR go/59506 net: use DialTimeout in TestSelfConnect Backported from master repository. This avoids problems with systems that take a long time to find out nothing is listening, while still testing for the self-connect misfeature since a self-connect should be fast. With this we may be able to remove the test for non-Linux systems. Tested (on GNU/Linux) by editing selfConnect in tcpsock_posix.go to always return false and verifying that TestSelfConnect then fails with and without this change. Idea from Uros Bizjak. From-SVN: r206224
558 lines
14 KiB
Go
558 lines
14 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 (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"reflect"
|
|
"regexp"
|
|
"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")
|
|
}
|
|
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
|
|
|
|
// 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", "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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
// Test that Dial does not honor self-connects.
|
|
// See the comment in DialTCP.
|
|
|
|
// Find a port that would be used as a local address.
|
|
l, err := Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c, err := Dial("tcp", l.Addr().String())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
addr := c.LocalAddr().String()
|
|
c.Close()
|
|
l.Close()
|
|
|
|
// Try to connect to that address repeatedly.
|
|
n := 100000
|
|
if testing.Short() {
|
|
n = 1000
|
|
}
|
|
switch runtime.GOOS {
|
|
case "darwin", "dragonfly", "freebsd", "netbsd", "openbsd", "plan9", "solaris", "windows":
|
|
// Non-Linux systems take a long time to figure
|
|
// out that there is nothing listening on localhost.
|
|
n = 100
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
c, err := DialTimeout("tcp", addr, time.Millisecond)
|
|
if err == nil {
|
|
c.Close()
|
|
t.Errorf("#%d: Dial %q succeeded", i, addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
var runErrorTest = flag.Bool("run_error_test", false, "let TestDialError check for dns errors")
|
|
|
|
type DialErrorTest struct {
|
|
Net string
|
|
Raddr string
|
|
Pattern string
|
|
}
|
|
|
|
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)",
|
|
},
|
|
}
|
|
|
|
var duplicateErrorPattern = `dial (.*) dial (.*)`
|
|
|
|
func TestDialError(t *testing.T) {
|
|
if !*runErrorTest {
|
|
t.Logf("test disabled; use -run_error_test to enable")
|
|
return
|
|
}
|
|
for i, tt := range dialErrorTests {
|
|
c, err := Dial(tt.Net, tt.Raddr)
|
|
if c != nil {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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}},
|
|
}
|
|
|
|
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)
|
|
}
|
|
if !reflect.DeepEqual(tt.err, err) {
|
|
t.Fatalf("got %#v; expected %#v", err, tt.err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDialTimeoutFDLeak(t *testing.T) {
|
|
if runtime.GOOS != "linux" {
|
|
// TODO(bradfitz): test on other platforms
|
|
t.Skipf("skipping test on %q", runtime.GOOS)
|
|
}
|
|
|
|
ln := newLocalListener(t)
|
|
defer ln.Close()
|
|
|
|
type connErr struct {
|
|
conn Conn
|
|
err error
|
|
}
|
|
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}
|
|
}()
|
|
}
|
|
|
|
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
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
for _, c := range toClose {
|
|
c.Close()
|
|
}
|
|
for i := 0; i < 100; i++ {
|
|
if got := numFD(); got < dials {
|
|
// Test passes.
|
|
return
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if got := numFD(); got >= dials {
|
|
t.Errorf("num fds after %d timeouts = %d; want <%d", dials, got, dials)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
ntcp += bytes.Count(lsof, []byte("TCP"))
|
|
for _, state := range []string{"LISTEN", "SYN_SENT", "SYN_RECEIVED", "ESTABLISHED"} {
|
|
nopen += bytes.Count(lsof, []byte(state))
|
|
}
|
|
for _, state := range []string{"CLOSED", "CLOSE_WAIT", "LAST_ACK", "FIN_WAIT_1", "FIN_WAIT_2", "CLOSING", "TIME_WAIT"} {
|
|
nclose += bytes.Count(lsof, []byte(state))
|
|
}
|
|
return ntcp, nopen, nclose, nil
|
|
}
|
|
|
|
func TestDialMultiFDLeak(t *testing.T) {
|
|
if !supportsIPv4 || !supportsIPv6 {
|
|
t.Skip("neither ipv4 nor ipv6 is supported")
|
|
}
|
|
|
|
halfDeadServer := func(dss *dualStackServer, ln Listener) {
|
|
for {
|
|
if c, err := ln.Accept(); err != nil {
|
|
return
|
|
} else {
|
|
// It just keeps established
|
|
// connections like a half-dead server
|
|
// does.
|
|
dss.putConn(c)
|
|
}
|
|
}
|
|
}
|
|
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(halfDeadServer); err != nil {
|
|
t.Fatalf("dualStackServer.buildup failed: %v", err)
|
|
}
|
|
|
|
_, before, _, err := numTCP()
|
|
if err != nil {
|
|
t.Skipf("skipping test; error finding or running lsof: %v", err)
|
|
}
|
|
|
|
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()
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
time.Sleep(T2)
|
|
|
|
ntcp, after, nclose, err := numTCP()
|
|
if err != nil {
|
|
t.Skipf("skipping test; error finding or running lsof: %v", err)
|
|
}
|
|
t.Logf("tcp sessions: %v, open sessions: %v, closing sessions: %v", ntcp, after, nclose)
|
|
|
|
if after != before {
|
|
t.Fatalf("got %v open sessions; expected %v", after, before)
|
|
}
|
|
}
|
|
|
|
func numFD() int {
|
|
if runtime.GOOS == "linux" {
|
|
f, err := os.Open("/proc/self/fd")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer f.Close()
|
|
names, err := f.Readdirnames(0)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return len(names)
|
|
}
|
|
// All tests using this should be skipped anyway, but:
|
|
panic("numFDs not implemented on " + runtime.GOOS)
|
|
}
|
|
|
|
// Assert that a failed Dial attempt does not leak
|
|
// runtime.PollDesc structures
|
|
func TestDialFailPDLeak(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping test in short mode")
|
|
}
|
|
if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
|
|
// Just skip the test because it takes too long.
|
|
t.Skipf("skipping test on %q/%q", runtime.GOOS, runtime.GOARCH)
|
|
}
|
|
|
|
maxprocs := runtime.GOMAXPROCS(0)
|
|
loops := 10 + maxprocs
|
|
// 500 is enough to turn over the chunk of pollcache.
|
|
// See allocPollDesc in runtime/netpoll.goc.
|
|
const count = 500
|
|
var old runtime.MemStats // used by sysdelta
|
|
runtime.ReadMemStats(&old)
|
|
sysdelta := func() uint64 {
|
|
var new runtime.MemStats
|
|
runtime.ReadMemStats(&new)
|
|
delta := old.Sys - new.Sys
|
|
old = new
|
|
return delta
|
|
}
|
|
d := &Dialer{Timeout: time.Nanosecond} // don't bother TCP with handshaking
|
|
failcount := 0
|
|
for i := 0; i < loops; i++ {
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < count; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if c, err := d.Dial("tcp", "127.0.0.1:1"); err == nil {
|
|
t.Error("dial should not succeed")
|
|
c.Close()
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
if t.Failed() {
|
|
t.FailNow()
|
|
}
|
|
if delta := sysdelta(); delta > 0 {
|
|
failcount++
|
|
}
|
|
// there are always some allocations on the first loop
|
|
if failcount > maxprocs+2 {
|
|
t.Error("detected possible memory leak in runtime")
|
|
t.FailNow()
|
|
}
|
|
}
|
|
}
|
|
|
|
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()
|
|
ch := make(chan error, 1)
|
|
go func() {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
ch <- fmt.Errorf("Accept failed: %v", err)
|
|
return
|
|
}
|
|
defer c.Close()
|
|
ch <- nil
|
|
}()
|
|
|
|
laddr, err := ResolveTCPAddr("tcp4", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("ResolveTCPAddr failed: %v", err)
|
|
}
|
|
d := &Dialer{LocalAddr: laddr}
|
|
c, err := d.Dial("tcp4", ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("Dial failed: %v", err)
|
|
}
|
|
defer c.Close()
|
|
c.Read(make([]byte, 1))
|
|
err = <-ch
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func TestDialDualStackLocalhost(t *testing.T) {
|
|
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")
|
|
}
|
|
|
|
touchAndByeServer := func(dss *dualStackServer, ln Listener) {
|
|
for {
|
|
if c, err := ln.Accept(); err != nil {
|
|
return
|
|
} else {
|
|
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 {
|
|
dss.teardownNetwork("tcp4")
|
|
} else if addr.IP.To16() != nil && addr.IP.To4() == nil {
|
|
dss.teardownNetwork("tcp6")
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
}
|