mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user