Commit 4ed74a9f8a

Andrew Kelley <andrew@ziglang.org>
2025-10-21 13:30:36
std.Io.Threaded: implement netConnectIp for Windows
1 parent 67df66c
Changed files (2)
lib/std/Io/Threaded.zig
@@ -252,7 +252,7 @@ pub fn io(t: *Threaded) Io {
                 else => netBindIpPosix,
             },
             .netConnectIp = switch (builtin.os.tag) {
-                .windows => @panic("TODO"),
+                .windows => netConnectIpWindows,
                 else => netConnectIpPosix,
             },
             .netConnectUnix = netConnectUnix,
@@ -2810,28 +2810,10 @@ fn netListenIpWindows(
     if (!have_networking) return error.NetworkDown;
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     const family = posixAddressFamily(&address);
-    const mode = posixSocketMode(options.mode);
-    const protocol = posixProtocol(options.protocol);
-
-    const socket_handle = while (true) {
-        try t.checkCancel();
-        const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
-        const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags);
-        if (rc != ws2_32.INVALID_SOCKET) break rc;
-        switch (ws2_32.WSAGetLastError()) {
-            .EINTR => continue,
-            .ECANCELLED, .E_CANCELLED => return error.Canceled,
-            .NOTINITIALISED => {
-                try initializeWsa(t);
-                continue;
-            },
-            .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
-            .EMFILE => return error.ProcessFdQuotaExceeded,
-            .ENOBUFS => return error.SystemResources,
-            .EPROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
-            else => |err| return windows.unexpectedWSAError(err),
-        }
-    };
+    const socket_handle = try openSocketWsa(t, family, .{
+        .mode = options.mode,
+        .protocol = options.protocol,
+    });
     errdefer closeSocketWindows(socket_handle);
 
     if (options.reuse_address)
@@ -2885,24 +2867,7 @@ fn netListenIpWindows(
         }
     }
 
-    while (true) {
-        try t.checkCancel();
-        const rc = ws2_32.getsockname(socket_handle, &storage.any, &addr_len);
-        if (rc != ws2_32.SOCKET_ERROR) break;
-        switch (ws2_32.WSAGetLastError()) {
-            .EINTR => continue,
-            .ECANCELLED, .E_CANCELLED => return error.Canceled,
-            .NOTINITIALISED => {
-                try initializeWsa(t);
-                continue;
-            },
-            .ENETDOWN => return error.NetworkDown,
-            .EFAULT => |err| return wsaErrorBug(err),
-            .ENOTSOCK => |err| return wsaErrorBug(err),
-            .EINVAL => |err| return wsaErrorBug(err),
-            else => |err| return windows.unexpectedWSAError(err),
-        }
-    }
+    try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
 
     return .{
         .socket = .{
@@ -3076,6 +3041,27 @@ fn posixGetSockName(t: *Threaded, socket_fd: posix.fd_t, addr: *posix.sockaddr,
     }
 }
 
+fn wsaGetSockName(t: *Threaded, handle: ws2_32.SOCKET, addr: *ws2_32.sockaddr, addr_len: *i32) !void {
+    while (true) {
+        try t.checkCancel();
+        const rc = ws2_32.getsockname(handle, addr, addr_len);
+        if (rc != ws2_32.SOCKET_ERROR) break;
+        switch (ws2_32.WSAGetLastError()) {
+            .EINTR => continue,
+            .ECANCELLED, .E_CANCELLED => return error.Canceled,
+            .NOTINITIALISED => {
+                try initializeWsa(t);
+                continue;
+            },
+            .ENETDOWN => return error.NetworkDown,
+            .EFAULT => |err| return wsaErrorBug(err),
+            .ENOTSOCK => |err| return wsaErrorBug(err),
+            .EINVAL => |err| return wsaErrorBug(err),
+            else => |err| return windows.unexpectedWSAError(err),
+        }
+    }
+}
+
 fn setSocketOption(t: *Threaded, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void {
     const o: []const u8 = @ptrCast(&option);
     while (true) {
@@ -3139,6 +3125,61 @@ fn netConnectIpPosix(
     } };
 }
 
+fn netConnectIpWindows(
+    userdata: ?*anyopaque,
+    address: *const IpAddress,
+    options: IpAddress.ConnectOptions,
+) IpAddress.ConnectError!net.Stream {
+    if (!have_networking) return error.NetworkDown;
+    if (options.timeout != .none) @panic("TODO");
+    const t: *Threaded = @ptrCast(@alignCast(userdata));
+    const family = posixAddressFamily(address);
+    const socket_handle = try openSocketWsa(t, family, .{
+        .mode = options.mode,
+        .protocol = options.protocol,
+    });
+    errdefer closeSocketWindows(socket_handle);
+
+    var storage: WsaAddress = undefined;
+    var addr_len = addressToWsa(address, &storage);
+
+    while (true) {
+        const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
+        if (rc != ws2_32.SOCKET_ERROR) break;
+        switch (ws2_32.WSAGetLastError()) {
+            .EINTR => continue,
+            .ECANCELLED, .E_CANCELLED => return error.Canceled,
+            .NOTINITIALISED => {
+                try initializeWsa(t);
+                continue;
+            },
+
+            .EADDRNOTAVAIL => return error.AddressUnavailable,
+            .ECONNREFUSED => return error.ConnectionRefused,
+            .ECONNRESET => return error.ConnectionResetByPeer,
+            .ETIMEDOUT => return error.Timeout,
+            .EHOSTUNREACH => return error.HostUnreachable,
+            .ENETUNREACH => return error.NetworkUnreachable,
+            .EFAULT => |err| return wsaErrorBug(err),
+            .EINVAL => |err| return wsaErrorBug(err),
+            .EISCONN => |err| return wsaErrorBug(err),
+            .ENOTSOCK => |err| return wsaErrorBug(err),
+            .EWOULDBLOCK => return error.WouldBlock,
+            .EACCES => return error.AccessDenied,
+            .ENOBUFS => return error.SystemResources,
+            .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
+            else => |err| return windows.unexpectedWSAError(err),
+        }
+    }
+
+    try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
+
+    return .{ .socket = .{
+        .handle = socket_handle,
+        .address = addressFromWsa(&storage),
+    } };
+}
+
 fn netConnectUnix(
     userdata: ?*anyopaque,
     address: *const net.UnixAddress,
@@ -3184,28 +3225,12 @@ fn netBindIpWindows(
     if (!have_networking) return error.NetworkDown;
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     const family = posixAddressFamily(address);
-    const mode = posixSocketMode(options.mode);
-    const protocol = posixProtocol(options.protocol);
-    const socket_handle = while (true) {
-        try t.checkCancel();
-        const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
-        const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags);
-        if (rc != ws2_32.INVALID_SOCKET) break rc;
-        switch (ws2_32.WSAGetLastError()) {
-            .EINTR => continue,
-            .ECANCELLED, .E_CANCELLED => return error.Canceled,
-            .NOTINITIALISED => {
-                try initializeWsa(t);
-                continue;
-            },
-            .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
-            .EMFILE => return error.ProcessFdQuotaExceeded,
-            .ENOBUFS => return error.SystemResources,
-            .EPROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
-            else => |err| return windows.unexpectedWSAError(err),
-        }
-    };
+    const socket_handle = try openSocketWsa(t, family, .{
+        .mode = options.mode,
+        .protocol = options.protocol,
+    });
     errdefer closeSocketWindows(socket_handle);
+
     var storage: WsaAddress = undefined;
     var addr_len = addressToWsa(address, &storage);
 
@@ -3231,24 +3256,7 @@ fn netBindIpWindows(
         }
     }
 
-    while (true) {
-        try t.checkCancel();
-        const rc = ws2_32.getsockname(socket_handle, &storage.any, &addr_len);
-        if (rc != ws2_32.SOCKET_ERROR) break;
-        switch (ws2_32.WSAGetLastError()) {
-            .EINTR => continue,
-            .ECANCELLED, .E_CANCELLED => return error.Canceled,
-            .NOTINITIALISED => {
-                try initializeWsa(t);
-                continue;
-            },
-            .ENETDOWN => return error.NetworkDown,
-            .EFAULT => |err| return wsaErrorBug(err),
-            .ENOTSOCK => |err| return wsaErrorBug(err),
-            .EINVAL => |err| return wsaErrorBug(err),
-            else => |err| return windows.unexpectedWSAError(err),
-        }
-    }
+    try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
 
     return .{
         .handle = socket_handle,
@@ -3317,6 +3325,30 @@ fn openSocketPosix(
     return socket_fd;
 }
 
+fn openSocketWsa(t: *Threaded, family: posix.sa_family_t, options: IpAddress.BindOptions) !ws2_32.SOCKET {
+    const mode = posixSocketMode(options.mode);
+    const protocol = posixProtocol(options.protocol);
+    const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
+    while (true) {
+        try t.checkCancel();
+        const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags);
+        if (rc != ws2_32.INVALID_SOCKET) return rc;
+        switch (ws2_32.WSAGetLastError()) {
+            .EINTR => continue,
+            .ECANCELLED, .E_CANCELLED => return error.Canceled,
+            .NOTINITIALISED => {
+                try initializeWsa(t);
+                continue;
+            },
+            .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
+            .EMFILE => return error.ProcessFdQuotaExceeded,
+            .ENOBUFS => return error.SystemResources,
+            .EPROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
+            else => |err| return windows.unexpectedWSAError(err),
+        }
+    }
+}
+
 fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Server.AcceptError!net.Stream {
     if (!have_networking) return error.NetworkDown;
     const t: *Threaded = @ptrCast(@alignCast(userdata));
@@ -3376,11 +3408,11 @@ fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net
     while (true) {
         try t.checkCancel();
         const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len);
-        if (rc != windows.ws2_32.INVALID_SOCKET) return .{ .socket = .{
+        if (rc != ws2_32.INVALID_SOCKET) return .{ .socket = .{
             .handle = rc,
             .address = addressFromWsa(&storage),
         } };
-        switch (windows.ws2_32.WSAGetLastError()) {
+        switch (ws2_32.WSAGetLastError()) {
             .EINTR => continue,
             .ECANCELLED, .E_CANCELLED => return error.Canceled,
             .NOTINITIALISED => {
lib/std/posix.zig
@@ -3757,86 +3757,11 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
     }
 }
 
-pub const ConnectError = error{
-    /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on  the  socket
-    /// file,  or  search  permission  is  denied  for  one of the directories in the path prefix.
-    /// or
-    /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled  or
-    /// the connection request failed because of a local firewall rule.
-    AccessDenied,
-
-    /// See AccessDenied
-    PermissionDenied,
-
-    /// Local address is already in use.
-    AddressInUse,
-
-    /// (Internet  domain  sockets)  The  socket  referred  to  by sockfd had not previously been bound to an
-    /// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers
-    /// in    the    ephemeral    port    range    are   currently   in   use.    See   the   discussion   of
-    /// /proc/sys/net/ipv4/ip_local_port_range in ip(7).
-    AddressUnavailable,
+pub const ConnectError = std.Io.net.IpAddress.ConnectError || std.Io.net.UnixAddress.ConnectError;
 
-    /// The passed address didn't have the correct address family in its sa_family field.
-    AddressFamilyUnsupported,
-
-    /// Insufficient entries in the routing cache.
-    SystemResources,
-
-    /// A connect() on a stream socket found no one listening on the remote address.
-    ConnectionRefused,
-
-    /// Network is unreachable.
-    NetworkUnreachable,
-
-    /// Timeout  while  attempting  connection.   The server may be too busy to accept new connections.  Note
-    /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
-    Timeout,
-
-    /// This error occurs when no global event loop is configured,
-    /// and connecting to the socket would block.
-    WouldBlock,
-
-    /// The given path for the unix socket does not exist.
-    FileNotFound,
-
-    /// Connection was reset by peer before connect could complete.
-    ConnectionResetByPeer,
-
-    /// Socket is non-blocking and already has a pending connection in progress.
-    ConnectionPending,
-
-    /// Socket was already connected
-    AlreadyConnected,
-} || UnexpectedError;
-
-/// Initiate a connection on a socket.
-/// If `sockfd` is opened in non blocking mode, the function will
-/// return error.WouldBlock when EAGAIN or EINPROGRESS is received.
 pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {
     if (native_os == .windows) {
-        const rc = windows.ws2_32.connect(sock, sock_addr, @intCast(len));
-        if (rc == 0) return;
-        switch (windows.ws2_32.WSAGetLastError()) {
-            .EADDRINUSE => return error.AddressInUse,
-            .EADDRNOTAVAIL => return error.AddressUnavailable,
-            .ECONNREFUSED => return error.ConnectionRefused,
-            .ECONNRESET => return error.ConnectionResetByPeer,
-            .ETIMEDOUT => return error.Timeout,
-            .EHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well?
-            .ENETUNREACH,
-            => return error.NetworkUnreachable,
-            .EFAULT => unreachable,
-            .EINVAL => unreachable,
-            .EISCONN => return error.AlreadyConnected,
-            .ENOTSOCK => unreachable,
-            .EWOULDBLOCK => return error.WouldBlock,
-            .EACCES => unreachable,
-            .ENOBUFS => return error.SystemResources,
-            .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
-            else => |err| return windows.unexpectedWSAError(err),
-        }
-        return;
+        @compileError("use std.Io instead");
     }
 
     while (true) {