summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/socket/extconf.rb6
-rw-r--r--ext/socket/raddrinfo.c2
-rw-r--r--ext/socket/rubysocket.h15
-rw-r--r--ext/socket/socket.c233
4 files changed, 255 insertions, 1 deletions
diff --git a/ext/socket/extconf.rb b/ext/socket/extconf.rb
index 718aa1f119..24eb736af0 100644
--- a/ext/socket/extconf.rb
+++ b/ext/socket/extconf.rb
@@ -285,6 +285,12 @@ EOF
have_header("resolv.h")
end
+have_header("ifaddrs.h")
+have_func("getifaddrs")
+have_header("sys/ioctl.h")
+have_header("sys/sockio.h")
+have_header("net/if.h")
+
unless have_type("socklen_t", headers)
$defs << "-Dsocklen_t=int"
end
diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c
index 05cd2b2138..f5ea91af85 100644
--- a/ext/socket/raddrinfo.c
+++ b/ext/socket/raddrinfo.c
@@ -550,7 +550,7 @@ init_addrinfo(rb_addrinfo_t *rai, struct sockaddr *sa, size_t len,
rai->inspectname = inspectname;
}
-static VALUE
+VALUE
addrinfo_new(struct sockaddr *addr, socklen_t len,
int family, int socktype, int protocol,
VALUE canonname, VALUE inspectname)
diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h
index 6d4a70a95d..0ba473a216 100644
--- a/ext/socket/rubysocket.h
+++ b/ext/socket/rubysocket.h
@@ -60,6 +60,19 @@
#endif
#endif
+#ifdef HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+#ifdef HAVE_NET_IF_H
+#include <net/if.h>
+#endif
+
#ifndef EWOULDBLOCK
#define EWOULDBLOCK EAGAIN
#endif
@@ -184,6 +197,8 @@ struct addrinfo* sock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints
VALUE fd_socket_addrinfo(int fd, struct sockaddr *addr, socklen_t len);
VALUE io_socket_addrinfo(VALUE io, struct sockaddr *addr, socklen_t len);
+VALUE addrinfo_new(struct sockaddr *addr, socklen_t len, int family, int socktype, int protocol, VALUE canonname, VALUE inspectname);
+
VALUE make_ipaddr(struct sockaddr *addr);
VALUE ipaddr(struct sockaddr *sockaddr, int norevlookup);
VALUE make_hostent(VALUE host, struct addrinfo *addr, VALUE (*ipaddr)(struct sockaddr *, size_t));
diff --git a/ext/socket/socket.c b/ext/socket/socket.c
index 6334dd2a8c..73d3a85cf0 100644
--- a/ext/socket/socket.c
+++ b/ext/socket/socket.c
@@ -1386,6 +1386,237 @@ sock_s_unpack_sockaddr_un(VALUE self, VALUE addr)
}
#endif
+#if defined(HAVE_GETIFADDRS) || defined(SIOCGLIFCONF) || defined(SIOCGIFCONF)
+static VALUE
+sockaddr_obj(struct sockaddr *addr)
+{
+ socklen_t len;
+
+ if (addr == NULL)
+ return Qnil;
+
+ switch (addr->sa_family) {
+ case AF_INET:
+ len = sizeof(struct sockaddr_in);
+ break;
+
+#ifdef AF_INET6
+ case AF_INET6:
+ len = sizeof(struct sockaddr_in6);
+ break;
+#endif
+
+#ifdef HAVE_SYS_UN_H
+ case AF_UNIX:
+ len = sizeof(struct sockaddr_un);
+ break;
+#endif
+
+ default:
+ len = sizeof(struct sockaddr_in);
+ break;
+ }
+#ifdef SA_LEN
+ if (len < SA_LEN(addr))
+ len = SA_LEN(addr);
+#endif
+
+ return addrinfo_new(addr, len, 0, 0, 0, Qnil, Qnil);
+}
+#endif
+
+/*
+ * call-seq:
+ * Socket.list_ip_address => array
+ *
+ * Returns local IP addresses as an array.
+ *
+ * The array contains AddrInfo objects.
+ *
+ * pp Socket.list_ip_address
+ * #=> [#<AddrInfo: 127.0.0.1>,
+ * #<AddrInfo: 192.168.0.128>,
+ * #<AddrInfo: ::1>,
+ * ...]
+ *
+ */
+static VALUE
+socket_s_list_ip_address(VALUE self)
+{
+#if defined(HAVE_GETIFADDRS)
+ struct ifaddrs *ifp = NULL;
+ struct ifaddrs *p;
+ int ret;
+ VALUE list;
+
+ ret = getifaddrs(&ifp);
+ if (ret == -1) {
+ rb_sys_fail("getifaddrs");
+ }
+
+ list = rb_ary_new();
+ for (p = ifp; p; p = p->ifa_next) {
+ if (p->ifa_addr != NULL && IS_IP_FAMILY(p->ifa_addr->sa_family)) {
+ rb_ary_push(list, sockaddr_obj(p->ifa_addr));
+ }
+ }
+
+ freeifaddrs(ifp);
+
+ return list;
+#elif defined(SIOCGLIFCONF) && defined(SIOCGLIFNUM) && !defined(__hpux)
+ /* Solaris if_tcp(7P) */
+ /* HP-UX has SIOCGLIFCONF too. But it uses differenet struct */
+ int fd = -1;
+ int ret;
+ struct lifnum ln;
+ struct lifconf lc;
+ char *reason = NULL;
+ int save_errno;
+ int i;
+ VALUE list = Qnil;
+
+ lc.lifc_buf = NULL;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd == -1)
+ rb_sys_fail("socket");
+
+ memset(&ln, 0, sizeof(ln));
+ ln.lifn_family = AF_UNSPEC;
+
+ ret = ioctl(fd, SIOCGLIFNUM, &ln);
+ if (ret == -1) {
+ reason = "SIOCGLIFNUM";
+ goto finish;
+ }
+
+ memset(&lc, 0, sizeof(lc));
+ lc.lifc_family = AF_UNSPEC;
+ lc.lifc_flags = 0;
+ lc.lifc_len = sizeof(struct lifreq) * ln.lifn_count;
+ lc.lifc_req = xmalloc(lc.lifc_len);
+
+ ret = ioctl(fd, SIOCGLIFCONF, &lc);
+ if (ret == -1) {
+ reason = "SIOCGLIFCONF";
+ goto finish;
+ }
+
+ close(fd);
+ fd = -1;
+
+ list = rb_ary_new();
+ for (i = 0; i < ln.lifn_count; i++) {
+ struct lifreq *req = &lc.lifc_req[i];
+ if (IS_IP_FAMILY(req->lifr_addr.ss_family)) {
+ rb_ary_push(list, sockaddr_obj((struct sockaddr *)&req->lifr_addr));
+ }
+ }
+
+ finish:
+ save_errno = errno;
+ if (lc.lifc_buf != NULL)
+ xfree(lc.lifc_req);
+ if (fd != -1)
+ close(fd);
+ errno = save_errno;
+
+ if (reason)
+ rb_sys_fail(reason);
+ return list;
+
+#elif defined(SIOCGIFCONF)
+ int fd = -1;
+ int ret;
+#define EXTRA_SPACE (sizeof(struct ifconf) + sizeof(struct sockaddr_storage))
+ char initbuf[4096+EXTRA_SPACE];
+ char *buf = initbuf;
+ int bufsize;
+ struct ifconf conf;
+ struct ifreq *req;
+ VALUE list = Qnil;
+ char *reason = NULL;
+ int save_errno;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd == -1)
+ rb_sys_fail("socket");
+
+ bufsize = sizeof(initbuf);
+ buf = initbuf;
+
+ retry:
+ conf.ifc_len = bufsize;
+ conf.ifc_req = (struct ifreq *)buf;
+
+ /* fprintf(stderr, "bufsize: %d\n", bufsize); */
+
+ ret = ioctl(fd, SIOCGIFCONF, &conf);
+ if (ret == -1) {
+ reason = "SIOCGIFCONF";
+ goto finish;
+ }
+
+ /* fprintf(stderr, "conf.ifc_len: %d\n", conf.ifc_len); */
+
+ if (bufsize - EXTRA_SPACE < conf.ifc_len) {
+ if (bufsize < conf.ifc_len) {
+ /* NetBSD returns required size for all interfaces. */
+ bufsize = conf.ifc_len + EXTRA_SPACE;
+ }
+ else {
+ bufsize = bufsize << 1;
+ }
+ if (buf == initbuf)
+ buf = NULL;
+ buf = xrealloc(buf, bufsize);
+ goto retry;
+ }
+
+ close(fd);
+ fd = -1;
+
+ list = rb_ary_new();
+ req = conf.ifc_req;
+ while ((char*)req < (char*)conf.ifc_req + conf.ifc_len) {
+ struct sockaddr *addr = &req->ifr_addr;
+ if (IS_IP_FAMILY(addr->sa_family)) {
+ rb_ary_push(list, sockaddr_obj(addr));
+ }
+#ifdef HAVE_SA_LEN
+# ifndef _SIZEOF_ADDR_IFREQ
+# define _SIZEOF_ADDR_IFREQ(r) \
+ (sizeof(struct ifreq) + \
+ (sizeof(struct sockaddr) < (r).ifr_addr.sa_len ? \
+ (r).ifr_addr.sa_len - sizeof(struct sockaddr) : \
+ 0))
+# endif
+ req = (struct ifreq *)((char*)req + _SIZEOF_ADDR_IFREQ(*req));
+#else
+ req = (struct ifreq *)((char*)req + sizeof(struct ifreq));
+#endif
+ }
+
+ finish:
+
+ save_errno = errno;
+ if (buf != initbuf)
+ xfree(buf);
+ if (fd != -1)
+ close(fd);
+ errno = save_errno;
+
+ if (reason)
+ rb_sys_fail(reason);
+ return list;
+
+#undef EXTRA_SPACE
+#else
+ rb_notimplement();
+#endif
+}
+
/*
* Class +Socket+ provides access to the underlying operating system
* socket implementations. It can be used to provide more operating system
@@ -1449,4 +1680,6 @@ Init_socket()
rb_define_singleton_method(rb_cSocket, "pack_sockaddr_un", sock_s_pack_sockaddr_un, 1);
rb_define_singleton_method(rb_cSocket, "unpack_sockaddr_un", sock_s_unpack_sockaddr_un, 1);
#endif
+
+ rb_define_singleton_method(rb_cSocket, "list_ip_address", socket_s_list_ip_address, 0);
}