| // 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", "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 := Dial("tcp", addr) |
| 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() |
| } |
| } |
| } |