blob: 30f8af3aeeadcba8d6dfce42c7b675c499571661 [file] [log] [blame]
// Copyright 2015 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
package net
import (
"context"
"errors"
"fmt"
"internal/poll"
"io"
"io/fs"
"net/internal/socktest"
"os"
"runtime"
"strings"
"testing"
"time"
)
func (e *OpError) isValid() error {
if e.Op == "" {
return fmt.Errorf("OpError.Op is empty: %v", e)
}
if e.Net == "" {
return fmt.Errorf("OpError.Net is empty: %v", e)
}
for _, addr := range []Addr{e.Source, e.Addr} {
switch addr := addr.(type) {
case nil:
case *TCPAddr:
if addr == nil {
return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e)
}
case *UDPAddr:
if addr == nil {
return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e)
}
case *IPAddr:
if addr == nil {
return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e)
}
case *IPNet:
if addr == nil {
return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e)
}
case *UnixAddr:
if addr == nil {
return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e)
}
case *pipeAddr:
if addr == nil {
return fmt.Errorf("OpError.Source or Addr is non-nil interface: %#v, %v", addr, e)
}
case fileAddr:
if addr == "" {
return fmt.Errorf("OpError.Source or Addr is empty: %#v, %v", addr, e)
}
default:
return fmt.Errorf("OpError.Source or Addr is unknown type: %T, %v", addr, e)
}
}
if e.Err == nil {
return fmt.Errorf("OpError.Err is empty: %v", e)
}
return nil
}
// parseDialError parses nestedErr and reports whether it is a valid
// error value from Dial, Listen functions.
// It returns nil when nestedErr is valid.
func parseDialError(nestedErr error) error {
if nestedErr == nil {
return nil
}
switch err := nestedErr.(type) {
case *OpError:
if err := err.isValid(); err != nil {
return err
}
nestedErr = err.Err
goto second
}
return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr)
second:
if isPlatformError(nestedErr) {
return nil
}
switch err := nestedErr.(type) {
case *AddrError, addrinfoErrno, *timeoutError, *DNSError, InvalidAddrError, *ParseError, *poll.DeadlineExceededError, UnknownNetworkError:
return nil
case *os.SyscallError:
nestedErr = err.Err
goto third
case *fs.PathError: // for Plan 9
nestedErr = err.Err
goto third
}
switch nestedErr {
case errCanceled, ErrClosed, errMissingAddress, errNoSuitableAddress,
context.DeadlineExceeded, context.Canceled:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
third:
if isPlatformError(nestedErr) {
return nil
}
return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr)
}
var dialErrorTests = []struct {
network, address string
}{
{"foo", ""},
{"bar", "baz"},
{"datakit", "mh/astro/r70"},
{"tcp", ""},
{"tcp", "127.0.0.1:☺"},
{"tcp", "no-such-name:80"},
{"tcp", "mh/astro/r70:http"},
{"tcp", JoinHostPort("127.0.0.1", "-1")},
{"tcp", JoinHostPort("127.0.0.1", "123456789")},
{"udp", JoinHostPort("127.0.0.1", "-1")},
{"udp", JoinHostPort("127.0.0.1", "123456789")},
{"ip:icmp", "127.0.0.1"},
{"unix", "/path/to/somewhere"},
{"unixgram", "/path/to/somewhere"},
{"unixpacket", "/path/to/somewhere"},
}
func TestDialError(t *testing.T) {
switch runtime.GOOS {
case "plan9":
t.Skipf("%s does not have full support of socktest", runtime.GOOS)
}
origTestHookLookupIP := testHookLookupIP
defer func() { testHookLookupIP = origTestHookLookupIP }()
testHookLookupIP = func(ctx context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
return nil, &DNSError{Err: "dial error test", Name: "name", Server: "server", IsTimeout: true}
}
sw.Set(socktest.FilterConnect, func(so *socktest.Status) (socktest.AfterFilter, error) {
return nil, errOpNotSupported
})
defer sw.Set(socktest.FilterConnect, nil)
d := Dialer{Timeout: someTimeout}
for i, tt := range dialErrorTests {
c, err := d.Dial(tt.network, tt.address)
if err == nil {
t.Errorf("#%d: should fail; %s:%s->%s", i, c.LocalAddr().Network(), c.LocalAddr(), c.RemoteAddr())
c.Close()
continue
}
if tt.network == "tcp" || tt.network == "udp" {
nerr := err
if op, ok := nerr.(*OpError); ok {
nerr = op.Err
}
if sys, ok := nerr.(*os.SyscallError); ok {
nerr = sys.Err
}
if nerr == errOpNotSupported {
t.Errorf("#%d: should fail without %v; %s:%s->", i, nerr, tt.network, tt.address)
continue
}
}
if c != nil {
t.Errorf("Dial returned non-nil interface %T(%v) with err != nil", c, c)
}
if err = parseDialError(err); err != nil {
t.Errorf("#%d: %v", i, err)
continue
}
}
}
func TestProtocolDialError(t *testing.T) {
switch runtime.GOOS {
case "solaris", "illumos":
t.Skipf("not supported on %s", runtime.GOOS)
}
for _, network := range []string{"tcp", "udp", "ip:4294967296", "unix", "unixpacket", "unixgram"} {
var err error
switch network {
case "tcp":
_, err = DialTCP(network, nil, &TCPAddr{Port: 1 << 16})
case "udp":
_, err = DialUDP(network, nil, &UDPAddr{Port: 1 << 16})
case "ip:4294967296":
_, err = DialIP(network, nil, nil)
case "unix", "unixpacket", "unixgram":
_, err = DialUnix(network, nil, &UnixAddr{Name: "//"})
}
if err == nil {
t.Errorf("%s: should fail", network)
continue
}
if err = parseDialError(err); err != nil {
t.Errorf("%s: %v", network, err)
continue
}
}
}
func TestDialAddrError(t *testing.T) {
switch runtime.GOOS {
case "plan9":
t.Skipf("not supported on %s", runtime.GOOS)
}
if !supportsIPv4() || !supportsIPv6() {
t.Skip("both IPv4 and IPv6 are required")
}
for _, tt := range []struct {
network string
lit string
addr *TCPAddr
}{
{"tcp4", "::1", nil},
{"tcp4", "", &TCPAddr{IP: IPv6loopback}},
// We don't test the {"tcp6", "byte sequence", nil}
// case for now because there is no easy way to
// control name resolution.
{"tcp6", "", &TCPAddr{IP: IP{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}}},
} {
var err error
var c Conn
var op string
if tt.lit != "" {
c, err = Dial(tt.network, JoinHostPort(tt.lit, "0"))
op = fmt.Sprintf("Dial(%q, %q)", tt.network, JoinHostPort(tt.lit, "0"))
} else {
c, err = DialTCP(tt.network, nil, tt.addr)
op = fmt.Sprintf("DialTCP(%q, %q)", tt.network, tt.addr)
}
if err == nil {
c.Close()
t.Errorf("%s succeeded, want error", op)
continue
}
if perr := parseDialError(err); perr != nil {
t.Errorf("%s: %v", op, perr)
continue
}
operr := err.(*OpError).Err
aerr, ok := operr.(*AddrError)
if !ok {
t.Errorf("%s: %v is %T, want *AddrError", op, err, operr)
continue
}
want := tt.lit
if tt.lit == "" {
want = tt.addr.IP.String()
}
if aerr.Addr != want {
t.Errorf("%s: %v, error Addr=%q, want %q", op, err, aerr.Addr, want)
}
}
}
var listenErrorTests = []struct {
network, address string
}{
{"foo", ""},
{"bar", "baz"},
{"datakit", "mh/astro/r70"},
{"tcp", "127.0.0.1:☺"},
{"tcp", "no-such-name:80"},
{"tcp", "mh/astro/r70:http"},
{"tcp", JoinHostPort("127.0.0.1", "-1")},
{"tcp", JoinHostPort("127.0.0.1", "123456789")},
{"unix", "/path/to/somewhere"},
{"unixpacket", "/path/to/somewhere"},
}
func TestListenError(t *testing.T) {
switch runtime.GOOS {
case "plan9":
t.Skipf("%s does not have full support of socktest", runtime.GOOS)
}
origTestHookLookupIP := testHookLookupIP
defer func() { testHookLookupIP = origTestHookLookupIP }()
testHookLookupIP = func(_ context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
return nil, &DNSError{Err: "listen error test", Name: "name", Server: "server", IsTimeout: true}
}
sw.Set(socktest.FilterListen, func(so *socktest.Status) (socktest.AfterFilter, error) {
return nil, errOpNotSupported
})
defer sw.Set(socktest.FilterListen, nil)
for i, tt := range listenErrorTests {
ln, err := Listen(tt.network, tt.address)
if err == nil {
t.Errorf("#%d: should fail; %s:%s->", i, ln.Addr().Network(), ln.Addr())
ln.Close()
continue
}
if tt.network == "tcp" {
nerr := err
if op, ok := nerr.(*OpError); ok {
nerr = op.Err
}
if sys, ok := nerr.(*os.SyscallError); ok {
nerr = sys.Err
}
if nerr == errOpNotSupported {
t.Errorf("#%d: should fail without %v; %s:%s->", i, nerr, tt.network, tt.address)
continue
}
}
if ln != nil {
t.Errorf("Listen returned non-nil interface %T(%v) with err != nil", ln, ln)
}
if err = parseDialError(err); err != nil {
t.Errorf("#%d: %v", i, err)
continue
}
}
}
var listenPacketErrorTests = []struct {
network, address string
}{
{"foo", ""},
{"bar", "baz"},
{"datakit", "mh/astro/r70"},
{"udp", "127.0.0.1:☺"},
{"udp", "no-such-name:80"},
{"udp", "mh/astro/r70:http"},
{"udp", JoinHostPort("127.0.0.1", "-1")},
{"udp", JoinHostPort("127.0.0.1", "123456789")},
}
func TestListenPacketError(t *testing.T) {
switch runtime.GOOS {
case "plan9":
t.Skipf("%s does not have full support of socktest", runtime.GOOS)
}
origTestHookLookupIP := testHookLookupIP
defer func() { testHookLookupIP = origTestHookLookupIP }()
testHookLookupIP = func(_ context.Context, fn func(context.Context, string, string) ([]IPAddr, error), network, host string) ([]IPAddr, error) {
return nil, &DNSError{Err: "listen error test", Name: "name", Server: "server", IsTimeout: true}
}
for i, tt := range listenPacketErrorTests {
c, err := ListenPacket(tt.network, tt.address)
if err == nil {
t.Errorf("#%d: should fail; %s:%s->", i, c.LocalAddr().Network(), c.LocalAddr())
c.Close()
continue
}
if c != nil {
t.Errorf("ListenPacket returned non-nil interface %T(%v) with err != nil", c, c)
}
if err = parseDialError(err); err != nil {
t.Errorf("#%d: %v", i, err)
continue
}
}
}
func TestProtocolListenError(t *testing.T) {
switch runtime.GOOS {
case "plan9":
t.Skipf("not supported on %s", runtime.GOOS)
}
for _, network := range []string{"tcp", "udp", "ip:4294967296", "unix", "unixpacket", "unixgram"} {
var err error
switch network {
case "tcp":
_, err = ListenTCP(network, &TCPAddr{Port: 1 << 16})
case "udp":
_, err = ListenUDP(network, &UDPAddr{Port: 1 << 16})
case "ip:4294967296":
_, err = ListenIP(network, nil)
case "unix", "unixpacket":
_, err = ListenUnix(network, &UnixAddr{Name: "//"})
case "unixgram":
_, err = ListenUnixgram(network, &UnixAddr{Name: "//"})
}
if err == nil {
t.Errorf("%s: should fail", network)
continue
}
if err = parseDialError(err); err != nil {
t.Errorf("%s: %v", network, err)
continue
}
}
}
// parseReadError parses nestedErr and reports whether it is a valid
// error value from Read functions.
// It returns nil when nestedErr is valid.
func parseReadError(nestedErr error) error {
if nestedErr == nil {
return nil
}
switch err := nestedErr.(type) {
case *OpError:
if err := err.isValid(); err != nil {
return err
}
nestedErr = err.Err
goto second
}
if nestedErr == io.EOF {
return nil
}
return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr)
second:
if isPlatformError(nestedErr) {
return nil
}
switch err := nestedErr.(type) {
case *os.SyscallError:
nestedErr = err.Err
goto third
}
switch nestedErr {
case ErrClosed, errTimeout, poll.ErrNotPollable, os.ErrDeadlineExceeded:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
third:
if isPlatformError(nestedErr) {
return nil
}
return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr)
}
// parseWriteError parses nestedErr and reports whether it is a valid
// error value from Write functions.
// It returns nil when nestedErr is valid.
func parseWriteError(nestedErr error) error {
if nestedErr == nil {
return nil
}
switch err := nestedErr.(type) {
case *OpError:
if err := err.isValid(); err != nil {
return err
}
nestedErr = err.Err
goto second
}
return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr)
second:
if isPlatformError(nestedErr) {
return nil
}
switch err := nestedErr.(type) {
case *AddrError, addrinfoErrno, *timeoutError, *DNSError, InvalidAddrError, *ParseError, *poll.DeadlineExceededError, UnknownNetworkError:
return nil
case *os.SyscallError:
nestedErr = err.Err
goto third
}
switch nestedErr {
case errCanceled, ErrClosed, errMissingAddress, errTimeout, os.ErrDeadlineExceeded, ErrWriteToConnected, io.ErrUnexpectedEOF:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
third:
if isPlatformError(nestedErr) {
return nil
}
return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr)
}
// parseCloseError parses nestedErr and reports whether it is a valid
// error value from Close functions.
// It returns nil when nestedErr is valid.
func parseCloseError(nestedErr error, isShutdown bool) error {
if nestedErr == nil {
return nil
}
// Because historically we have not exported the error that we
// return for an operation on a closed network connection,
// there are programs that test for the exact error string.
// Verify that string here so that we don't break those
// programs unexpectedly. See issues #4373 and #19252.
want := "use of closed network connection"
if !isShutdown && !strings.Contains(nestedErr.Error(), want) {
return fmt.Errorf("error string %q does not contain expected string %q", nestedErr, want)
}
if !isShutdown && !errors.Is(nestedErr, ErrClosed) {
return fmt.Errorf("errors.Is(%v, errClosed) returns false, want true", nestedErr)
}
switch err := nestedErr.(type) {
case *OpError:
if err := err.isValid(); err != nil {
return err
}
nestedErr = err.Err
goto second
}
return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr)
second:
if isPlatformError(nestedErr) {
return nil
}
switch err := nestedErr.(type) {
case *os.SyscallError:
nestedErr = err.Err
goto third
case *fs.PathError: // for Plan 9
nestedErr = err.Err
goto third
}
switch nestedErr {
case ErrClosed:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
third:
if isPlatformError(nestedErr) {
return nil
}
switch nestedErr {
case fs.ErrClosed: // for Plan 9
return nil
}
return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr)
}
func TestCloseError(t *testing.T) {
ln, err := newLocalListener("tcp")
if err != nil {
t.Fatal(err)
}
defer ln.Close()
c, err := Dial(ln.Addr().Network(), ln.Addr().String())
if err != nil {
t.Fatal(err)
}
defer c.Close()
for i := 0; i < 3; i++ {
err = c.(*TCPConn).CloseRead()
if perr := parseCloseError(err, true); perr != nil {
t.Errorf("#%d: %v", i, perr)
}
}
for i := 0; i < 3; i++ {
err = c.(*TCPConn).CloseWrite()
if perr := parseCloseError(err, true); perr != nil {
t.Errorf("#%d: %v", i, perr)
}
}
for i := 0; i < 3; i++ {
err = c.Close()
if perr := parseCloseError(err, false); perr != nil {
t.Errorf("#%d: %v", i, perr)
}
err = ln.Close()
if perr := parseCloseError(err, false); perr != nil {
t.Errorf("#%d: %v", i, perr)
}
}
pc, err := ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer pc.Close()
for i := 0; i < 3; i++ {
err = pc.Close()
if perr := parseCloseError(err, false); perr != nil {
t.Errorf("#%d: %v", i, perr)
}
}
}
// parseAcceptError parses nestedErr and reports whether it is a valid
// error value from Accept functions.
// It returns nil when nestedErr is valid.
func parseAcceptError(nestedErr error) error {
if nestedErr == nil {
return nil
}
switch err := nestedErr.(type) {
case *OpError:
if err := err.isValid(); err != nil {
return err
}
nestedErr = err.Err
goto second
}
return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr)
second:
if isPlatformError(nestedErr) {
return nil
}
switch err := nestedErr.(type) {
case *os.SyscallError:
nestedErr = err.Err
goto third
case *fs.PathError: // for Plan 9
nestedErr = err.Err
goto third
}
switch nestedErr {
case ErrClosed, errTimeout, poll.ErrNotPollable, os.ErrDeadlineExceeded:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
third:
if isPlatformError(nestedErr) {
return nil
}
return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr)
}
func TestAcceptError(t *testing.T) {
handler := func(ls *localServer, ln Listener) {
for {
ln.(*TCPListener).SetDeadline(time.Now().Add(5 * time.Millisecond))
c, err := ln.Accept()
if perr := parseAcceptError(err); perr != nil {
t.Error(perr)
}
if err != nil {
if c != nil {
t.Errorf("Accept returned non-nil interface %T(%v) with err != nil", c, c)
}
if nerr, ok := err.(Error); !ok || (!nerr.Timeout() && !nerr.Temporary()) {
return
}
continue
}
c.Close()
}
}
ls, err := newLocalServer("tcp")
if err != nil {
t.Fatal(err)
}
if err := ls.buildup(handler); err != nil {
ls.teardown()
t.Fatal(err)
}
time.Sleep(100 * time.Millisecond)
ls.teardown()
}
// parseCommonError parses nestedErr and reports whether it is a valid
// error value from miscellaneous functions.
// It returns nil when nestedErr is valid.
func parseCommonError(nestedErr error) error {
if nestedErr == nil {
return nil
}
switch err := nestedErr.(type) {
case *OpError:
if err := err.isValid(); err != nil {
return err
}
nestedErr = err.Err
goto second
}
return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr)
second:
if isPlatformError(nestedErr) {
return nil
}
switch err := nestedErr.(type) {
case *os.SyscallError:
nestedErr = err.Err
goto third
case *os.LinkError:
nestedErr = err.Err
goto third
case *fs.PathError:
nestedErr = err.Err
goto third
}
switch nestedErr {
case ErrClosed:
return nil
}
return fmt.Errorf("unexpected type on 2nd nested level: %T", nestedErr)
third:
if isPlatformError(nestedErr) {
return nil
}
return fmt.Errorf("unexpected type on 3rd nested level: %T", nestedErr)
}
func TestFileError(t *testing.T) {
switch runtime.GOOS {
case "windows":
t.Skipf("not supported on %s", runtime.GOOS)
}
f, err := os.CreateTemp("", "go-nettest")
if err != nil {
t.Fatal(err)
}
defer os.Remove(f.Name())
defer f.Close()
c, err := FileConn(f)
if err != nil {
if c != nil {
t.Errorf("FileConn returned non-nil interface %T(%v) with err != nil", c, c)
}
if perr := parseCommonError(err); perr != nil {
t.Error(perr)
}
} else {
c.Close()
t.Error("should fail")
}
ln, err := FileListener(f)
if err != nil {
if ln != nil {
t.Errorf("FileListener returned non-nil interface %T(%v) with err != nil", ln, ln)
}
if perr := parseCommonError(err); perr != nil {
t.Error(perr)
}
} else {
ln.Close()
t.Error("should fail")
}
pc, err := FilePacketConn(f)
if err != nil {
if pc != nil {
t.Errorf("FilePacketConn returned non-nil interface %T(%v) with err != nil", pc, pc)
}
if perr := parseCommonError(err); perr != nil {
t.Error(perr)
}
} else {
pc.Close()
t.Error("should fail")
}
ln, err = newLocalListener("tcp")
if err != nil {
t.Fatal(err)
}
for i := 0; i < 3; i++ {
f, err := ln.(*TCPListener).File()
if err != nil {
if perr := parseCommonError(err); perr != nil {
t.Error(perr)
}
} else {
f.Close()
}
ln.Close()
}
}
func parseLookupPortError(nestedErr error) error {
if nestedErr == nil {
return nil
}
switch nestedErr.(type) {
case *AddrError, *DNSError:
return nil
case *fs.PathError: // for Plan 9
return nil
}
return fmt.Errorf("unexpected type on 1st nested level: %T", nestedErr)
}