mruby-socket: translate Winsock errors to errno on Windows

Winsock APIs (socket, bind, connect, accept, recv, send, ...) report
errors via WSAGetLastError() and do not set errno, so mrb_sys_fail
on Windows was reading a stale or zero errno. Result: every socket
failure raised SystemCallError with errno 0 ("Success") instead of
the appropriate Errno::* class.

Add mrb_hal_socket_set_errno_from_last_error() to the HAL:
- POSIX: no-op (failed calls already set errno)
- Windows: maps WSAGetLastError() to a POSIX errno via wsa_to_errno()
  with 32 cases covering the common Winsock error codes; unmapped
  codes fall back to EIO. Each case is #ifdef-guarded against older
  MSVC CRTs that lack a particular Exxx.

In src/socket.c, route the 22 socket-API failure sites through a new
sock_sys_fail() helper that calls the HAL translator before
mrb_sys_fail. Also fix mrb_hal_socket_set_nonblock() on Windows,
which was returning -1 without setting errno after ioctlsocket
failure.

POSIX behavior unchanged (verified: TCPSocket connect refused ->
Errno::ECONNREFUSED, bind to privileged port -> Errno::EACCES, bad
sockopt -> Errno::EOPNOTSUPP).

Closes #6819, reported by Asmod4n.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-05-07 10:41:05 +09:00
parent ada9ca7236
commit f267925d45
4 changed files with 171 additions and 22 deletions
+10
View File
@@ -28,6 +28,16 @@ void mrb_hal_socket_init(mrb_state *mrb);
/* Finalize socket subsystem (e.g., WSACleanup on Windows) */
void mrb_hal_socket_final(mrb_state *mrb);
/*
* Error Handling
*/
/* Translate the most recent socket-API error into a POSIX errno value and
* store it in errno. On Windows, this reads WSAGetLastError() and maps it;
* on POSIX, this is a no-op (errno is already set by the failed call).
* Call this immediately after a socket-API failure, before mrb_sys_fail. */
void mrb_hal_socket_set_errno_from_last_error(void);
/*
* Socket Control Operations
*/
@@ -41,6 +41,16 @@ mrb_hal_socket_final(mrb_state *mrb)
/* No cleanup needed for POSIX sockets */
}
/*
* Error Handling
*/
void
mrb_hal_socket_set_errno_from_last_error(void)
{
/* POSIX socket calls already set errno on failure; nothing to do. */
}
/*
* Socket Control Operations
*/
+119
View File
@@ -43,6 +43,124 @@ mrb_hal_socket_final(mrb_state *mrb)
WSACleanup();
}
/*
* Error Handling
*/
/* Map a Winsock error code to a POSIX errno value. Each case is guarded
* with #ifdef so older MSVC CRTs that lack a particular Exxx still build;
* unknown codes fall back to EIO so mrb_sys_fail still produces a non-zero
* SystemCallError rather than reporting "success." */
static int
wsa_to_errno(int wsa_err)
{
switch (wsa_err) {
case 0: return 0;
#ifdef EINTR
case WSAEINTR: return EINTR;
#endif
#ifdef EBADF
case WSAEBADF: return EBADF;
#endif
#ifdef EACCES
case WSAEACCES: return EACCES;
#endif
#ifdef EFAULT
case WSAEFAULT: return EFAULT;
#endif
#ifdef EINVAL
case WSAEINVAL: return EINVAL;
#endif
#ifdef EMFILE
case WSAEMFILE: return EMFILE;
#endif
#ifdef EWOULDBLOCK
case WSAEWOULDBLOCK: return EWOULDBLOCK;
#endif
#ifdef EINPROGRESS
case WSAEINPROGRESS: return EINPROGRESS;
#endif
#ifdef EALREADY
case WSAEALREADY: return EALREADY;
#endif
#ifdef ENOTSOCK
case WSAENOTSOCK: return ENOTSOCK;
#endif
#ifdef EDESTADDRREQ
case WSAEDESTADDRREQ: return EDESTADDRREQ;
#endif
#ifdef EMSGSIZE
case WSAEMSGSIZE: return EMSGSIZE;
#endif
#ifdef EPROTOTYPE
case WSAEPROTOTYPE: return EPROTOTYPE;
#endif
#ifdef ENOPROTOOPT
case WSAENOPROTOOPT: return ENOPROTOOPT;
#endif
#ifdef EPROTONOSUPPORT
case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT;
#endif
#ifdef EOPNOTSUPP
case WSAEOPNOTSUPP: return EOPNOTSUPP;
#endif
#ifdef EAFNOSUPPORT
case WSAEAFNOSUPPORT: return EAFNOSUPPORT;
case WSAEPFNOSUPPORT: return EAFNOSUPPORT;
case WSAESOCKTNOSUPPORT: return EAFNOSUPPORT;
#endif
#ifdef EADDRINUSE
case WSAEADDRINUSE: return EADDRINUSE;
#endif
#ifdef EADDRNOTAVAIL
case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL;
#endif
#ifdef ENETDOWN
case WSAENETDOWN: return ENETDOWN;
#endif
#ifdef ENETUNREACH
case WSAENETUNREACH: return ENETUNREACH;
#endif
#ifdef ENETRESET
case WSAENETRESET: return ENETRESET;
#endif
#ifdef ECONNABORTED
case WSAECONNABORTED: return ECONNABORTED;
#endif
#ifdef ECONNRESET
case WSAECONNRESET: return ECONNRESET;
#endif
#ifdef ENOBUFS
case WSAENOBUFS: return ENOBUFS;
#endif
#ifdef EISCONN
case WSAEISCONN: return EISCONN;
#endif
#ifdef ENOTCONN
case WSAENOTCONN: return ENOTCONN;
#endif
#ifdef ETIMEDOUT
case WSAETIMEDOUT: return ETIMEDOUT;
#endif
#ifdef ECONNREFUSED
case WSAECONNREFUSED: return ECONNREFUSED;
#endif
#ifdef EHOSTUNREACH
case WSAEHOSTUNREACH: return EHOSTUNREACH;
#endif
#ifdef ENAMETOOLONG
case WSAENAMETOOLONG: return ENAMETOOLONG;
#endif
default: return EIO;
}
}
void
mrb_hal_socket_set_errno_from_last_error(void)
{
errno = wsa_to_errno(WSAGetLastError());
}
/*
* Socket Control Operations
*/
@@ -54,6 +172,7 @@ mrb_hal_socket_set_nonblock(mrb_state *mrb, int fd, int nonblock)
u_long mode = nonblock ? 1 : 0;
int result = ioctlsocket(fd, FIONBIO, &mode);
if (result != NO_ERROR) {
mrb_hal_socket_set_errno_from_last_error();
return -1;
}
return 0;
+32 -22
View File
@@ -134,6 +134,16 @@ static inline const char *get_pf_name(int family) {
#define E_SOCKET_ERROR mrb_class_get_id(mrb, MRB_SYM(SocketError))
/* Raise a SystemCallError for the most recent socket-API failure.
* On Windows the HAL translates WSAGetLastError() into errno first;
* on POSIX errno is already set, so this is just mrb_sys_fail. */
static mrb_noreturn void
sock_sys_fail(mrb_state *mrb, const char *mesg)
{
mrb_hal_socket_set_errno_from_last_error();
mrb_sys_fail(mrb, mesg);
}
struct gen_addrinfo_args {
struct RClass *klass;
struct addrinfo *addrinfo;
@@ -295,7 +305,7 @@ sa2addrlist(mrb_state *mrb, const struct sockaddr *sa, socklen_t salen)
port = ntohs(port);
mrb_value host = mrb_str_new_capa(mrb, NI_MAXHOST);
if (getnameinfo(sa, salen, RSTRING_PTR(host), NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == -1)
mrb_sys_fail(mrb, "getnameinfo");
sock_sys_fail(mrb, "getnameinfo");
mrb_str_resize(mrb, host, strlen(RSTRING_PTR(host)));
mrb_value ary = mrb_ary_new_capa(mrb, 4);
@@ -344,7 +354,7 @@ mrb_basicsocket_getpeereid(mrb_state *mrb, mrb_value self)
uid_t euid;
int s = socket_fd(mrb, self);
if (getpeereid(s, &euid, &egid) != 0)
mrb_sys_fail(mrb, "getpeereid");
sock_sys_fail(mrb, "getpeereid");
mrb_value ary = mrb_ary_new_capa(mrb, 2);
mrb_ary_push(mrb, ary, mrb_fixnum_value((mrb_int)euid));
@@ -371,7 +381,7 @@ mrb_basicsocket_getpeername(mrb_state *mrb, mrb_value self)
socklen_t salen = sizeof(ss);
if (getpeername(socket_fd(mrb, self), (struct sockaddr*)&ss, &salen) != 0)
mrb_sys_fail(mrb, "getpeername");
sock_sys_fail(mrb, "getpeername");
return mrb_str_new(mrb, (char*)&ss, salen);
}
@@ -391,7 +401,7 @@ mrb_basicsocket_getsockname(mrb_state *mrb, mrb_value self)
socklen_t salen = sizeof(ss);
if (getsockname(socket_fd(mrb, self), (struct sockaddr*)&ss, &salen) != 0)
mrb_sys_fail(mrb, "getsockname");
sock_sys_fail(mrb, "getsockname");
return mrb_str_new(mrb, (char*)&ss, salen);
}
@@ -636,7 +646,7 @@ mrb_basicsocket_getsockopt(mrb_state *mrb, mrb_value self)
socklen_t optlen = sizeof(opt);
if (getsockopt(s, (int)level, (int)optname, opt, &optlen) == -1)
mrb_sys_fail(mrb, "getsockopt");
sock_sys_fail(mrb, "getsockopt");
mrb_int family = socket_family(s);
mrb_value data = mrb_str_new(mrb, opt, optlen);
mrb_value args[4] = {mrb_fixnum_value(family), mrb_fixnum_value(level), mrb_fixnum_value(optname), data};
@@ -662,7 +672,7 @@ mrb_basicsocket_recv(mrb_state *mrb, mrb_value self)
mrb_value buf = mrb_str_new_capa(mrb, maxlen);
ssize_t n = recv(socket_fd(mrb, self), RSTRING_PTR(buf), (fsize_t)maxlen, (int)flags);
if (n == -1)
mrb_sys_fail(mrb, "recv");
sock_sys_fail(mrb, "recv");
mrb_str_resize(mrb, buf, (mrb_int)n);
return buf;
}
@@ -686,7 +696,7 @@ mrb_basicsocket_recvfrom(mrb_state *mrb, mrb_value self)
mrb_value sa = mrb_str_new_capa(mrb, socklen);
ssize_t n = recvfrom(socket_fd(mrb, self), RSTRING_PTR(buf), (fsize_t)maxlen, (int)flags, (struct sockaddr*)RSTRING_PTR(sa), &socklen);
if (n == -1)
mrb_sys_fail(mrb, "recvfrom");
sock_sys_fail(mrb, "recvfrom");
mrb_str_resize(mrb, buf, (mrb_int)n);
mrb_str_resize(mrb, sa, (mrb_int)socklen);
@@ -721,7 +731,7 @@ mrb_basicsocket_send(mrb_state *mrb, mrb_value self)
n = sendto(socket_fd(mrb, self), RSTRING_PTR(mesg), (fsize_t)RSTRING_LEN(mesg), (int)flags, (const struct sockaddr*)RSTRING_PTR(dest), (fsize_t)RSTRING_LEN(dest));
}
if (n == -1)
mrb_sys_fail(mrb, "send");
sock_sys_fail(mrb, "send");
return mrb_fixnum_value((mrb_int)n);
}
@@ -743,7 +753,7 @@ mrb_basicsocket_setnonblock(mrb_state *mrb, mrb_value self)
int fd = socket_fd(mrb, self);
if (mrb_hal_socket_set_nonblock(mrb, fd, nonblocking) == -1)
mrb_sys_fail(mrb, "set_nonblock");
sock_sys_fail(mrb, "set_nonblock");
return mrb_nil_value();
}
@@ -801,7 +811,7 @@ mrb_basicsocket_setsockopt(mrb_state *mrb, mrb_value self)
int s = socket_fd(mrb, self);
if (setsockopt(s, (int)level, (int)optname, RSTRING_PTR(optval), (socklen_t)RSTRING_LEN(optval)) == -1)
mrb_sys_fail(mrb, "setsockopt");
sock_sys_fail(mrb, "setsockopt");
return mrb_fixnum_value(0);
}
@@ -822,7 +832,7 @@ mrb_basicsocket_shutdown(mrb_state *mrb, mrb_value self)
mrb_get_args(mrb, "|i", &how);
if (shutdown(socket_fd(mrb, self), (int)how) != 0)
mrb_sys_fail(mrb, "shutdown");
sock_sys_fail(mrb, "shutdown");
return mrb_fixnum_value(0);
}
@@ -935,7 +945,7 @@ mrb_ipsocket_recvfrom(mrb_state *mrb, mrb_value self)
ssize_t n = recvfrom(fd, RSTRING_PTR(buf), (fsize_t)maxlen, (int)flags,
(struct sockaddr*)&ss, &socklen);
if (n == -1) {
mrb_sys_fail(mrb, "recvfrom");
sock_sys_fail(mrb, "recvfrom");
}
mrb_str_resize(mrb, buf, (mrb_int)n);
@@ -965,7 +975,7 @@ mrb_socket_gethostname(mrb_state *mrb, mrb_value cls)
mrb_value buf = mrb_str_new_capa(mrb, (mrb_int)bufsize);
if (gethostname(RSTRING_PTR(buf), (fsize_t)bufsize) != 0)
mrb_sys_fail(mrb, "gethostname");
sock_sys_fail(mrb, "gethostname");
mrb_str_resize(mrb, buf, (mrb_int)strlen(RSTRING_PTR(buf)));
return buf;
}
@@ -985,7 +995,7 @@ mrb_socket_accept(mrb_state *mrb, mrb_value klass)
mrb_get_args(mrb, "i", &s0);
int s1 = (int)accept(s0, NULL, NULL);
if (s1 == -1) {
mrb_sys_fail(mrb, "accept");
sock_sys_fail(mrb, "accept");
}
return mrb_fixnum_value(s1);
}
@@ -1003,7 +1013,7 @@ mrb_socket_accept2(mrb_state *mrb, mrb_value klass)
int s1 = (int)accept(s0, (struct sockaddr*)RSTRING_PTR(sastr), &socklen);
if (s1 == -1) {
mrb_sys_fail(mrb, "accept");
sock_sys_fail(mrb, "accept");
}
mrb_str_resize(mrb, sastr, socklen);
@@ -1026,7 +1036,7 @@ mrb_socket_bind(mrb_state *mrb, mrb_value klass)
mrb_get_args(mrb, "iS", &s, &sastr);
if (bind((int)s, (struct sockaddr*)RSTRING_PTR(sastr), (socklen_t)RSTRING_LEN(sastr)) == -1) {
mrb_sys_fail(mrb, "bind");
sock_sys_fail(mrb, "bind");
}
return mrb_nil_value();
}
@@ -1045,7 +1055,7 @@ mrb_socket_connect(mrb_state *mrb, mrb_value klass)
mrb_get_args(mrb, "iS", &s, &sastr);
if (connect((int)s, (struct sockaddr*)RSTRING_PTR(sastr), (socklen_t)RSTRING_LEN(sastr)) == -1) {
mrb_sys_fail(mrb, "connect");
sock_sys_fail(mrb, "connect");
}
return mrb_nil_value();
}
@@ -1063,7 +1073,7 @@ mrb_socket_listen(mrb_state *mrb, mrb_value klass)
mrb_get_args(mrb, "ii", &s, &backlog);
if (listen((int)s, (int)backlog) == -1) {
mrb_sys_fail(mrb, "listen");
sock_sys_fail(mrb, "listen");
}
return mrb_nil_value();
}
@@ -1119,7 +1129,7 @@ mrb_socket_socketpair(mrb_state *mrb, mrb_value klass)
mrb_get_args(mrb, "iii", &domain, &type, &protocol);
if (mrb_hal_socket_socketpair(mrb, (int)domain, (int)type, (int)protocol, sv) == -1) {
mrb_sys_fail(mrb, "socketpair");
sock_sys_fail(mrb, "socketpair");
}
mrb_value ary = mrb_ary_new_capa(mrb, 2);
@@ -1145,7 +1155,7 @@ mrb_socket_socket(mrb_state *mrb, mrb_value klass)
int s = (int)socket((int)domain, (int)type, (int)protocol);
if (s == -1)
mrb_sys_fail(mrb, "socket");
sock_sys_fail(mrb, "socket");
return mrb_fixnum_value(s);
}
@@ -1220,7 +1230,7 @@ mrb_win32_basicsocket_sysread(mrb_state *mrb, mrb_value self)
}
break;
case SOCKET_ERROR: /* Error */
mrb_sys_fail(mrb, "recv");
sock_sys_fail(mrb, "recv");
break;
default:
if (RSTRING_LEN(buf) != ret) {
@@ -1263,7 +1273,7 @@ mrb_win32_basicsocket_syswrite(mrb_state *mrb, mrb_value self)
int n = send(sd, RSTRING_PTR(str), (int)RSTRING_LEN(str), 0);
if (n == SOCKET_ERROR)
mrb_sys_fail(mrb, "send");
sock_sys_fail(mrb, "send");
return mrb_int_value(mrb, n);
}