github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/sysfs/sock_windows.go (about) 1 //go:build windows 2 3 package sysfs 4 5 import ( 6 "net" 7 "syscall" 8 "unsafe" 9 10 "github.com/wasilibs/wazerox/experimental/sys" 11 "github.com/wasilibs/wazerox/internal/fsapi" 12 socketapi "github.com/wasilibs/wazerox/internal/sock" 13 ) 14 15 const ( 16 // MSG_PEEK is the flag PEEK for syscall.Recvfrom on Windows. 17 // This constant is not exported on this platform. 18 MSG_PEEK = 0x2 19 // _FIONBIO is the flag to set the O_NONBLOCK flag on socket handles using ioctlsocket. 20 _FIONBIO = 0x8004667e 21 ) 22 23 var ( 24 // modws2_32 is WinSock. 25 modws2_32 = syscall.NewLazyDLL("ws2_32.dll") 26 // procrecvfrom exposes recvfrom from WinSock. 27 procrecvfrom = modws2_32.NewProc("recvfrom") 28 // procioctlsocket exposes ioctlsocket from WinSock. 29 procioctlsocket = modws2_32.NewProc("ioctlsocket") 30 ) 31 32 // recvfrom exposes the underlying syscall in Windows. 33 // 34 // Note: since we are only using this to expose MSG_PEEK, 35 // we do not need really need all the parameters that are actually 36 // allowed in WinSock. 37 // We ignore `from *sockaddr` and `fromlen *int`. 38 func recvfrom(s syscall.Handle, buf []byte, flags int32) (n int, errno sys.Errno) { 39 var _p0 *byte 40 if len(buf) > 0 { 41 _p0 = &buf[0] 42 } 43 r0, _, e1 := syscall.SyscallN( 44 procrecvfrom.Addr(), 45 uintptr(s), 46 uintptr(unsafe.Pointer(_p0)), 47 uintptr(len(buf)), 48 uintptr(flags), 49 0, // from *sockaddr (optional) 50 0) // fromlen *int (optional) 51 return int(r0), sys.UnwrapOSError(e1) 52 } 53 54 func setNonblockSocket(fd syscall.Handle, enabled bool) sys.Errno { 55 opt := uint64(0) 56 if enabled { 57 opt = 1 58 } 59 // ioctlsocket(fd, FIONBIO, &opt) 60 _, _, errno := syscall.SyscallN( 61 procioctlsocket.Addr(), 62 uintptr(fd), 63 uintptr(_FIONBIO), 64 uintptr(unsafe.Pointer(&opt))) 65 return sys.UnwrapOSError(errno) 66 } 67 68 // syscallConnControl extracts a syscall.RawConn from the given syscall.Conn and applies 69 // the given fn to a file descriptor, returning an integer or a nonzero syscall.Errno on failure. 70 // 71 // syscallConnControl streamlines the pattern of extracting the syscall.Rawconn, 72 // invoking its syscall.RawConn.Control method, then handling properly the errors that may occur 73 // within fn or returned by syscall.RawConn.Control itself. 74 func syscallConnControl(conn syscall.Conn, fn func(fd uintptr) (int, sys.Errno)) (n int, errno sys.Errno) { 75 syscallConn, err := conn.SyscallConn() 76 if err != nil { 77 return 0, sys.UnwrapOSError(err) 78 } 79 // Prioritize the inner errno over Control 80 if controlErr := syscallConn.Control(func(fd uintptr) { 81 n, errno = fn(fd) 82 }); errno == 0 { 83 errno = sys.UnwrapOSError(controlErr) 84 } 85 return 86 } 87 88 func _pollSock(conn syscall.Conn, flag fsapi.Pflag, timeoutMillis int32) (bool, sys.Errno) { 89 if flag != fsapi.POLLIN { 90 return false, sys.ENOTSUP 91 } 92 n, errno := syscallConnControl(conn, func(fd uintptr) (int, sys.Errno) { 93 return _poll([]pollFd{newPollFd(fd, _POLLIN, 0)}, timeoutMillis) 94 }) 95 return n > 0, errno 96 } 97 98 // newTCPListenerFile is a constructor for a socketapi.TCPSock. 99 // 100 // Note: currently the Windows implementation of socketapi.TCPSock 101 // returns a winTcpListenerFile, which is a specialized TCPSock 102 // that delegates to a .net.TCPListener. 103 // The current strategy is to delegate most behavior to the Go 104 // standard library, instead of invoke syscalls/Win32 APIs 105 // because they are sensibly different from Unix's. 106 func newTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { 107 return &winTcpListenerFile{tl: tl} 108 } 109 110 var _ socketapi.TCPSock = (*winTcpListenerFile)(nil) 111 112 type winTcpListenerFile struct { 113 baseSockFile 114 115 tl *net.TCPListener 116 closed bool 117 nonblock bool 118 } 119 120 // Accept implements the same method as documented on socketapi.TCPSock 121 func (f *winTcpListenerFile) Accept() (socketapi.TCPConn, sys.Errno) { 122 // Ensure we have an incoming connection using winsock_select, otherwise return immediately. 123 if f.nonblock { 124 if ready, errno := _pollSock(f.tl, fsapi.POLLIN, 0); !ready || errno != 0 { 125 return nil, sys.EAGAIN 126 } 127 } 128 129 // Accept normally blocks goroutines, but we 130 // made sure that we have an incoming connection, 131 // so we should be safe. 132 if conn, err := f.tl.Accept(); err != nil { 133 return nil, sys.UnwrapOSError(err) 134 } else { 135 return newTcpConn(conn.(*net.TCPConn)), 0 136 } 137 } 138 139 // Close implements the same method as documented on sys.File 140 func (f *winTcpListenerFile) Close() sys.Errno { 141 if !f.closed { 142 return sys.UnwrapOSError(f.tl.Close()) 143 } 144 return 0 145 } 146 147 // Addr is exposed for testing. 148 func (f *winTcpListenerFile) Addr() *net.TCPAddr { 149 return f.tl.Addr().(*net.TCPAddr) 150 } 151 152 // IsNonblock implements the same method as documented on fsapi.File 153 func (f *winTcpListenerFile) IsNonblock() bool { 154 return f.nonblock 155 } 156 157 // SetNonblock implements the same method as documented on fsapi.File 158 func (f *winTcpListenerFile) SetNonblock(enabled bool) sys.Errno { 159 f.nonblock = enabled 160 _, errno := syscallConnControl(f.tl, func(fd uintptr) (int, sys.Errno) { 161 return 0, setNonblockSocket(syscall.Handle(fd), enabled) 162 }) 163 return errno 164 } 165 166 // Poll implements the same method as documented on fsapi.File 167 func (f *winTcpListenerFile) Poll(fsapi.Pflag, int32) (ready bool, errno sys.Errno) { 168 return false, sys.ENOSYS 169 } 170 171 var _ socketapi.TCPConn = (*winTcpConnFile)(nil) 172 173 // winTcpConnFile is a blocking connection. 174 // 175 // It is a wrapper for an underlying net.TCPConn. 176 type winTcpConnFile struct { 177 baseSockFile 178 179 tc *net.TCPConn 180 181 // nonblock is true when the underlying connection is flagged as non-blocking. 182 // This ensures that reads and writes return sys.EAGAIN without blocking the caller. 183 nonblock bool 184 // closed is true when closed was called. This ensures proper sys.EBADF 185 closed bool 186 } 187 188 func newTcpConn(tc *net.TCPConn) socketapi.TCPConn { 189 return &winTcpConnFile{tc: tc} 190 } 191 192 // Read implements the same method as documented on sys.File 193 func (f *winTcpConnFile) Read(buf []byte) (n int, errno sys.Errno) { 194 if len(buf) == 0 { 195 return 0, 0 // Short-circuit 0-len reads. 196 } 197 if nonBlockingFileReadSupported && f.IsNonblock() { 198 n, errno = syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) { 199 return readSocket(syscall.Handle(fd), buf) 200 }) 201 } else { 202 n, errno = read(f.tc, buf) 203 } 204 if errno != 0 { 205 // Defer validation overhead until we've already had an error. 206 errno = fileError(f, f.closed, errno) 207 } 208 return 209 } 210 211 // Write implements the same method as documented on sys.File 212 func (f *winTcpConnFile) Write(buf []byte) (n int, errno sys.Errno) { 213 if nonBlockingFileWriteSupported && f.IsNonblock() { 214 return syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) { 215 return writeSocket(fd, buf) 216 }) 217 } else { 218 n, errno = write(f.tc, buf) 219 } 220 if errno != 0 { 221 // Defer validation overhead until we've already had an error. 222 errno = fileError(f, f.closed, errno) 223 } 224 return 225 } 226 227 // Recvfrom implements the same method as documented on socketapi.TCPConn 228 func (f *winTcpConnFile) Recvfrom(p []byte, flags int) (n int, errno sys.Errno) { 229 if flags != MSG_PEEK { 230 errno = sys.EINVAL 231 return 232 } 233 return syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) { 234 return recvfrom(syscall.Handle(fd), p, MSG_PEEK) 235 }) 236 } 237 238 // Shutdown implements the same method as documented on sys.Conn 239 func (f *winTcpConnFile) Shutdown(how int) sys.Errno { 240 // FIXME: can userland shutdown listeners? 241 var err error 242 switch how { 243 case syscall.SHUT_RD: 244 err = f.tc.CloseRead() 245 case syscall.SHUT_WR: 246 err = f.tc.CloseWrite() 247 case syscall.SHUT_RDWR: 248 return f.close() 249 default: 250 return sys.EINVAL 251 } 252 return sys.UnwrapOSError(err) 253 } 254 255 // Close implements the same method as documented on sys.File 256 func (f *winTcpConnFile) Close() sys.Errno { 257 return f.close() 258 } 259 260 func (f *winTcpConnFile) close() sys.Errno { 261 if f.closed { 262 return 0 263 } 264 f.closed = true 265 return f.Shutdown(syscall.SHUT_RDWR) 266 } 267 268 // IsNonblock implements the same method as documented on fsapi.File 269 func (f *winTcpConnFile) IsNonblock() bool { 270 return f.nonblock 271 } 272 273 // SetNonblock implements the same method as documented on fsapi.File 274 func (f *winTcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) { 275 f.nonblock = true 276 _, errno = syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) { 277 return 0, sys.UnwrapOSError(setNonblockSocket(syscall.Handle(fd), enabled)) 278 }) 279 return 280 } 281 282 // Poll implements the same method as documented on fsapi.File 283 func (f *winTcpConnFile) Poll(fsapi.Pflag, int32) (ready bool, errno sys.Errno) { 284 return false, sys.ENOSYS 285 }