Files
Yukihiro "Matz" Matsumoto 43c0b50cbd mruby-socket: implement Socket.ip_address_list
Returns an Array of Addrinfo objects for all local IP addresses
(IPv4 and IPv6) on every network interface, matching CRuby's API.

Backed by getifaddrs(3) on POSIX and GetAdaptersAddresses on
Windows (requires iphlpapi.lib, added to the Windows linker libs).
The HAL returns binary sockaddr strings; src/socket.c wraps each
into Addrinfo so the wrapping code stays platform-agnostic.

Resolves the second error reported in #5659 (after the IO.select
fix from the HAL split): "undefined method 'ip_address_list' for
Class". Issue #5659 itself is closed; this lands the missing API.

Ref #5659.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-09 10:22:19 +09:00

321 lines
7.9 KiB
C

/*
** socket_hal.c - Windows HAL implementation for mruby-socket
**
** See Copyright Notice in mruby.h
**
** Windows implementation for socket operations using Winsock APIs.
** Supported platforms: Windows, MinGW
*/
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 // need Windows XP or later
#endif
#include <mruby.h>
#include <mruby/array.h>
#include <mruby/string.h>
#include <mruby/class.h>
#include <mruby/error.h>
#include "socket_hal.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <windows.h>
#include <errno.h>
#include <string.h>
/*
* Socket HAL Initialization/Finalization
*/
void
mrb_hal_socket_init(mrb_state *mrb)
{
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != NO_ERROR) {
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(RuntimeError)), "WSAStartup failed");
}
}
void
mrb_hal_socket_final(mrb_state *mrb)
{
(void)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
*/
int
mrb_hal_socket_set_nonblock(mrb_state *mrb, int fd, int nonblock)
{
(void)mrb;
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;
}
/*
* Address Conversion Functions
*/
const char*
mrb_hal_socket_inet_ntop(int af, const void *src, char *dst, size_t size)
{
if (af == AF_INET) {
struct sockaddr_in in = {0};
in.sin_family = AF_INET;
memcpy(&in.sin_addr, src, sizeof(struct in_addr));
if (getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in),
dst, (DWORD)size, NULL, 0, NI_NUMERICHOST) == 0) {
return dst;
}
return NULL;
}
else if (af == AF_INET6) {
struct sockaddr_in6 in = {0};
in.sin6_family = AF_INET6;
memcpy(&in.sin6_addr, src, sizeof(struct in6_addr));
if (getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6),
dst, (DWORD)size, NULL, 0, NI_NUMERICHOST) == 0) {
return dst;
}
return NULL;
}
return NULL;
}
int
mrb_hal_socket_inet_pton(int af, const char *src, void *dst)
{
struct addrinfo hints = {0};
hints.ai_family = af;
hints.ai_flags = AI_NUMERICHOST;
struct addrinfo *res;
if (getaddrinfo(src, NULL, &hints, &res) != 0) {
return 0; /* Invalid address */
}
if (res == NULL) {
return 0;
}
if (af == AF_INET && res->ai_family == AF_INET) {
memcpy(dst, &((struct sockaddr_in*)res->ai_addr)->sin_addr, sizeof(struct in_addr));
freeaddrinfo(res);
return 1;
}
else if (af == AF_INET6 && res->ai_family == AF_INET6) {
memcpy(dst, &((struct sockaddr_in6*)res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
freeaddrinfo(res);
return 1;
}
freeaddrinfo(res);
return 0;
}
/*
* Platform-Specific Socket Features
*/
mrb_value
mrb_hal_socket_sockaddr_un(mrb_state *mrb, const char *path, size_t pathlen)
{
(void)path;
(void)pathlen;
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(NotImplementedError)),
"sockaddr_un unsupported on Windows");
return mrb_nil_value();
}
int
mrb_hal_socket_socketpair(mrb_state *mrb, int domain, int type, int protocol, int sv[2])
{
(void)mrb;
(void)domain;
(void)type;
(void)protocol;
(void)sv;
/* socketpair is not supported on Windows */
errno = ENOSYS;
return -1;
}
mrb_value
mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen)
{
(void)sockaddr;
(void)socklen;
mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(NotImplementedError)),
"unix_path unsupported on Windows");
return mrb_nil_value();
}
mrb_value
mrb_hal_socket_ip_address_list(mrb_state *mrb)
{
/* MSDN recommends 15 KiB as the initial buffer size to handle most
adapter configurations in a single call. */
ULONG buflen = 15000;
IP_ADAPTER_ADDRESSES *adapters = (IP_ADAPTER_ADDRESSES*)mrb_malloc(mrb, buflen);
ULONG ret = ERROR_BUFFER_OVERFLOW;
for (int retries = 0; retries < 3 && ret == ERROR_BUFFER_OVERFLOW; retries++) {
ret = GetAdaptersAddresses(AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,
NULL, adapters, &buflen);
if (ret == ERROR_BUFFER_OVERFLOW) {
adapters = (IP_ADAPTER_ADDRESSES*)mrb_realloc(mrb, adapters, buflen);
}
}
if (ret != ERROR_SUCCESS) {
mrb_free(mrb, adapters);
mrb_raisef(mrb, mrb_class_get_id(mrb, MRB_SYM(SocketError)),
"GetAdaptersAddresses failed (Win32 error %u)", (unsigned int)ret);
}
mrb_value ary = mrb_ary_new(mrb);
int arena_idx = mrb_gc_arena_save(mrb);
for (IP_ADAPTER_ADDRESSES *ad = adapters; ad != NULL; ad = ad->Next) {
for (IP_ADAPTER_UNICAST_ADDRESS *ua = ad->FirstUnicastAddress; ua != NULL; ua = ua->Next) {
SOCKADDR *sa = ua->Address.lpSockaddr;
int salen;
switch (sa->sa_family) {
case AF_INET: salen = sizeof(SOCKADDR_IN); break;
case AF_INET6: salen = sizeof(SOCKADDR_IN6); break;
default: continue;
}
mrb_ary_push(mrb, ary, mrb_str_new(mrb, (const char*)sa, salen));
mrb_gc_arena_restore(mrb, arena_idx);
}
}
mrb_free(mrb, adapters);
return ary;
}