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