1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build unix
6
7package net
8
9import (
10	"context"
11	"internal/poll"
12	"os"
13	"runtime"
14	"syscall"
15)
16
17const (
18	readSyscallName     = "read"
19	readFromSyscallName = "recvfrom"
20	readMsgSyscallName  = "recvmsg"
21	writeSyscallName    = "write"
22	writeToSyscallName  = "sendto"
23	writeMsgSyscallName = "sendmsg"
24)
25
26func newFD(sysfd, family, sotype int, net string) (*netFD, error) {
27	ret := &netFD{
28		pfd: poll.FD{
29			Sysfd:         sysfd,
30			IsStream:      sotype == syscall.SOCK_STREAM,
31			ZeroReadIsEOF: sotype != syscall.SOCK_DGRAM && sotype != syscall.SOCK_RAW,
32		},
33		family: family,
34		sotype: sotype,
35		net:    net,
36	}
37	return ret, nil
38}
39
40func (fd *netFD) init() error {
41	return fd.pfd.Init(fd.net, true)
42}
43
44func (fd *netFD) name() string {
45	var ls, rs string
46	if fd.laddr != nil {
47		ls = fd.laddr.String()
48	}
49	if fd.raddr != nil {
50		rs = fd.raddr.String()
51	}
52	return fd.net + ":" + ls + "->" + rs
53}
54
55func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (rsa syscall.Sockaddr, ret error) {
56	// Do not need to call fd.writeLock here,
57	// because fd is not yet accessible to user,
58	// so no concurrent operations are possible.
59	switch err := connectFunc(fd.pfd.Sysfd, ra); err {
60	case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR:
61	case nil, syscall.EISCONN:
62		select {
63		case <-ctx.Done():
64			return nil, mapErr(ctx.Err())
65		default:
66		}
67		if err := fd.pfd.Init(fd.net, true); err != nil {
68			return nil, err
69		}
70		runtime.KeepAlive(fd)
71		return nil, nil
72	case syscall.EINVAL:
73		// On Solaris and illumos we can see EINVAL if the socket has
74		// already been accepted and closed by the server.  Treat this
75		// as a successful connection--writes to the socket will see
76		// EOF.  For details and a test case in C see
77		// https://golang.org/issue/6828.
78		if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" {
79			return nil, nil
80		}
81		fallthrough
82	default:
83		return nil, os.NewSyscallError("connect", err)
84	}
85	if err := fd.pfd.Init(fd.net, true); err != nil {
86		return nil, err
87	}
88	if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
89		fd.pfd.SetWriteDeadline(deadline)
90		defer fd.pfd.SetWriteDeadline(noDeadline)
91	}
92
93	// Start the "interrupter" goroutine, if this context might be canceled.
94	//
95	// The interrupter goroutine waits for the context to be done and
96	// interrupts the dial (by altering the fd's write deadline, which
97	// wakes up waitWrite).
98	ctxDone := ctx.Done()
99	if ctxDone != nil {
100		// Wait for the interrupter goroutine to exit before returning
101		// from connect.
102		done := make(chan struct{})
103		interruptRes := make(chan error)
104		defer func() {
105			close(done)
106			if ctxErr := <-interruptRes; ctxErr != nil && ret == nil {
107				// The interrupter goroutine called SetWriteDeadline,
108				// but the connect code below had returned from
109				// waitWrite already and did a successful connect (ret
110				// == nil). Because we've now poisoned the connection
111				// by making it unwritable, don't return a successful
112				// dial. This was issue 16523.
113				ret = mapErr(ctxErr)
114				fd.Close() // prevent a leak
115			}
116		}()
117		go func() {
118			select {
119			case <-ctxDone:
120				// Force the runtime's poller to immediately give up
121				// waiting for writability, unblocking waitWrite
122				// below.
123				fd.pfd.SetWriteDeadline(aLongTimeAgo)
124				testHookCanceledDial()
125				interruptRes <- ctx.Err()
126			case <-done:
127				interruptRes <- nil
128			}
129		}()
130	}
131
132	for {
133		// Performing multiple connect system calls on a
134		// non-blocking socket under Unix variants does not
135		// necessarily result in earlier errors being
136		// returned. Instead, once runtime-integrated network
137		// poller tells us that the socket is ready, get the
138		// SO_ERROR socket option to see if the connection
139		// succeeded or failed. See issue 7474 for further
140		// details.
141		if err := fd.pfd.WaitWrite(); err != nil {
142			select {
143			case <-ctxDone:
144				return nil, mapErr(ctx.Err())
145			default:
146			}
147			return nil, err
148		}
149		nerr, err := getsockoptIntFunc(fd.pfd.Sysfd, syscall.SOL_SOCKET, syscall.SO_ERROR)
150		if err != nil {
151			return nil, os.NewSyscallError("getsockopt", err)
152		}
153		switch err := syscall.Errno(nerr); err {
154		case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR:
155		case syscall.EISCONN:
156			return nil, nil
157		case syscall.Errno(0):
158			// The runtime poller can wake us up spuriously;
159			// see issues 14548 and 19289. Check that we are
160			// really connected; if not, wait again.
161			if rsa, err := syscall.Getpeername(fd.pfd.Sysfd); err == nil {
162				return rsa, nil
163			}
164		default:
165			return nil, os.NewSyscallError("connect", err)
166		}
167		runtime.KeepAlive(fd)
168	}
169}
170
171func (fd *netFD) accept() (netfd *netFD, err error) {
172	d, rsa, errcall, err := fd.pfd.Accept()
173	if err != nil {
174		if errcall != "" {
175			err = wrapSyscallError(errcall, err)
176		}
177		return nil, err
178	}
179
180	if netfd, err = newFD(d, fd.family, fd.sotype, fd.net); err != nil {
181		poll.CloseFunc(d)
182		return nil, err
183	}
184	if err = netfd.init(); err != nil {
185		netfd.Close()
186		return nil, err
187	}
188	lsa, _ := syscall.Getsockname(netfd.pfd.Sysfd)
189	netfd.setAddr(netfd.addrFunc()(lsa), netfd.addrFunc()(rsa))
190	return netfd, nil
191}
192
193// Defined in os package.
194func newUnixFile(fd int, name string) *os.File
195
196func (fd *netFD) dup() (f *os.File, err error) {
197	ns, call, err := fd.pfd.Dup()
198	if err != nil {
199		if call != "" {
200			err = os.NewSyscallError(call, err)
201		}
202		return nil, err
203	}
204
205	return newUnixFile(ns, fd.name()), nil
206}
207