std.Io.Threaded: implement netConnectIp for Windows

This commit is contained in:
Andrew Kelley 2025-10-21 04:30:36 -07:00
parent 67df66c26c
commit 4ed74a9f8a
2 changed files with 116 additions and 159 deletions

View file

@ -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 => {

View file

@ -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,
pub const ConnectError = std.Io.net.IpAddress.ConnectError || std.Io.net.UnixAddress.ConnectError;
/// 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,
/// 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) {