--- /dev/null
+On Windows, the [TCPConn.File], [UDPConn.File], [UnixConn.File],
+[IPConn.File], [TCPListener.File], and [UnixListener.File]
+methods are now supported.
\ No newline at end of file
--- /dev/null
+On Windows, the [FileConn], [FilePacketConn], [FileListener]
+functions are now supported.
func WSASendtoInet6(s syscall.Handle, bufs *syscall.WSABuf, bufcnt uint32, sent *uint32, flags uint32, to *syscall.SockaddrInet6, overlapped *syscall.Overlapped, croutine *byte) (err error)
const (
+ SO_TYPE = 0x1008
SIO_TCP_INITIAL_RTO = syscall.IOC_IN | syscall.IOC_VENDOR | 17
TCP_INITIAL_RTO_UNSPECIFIED_RTT = ^uint16(0)
TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS = ^uint8(1)
}
//sys WSASocket(af int32, typ int32, protocol int32, protinfo *syscall.WSAProtocolInfo, group uint32, flags uint32) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = ws2_32.WSASocketW
+//sys WSADuplicateSocket(s syscall.Handle, processID uint32, info *syscall.WSAProtocolInfo) (err error) [failretval!=0] = ws2_32.WSADuplicateSocketW
//sys WSAGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
func loadWSASendRecvMsg() error {
procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock")
procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock")
procGetProfilesDirectoryW = moduserenv.NewProc("GetProfilesDirectoryW")
+ procWSADuplicateSocketW = modws2_32.NewProc("WSADuplicateSocketW")
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
procWSASocketW = modws2_32.NewProc("WSASocketW")
)
return
}
+func WSADuplicateSocket(s syscall.Handle, processID uint32, info *syscall.WSAProtocolInfo) (err error) {
+ r1, _, e1 := syscall.Syscall(procWSADuplicateSocketW.Addr(), 3, uintptr(s), uintptr(processID), uintptr(unsafe.Pointer(info)))
+ if r1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
func WSAGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
var _p0 uint32
if wait {
}
func TestFileError(t *testing.T) {
- switch runtime.GOOS {
- case "windows":
- t.Skipf("not supported on %s", runtime.GOOS)
- }
-
f, err := os.CreateTemp("", "go-nettest")
if err != nil {
t.Fatal(err)
raddr Addr
}
+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) setAddr(laddr, raddr Addr) {
fd.laddr = laddr
fd.raddr = raddr
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,
return netfd, nil
}
-// Unimplemented functions.
-
func (fd *netFD) dup() (*os.File, error) {
- // TODO: Implement this, perhaps using internal/poll.DupCloseOnExec.
- return nil, syscall.EWINDOWS
+ // 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
+ }
+ return os.NewFile(uintptr(h), fd.name()), nil
}
import "os"
-// BUG(mikio): On JS and Windows, the FileConn, FileListener and
+// BUG(mikio): On JS, the FileConn, FileListener and
// FilePacketConn functions are not implemented.
type fileAddr string
--- /dev/null
+// Copyright 2025 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 unix || windows
+
+package net
+
+import (
+ "internal/poll"
+ "os"
+ "syscall"
+)
+
+func newFileFD(f *os.File) (*netFD, error) {
+ s, err := dupFileSocket(f)
+ if err != nil {
+ return nil, err
+ }
+ family := syscall.AF_UNSPEC
+ sotype, err := syscall.GetsockoptInt(s, syscall.SOL_SOCKET, _SO_TYPE)
+ if err != nil {
+ poll.CloseFunc(s)
+ return nil, os.NewSyscallError("getsockopt", err)
+ }
+ lsa, _ := syscall.Getsockname(s)
+ rsa, _ := syscall.Getpeername(s)
+ switch lsa.(type) {
+ case *syscall.SockaddrInet4:
+ family = syscall.AF_INET
+ case *syscall.SockaddrInet6:
+ family = syscall.AF_INET6
+ case *syscall.SockaddrUnix:
+ family = syscall.AF_UNIX
+ default:
+ poll.CloseFunc(s)
+ return nil, syscall.EPROTONOSUPPORT
+ }
+ fd, err := newFD(s, family, sotype, "")
+ if err != nil {
+ poll.CloseFunc(s)
+ return nil, err
+ }
+ laddr := fd.addrFunc()(lsa)
+ raddr := fd.addrFunc()(rsa)
+ fd.net = laddr.Network()
+ if err := fd.init(); err != nil {
+ fd.Close()
+ return nil, err
+ }
+ fd.setAddr(laddr, raddr)
+ return fd, nil
+}
+
+func fileConn(f *os.File) (Conn, error) {
+ fd, err := newFileFD(f)
+ if err != nil {
+ return nil, err
+ }
+ switch fd.laddr.(type) {
+ case *TCPAddr:
+ return newTCPConn(fd, defaultTCPKeepAliveIdle, KeepAliveConfig{}, testPreHookSetKeepAlive, testHookSetKeepAlive), nil
+ case *UDPAddr:
+ return newUDPConn(fd), nil
+ case *IPAddr:
+ return newIPConn(fd), nil
+ case *UnixAddr:
+ return newUnixConn(fd), nil
+ }
+ fd.Close()
+ return nil, syscall.EINVAL
+}
+
+func fileListener(f *os.File) (Listener, error) {
+ fd, err := newFileFD(f)
+ if err != nil {
+ return nil, err
+ }
+ switch laddr := fd.laddr.(type) {
+ case *TCPAddr:
+ return &TCPListener{fd: fd}, nil
+ case *UnixAddr:
+ return &UnixListener{fd: fd, path: laddr.Name, unlink: false}, nil
+ }
+ fd.Close()
+ return nil, syscall.EINVAL
+}
+
+func filePacketConn(f *os.File) (PacketConn, error) {
+ fd, err := newFileFD(f)
+ if err != nil {
+ return nil, err
+ }
+ switch fd.laddr.(type) {
+ case *UDPAddr:
+ return newUDPConn(fd), nil
+ case *IPAddr:
+ return newIPConn(fd), nil
+ case *UnixAddr:
+ return newUnixConn(fd), nil
+ }
+ fd.Close()
+ return nil, syscall.EINVAL
+}
func TestFileConn(t *testing.T) {
switch runtime.GOOS {
- case "plan9", "windows", "js", "wasip1":
+ case "plan9", "js", "wasip1":
t.Skipf("not supported on %s", runtime.GOOS)
}
func TestFileListener(t *testing.T) {
switch runtime.GOOS {
- case "plan9", "windows", "js", "wasip1":
+ case "plan9", "js", "wasip1":
t.Skipf("not supported on %s", runtime.GOOS)
}
func TestFilePacketConn(t *testing.T) {
switch runtime.GOOS {
- case "plan9", "windows", "js", "wasip1":
+ case "plan9", "js", "wasip1":
t.Skipf("not supported on %s", runtime.GOOS)
}
// Issue 24483.
func TestFileCloseRace(t *testing.T) {
switch runtime.GOOS {
- case "plan9", "windows", "js", "wasip1":
+ case "plan9", "js", "wasip1":
t.Skipf("not supported on %s", runtime.GOOS)
}
if !testableNetwork("tcp") {
"syscall"
)
-func dupSocket(f *os.File) (int, error) {
+const _SO_TYPE = syscall.SO_TYPE
+
+func dupFileSocket(f *os.File) (int, error) {
s, call, err := poll.DupCloseOnExec(int(f.Fd()))
if err != nil {
if call != "" {
}
return s, nil
}
-
-func newFileFD(f *os.File) (*netFD, error) {
- s, err := dupSocket(f)
- if err != nil {
- return nil, err
- }
- family := syscall.AF_UNSPEC
- sotype, err := syscall.GetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_TYPE)
- if err != nil {
- poll.CloseFunc(s)
- return nil, os.NewSyscallError("getsockopt", err)
- }
- lsa, _ := syscall.Getsockname(s)
- rsa, _ := syscall.Getpeername(s)
- switch lsa.(type) {
- case *syscall.SockaddrInet4:
- family = syscall.AF_INET
- case *syscall.SockaddrInet6:
- family = syscall.AF_INET6
- case *syscall.SockaddrUnix:
- family = syscall.AF_UNIX
- default:
- poll.CloseFunc(s)
- return nil, syscall.EPROTONOSUPPORT
- }
- fd, err := newFD(s, family, sotype, "")
- if err != nil {
- poll.CloseFunc(s)
- return nil, err
- }
- laddr := fd.addrFunc()(lsa)
- raddr := fd.addrFunc()(rsa)
- fd.net = laddr.Network()
- if err := fd.init(); err != nil {
- fd.Close()
- return nil, err
- }
- fd.setAddr(laddr, raddr)
- return fd, nil
-}
-
-func fileConn(f *os.File) (Conn, error) {
- fd, err := newFileFD(f)
- if err != nil {
- return nil, err
- }
- switch fd.laddr.(type) {
- case *TCPAddr:
- return newTCPConn(fd, defaultTCPKeepAliveIdle, KeepAliveConfig{}, testPreHookSetKeepAlive, testHookSetKeepAlive), nil
- case *UDPAddr:
- return newUDPConn(fd), nil
- case *IPAddr:
- return newIPConn(fd), nil
- case *UnixAddr:
- return newUnixConn(fd), nil
- }
- fd.Close()
- return nil, syscall.EINVAL
-}
-
-func fileListener(f *os.File) (Listener, error) {
- fd, err := newFileFD(f)
- if err != nil {
- return nil, err
- }
- switch laddr := fd.laddr.(type) {
- case *TCPAddr:
- return &TCPListener{fd: fd}, nil
- case *UnixAddr:
- return &UnixListener{fd: fd, path: laddr.Name, unlink: false}, nil
- }
- fd.Close()
- return nil, syscall.EINVAL
-}
-
-func filePacketConn(f *os.File) (PacketConn, error) {
- fd, err := newFileFD(f)
- if err != nil {
- return nil, err
- }
- switch fd.laddr.(type) {
- case *UDPAddr:
- return newUDPConn(fd), nil
- case *IPAddr:
- return newIPConn(fd), nil
- case *UnixAddr:
- return newUnixConn(fd), nil
- }
- fd.Close()
- return nil, syscall.EINVAL
-}
package net
import (
+ "internal/syscall/windows"
"os"
"syscall"
)
-func fileConn(f *os.File) (Conn, error) {
- // TODO: Implement this
- return nil, syscall.EWINDOWS
-}
+const _SO_TYPE = windows.SO_TYPE
-func fileListener(f *os.File) (Listener, error) {
- // TODO: Implement this
- return nil, syscall.EWINDOWS
+func dupSocket(h syscall.Handle) (syscall.Handle, error) {
+ var info syscall.WSAProtocolInfo
+ err := windows.WSADuplicateSocket(h, uint32(syscall.Getpid()), &info)
+ if err != nil {
+ return 0, err
+ }
+ return windows.WSASocket(-1, -1, -1, &info, 0, windows.WSA_FLAG_OVERLAPPED|windows.WSA_FLAG_NO_HANDLE_INHERIT)
}
-func filePacketConn(f *os.File) (PacketConn, error) {
- // TODO: Implement this
- return nil, syscall.EWINDOWS
+func dupFileSocket(f *os.File) (syscall.Handle, error) {
+ // The resulting handle should not be associated to an IOCP, else the IO operations
+ // will block an OS thread, and that's not what net package users expect.
+ h, err := dupSocket(syscall.Handle(f.Fd()))
+ if err != nil {
+ return 0, err
+ }
+ return h, nil
}
package net
+import "os/exec"
+
func installTestHooks() {}
func uninstallTestHooks() {}
func enableSocketConnect() {}
func disableSocketConnect(network string) {}
+
+func addCmdInheritedHandle(cmd *exec.Cmd, fd uintptr) {}
package net
-import "internal/poll"
+import (
+ "internal/poll"
+ "os/exec"
+)
var (
// Placeholders for saving original socket system calls.
poll.CloseFunc(s)
}
}
+
+func addCmdInheritedHandle(cmd *exec.Cmd, fd uintptr) {}
package net
+import "os/exec"
+
func installTestHooks() {}
func uninstallTestHooks() {}
func forceCloseSockets() {}
+
+func addCmdInheritedHandle(cmd *exec.Cmd, fd uintptr) {}
package net
-import "internal/poll"
+import (
+ "internal/poll"
+ "os/exec"
+ "syscall"
+)
var (
// Placeholders for saving original socket system calls.
poll.CloseFunc(s)
}
}
+
+func addCmdInheritedHandle(cmd *exec.Cmd, fd uintptr) {
+ // Inherited handles are not inherited by default in Windows.
+ // We need to set the handle inheritance flag explicitly.
+ // See https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa#parameters
+ // for more details.
+ if cmd.SysProcAttr == nil {
+ cmd.SysProcAttr = &syscall.SysProcAttr{}
+ }
+ cmd.SysProcAttr.AdditionalInheritedHandles = append(cmd.SysProcAttr.AdditionalInheritedHandles, syscall.Handle(fd))
+}
func spawnTestSocketPair(t testing.TB, net string) (client, server Conn) {
t.Helper()
+ if !testableNetwork(net) {
+ t.Skipf("network %q not supported", net)
+ }
+
ln := newLocalListener(t, net)
defer ln.Close()
var cerr, serr error
func startTestSocketPeer(t testing.TB, conn Conn, op string, chunkSize, totalSize int) (func(t testing.TB), error) {
t.Helper()
-
- if runtime.GOOS == "windows" {
- // TODO(panjf2000): Windows has not yet implemented FileConn,
- // remove this when it's implemented in https://go.dev/issues/9503.
- t.Fatalf("startTestSocketPeer is not supported on %s", runtime.GOOS)
- }
-
f, err := conn.(interface{ File() (*os.File, error) }).File()
if err != nil {
return nil, err
"GO_NET_TEST_TRANSFER_TOTAL_SIZE=" + strconv.Itoa(totalSize),
"TMPDIR=" + os.Getenv("TMPDIR"),
}
- cmd.ExtraFiles = append(cmd.ExtraFiles, f)
+ if runtime.GOOS == "windows" {
+ // Windows doesn't support ExtraFiles
+ fd := f.Fd()
+ cmd.Env = append(cmd.Env, "GO_NET_TEST_TRANSFER_FD="+strconv.FormatUint(uint64(fd), 10))
+ addCmdInheritedHandle(cmd, fd)
+ } else {
+ cmd.ExtraFiles = append(cmd.ExtraFiles, f)
+ }
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
defer os.Exit(0)
- f := os.NewFile(uintptr(3), "splice-test-conn")
+ var fd uintptr
+ if runtime.GOOS == "windows" {
+ v, err := strconv.ParseUint(os.Getenv("GO_NET_TEST_TRANSFER_FD"), 10, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fd = uintptr(v)
+ } else {
+ fd = uintptr(3)
+ }
+ f := os.NewFile(fd, "splice-test-conn")
defer f.Close()
conn, err := FileConn(f)
// The returned os.File's file descriptor is different from the connection's.
// Attempting to change properties of the original using this duplicate
// may or may not have the desired effect.
+//
+// On Windows, the returned os.File's file descriptor is not usable
+// on other processes.
func (c *conn) File() (f *os.File, err error) {
f, err = c.fd.dup()
if err != nil {
// The returned os.File's file descriptor is different from the
// connection's. Attempting to change properties of the original
// using this duplicate may or may not have the desired effect.
+//
+// On Windows, the returned os.File's file descriptor is not
+// usable on other processes.
func (l *TCPListener) File() (f *os.File, err error) {
if !l.ok() {
return nil, syscall.EINVAL
// The returned [os.File]'s file descriptor is different from the
// connection's. Attempting to change properties of the original
// using this duplicate may or may not have the desired effect.
+//
+// On Windows, the returned os.File's file descriptor is not
+// usable on other processes.
func (l *UnixListener) File() (f *os.File, err error) {
if !l.ok() {
return nil, syscall.EINVAL
// FileListener should not.
t.Run("FileListener", func(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip("skipping: FileListener not implemented on windows")
- }
l := listen(t)
f, _ := l.File()
l1, _ := FileListener(f)
})
t.Run("FileListener/SetUnlinkOnClose(true)", func(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip("skipping: FileListener not implemented on windows")
- }
l := listen(t)
f, _ := l.File()
l1, _ := FileListener(f)
})
t.Run("FileListener/SetUnlinkOnClose(false)", func(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip("skipping: FileListener not implemented on windows")
- }
l := listen(t)
f, _ := l.File()
l1, _ := FileListener(f)
return newFile(h, name, "console", false)
}
+var wsaLoaded atomic.Bool
+
+// isWSALoaded returns true if the ws2_32.dll module is loaded.
+func isWSALoaded() bool {
+ // ws2_32.dll may be delay loaded, we can only short-circuit
+ // if we know it is loaded.
+ if wsaLoaded.Load() {
+ return true
+ }
+ var ws2_32_dll = [...]uint16{'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0}
+ _, err := windows.GetModuleHandle(unsafe.SliceData(ws2_32_dll[:]))
+ wsaLoaded.Store(err == nil)
+ return err == nil
+}
+
// newFileFromNewFile is called by [NewFile].
func newFileFromNewFile(fd uintptr, name string) *File {
h := syscall.Handle(fd)
if h == syscall.InvalidHandle {
return nil
}
+ kind := "file"
+ var sotype int
+ if t, err := syscall.GetFileType(h); err == nil && t == syscall.FILE_TYPE_PIPE {
+ kind = "pipe"
+ // Windows reports sockets as FILE_TYPE_PIPE.
+ // We need to call getsockopt and check the socket type to distinguish between sockets and pipes.
+ // If the call fails, we assume it's a pipe.
+ // Avoid calling getsockopt if the WSA module is not loaded, it is a heavy dependency
+ // and sockets can only be created using that module.
+ if isWSALoaded() {
+ if sotype, err = syscall.GetsockoptInt(h, syscall.SOL_SOCKET, windows.SO_TYPE); err == nil {
+ kind = "net"
+ }
+ }
+ }
nonBlocking, _ := windows.IsNonblock(syscall.Handle(fd))
- return newFile(h, name, "file", nonBlocking)
+ f := newFile(h, name, kind, nonBlocking)
+ if kind == "net" {
+ f.pfd.IsStream = sotype == syscall.SOCK_STREAM
+ f.pfd.ZeroReadIsEOF = sotype != syscall.SOCK_DGRAM && sotype != syscall.SOCK_RAW
+ }
+ return f
}
func epipecheck(file *File, e error) {