| // Copyright 2009 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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris | |
| // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris | |
| package net | |
| import ( | |
| "context" | |
| "internal/poll" | |
| "os" | |
| "runtime" | |
| "syscall" | |
| ) | |
| const ( | |
| readSyscallName = "read" | |
| readFromSyscallName = "recvfrom" | |
| readMsgSyscallName = "recvmsg" | |
| writeSyscallName = "write" | |
| writeToSyscallName = "sendto" | |
| writeMsgSyscallName = "sendmsg" | |
| ) | |
| func newFD(sysfd, family, sotype int, net string) (*netFD, error) { | |
| ret := &netFD{ | |
| pfd: poll.FD{ | |
| Sysfd: sysfd, | |
| IsStream: sotype == syscall.SOCK_STREAM, | |
| ZeroReadIsEOF: sotype != syscall.SOCK_DGRAM && sotype != syscall.SOCK_RAW, | |
| }, | |
| family: family, | |
| sotype: sotype, | |
| net: net, | |
| } | |
| return ret, nil | |
| } | |
| func (fd *netFD) init() error { | |
| return fd.pfd.Init(fd.net, true) | |
| } | |
| func (fd *netFD) name() string { | |
| var ls, rs string | |
| if fd.laddr != nil { | |
| ls = fd.laddr.String() | |
| } | |
| if fd.raddr != nil { | |
| rs = fd.raddr.String() | |
| } | |
| return fd.net + ":" + ls + "->" + rs | |
| } | |
| func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (rsa syscall.Sockaddr, ret error) { | |
| // Do not need to call fd.writeLock here, | |
| // because fd is not yet accessible to user, | |
| // so no concurrent operations are possible. | |
| switch err := connectFunc(fd.pfd.Sysfd, ra); err { | |
| case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: | |
| case nil, syscall.EISCONN: | |
| select { | |
| case <-ctx.Done(): | |
| return nil, mapErr(ctx.Err()) | |
| default: | |
| } | |
| if err := fd.pfd.Init(fd.net, true); err != nil { | |
| return nil, err | |
| } | |
| runtime.KeepAlive(fd) | |
| return nil, nil | |
| case syscall.EINVAL: | |
| // On Solaris and illumos we can see EINVAL if the socket has | |
| // already been accepted and closed by the server. Treat this | |
| // as a successful connection--writes to the socket will see | |
| // EOF. For details and a test case in C see | |
| // https://golang.org/issue/6828. | |
| if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" { | |
| return nil, nil | |
| } | |
| fallthrough | |
| default: | |
| return nil, os.NewSyscallError("connect", err) | |
| } | |
| if err := fd.pfd.Init(fd.net, true); err != nil { | |
| return nil, err | |
| } | |
| if deadline, hasDeadline := ctx.Deadline(); hasDeadline { | |
| fd.pfd.SetWriteDeadline(deadline) | |
| defer fd.pfd.SetWriteDeadline(noDeadline) | |
| } | |
| // Start the "interrupter" goroutine, if this context might be canceled. | |
| // (The background context cannot) | |
| // | |
| // The interrupter goroutine waits for the context to be done and | |
| // interrupts the dial (by altering the fd's write deadline, which | |
| // wakes up waitWrite). | |
| if ctx != context.Background() { | |
| // Wait for the interrupter goroutine to exit before returning | |
| // from connect. | |
| done := make(chan struct{}) | |
| interruptRes := make(chan error) | |
| defer func() { | |
| close(done) | |
| if ctxErr := <-interruptRes; ctxErr != nil && ret == nil { | |
| // The interrupter goroutine called SetWriteDeadline, | |
| // but the connect code below had returned from | |
| // waitWrite already and did a successful connect (ret | |
| // == nil). Because we've now poisoned the connection | |
| // by making it unwritable, don't return a successful | |
| // dial. This was issue 16523. | |
| ret = mapErr(ctxErr) | |
| fd.Close() // prevent a leak | |
| } | |
| }() | |
| go func() { | |
| select { | |
| case <-ctx.Done(): | |
| // Force the runtime's poller to immediately give up | |
| // waiting for writability, unblocking waitWrite | |
| // below. | |
| fd.pfd.SetWriteDeadline(aLongTimeAgo) | |
| testHookCanceledDial() | |
| interruptRes <- ctx.Err() | |
| case <-done: | |
| interruptRes <- nil | |
| } | |
| }() | |
| } | |
| for { | |
| // Performing multiple connect system calls on a | |
| // non-blocking socket under Unix variants does not | |
| // necessarily result in earlier errors being | |
| // returned. Instead, once runtime-integrated network | |
| // poller tells us that the socket is ready, get the | |
| // SO_ERROR socket option to see if the connection | |
| // succeeded or failed. See issue 7474 for further | |
| // details. | |
| if err := fd.pfd.WaitWrite(); err != nil { | |
| select { | |
| case <-ctx.Done(): | |
| return nil, mapErr(ctx.Err()) | |
| default: | |
| } | |
| return nil, err | |
| } | |
| nerr, err := getsockoptIntFunc(fd.pfd.Sysfd, syscall.SOL_SOCKET, syscall.SO_ERROR) | |
| if err != nil { | |
| return nil, os.NewSyscallError("getsockopt", err) | |
| } | |
| switch err := syscall.Errno(nerr); err { | |
| case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: | |
| case syscall.EISCONN: | |
| return nil, nil | |
| case syscall.Errno(0): | |
| // The runtime poller can wake us up spuriously; | |
| // see issues 14548 and 19289. Check that we are | |
| // really connected; if not, wait again. | |
| if rsa, err := syscall.Getpeername(fd.pfd.Sysfd); err == nil { | |
| return rsa, nil | |
| } | |
| default: | |
| return nil, os.NewSyscallError("connect", err) | |
| } | |
| runtime.KeepAlive(fd) | |
| } | |
| } | |
| func (fd *netFD) accept() (netfd *netFD, err error) { | |
| d, rsa, errcall, err := fd.pfd.Accept() | |
| if err != nil { | |
| if errcall != "" { | |
| err = wrapSyscallError(errcall, err) | |
| } | |
| return nil, err | |
| } | |
| if netfd, err = newFD(d, fd.family, fd.sotype, fd.net); err != nil { | |
| poll.CloseFunc(d) | |
| return nil, err | |
| } | |
| if err = netfd.init(); err != nil { | |
| netfd.Close() | |
| return nil, err | |
| } | |
| lsa, _ := syscall.Getsockname(netfd.pfd.Sysfd) | |
| netfd.setAddr(netfd.addrFunc()(lsa), netfd.addrFunc()(rsa)) | |
| return netfd, nil | |
| } | |
| func (fd *netFD) dup() (f *os.File, err error) { | |
| ns, call, err := fd.pfd.Dup() | |
| if err != nil { | |
| if call != "" { | |
| err = os.NewSyscallError(call, err) | |
| } | |
| return nil, err | |
| } | |
| return os.NewFile(uintptr(ns), fd.name()), nil | |
| } |