From 43c0b50cbdb3911c5b7bcaf9f802c3ab9ad78978 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Sat, 9 May 2026 10:21:00 +0900 Subject: [PATCH] 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 --- mrbgems/mruby-socket/include/socket_hal.h | 9 ++++ mrbgems/mruby-socket/mrbgem.rake | 1 + mrbgems/mruby-socket/mrblib/socket.rb | 1 - mrbgems/mruby-socket/ports/posix/socket_hal.c | 26 +++++++++++ mrbgems/mruby-socket/ports/win/socket_hal.c | 43 +++++++++++++++++++ mrbgems/mruby-socket/src/socket.c | 31 +++++++++++++ mrbgems/mruby-socket/test/socket.rb | 14 ++++++ 7 files changed, 124 insertions(+), 1 deletion(-) diff --git a/mrbgems/mruby-socket/include/socket_hal.h b/mrbgems/mruby-socket/include/socket_hal.h index 89ac95b7c..4ad75e88e 100644 --- a/mrbgems/mruby-socket/include/socket_hal.h +++ b/mrbgems/mruby-socket/include/socket_hal.h @@ -86,6 +86,15 @@ int mrb_hal_socket_socketpair(mrb_state *mrb, int domain, int type, int protocol * Returns: Unix socket path string, or raises exception if not supported */ mrb_value mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen); +/* Enumerate local IP addresses for all network interfaces. + * Returns an Array of String values, each a binary sockaddr_in (AF_INET) or + * sockaddr_in6 (AF_INET6) ready to be passed to Addrinfo.new. Loopback, + * link-local, and other interface-local addresses are included; the caller + * is responsible for filtering if needed. + * Raises a SystemCallError on failure of the underlying platform call + * (getifaddrs / GetAdaptersAddresses). */ +mrb_value mrb_hal_socket_ip_address_list(mrb_state *mrb); + #ifdef __cplusplus } #endif diff --git a/mrbgems/mruby-socket/mrbgem.rake b/mrbgems/mruby-socket/mrbgem.rake index f6e6dffb0..3775ec57f 100644 --- a/mrbgems/mruby-socket/mrbgem.rake +++ b/mrbgems/mruby-socket/mrbgem.rake @@ -12,5 +12,6 @@ MRuby::Gem::Specification.new('mruby-socket') do |spec| if spec.for_windows? spec.linker.libraries << "wsock32" spec.linker.libraries << "ws2_32" + spec.linker.libraries << "iphlpapi" # for GetAdaptersAddresses (Socket.ip_address_list) end end diff --git a/mrbgems/mruby-socket/mrblib/socket.rb b/mrbgems/mruby-socket/mrblib/socket.rb index f350d6cd5..ce9b2a31b 100644 --- a/mrbgems/mruby-socket/mrblib/socket.rb +++ b/mrbgems/mruby-socket/mrblib/socket.rb @@ -778,7 +778,6 @@ class Socket < BasicSocket end #def self.getnameinfo - #def self.ip_address_list # # call-seq: diff --git a/mrbgems/mruby-socket/ports/posix/socket_hal.c b/mrbgems/mruby-socket/ports/posix/socket_hal.c index 6654596c3..a2f6ad669 100644 --- a/mrbgems/mruby-socket/ports/posix/socket_hal.c +++ b/mrbgems/mruby-socket/ports/posix/socket_hal.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include @@ -147,3 +149,27 @@ mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen) return mrb_str_new_cstr(mrb, ((const struct sockaddr_un*)sockaddr)->sun_path); } + +mrb_value +mrb_hal_socket_ip_address_list(mrb_state *mrb) +{ + struct ifaddrs *ifap = NULL; + if (getifaddrs(&ifap) != 0) { + mrb_sys_fail(mrb, "getifaddrs"); + } + mrb_value ary = mrb_ary_new(mrb); + int arena_idx = mrb_gc_arena_save(mrb); + for (struct ifaddrs *ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) continue; + socklen_t salen; + switch (ifa->ifa_addr->sa_family) { + case AF_INET: salen = sizeof(struct sockaddr_in); break; + case AF_INET6: salen = sizeof(struct sockaddr_in6); break; + default: continue; + } + mrb_ary_push(mrb, ary, mrb_str_new(mrb, (const char*)ifa->ifa_addr, salen)); + mrb_gc_arena_restore(mrb, arena_idx); + } + freeifaddrs(ifap); + return ary; +} diff --git a/mrbgems/mruby-socket/ports/win/socket_hal.c b/mrbgems/mruby-socket/ports/win/socket_hal.c index 3e4a9583b..de0122666 100644 --- a/mrbgems/mruby-socket/ports/win/socket_hal.c +++ b/mrbgems/mruby-socket/ports/win/socket_hal.c @@ -12,12 +12,14 @@ #endif #include +#include #include #include #include #include "socket_hal.h" #include #include +#include #include #include #include @@ -275,3 +277,44 @@ mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen) "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; +} diff --git a/mrbgems/mruby-socket/src/socket.c b/mrbgems/mruby-socket/src/socket.c index 886d9d6f7..ffb4c6314 100644 --- a/mrbgems/mruby-socket/src/socket.c +++ b/mrbgems/mruby-socket/src/socket.c @@ -980,6 +980,36 @@ mrb_socket_gethostname(mrb_state *mrb, mrb_value cls) return buf; } +/* + * call-seq: + * Socket.ip_address_list -> array + * + * Returns an array of `Addrinfo` objects representing all local IP addresses + * on every network interface (both IPv4 and IPv6). Loopback and link-local + * addresses are included; the caller is responsible for filtering. + * + * Socket.ip_address_list + * #=> [#, #, #, ...] + * + * Backed by `getifaddrs(3)` on POSIX and `GetAdaptersAddresses` on Windows. + */ +static mrb_value +mrb_socket_ip_address_list(mrb_state *mrb, mrb_value klass) +{ + (void)klass; + mrb_value sas = mrb_hal_socket_ip_address_list(mrb); + struct RClass *ainfo = mrb_class_get_id(mrb, MRB_SYM(Addrinfo)); + mrb_value result = mrb_ary_new_capa(mrb, RARRAY_LEN(sas)); + int arena_idx = mrb_gc_arena_save(mrb); + for (mrb_int i = 0; i < RARRAY_LEN(sas); i++) { + mrb_value sa = RARRAY_PTR(sas)[i]; + mrb_value addr = mrb_obj_new(mrb, ainfo, 1, &sa); + mrb_ary_push(mrb, result, addr); + mrb_gc_arena_restore(mrb, arena_idx); + } + return result; +} + /* * call-seq: * Socket._accept(fd) -> [new_fd, sockaddr] @@ -1352,6 +1382,7 @@ mrb_mruby_socket_gem_init(mrb_state* mrb) mrb_define_class_method_id(mrb, sock, MRB_SYM(_sockaddr_family), mrb_socket_sockaddr_family, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, sock, MRB_SYM(_socket), mrb_socket_socket, MRB_ARGS_REQ(3)); mrb_define_class_method_id(mrb, sock, MRB_SYM(gethostname), mrb_socket_gethostname, MRB_ARGS_NONE()); + mrb_define_class_method_id(mrb, sock, MRB_SYM(ip_address_list), mrb_socket_ip_address_list, MRB_ARGS_NONE()); mrb_define_class_method_id(mrb, sock, MRB_SYM(sockaddr_un), mrb_socket_sockaddr_un, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, sock, MRB_SYM(socketpair), mrb_socket_socketpair, MRB_ARGS_REQ(3)); diff --git a/mrbgems/mruby-socket/test/socket.rb b/mrbgems/mruby-socket/test/socket.rb index b64a67919..3a57710b0 100644 --- a/mrbgems/mruby-socket/test/socket.rb +++ b/mrbgems/mruby-socket/test/socket.rb @@ -36,3 +36,17 @@ assert('Socket#recvfrom') do end end # win? + +# Socket.ip_address_list works on both POSIX (getifaddrs) and Windows +# (GetAdaptersAddresses), so this test runs everywhere. +assert('Socket.ip_address_list') do + list = Socket.ip_address_list + assert_kind_of Array, list + # Every host should have at least one address (loopback at minimum). + assert_true list.length >= 1 + list.each do |ai| + assert_kind_of Addrinfo, ai + # Only AF_INET and AF_INET6 are returned. + assert_true [Socket::AF_INET, Socket::AF_INET6].include?(ai.afamily) + end +end