f8d9fa9e80
This upgrades all of libgo other than the runtime package to the Go 1.4 release. In Go 1.4 much of the runtime was rewritten into Go. Merging that code will take more time and will not change the API, so I'm putting it off for now. There are a few runtime changes anyhow, to accomodate other packages that rely on minor modifications to the runtime support. The compiler changes slightly to add a one-bit flag to each type descriptor kind that is stored directly in an interface, which for gccgo is currently only pointer types. Another one-bit flag (gcprog) is reserved because it is used by the gc compiler, but gccgo does not currently use it. There is another error check in the compiler since I ran across it during testing. gotools/: * Makefile.am (go_cmd_go_files): Sort entries. Add generate.go. * Makefile.in: Rebuild. From-SVN: r219627
549 lines
13 KiB
Go
549 lines
13 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", "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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
if c.LocalAddr().String() == addr {
|
|
t.Errorf("#%d: Dial %q self-connect", i, addr)
|
|
} else {
|
|
t.Logf("#%d: Dial %q succeeded - possibly racing with other listener", i, addr)
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
t.Skip("flaky test - golang.org/issue/8764")
|
|
|
|
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)
|
|
}
|
|
|
|
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) {
|
|
switch runtime.GOOS {
|
|
case "nacl":
|
|
t.Skipf("skipping test on %q", runtime.GOOS)
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDialerKeepAlive(t *testing.T) {
|
|
ln := newLocalListener(t)
|
|
defer ln.Close()
|
|
defer func() {
|
|
testHookSetKeepAlive = func() {}
|
|
}()
|
|
go func() {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
c.Close()
|
|
}
|
|
}()
|
|
for _, keepAlive := range []bool{false, true} {
|
|
got := false
|
|
testHookSetKeepAlive = func() { got = true }
|
|
var d Dialer
|
|
if keepAlive {
|
|
d.KeepAlive = 30 * time.Second
|
|
}
|
|
c, err := d.Dial("tcp", ln.Addr().String())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c.Close()
|
|
if got != keepAlive {
|
|
t.Errorf("Dialer.KeepAlive = %v: SetKeepAlive called = %v, want %v", d.KeepAlive, got, !got)
|
|
}
|
|
}
|
|
}
|