| // Copyright 2010 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 ( | |
| "context" | |
| "internal/poll" | |
| "internal/syscall/windows" | |
| "os" | |
| "runtime" | |
| "syscall" | |
| "unsafe" | |
| ) | |
| const ( | |
| readSyscallName = "wsarecv" | |
| readFromSyscallName = "wsarecvfrom" | |
| readMsgSyscallName = "wsarecvmsg" | |
| writeSyscallName = "wsasend" | |
| writeToSyscallName = "wsasendto" | |
| writeMsgSyscallName = "wsasendmsg" | |
| ) | |
| func init() { | |
| poll.InitWSA() | |
| } | |
| // canUseConnectEx reports whether we can use the ConnectEx Windows API call | |
| // for the given network type. | |
| func canUseConnectEx(net string) bool { | |
| switch net { | |
| case "tcp", "tcp4", "tcp6": | |
| return true | |
| } | |
| // ConnectEx windows API does not support connectionless sockets. | |
| return false | |
| } | |
| func newFD(sysfd syscall.Handle, family, sotype int, net string) *netFD { | |
| return &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, | |
| } | |
| } | |
| func (fd *netFD) init() error { | |
| if err := fd.pfd.Init(fd.net, true); err != nil { | |
| return err | |
| } | |
| switch fd.net { | |
| case "udp", "udp4", "udp6": | |
| // Disable reporting of PORT_UNREACHABLE errors. | |
| // See https://go.dev/issue/5834. | |
| ret := uint32(0) | |
| flag := uint32(0) | |
| size := uint32(unsafe.Sizeof(flag)) | |
| err := syscall.WSAIoctl(fd.pfd.Sysfd, syscall.SIO_UDP_CONNRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0) | |
| if err != nil { | |
| return wrapSyscallError("wsaioctl", err) | |
| } | |
| // Disable reporting of NET_UNREACHABLE errors. | |
| // See https://go.dev/issue/68614. | |
| ret = 0 | |
| flag = 0 | |
| size = uint32(unsafe.Sizeof(flag)) | |
| err = syscall.WSAIoctl(fd.pfd.Sysfd, windows.SIO_UDP_NETRESET, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &ret, nil, 0) | |
| if err != nil { | |
| return wrapSyscallError("wsaioctl", err) | |
| } | |
| } | |
| return nil | |
| } | |
| // Always returns nil for connected peer address result. | |
| func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (syscall.Sockaddr, error) { | |
| // Do not need to call fd.writeLock here, | |
| // because fd is not yet accessible to user, | |
| // so no concurrent operations are possible. | |
| if err := fd.init(); err != nil { | |
| return nil, err | |
| } | |
| if ctx.Done() != nil { | |
| // Propagate the Context's deadline and cancellation. | |
| // If the context is already done, or if it has a nonzero deadline, | |
| // ensure that that is applied before the call to ConnectEx begins | |
| // so that we don't return spurious connections. | |
| defer fd.pfd.SetWriteDeadline(noDeadline) | |
| if ctx.Err() != nil { | |
| fd.pfd.SetWriteDeadline(aLongTimeAgo) | |
| } else { | |
| if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() { | |
| fd.pfd.SetWriteDeadline(deadline) | |
| } | |
| done := make(chan struct{}) | |
| stop := context.AfterFunc(ctx, func() { | |
| // Force the runtime's poller to immediately give | |
| // up waiting for writability. | |
| fd.pfd.SetWriteDeadline(aLongTimeAgo) | |
| close(done) | |
| }) | |
| defer func() { | |
| if !stop() { | |
| // Wait for the call to SetWriteDeadline to complete so that we can | |
| // reset the deadline if everything else succeeded. | |
| <-done | |
| } | |
| }() | |
| } | |
| } | |
| if !canUseConnectEx(fd.net) { | |
| err := connectFunc(fd.pfd.Sysfd, ra) | |
| return nil, os.NewSyscallError("connect", err) | |
| } | |
| // ConnectEx windows API requires an unconnected, previously bound socket. | |
| if la == nil { | |
| switch ra.(type) { | |
| case *syscall.SockaddrInet4: | |
| la = &syscall.SockaddrInet4{} | |
| case *syscall.SockaddrInet6: | |
| la = &syscall.SockaddrInet6{} | |
| default: | |
| panic("unexpected type in connect") | |
| } | |
| if err := syscall.Bind(fd.pfd.Sysfd, la); err != nil { | |
| return nil, os.NewSyscallError("bind", err) | |
| } | |
| } | |
| var isloopback bool | |
| switch ra := ra.(type) { | |
| case *syscall.SockaddrInet4: | |
| isloopback = ra.Addr[0] == 127 | |
| case *syscall.SockaddrInet6: | |
| isloopback = ra.Addr == [16]byte(IPv6loopback) | |
| default: | |
| panic("unexpected type in connect") | |
| } | |
| if isloopback { | |
| // This makes ConnectEx() fails faster if the target port on the localhost | |
| // is not reachable, instead of waiting for 2s. | |
| params := windows.TCP_INITIAL_RTO_PARAMETERS{ | |
| Rtt: windows.TCP_INITIAL_RTO_UNSPECIFIED_RTT, // use the default or overridden by the Administrator | |
| MaxSynRetransmissions: 1, // minimum possible value before Windows 10.0.16299 | |
| } | |
| if windows.SupportTCPInitialRTONoSYNRetransmissions() { | |
| // In Windows 10.0.16299 TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS makes ConnectEx() fails instantly. | |
| params.MaxSynRetransmissions = windows.TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS | |
| } | |
| var out uint32 | |
| // Don't abort the connection if WSAIoctl fails, as it is only an optimization. | |
| // If it fails reliably, we expect TestDialClosedPortFailFast to detect it. | |
| _ = fd.pfd.WSAIoctl(windows.SIO_TCP_INITIAL_RTO, (*byte)(unsafe.Pointer(¶ms)), uint32(unsafe.Sizeof(params)), nil, 0, &out, nil, 0) | |
| } | |
| // Call ConnectEx API. | |
| if err := fd.pfd.ConnectEx(ra); err != nil { | |
| select { | |
| case <-ctx.Done(): | |
| return nil, mapErr(ctx.Err()) | |
| default: | |
| if _, ok := err.(syscall.Errno); ok { | |
| err = os.NewSyscallError("connectex", err) | |
| } | |
| return nil, err | |
| } | |
| } | |
| // Refresh socket properties. | |
| return nil, os.NewSyscallError("setsockopt", syscall.Setsockopt(fd.pfd.Sysfd, syscall.SOL_SOCKET, syscall.SO_UPDATE_CONNECT_CONTEXT, (*byte)(unsafe.Pointer(&fd.pfd.Sysfd)), int32(unsafe.Sizeof(fd.pfd.Sysfd)))) | |
| } | |
| func (c *conn) writeBuffers(v *Buffers) (int64, error) { | |
| if !c.ok() { | |
| return 0, syscall.EINVAL | |
| } | |
| n, err := c.fd.writeBuffers(v) | |
| if err != nil { | |
| return n, &OpError{Op: "wsasend", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} | |
| } | |
| return n, nil | |
| } | |
| func (fd *netFD) writeBuffers(buf *Buffers) (int64, error) { | |
| n, err := fd.pfd.Writev((*[][]byte)(buf)) | |
| runtime.KeepAlive(fd) | |
| return n, wrapSyscallError("wsasend", err) | |
| } | |
| func (fd *netFD) accept() (*netFD, error) { | |
| s, rawsa, rsan, errcall, err := fd.pfd.Accept(func() (syscall.Handle, error) { | |
| return sysSocket(fd.family, fd.sotype, 0) | |
| }) | |
| if err != nil { | |
| if errcall != "" { | |
| err = wrapSyscallError(errcall, err) | |
| } | |
| return nil, err | |
| } | |
| // Associate our new socket with IOCP. | |
| netfd := newFD(s, fd.family, fd.sotype, fd.net) | |
| if err := netfd.init(); err != nil { | |
| netfd.Close() | |
| return nil, err | |
| } | |
| // Get local and peer addr out of AcceptEx buffer. | |
| var lrsa, rrsa *syscall.RawSockaddrAny | |
| var llen, rlen int32 | |
| syscall.GetAcceptExSockaddrs((*byte)(unsafe.Pointer(&rawsa[0])), | |
| 0, rsan, rsan, &lrsa, &llen, &rrsa, &rlen) | |
| lsa, _ := lrsa.Sockaddr() | |
| rsa, _ := rrsa.Sockaddr() | |
| netfd.setAddr(netfd.addrFunc()(lsa), netfd.addrFunc()(rsa)) | |
| return netfd, nil | |
| } | |
| // Defined in os package. | |
| func newWindowsFile(h syscall.Handle, name string) *os.File | |
| func (fd *netFD) dup() (*os.File, error) { | |
| // Disassociate the IOCP from the socket, | |
| // it is not safe to share a duplicated handle | |
| // that is associated with IOCP. | |
| if err := fd.pfd.DisassociateIOCP(); err != nil { | |
| return nil, err | |
| } | |
| var h syscall.Handle | |
| var syserr error | |
| err := fd.pfd.RawControl(func(fd uintptr) { | |
| h, syserr = dupSocket(syscall.Handle(fd)) | |
| }) | |
| if err == nil { | |
| err = syserr | |
| } | |
| if err != nil { | |
| return nil, err | |
| } | |
| // All WSASocket calls must be match with a syscall.Closesocket call, | |
| // but os.NewFile calls syscall.CloseHandle instead. We need to use | |
| // a hidden function so that the returned file is aware of this fact. | |
| return newWindowsFile(h, fd.name()), nil | |
| } |