ruby/ext/socket/ipsocket.c
Misaki Shioi 2be117a97d
Fix heap-use-after-free in free_fast_fallback_getaddrinfo_entry (#13231)
This change addresses the following ASAN error:

```
==36597==ERROR: AddressSanitizer: heap-use-after-free on address 0x512000396ba8 at pc 0x7fcad5cbad9f bp 0x7fff19739af0 sp 0x7fff19739ae8
  WRITE of size 8 at 0x512000396ba8 thread T0
  [643/756] 36600=optparse/test_summary
      #0 0x7fcad5cbad9e in free_fast_fallback_getaddrinfo_entry /home/runner/work/ruby-dev-builder/ruby-dev-builder/ext/socket/raddrinfo.c:3046:22
      #1 0x7fcad5c9fb48 in fast_fallback_inetsock_cleanup /home/runner/work/ruby-dev-builder/ruby-dev-builder/ext/socket/ipsocket.c:1179:17
      #2 0x7fcadf3b611a in rb_ensure /home/runner/work/ruby-dev-builder/ruby-dev-builder/eval.c:1081:5
      #3 0x7fcad5c9b44b in rsock_init_inetsock /home/runner/work/ruby-dev-builder/ruby-dev-builder/ext/socket/ipsocket.c:1289:20
      #4 0x7fcad5ca22b8 in tcp_init /home/runner/work/ruby-dev-builder/ruby-dev-builder/ext/socket/tcpsocket.c:76:12
      #5 0x7fcadf83ba70 in vm_call0_cfunc_with_frame /home/runner/work/ruby-dev-builder/ruby-dev-builder/./vm_eval.c:164:15
...
```

A `struct fast_fallback_getaddrinfo_shared` is shared between the main thread and two child threads.
This struct contains an array of `fast_fallback_getaddrinfo_entry`.

`fast_fallback_getaddrinfo_entry` and `fast_fallback_getaddrinfo_shared` were freed separately, and if `fast_fallback_getaddrinfo_shared` was freed first and then an attempt was made to free a `fast_fallback_getaddrinfo_entry`, a `heap-use-after-free` could occur.

This change avoids that possibility by separating the deallocation of the addrinfo memory held by `fast_fallback_getaddrinfo_entry` from the access and lifecycle of the `fast_fallback_getaddrinfo_entry` itself.
2025-05-03 21:39:57 +09:00

1524 lines
54 KiB
C

/************************************************
ipsocket.c -
created at: Thu Mar 31 12:21:29 JST 1994
Copyright (C) 1993-2007 Yukihiro Matsumoto
************************************************/
#include "rubysocket.h"
#include <stdio.h>
struct inetsock_arg
{
VALUE self;
VALUE io;
struct {
VALUE host, serv;
struct rb_addrinfo *res;
} remote, local;
int type;
VALUE resolv_timeout;
VALUE connect_timeout;
};
static VALUE
inetsock_cleanup(VALUE v)
{
struct inetsock_arg *arg = (void *)v;
if (arg->remote.res) {
rb_freeaddrinfo(arg->remote.res);
arg->remote.res = 0;
}
if (arg->local.res) {
rb_freeaddrinfo(arg->local.res);
arg->local.res = 0;
}
if (arg->io != Qnil) {
rb_io_close(arg->io);
arg->io = Qnil;
}
return Qnil;
}
static VALUE
init_inetsock_internal(VALUE v)
{
struct inetsock_arg *arg = (void *)v;
int error = 0;
int type = arg->type;
struct addrinfo *res, *lres;
int status = 0, local = 0;
int family = AF_UNSPEC;
const char *syscall = 0;
VALUE connect_timeout = arg->connect_timeout;
arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv,
family, SOCK_STREAM,
(type == INET_SERVER) ? AI_PASSIVE : 0);
/*
* Maybe also accept a local address
*/
if (type != INET_SERVER && (!NIL_P(arg->local.host) || !NIL_P(arg->local.serv))) {
arg->local.res = rsock_addrinfo(arg->local.host, arg->local.serv,
family, SOCK_STREAM, 0);
}
VALUE io = Qnil;
for (res = arg->remote.res->ai; res; res = res->ai_next) {
#if !defined(INET6) && defined(AF_INET6)
if (res->ai_family == AF_INET6)
continue;
#endif
lres = NULL;
if (arg->local.res) {
for (lres = arg->local.res->ai; lres; lres = lres->ai_next) {
if (lres->ai_family == res->ai_family)
break;
}
if (!lres) {
if (res->ai_next || status < 0)
continue;
/* Use a different family local address if no choice, this
* will cause EAFNOSUPPORT. */
lres = arg->local.res->ai;
}
}
status = rsock_socket(res->ai_family,res->ai_socktype,res->ai_protocol);
syscall = "socket(2)";
if (status < 0) {
error = errno;
continue;
}
int fd = status;
io = arg->io = rsock_init_sock(arg->self, fd);
if (type == INET_SERVER) {
#if !defined(_WIN32) && !defined(__CYGWIN__)
status = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
(char*)&status, (socklen_t)sizeof(status));
#endif
status = bind(fd, res->ai_addr, res->ai_addrlen);
syscall = "bind(2)";
}
else {
if (lres) {
#if !defined(_WIN32) && !defined(__CYGWIN__)
status = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
(char*)&status, (socklen_t)sizeof(status));
#endif
status = bind(fd, lres->ai_addr, lres->ai_addrlen);
local = status;
syscall = "bind(2)";
}
if (status >= 0) {
status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), connect_timeout);
syscall = "connect(2)";
}
}
if (status < 0) {
error = errno;
arg->io = Qnil;
rb_io_close(io);
io = Qnil;
continue;
} else {
break;
}
}
if (status < 0) {
VALUE host, port;
if (local < 0) {
host = arg->local.host;
port = arg->local.serv;
} else {
host = arg->remote.host;
port = arg->remote.serv;
}
rsock_syserr_fail_host_port(error, syscall, host, port);
}
// Don't close the socket in `inetsock_cleanup` if we are returning it:
arg->io = Qnil;
if (type == INET_SERVER && io != Qnil) {
status = listen(rb_io_descriptor(io), SOMAXCONN);
if (status < 0) {
error = errno;
rb_io_close(io);
rb_syserr_fail(error, "listen(2)");
}
}
/* create new instance */
return io;
}
#if FAST_FALLBACK_INIT_INETSOCK_IMPL == 0
VALUE
rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE _fast_fallback, VALUE _test_mode_settings)
{
struct inetsock_arg arg;
arg.self = self;
arg.io = Qnil;
arg.remote.host = remote_host;
arg.remote.serv = remote_serv;
arg.remote.res = 0;
arg.local.host = local_host;
arg.local.serv = local_serv;
arg.local.res = 0;
arg.type = type;
arg.resolv_timeout = resolv_timeout;
arg.connect_timeout = connect_timeout;
return rb_ensure(init_inetsock_internal, (VALUE)&arg,
inetsock_cleanup, (VALUE)&arg);
}
#elif FAST_FALLBACK_INIT_INETSOCK_IMPL == 1
#define IPV6_ENTRY_POS 0
#define IPV4_ENTRY_POS 1
#define RESOLUTION_ERROR 0
#define SYSCALL_ERROR 1
static int
is_specified_ip_address(const char *hostname)
{
if (!hostname) return false;
struct in_addr ipv4addr;
struct in6_addr ipv6addr;
return (inet_pton(AF_INET6, hostname, &ipv6addr) == 1 ||
inet_pton(AF_INET, hostname, &ipv4addr) == 1);
}
struct fast_fallback_inetsock_arg
{
VALUE self;
VALUE io;
struct {
VALUE host, serv;
struct rb_addrinfo *res;
} remote, local;
int type;
VALUE resolv_timeout;
VALUE connect_timeout;
const char *hostp, *portp;
int *families;
int family_size;
int additional_flags;
struct fast_fallback_getaddrinfo_entry *getaddrinfo_entries[2];
struct fast_fallback_getaddrinfo_shared *getaddrinfo_shared;
rb_fdset_t readfds, writefds;
int wait;
int connection_attempt_fds_size;
int *connection_attempt_fds;
VALUE test_mode_settings;
};
static struct fast_fallback_getaddrinfo_shared *
allocate_fast_fallback_getaddrinfo_shared(int family_size)
{
struct fast_fallback_getaddrinfo_shared *shared;
shared = (struct fast_fallback_getaddrinfo_shared *)calloc(
1,
sizeof(struct fast_fallback_getaddrinfo_shared) + (family_size == 1 ? 0 : 2) * sizeof(struct fast_fallback_getaddrinfo_entry)
);
return shared;
}
static void
allocate_fast_fallback_getaddrinfo_hints(struct addrinfo *hints, int family, int remote_addrinfo_hints, int additional_flags)
{
MEMZERO(hints, struct addrinfo, 1);
hints->ai_family = family;
hints->ai_socktype = SOCK_STREAM;
hints->ai_protocol = IPPROTO_TCP;
hints->ai_flags = remote_addrinfo_hints;
hints->ai_flags |= additional_flags;
}
static int*
allocate_connection_attempt_fds(int additional_capacity)
{
int *fds = (int *)malloc(additional_capacity * sizeof(int));
if (!fds) rb_syserr_fail(errno, "malloc(3)");
for (int i = 0; i < additional_capacity; i++) fds[i] = -1;
return fds;
}
static int
reallocate_connection_attempt_fds(int **fds, int current_capacity, int additional_capacity)
{
int new_capacity = current_capacity + additional_capacity;
int *new_fds;
new_fds = realloc(*fds, new_capacity * sizeof(int));
if (new_fds == NULL) {
rb_syserr_fail(errno, "realloc(3)");
}
*fds = new_fds;
for (int i = current_capacity; i < new_capacity; i++) (*fds)[i] = -1;
return new_capacity;
}
struct hostname_resolution_result
{
struct addrinfo *ai;
int finished;
int has_error;
};
struct hostname_resolution_store
{
struct hostname_resolution_result v6;
struct hostname_resolution_result v4;
int is_all_finished;
};
static int
any_addrinfos(struct hostname_resolution_store *resolution_store)
{
return resolution_store->v6.ai || resolution_store->v4.ai;
}
static struct timespec
current_clocktime_ts(void)
{
struct timespec ts;
if ((clock_gettime(CLOCK_MONOTONIC, &ts)) < 0) {
rb_syserr_fail(errno, "clock_gettime(2)");
}
return ts;
}
static void
set_timeout_tv(struct timeval *tv, long ms, struct timespec from)
{
long sec = ms / 1000;
long nsec = (ms % 1000) * 1000000;
long result_sec = from.tv_sec + sec;
long result_nsec = from.tv_nsec + nsec;
result_sec += result_nsec / 1000000000;
result_nsec = result_nsec % 1000000000;
tv->tv_sec = result_sec;
tv->tv_usec = (int)(result_nsec / 1000);
}
static struct timeval
add_ts_to_tv(struct timeval tv, struct timespec ts)
{
long ts_usec = ts.tv_nsec / 1000;
tv.tv_sec += ts.tv_sec;
tv.tv_usec += ts_usec;
if (tv.tv_usec >= 1000000) {
tv.tv_sec += tv.tv_usec / 1000000;
tv.tv_usec = tv.tv_usec % 1000000;
}
return tv;
}
static VALUE
tv_to_seconds(struct timeval *timeout) {
if (timeout == NULL) return Qnil;
double seconds = (double)timeout->tv_sec + (double)timeout->tv_usec / 1000000.0;
return DBL2NUM(seconds);
}
static int
is_infinity(struct timeval tv)
{
// { -1, -1 } as infinity
return tv.tv_sec == -1 || tv.tv_usec == -1;
}
static int
is_timeout_tv(struct timeval *timeout_tv, struct timespec now) {
if (!timeout_tv) return false;
if (timeout_tv->tv_sec == -1 && timeout_tv->tv_usec == -1) return false;
struct timespec ts;
ts.tv_sec = timeout_tv->tv_sec;
ts.tv_nsec = timeout_tv->tv_usec * 1000;
if (now.tv_sec > ts.tv_sec) return true;
if (now.tv_sec == ts.tv_sec && now.tv_nsec >= ts.tv_nsec) return true;
return false;
}
static struct timeval *
select_expires_at(
struct hostname_resolution_store *resolution_store,
struct timeval *resolution_delay,
struct timeval *connection_attempt_delay,
struct timeval *user_specified_resolv_timeout_at,
struct timeval *user_specified_connect_timeout_at
) {
if (any_addrinfos(resolution_store)) {
return resolution_delay ? resolution_delay : connection_attempt_delay;
}
struct timeval *timeout = NULL;
if (user_specified_resolv_timeout_at) {
if (is_infinity(*user_specified_resolv_timeout_at)) return NULL;
timeout = user_specified_resolv_timeout_at;
}
if (user_specified_connect_timeout_at) {
if (is_infinity(*user_specified_connect_timeout_at)) return NULL;
if (!timeout || timercmp(user_specified_connect_timeout_at, timeout, >)) {
return user_specified_connect_timeout_at;
}
}
return timeout;
}
static struct timeval
tv_to_timeout(struct timeval *ends_at, struct timespec now)
{
struct timeval delay;
struct timespec expires_at;
expires_at.tv_sec = ends_at->tv_sec;
expires_at.tv_nsec = ends_at->tv_usec * 1000;
struct timespec diff;
diff.tv_sec = expires_at.tv_sec - now.tv_sec;
if (expires_at.tv_nsec >= now.tv_nsec) {
diff.tv_nsec = expires_at.tv_nsec - now.tv_nsec;
} else {
diff.tv_sec -= 1;
diff.tv_nsec = (1000000000 + expires_at.tv_nsec) - now.tv_nsec;
}
delay.tv_sec = diff.tv_sec;
delay.tv_usec = (int)diff.tv_nsec / 1000;
return delay;
}
static struct addrinfo *
pick_addrinfo(struct hostname_resolution_store *resolution_store, int last_family)
{
int priority_on_v6[2] = { AF_INET6, AF_INET };
int priority_on_v4[2] = { AF_INET, AF_INET6 };
int *precedences = last_family == AF_INET6 ? priority_on_v4 : priority_on_v6;
struct addrinfo *selected_ai = NULL;
for (int i = 0; i < 2; i++) {
if (precedences[i] == AF_INET6) {
selected_ai = resolution_store->v6.ai;
if (selected_ai) {
resolution_store->v6.ai = selected_ai->ai_next;
break;
}
} else {
selected_ai = resolution_store->v4.ai;
if (selected_ai) {
resolution_store->v4.ai = selected_ai->ai_next;
break;
}
}
}
return selected_ai;
}
static void
socket_nonblock_set(int fd)
{
int flags = fcntl(fd, F_GETFL);
if (flags < 0) rb_syserr_fail(errno, "fcntl(2)");
if ((flags & O_NONBLOCK) != 0) return;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) rb_syserr_fail(errno, "fcntl(2)");
return;
}
static int
in_progress_fds(int fds_size)
{
return fds_size > 0;
}
static void
remove_connection_attempt_fd(int *fds, int *fds_size, int removing_fd) {
int i, j;
for (i = 0; i < *fds_size; i++) {
if (fds[i] != removing_fd) continue;
for (j = i; j < *fds_size - 1; j++) {
fds[j] = fds[j + 1];
}
(*fds_size)--;
fds[*fds_size] = -1;
break;
}
}
struct fast_fallback_error
{
int type;
int ecode;
};
static VALUE
init_fast_fallback_inetsock_internal(VALUE v)
{
struct fast_fallback_inetsock_arg *arg = (void *)v;
VALUE io = arg->io;
VALUE resolv_timeout = arg->resolv_timeout;
VALUE connect_timeout = arg->connect_timeout;
VALUE test_mode_settings = arg->test_mode_settings;
struct addrinfo *remote_ai = NULL, *local_ai = NULL;
int connected_fd = -1, status = 0, local_status = 0;
int remote_addrinfo_hints = 0;
struct fast_fallback_error last_error = { 0, 0 };
const char *syscall = 0;
VALUE host, serv;
#ifdef HAVE_CONST_AI_ADDRCONFIG
remote_addrinfo_hints |= AI_ADDRCONFIG;
#endif
pthread_t threads[arg->family_size];
char resolved_type[2];
ssize_t resolved_type_size;
int hostname_resolution_waiter = -1, hostname_resolution_notifier = -1;
int pipefd[2];
int nfds = 0;
struct timeval *ends_at = NULL;
struct timeval delay = (struct timeval){ -1, -1 };
struct timeval *delay_p = NULL;
struct hostname_resolution_store resolution_store;
resolution_store.is_all_finished = false;
resolution_store.v6.ai = NULL;
resolution_store.v6.finished = false;
resolution_store.v6.has_error = false;
resolution_store.v4.ai = NULL;
resolution_store.v4.finished = false;
resolution_store.v4.has_error = false;
int last_family = 0;
int additional_capacity = 10;
int current_capacity = additional_capacity;
arg->connection_attempt_fds = allocate_connection_attempt_fds(additional_capacity);
arg->connection_attempt_fds_size = 0;
struct timeval resolution_delay_storage;
struct timeval *resolution_delay_expires_at = NULL;
struct timeval connection_attempt_delay_strage;
struct timeval *connection_attempt_delay_expires_at = NULL;
struct timeval user_specified_resolv_timeout_storage;
struct timeval *user_specified_resolv_timeout_at = NULL;
struct timeval user_specified_connect_timeout_storage;
struct timeval *user_specified_connect_timeout_at = NULL;
struct timespec now = current_clocktime_ts();
/* start of hostname resolution */
if (arg->family_size == 1) {
arg->wait = -1;
arg->getaddrinfo_shared = NULL;
int family = arg->families[0];
arg->remote.res = rsock_addrinfo(
arg->remote.host,
arg->remote.serv,
family,
SOCK_STREAM,
0
);
if (family == AF_INET6) {
resolution_store.v6.ai = arg->remote.res->ai;
resolution_store.v6.finished = true;
resolution_store.v4.finished = true;
} else if (family == AF_INET) {
resolution_store.v4.ai = arg->remote.res->ai;
resolution_store.v4.finished = true;
resolution_store.v6.finished = true;
}
resolution_store.is_all_finished = true;
} else {
if (pipe(pipefd) != 0) rb_syserr_fail(errno, "pipe(2)");
hostname_resolution_waiter = pipefd[0];
int waiter_flags = fcntl(hostname_resolution_waiter, F_GETFL, 0);
if (waiter_flags < 0) rb_syserr_fail(errno, "fcntl(2)");
if ((fcntl(hostname_resolution_waiter, F_SETFL, waiter_flags | O_NONBLOCK)) < 0) {
rb_syserr_fail(errno, "fcntl(2)");
}
arg->wait = hostname_resolution_waiter;
hostname_resolution_notifier = pipefd[1];
arg->getaddrinfo_shared = allocate_fast_fallback_getaddrinfo_shared(arg->family_size);
if (!arg->getaddrinfo_shared) rb_syserr_fail(errno, "calloc(3)");
rb_nativethread_lock_initialize(&arg->getaddrinfo_shared->lock);
arg->getaddrinfo_shared->notify = hostname_resolution_notifier;
arg->getaddrinfo_shared->node = arg->hostp ? ruby_strdup(arg->hostp) : NULL;
arg->getaddrinfo_shared->service = arg->portp ? ruby_strdup(arg->portp) : NULL;
arg->getaddrinfo_shared->refcount = arg->family_size + 1;
for (int i = 0; i < arg->family_size; i++) {
arg->getaddrinfo_entries[i] = &arg->getaddrinfo_shared->getaddrinfo_entries[i];
arg->getaddrinfo_entries[i]->shared = arg->getaddrinfo_shared;
struct addrinfo getaddrinfo_hints[arg->family_size];
allocate_fast_fallback_getaddrinfo_hints(
&getaddrinfo_hints[i],
arg->families[i],
remote_addrinfo_hints,
arg->additional_flags
);
arg->getaddrinfo_entries[i]->hints = getaddrinfo_hints[i];
arg->getaddrinfo_entries[i]->ai = NULL;
arg->getaddrinfo_entries[i]->family = arg->families[i];
arg->getaddrinfo_entries[i]->refcount = 2;
arg->getaddrinfo_entries[i]->has_syserr = false;
arg->getaddrinfo_entries[i]->test_sleep_ms = 0;
arg->getaddrinfo_entries[i]->test_ecode = 0;
/* for testing HEv2 */
if (!NIL_P(test_mode_settings) && RB_TYPE_P(test_mode_settings, T_HASH)) {
const char *family_sym = arg->families[i] == AF_INET6 ? "ipv6" : "ipv4";
VALUE test_delay_setting = rb_hash_aref(test_mode_settings, ID2SYM(rb_intern("delay")));
if (!NIL_P(test_delay_setting)) {
VALUE rb_test_delay_ms = rb_hash_aref(test_delay_setting, ID2SYM(rb_intern(family_sym)));
long test_delay_ms = NIL_P(rb_test_delay_ms) ? 0 : rb_test_delay_ms;
arg->getaddrinfo_entries[i]->test_sleep_ms = test_delay_ms;
}
VALUE test_error_setting = rb_hash_aref(test_mode_settings, ID2SYM(rb_intern("error")));
if (!NIL_P(test_error_setting)) {
VALUE rb_test_ecode = rb_hash_aref(test_error_setting, ID2SYM(rb_intern(family_sym)));
if (!NIL_P(rb_test_ecode)) {
arg->getaddrinfo_entries[i]->test_ecode = NUM2INT(rb_test_ecode);
}
}
}
if (raddrinfo_pthread_create(&threads[i], fork_safe_do_fast_fallback_getaddrinfo, arg->getaddrinfo_entries[i]) != 0) {
rsock_raise_resolution_error("getaddrinfo(3)", EAI_AGAIN);
}
pthread_detach(threads[i]);
}
if (NIL_P(resolv_timeout)) {
user_specified_resolv_timeout_storage = (struct timeval){ -1, -1 };
} else {
struct timeval resolv_timeout_tv = rb_time_interval(resolv_timeout);
user_specified_resolv_timeout_storage = add_ts_to_tv(resolv_timeout_tv, now);
}
user_specified_resolv_timeout_at = &user_specified_resolv_timeout_storage;
}
while (true) {
/* start of connection */
if (any_addrinfos(&resolution_store) &&
!resolution_delay_expires_at &&
!connection_attempt_delay_expires_at) {
while ((remote_ai = pick_addrinfo(&resolution_store, last_family))) {
int fd = -1;
#if !defined(INET6) && defined(AF_INET6)
if (remote_ai->ai_family == AF_INET6) {
if (any_addrinfos(&resolution_store)) continue;
if (!in_progress_fds(arg->connection_attempt_fds_size)) break;
if (resolution_store.is_all_finished) break;
if (local_status < 0) {
host = arg->local.host;
serv = arg->local.serv;
} else {
host = arg->remote.host;
serv = arg->remote.serv;
}
if (last_error.type == RESOLUTION_ERROR) {
rsock_raise_resolution_error(syscall, last_error.ecode);
} else {
rsock_syserr_fail_host_port(last_error.ecode, syscall, host, serv);
}
}
#endif
local_ai = NULL;
if (arg->local.res) {
for (local_ai = arg->local.res->ai; local_ai; local_ai = local_ai->ai_next) {
if (local_ai->ai_family == remote_ai->ai_family) break;
}
if (!local_ai) {
if (any_addrinfos(&resolution_store)) continue;
if (in_progress_fds(arg->connection_attempt_fds_size)) break;
if (!resolution_store.is_all_finished) break;
/* Use a different family local address if no choice, this
* will cause EAFNOSUPPORT. */
rsock_syserr_fail_host_port(EAFNOSUPPORT, syscall, arg->local.host, arg->local.serv);
}
}
status = rsock_socket(remote_ai->ai_family, remote_ai->ai_socktype, remote_ai->ai_protocol);
syscall = "socket(2)";
if (status < 0) {
last_error.type = SYSCALL_ERROR;
last_error.ecode = errno;
if (any_addrinfos(&resolution_store)) continue;
if (in_progress_fds(arg->connection_attempt_fds_size)) break;
if (!resolution_store.is_all_finished) break;
if (local_status < 0) {
host = arg->local.host;
serv = arg->local.serv;
} else {
host = arg->remote.host;
serv = arg->remote.serv;
}
if (last_error.type == RESOLUTION_ERROR) {
rsock_raise_resolution_error(syscall, last_error.ecode);
} else {
rsock_syserr_fail_host_port(last_error.ecode, syscall, host, serv);
}
}
fd = status;
if (local_ai) {
#if !defined(_WIN32) && !defined(__CYGWIN__)
status = 1;
if ((setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&status, (socklen_t)sizeof(status))) < 0) {
rb_syserr_fail(errno, "setsockopt(2)");
}
#endif
status = bind(fd, local_ai->ai_addr, local_ai->ai_addrlen);
local_status = status;
syscall = "bind(2)";
if (status < 0) {
last_error.type = SYSCALL_ERROR;
last_error.ecode = errno;
close(fd);
if (any_addrinfos(&resolution_store)) continue;
if (in_progress_fds(arg->connection_attempt_fds_size)) break;
if (!resolution_store.is_all_finished) break;
if (local_status < 0) {
host = arg->local.host;
serv = arg->local.serv;
} else {
host = arg->remote.host;
serv = arg->remote.serv;
}
if (last_error.type == RESOLUTION_ERROR) {
rsock_raise_resolution_error(syscall, last_error.ecode);
} else {
rsock_syserr_fail_host_port(last_error.ecode, syscall, host, serv);
}
}
}
syscall = "connect(2)";
if (any_addrinfos(&resolution_store) ||
in_progress_fds(arg->connection_attempt_fds_size) ||
!resolution_store.is_all_finished) {
socket_nonblock_set(fd);
status = connect(fd, remote_ai->ai_addr, remote_ai->ai_addrlen);
last_family = remote_ai->ai_family;
} else {
if (!NIL_P(connect_timeout)) {
user_specified_connect_timeout_storage = rb_time_interval(connect_timeout);
user_specified_connect_timeout_at = &user_specified_connect_timeout_storage;
}
VALUE timeout =
(user_specified_connect_timeout_at && is_infinity(*user_specified_connect_timeout_at)) ?
Qnil : tv_to_seconds(user_specified_connect_timeout_at);
io = arg->io = rsock_init_sock(arg->self, fd);
status = rsock_connect(io, remote_ai->ai_addr, remote_ai->ai_addrlen, 0, timeout);
}
if (status == 0) {
connected_fd = fd;
break;
}
if (errno == EINPROGRESS) {
if (current_capacity == arg->connection_attempt_fds_size) {
current_capacity = reallocate_connection_attempt_fds(
&arg->connection_attempt_fds,
current_capacity,
additional_capacity
);
}
arg->connection_attempt_fds[arg->connection_attempt_fds_size] = fd;
(arg->connection_attempt_fds_size)++;
set_timeout_tv(&connection_attempt_delay_strage, 250, now);
connection_attempt_delay_expires_at = &connection_attempt_delay_strage;
if (!any_addrinfos(&resolution_store)) {
if (NIL_P(connect_timeout)) {
user_specified_connect_timeout_storage = (struct timeval){ -1, -1 };
} else {
struct timeval connect_timeout_tv = rb_time_interval(connect_timeout);
user_specified_connect_timeout_storage = add_ts_to_tv(connect_timeout_tv, now);
}
user_specified_connect_timeout_at = &user_specified_connect_timeout_storage;
}
break;
}
last_error.type = SYSCALL_ERROR;
last_error.ecode = errno;
if (NIL_P(io)) {
close(fd);
} else {
rb_io_close(io);
}
if (any_addrinfos(&resolution_store)) continue;
if (in_progress_fds(arg->connection_attempt_fds_size)) break;
if (!resolution_store.is_all_finished) break;
if (local_status < 0) {
host = arg->local.host;
serv = arg->local.serv;
} else {
host = arg->remote.host;
serv = arg->remote.serv;
}
if (last_error.type == RESOLUTION_ERROR) {
rsock_raise_resolution_error(syscall, last_error.ecode);
} else {
rsock_syserr_fail_host_port(last_error.ecode, syscall, host, serv);
}
}
}
if (connected_fd >= 0) break;
ends_at = select_expires_at(
&resolution_store,
resolution_delay_expires_at,
connection_attempt_delay_expires_at,
user_specified_resolv_timeout_at,
user_specified_connect_timeout_at
);
if (ends_at) {
delay = tv_to_timeout(ends_at, now);
delay_p = &delay;
} else {
if (((resolution_store.v6.finished && !resolution_store.v4.finished) ||
(resolution_store.v4.finished && !resolution_store.v6.finished)) &&
!any_addrinfos(&resolution_store) &&
!in_progress_fds(arg->connection_attempt_fds_size)) {
/* A limited timeout is introduced to prevent select(2) from hanging when it is exclusively
* waiting for name resolution and write(2) failure occurs in a child thread. */
delay.tv_sec = 0;
delay.tv_usec = 50000;
delay_p = &delay;
} else {
delay_p = NULL;
}
}
nfds = 0;
rb_fd_zero(&arg->writefds);
if (in_progress_fds(arg->connection_attempt_fds_size)) {
int n = 0;
for (int i = 0; i < arg->connection_attempt_fds_size; i++) {
int cfd = arg->connection_attempt_fds[i];
if (cfd < 0) continue;
if (cfd > n) n = cfd;
rb_fd_set(cfd, &arg->writefds);
}
if (n > 0) n++;
nfds = n;
}
rb_fd_zero(&arg->readfds);
if (arg->family_size > 1) {
rb_fd_set(hostname_resolution_waiter, &arg->readfds);
if ((hostname_resolution_waiter + 1) > nfds) {
nfds = hostname_resolution_waiter + 1;
}
}
status = rb_thread_fd_select(nfds, &arg->readfds, &arg->writefds, NULL, delay_p);
now = current_clocktime_ts();
if (is_timeout_tv(resolution_delay_expires_at, now)) {
resolution_delay_expires_at = NULL;
}
if (is_timeout_tv(connection_attempt_delay_expires_at, now)) {
connection_attempt_delay_expires_at = NULL;
}
if (status < 0 && (errno && errno != EINTR)) rb_syserr_fail(errno, "select(2)");
if (status > 0) {
/* check for connection */
if (in_progress_fds(arg->connection_attempt_fds_size)) {
for (int i = 0; i < arg->connection_attempt_fds_size; i++) {
int fd = arg->connection_attempt_fds[i];
if (fd < 0 || !rb_fd_isset(fd, &arg->writefds)) continue;
int err;
socklen_t len = sizeof(err);
status = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
if (status < 0) {
last_error.type = SYSCALL_ERROR;
last_error.ecode = errno;
close(fd);
if (any_addrinfos(&resolution_store)) continue;
if (in_progress_fds(arg->connection_attempt_fds_size)) break;
if (!resolution_store.is_all_finished) break;
if (local_status < 0) {
host = arg->local.host;
serv = arg->local.serv;
} else {
host = arg->remote.host;
serv = arg->remote.serv;
}
if (last_error.type == RESOLUTION_ERROR) {
rsock_raise_resolution_error(syscall, last_error.ecode);
} else {
rsock_syserr_fail_host_port(last_error.ecode, syscall, host, serv);
}
}
if (err == 0) { /* success */
remove_connection_attempt_fd(
arg->connection_attempt_fds,
&arg->connection_attempt_fds_size,
fd
);
connected_fd = fd;
break;
} else { /* fail */
close(fd);
remove_connection_attempt_fd(
arg->connection_attempt_fds,
&arg->connection_attempt_fds_size,
fd
);
last_error.type = SYSCALL_ERROR;
last_error.ecode = err;
}
}
if (connected_fd >= 0) break;
if (!in_progress_fds(arg->connection_attempt_fds_size)) {
if (!any_addrinfos(&resolution_store) && resolution_store.is_all_finished) {
if (local_status < 0) {
host = arg->local.host;
serv = arg->local.serv;
} else {
host = arg->remote.host;
serv = arg->remote.serv;
}
if (last_error.type == RESOLUTION_ERROR) {
rsock_raise_resolution_error(syscall, last_error.ecode);
} else {
rsock_syserr_fail_host_port(last_error.ecode, syscall, host, serv);
}
}
connection_attempt_delay_expires_at = NULL;
user_specified_connect_timeout_at = NULL;
}
}
/* check for hostname resolution */
if (!resolution_store.is_all_finished && rb_fd_isset(hostname_resolution_waiter, &arg->readfds)) {
while (true) {
resolved_type_size = read(
hostname_resolution_waiter,
resolved_type,
sizeof(resolved_type) - 1
);
if (resolved_type_size > 0) {
resolved_type[resolved_type_size] = '\0';
if (resolved_type[0] == IPV6_HOSTNAME_RESOLVED) {
resolution_store.v6.finished = true;
if (arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err &&
arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err != EAI_ADDRFAMILY) {
if (!resolution_store.v4.finished || resolution_store.v4.has_error) {
last_error.type = RESOLUTION_ERROR;
last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err;
syscall = "getaddrinfo(3)";
}
resolution_store.v6.has_error = true;
} else {
resolution_store.v6.ai = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->ai;
}
if (resolution_store.v4.finished) {
resolution_store.is_all_finished = true;
resolution_delay_expires_at = NULL;
user_specified_resolv_timeout_at = NULL;
break;
}
} else if (resolved_type[0] == IPV4_HOSTNAME_RESOLVED) {
resolution_store.v4.finished = true;
if (arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err) {
if (!resolution_store.v6.finished || resolution_store.v6.has_error) {
last_error.type = RESOLUTION_ERROR;
last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err;
syscall = "getaddrinfo(3)";
}
resolution_store.v4.has_error = true;
} else {
resolution_store.v4.ai = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->ai;
}
if (resolution_store.v6.finished) {
resolution_store.is_all_finished = true;
resolution_delay_expires_at = NULL;
user_specified_resolv_timeout_at = NULL;
break;
}
} else {
/* Retry to read from hostname_resolution_waiter */
}
} else if (resolved_type_size < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
errno = 0;
break;
} else {
/* Retry to read from hostname_resolution_waiter */
}
if (!resolution_store.v6.finished &&
resolution_store.v4.finished &&
!resolution_store.v4.has_error) {
set_timeout_tv(&resolution_delay_storage, 50, now);
resolution_delay_expires_at = &resolution_delay_storage;
}
}
}
status = 0;
}
/* For cases where write(2) fails in child threads */
if (!resolution_store.is_all_finished) {
if (!resolution_store.v6.finished && arg->getaddrinfo_entries[IPV6_ENTRY_POS]->has_syserr) {
resolution_store.v6.finished = true;
if (arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err) {
if (!resolution_store.v4.finished || resolution_store.v4.has_error) {
last_error.type = RESOLUTION_ERROR;
last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err;
syscall = "getaddrinfo(3)";
}
resolution_store.v6.has_error = true;
} else {
resolution_store.v6.ai = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->ai;
}
if (resolution_store.v4.finished) {
resolution_store.is_all_finished = true;
resolution_delay_expires_at = NULL;
user_specified_resolv_timeout_at = NULL;
}
}
if (!resolution_store.v4.finished && arg->getaddrinfo_entries[IPV4_ENTRY_POS]->has_syserr) {
resolution_store.v4.finished = true;
if (arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err) {
if (!resolution_store.v6.finished || resolution_store.v6.has_error) {
last_error.type = RESOLUTION_ERROR;
last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err;
syscall = "getaddrinfo(3)";
}
resolution_store.v4.has_error = true;
} else {
resolution_store.v4.ai = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->ai;
}
if (resolution_store.v6.finished) {
resolution_store.is_all_finished = true;
resolution_delay_expires_at = NULL;
user_specified_resolv_timeout_at = NULL;
} else {
set_timeout_tv(&resolution_delay_storage, 50, now);
resolution_delay_expires_at = &resolution_delay_storage;
}
}
}
if (!any_addrinfos(&resolution_store)) {
if (!in_progress_fds(arg->connection_attempt_fds_size) &&
resolution_store.is_all_finished) {
if (local_status < 0) {
host = arg->local.host;
serv = arg->local.serv;
} else {
host = arg->remote.host;
serv = arg->remote.serv;
}
if (last_error.type == RESOLUTION_ERROR) {
rsock_raise_resolution_error(syscall, last_error.ecode);
} else {
rsock_syserr_fail_host_port(last_error.ecode, syscall, host, serv);
}
}
if ((is_timeout_tv(user_specified_resolv_timeout_at, now) ||
resolution_store.is_all_finished) &&
(is_timeout_tv(user_specified_connect_timeout_at, now) ||
!in_progress_fds(arg->connection_attempt_fds_size))) {
VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno"));
VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT"));
rb_raise(etimedout_error, "user specified timeout");
}
}
}
if (NIL_P(arg->io)) {
/* create new instance */
arg->io = rsock_init_sock(arg->self, connected_fd);
}
return arg->io;
}
static VALUE
fast_fallback_inetsock_cleanup(VALUE v)
{
struct fast_fallback_inetsock_arg *arg = (void *)v;
struct fast_fallback_getaddrinfo_shared *getaddrinfo_shared = arg->getaddrinfo_shared;
if (arg->remote.res) {
rb_freeaddrinfo(arg->remote.res);
arg->remote.res = 0;
}
if (arg->local.res) {
rb_freeaddrinfo(arg->local.res);
arg->local.res = 0;
}
if (arg->wait != -1) close(arg->wait);
if (getaddrinfo_shared) {
if (getaddrinfo_shared->notify != -1) close(getaddrinfo_shared->notify);
getaddrinfo_shared->notify = -1;
int shared_need_free = 0;
struct addrinfo *ais[arg->family_size];
for (int i = 0; i < arg->family_size; i++) ais[i] = NULL;
rb_nativethread_lock_lock(&getaddrinfo_shared->lock);
{
for (int i = 0; i < arg->family_size; i++) {
struct fast_fallback_getaddrinfo_entry *getaddrinfo_entry = arg->getaddrinfo_entries[i];
if (!getaddrinfo_entry) continue;
if (--(getaddrinfo_entry->refcount) == 0) {
ais[i] = getaddrinfo_entry->ai;
getaddrinfo_entry->ai = NULL;
}
}
if (--(getaddrinfo_shared->refcount) == 0) {
shared_need_free = 1;
}
}
rb_nativethread_lock_unlock(&getaddrinfo_shared->lock);
for (int i = 0; i < arg->family_size; i++) {
if (ais[i]) freeaddrinfo(ais[i]);
}
if (getaddrinfo_shared && shared_need_free) {
free_fast_fallback_getaddrinfo_shared(&getaddrinfo_shared);
}
}
int connection_attempt_fd;
for (int i = 0; i < arg->connection_attempt_fds_size; i++) {
connection_attempt_fd = arg->connection_attempt_fds[i];
if (connection_attempt_fd >= 0) {
int error = 0;
socklen_t len = sizeof(error);
getsockopt(connection_attempt_fd, SOL_SOCKET, SO_ERROR, &error, &len);
if (error == 0) shutdown(connection_attempt_fd, SHUT_RDWR);
close(connection_attempt_fd);
}
}
if (arg->readfds.fdset) rb_fd_term(&arg->readfds);
if (arg->writefds.fdset) rb_fd_term(&arg->writefds);
if (arg->connection_attempt_fds) {
free(arg->connection_attempt_fds);
arg->connection_attempt_fds = NULL;
}
return Qnil;
}
VALUE
rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout, VALUE fast_fallback, VALUE test_mode_settings)
{
if (type == INET_CLIENT && FAST_FALLBACK_INIT_INETSOCK_IMPL == 1 && RTEST(fast_fallback)) {
struct rb_addrinfo *local_res = NULL;
char *hostp, *portp;
char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
int additional_flags = 0;
hostp = raddrinfo_host_str(remote_host, hbuf, sizeof(hbuf), &additional_flags);
portp = raddrinfo_port_str(remote_serv, pbuf, sizeof(pbuf), &additional_flags);
if (!is_specified_ip_address(hostp)) {
int target_families[2] = { 0, 0 };
int resolving_family_size = 0;
/*
* Maybe also accept a local address
*/
if (!NIL_P(local_host) || !NIL_P(local_serv)) {
local_res = rsock_addrinfo(
local_host,
local_serv,
AF_UNSPEC,
SOCK_STREAM,
0
);
struct addrinfo *tmp_p = local_res->ai;
for (tmp_p; tmp_p != NULL; tmp_p = tmp_p->ai_next) {
if (target_families[0] == 0 && tmp_p->ai_family == AF_INET6) {
target_families[0] = AF_INET6;
resolving_family_size++;
}
if (target_families[1] == 0 && tmp_p->ai_family == AF_INET) {
target_families[1] = AF_INET;
resolving_family_size++;
}
}
} else {
resolving_family_size = 2;
target_families[0] = AF_INET6;
target_families[1] = AF_INET;
}
struct fast_fallback_inetsock_arg fast_fallback_arg;
memset(&fast_fallback_arg, 0, sizeof(fast_fallback_arg));
fast_fallback_arg.self = self;
fast_fallback_arg.io = Qnil;
fast_fallback_arg.remote.host = remote_host;
fast_fallback_arg.remote.serv = remote_serv;
fast_fallback_arg.remote.res = 0;
fast_fallback_arg.local.host = local_host;
fast_fallback_arg.local.serv = local_serv;
fast_fallback_arg.local.res = local_res;
fast_fallback_arg.type = type;
fast_fallback_arg.resolv_timeout = resolv_timeout;
fast_fallback_arg.connect_timeout = connect_timeout;
fast_fallback_arg.hostp = hostp;
fast_fallback_arg.portp = portp;
fast_fallback_arg.additional_flags = additional_flags;
int resolving_families[resolving_family_size];
int resolving_family_index = 0;
for (int i = 0; 2 > i; i++) {
if (target_families[i] != 0) {
resolving_families[resolving_family_index] = target_families[i];
resolving_family_index++;
}
}
fast_fallback_arg.families = resolving_families;
fast_fallback_arg.family_size = resolving_family_size;
fast_fallback_arg.test_mode_settings = test_mode_settings;
rb_fd_init(&fast_fallback_arg.readfds);
rb_fd_init(&fast_fallback_arg.writefds);
return rb_ensure(init_fast_fallback_inetsock_internal, (VALUE)&fast_fallback_arg,
fast_fallback_inetsock_cleanup, (VALUE)&fast_fallback_arg);
}
}
struct inetsock_arg arg;
arg.self = self;
arg.io = Qnil;
arg.remote.host = remote_host;
arg.remote.serv = remote_serv;
arg.remote.res = 0;
arg.local.host = local_host;
arg.local.serv = local_serv;
arg.local.res = 0;
arg.type = type;
arg.resolv_timeout = resolv_timeout;
arg.connect_timeout = connect_timeout;
return rb_ensure(init_inetsock_internal, (VALUE)&arg,
inetsock_cleanup, (VALUE)&arg);
}
#endif
static ID id_numeric, id_hostname;
int
rsock_revlookup_flag(VALUE revlookup, int *norevlookup)
{
#define return_norevlookup(x) {*norevlookup = (x); return 1;}
ID id;
switch (revlookup) {
case Qtrue: return_norevlookup(0);
case Qfalse: return_norevlookup(1);
case Qnil: break;
default:
Check_Type(revlookup, T_SYMBOL);
id = SYM2ID(revlookup);
if (id == id_numeric) return_norevlookup(1);
if (id == id_hostname) return_norevlookup(0);
rb_raise(rb_eArgError, "invalid reverse_lookup flag: :%s", rb_id2name(id));
}
return 0;
#undef return_norevlookup
}
/*
* call-seq:
* ipsocket.inspect -> string
*
* Return a string describing this IPSocket object.
*/
static VALUE
ip_inspect(VALUE sock)
{
VALUE str = rb_call_super(0, 0);
rb_io_t *fptr = RFILE(sock)->fptr;
union_sockaddr addr;
socklen_t len = (socklen_t)sizeof addr;
ID id;
if (fptr && fptr->fd >= 0 &&
getsockname(fptr->fd, &addr.addr, &len) >= 0 &&
(id = rsock_intern_family(addr.addr.sa_family)) != 0) {
VALUE family = rb_id2str(id);
char hbuf[1024], pbuf[1024];
long slen = RSTRING_LEN(str);
const char last = (slen > 1 && RSTRING_PTR(str)[slen - 1] == '>') ?
(--slen, '>') : 0;
str = rb_str_subseq(str, 0, slen);
rb_str_cat_cstr(str, ", ");
rb_str_append(str, family);
if (!rb_getnameinfo(&addr.addr, len, hbuf, sizeof(hbuf),
pbuf, sizeof(pbuf), NI_NUMERICHOST | NI_NUMERICSERV)) {
rb_str_cat_cstr(str, ", ");
rb_str_cat_cstr(str, hbuf);
rb_str_cat_cstr(str, ", ");
rb_str_cat_cstr(str, pbuf);
}
if (last) rb_str_cat(str, &last, 1);
}
return str;
}
/*
* call-seq:
* ipsocket.addr([reverse_lookup]) => [address_family, port, hostname, numeric_address]
*
* Returns the local address as an array which contains
* address_family, port, hostname and numeric_address.
*
* If +reverse_lookup+ is +true+ or +:hostname+,
* hostname is obtained from numeric_address using reverse lookup.
* Or if it is +false+, or +:numeric+,
* hostname is the same as numeric_address.
* Or if it is +nil+ or omitted, obeys to +ipsocket.do_not_reverse_lookup+.
* See +Socket.getaddrinfo+ also.
*
* TCPSocket.open("www.ruby-lang.org", 80) {|sock|
* p sock.addr #=> ["AF_INET", 49429, "hal", "192.168.0.128"]
* p sock.addr(true) #=> ["AF_INET", 49429, "hal", "192.168.0.128"]
* p sock.addr(false) #=> ["AF_INET", 49429, "192.168.0.128", "192.168.0.128"]
* p sock.addr(:hostname) #=> ["AF_INET", 49429, "hal", "192.168.0.128"]
* p sock.addr(:numeric) #=> ["AF_INET", 49429, "192.168.0.128", "192.168.0.128"]
* }
*
*/
static VALUE
ip_addr(int argc, VALUE *argv, VALUE sock)
{
union_sockaddr addr;
socklen_t len = (socklen_t)sizeof addr;
int norevlookup;
if (argc < 1 || !rsock_revlookup_flag(argv[0], &norevlookup))
norevlookup = rb_io_mode(sock) & FMODE_NOREVLOOKUP;
if (getsockname(rb_io_descriptor(sock), &addr.addr, &len) < 0)
rb_sys_fail("getsockname(2)");
return rsock_ipaddr(&addr.addr, len, norevlookup);
}
/*
* call-seq:
* ipsocket.peeraddr([reverse_lookup]) => [address_family, port, hostname, numeric_address]
*
* Returns the remote address as an array which contains
* address_family, port, hostname and numeric_address.
* It is defined for connection oriented socket such as TCPSocket.
*
* If +reverse_lookup+ is +true+ or +:hostname+,
* hostname is obtained from numeric_address using reverse lookup.
* Or if it is +false+, or +:numeric+,
* hostname is the same as numeric_address.
* Or if it is +nil+ or omitted, obeys to +ipsocket.do_not_reverse_lookup+.
* See +Socket.getaddrinfo+ also.
*
* TCPSocket.open("www.ruby-lang.org", 80) {|sock|
* p sock.peeraddr #=> ["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68"]
* p sock.peeraddr(true) #=> ["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68"]
* p sock.peeraddr(false) #=> ["AF_INET", 80, "221.186.184.68", "221.186.184.68"]
* p sock.peeraddr(:hostname) #=> ["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68"]
* p sock.peeraddr(:numeric) #=> ["AF_INET", 80, "221.186.184.68", "221.186.184.68"]
* }
*
*/
static VALUE
ip_peeraddr(int argc, VALUE *argv, VALUE sock)
{
union_sockaddr addr;
socklen_t len = (socklen_t)sizeof addr;
int norevlookup;
if (argc < 1 || !rsock_revlookup_flag(argv[0], &norevlookup))
norevlookup = rb_io_mode(sock) & FMODE_NOREVLOOKUP;
if (getpeername(rb_io_descriptor(sock), &addr.addr, &len) < 0)
rb_sys_fail("getpeername(2)");
return rsock_ipaddr(&addr.addr, len, norevlookup);
}
/*
* call-seq:
* ipsocket.recvfrom(maxlen) => [mesg, ipaddr]
* ipsocket.recvfrom(maxlen, flags) => [mesg, ipaddr]
*
* Receives a message and return the message as a string and
* an address which the message come from.
*
* _maxlen_ is the maximum number of bytes to receive.
*
* _flags_ should be a bitwise OR of Socket::MSG_* constants.
*
* ipaddr is the same as IPSocket#{peeraddr,addr}.
*
* u1 = UDPSocket.new
* u1.bind("127.0.0.1", 4913)
* u2 = UDPSocket.new
* u2.send "uuuu", 0, "127.0.0.1", 4913
* p u1.recvfrom(10) #=> ["uuuu", ["AF_INET", 33230, "localhost", "127.0.0.1"]]
*
*/
static VALUE
ip_recvfrom(int argc, VALUE *argv, VALUE sock)
{
return rsock_s_recvfrom(sock, argc, argv, RECV_IP);
}
/*
* call-seq:
* IPSocket.getaddress(host) => ipaddress
*
* Lookups the IP address of _host_.
*
* require 'socket'
*
* IPSocket.getaddress("localhost") #=> "127.0.0.1"
* IPSocket.getaddress("ip6-localhost") #=> "::1"
*
*/
static VALUE
ip_s_getaddress(VALUE obj, VALUE host)
{
union_sockaddr addr;
struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0);
socklen_t len = res->ai->ai_addrlen;
/* just take the first one */
memcpy(&addr, res->ai->ai_addr, len);
rb_freeaddrinfo(res);
return rsock_make_ipaddr(&addr.addr, len);
}
void
rsock_init_ipsocket(void)
{
/*
* Document-class: IPSocket < BasicSocket
*
* IPSocket is the super class of TCPSocket and UDPSocket.
*/
rb_cIPSocket = rb_define_class("IPSocket", rb_cBasicSocket);
rb_define_method(rb_cIPSocket, "inspect", ip_inspect, 0);
rb_define_method(rb_cIPSocket, "addr", ip_addr, -1);
rb_define_method(rb_cIPSocket, "peeraddr", ip_peeraddr, -1);
rb_define_method(rb_cIPSocket, "recvfrom", ip_recvfrom, -1);
rb_define_singleton_method(rb_cIPSocket, "getaddress", ip_s_getaddress, 1);
rb_undef_method(rb_cIPSocket, "getpeereid");
id_numeric = rb_intern_const("numeric");
id_hostname = rb_intern_const("hostname");
}