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  }