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>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-05-09 10:21:00 +09:00
parent 7d0d6fd413
commit 43c0b50cbd
7 changed files with 124 additions and 1 deletions
@@ -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
+1
View File
@@ -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
-1
View File
@@ -778,7 +778,6 @@ class Socket < BasicSocket
end
#def self.getnameinfo
#def self.ip_address_list
#
# call-seq:
@@ -8,6 +8,7 @@
*/
#include <mruby.h>
#include <mruby/array.h>
#include <mruby/string.h>
#include <mruby/class.h>
#include <mruby/error.h>
@@ -20,6 +21,7 @@
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <string.h>
#include <errno.h>
@@ -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;
}
@@ -12,12 +12,14 @@
#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>
@@ -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;
}
+31
View File
@@ -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
* #=> [#<Addrinfo: 127.0.0.1>, #<Addrinfo: ::1>, #<Addrinfo: 192.0.2.1>, ...]
*
* 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));
+14
View File
@@ -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