mirror of
https://github.com/ruby/ruby.git
synced 2025-08-23 13:04:13 +02:00

When making an outgoing TCP or UDP connection, set AI_ADDRCONFIG in the hints we send to getaddrinfo(3) (if supported). This will prompt the resolver to _NOT_ issue A or AAAA queries if the system does not actually have an IPv4 or IPv6 address (respectively). This makes outgoing connections marginally more efficient on non-dual-stack systems, since we don't have to try connecting to an address which can't possibly work. More importantly, however, this works around a race condition present in some older versions of glibc on aarch64 where it could accidently send the two outgoing DNS queries with the same DNS txnid, and get confused when receiving the responses. This manifests as outgoing connections sometimes taking 5 seconds (the DNS timeout before retry) to be made. Fixes #19144
410 lines
12 KiB
C
410 lines
12 KiB
C
/************************************************
|
|
|
|
ipsocket.c -
|
|
|
|
created at: Thu Mar 31 12:21:29 JST 1994
|
|
|
|
Copyright (C) 1993-2007 Yukihiro Matsumoto
|
|
|
|
************************************************/
|
|
|
|
#include "rubysocket.h"
|
|
|
|
struct inetsock_arg
|
|
{
|
|
VALUE sock;
|
|
struct {
|
|
VALUE host, serv;
|
|
struct rb_addrinfo *res;
|
|
} remote, local;
|
|
int type;
|
|
int fd;
|
|
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->fd >= 0) {
|
|
close(arg->fd);
|
|
}
|
|
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 fd, status = 0, local = 0;
|
|
int family = AF_UNSPEC;
|
|
const char *syscall = 0;
|
|
VALUE connect_timeout = arg->connect_timeout;
|
|
struct timeval tv_storage;
|
|
struct timeval *tv = NULL;
|
|
int remote_addrinfo_hints = 0;
|
|
|
|
if (!NIL_P(connect_timeout)) {
|
|
tv_storage = rb_time_interval(connect_timeout);
|
|
tv = &tv_storage;
|
|
}
|
|
|
|
if (type == INET_SERVER) {
|
|
remote_addrinfo_hints |= AI_PASSIVE;
|
|
}
|
|
#ifdef HAVE_CONST_AI_ADDRCONFIG
|
|
remote_addrinfo_hints |= AI_ADDRCONFIG;
|
|
#endif
|
|
|
|
arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv,
|
|
family, SOCK_STREAM, remote_addrinfo_hints);
|
|
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
arg->fd = fd = -1;
|
|
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)";
|
|
fd = status;
|
|
if (fd < 0) {
|
|
error = errno;
|
|
continue;
|
|
}
|
|
arg->fd = 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(fd, res->ai_addr, res->ai_addrlen,
|
|
(type == INET_SOCKS), tv);
|
|
syscall = "connect(2)";
|
|
}
|
|
}
|
|
|
|
if (status < 0) {
|
|
error = errno;
|
|
close(fd);
|
|
arg->fd = fd = -1;
|
|
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);
|
|
}
|
|
|
|
arg->fd = -1;
|
|
|
|
if (type == INET_SERVER) {
|
|
status = listen(fd, SOMAXCONN);
|
|
if (status < 0) {
|
|
error = errno;
|
|
close(fd);
|
|
rb_syserr_fail(error, "listen(2)");
|
|
}
|
|
}
|
|
|
|
/* create new instance */
|
|
return rsock_init_sock(arg->sock, fd);
|
|
}
|
|
|
|
VALUE
|
|
rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv,
|
|
VALUE local_host, VALUE local_serv, int type,
|
|
VALUE resolv_timeout, VALUE connect_timeout)
|
|
{
|
|
struct inetsock_arg arg;
|
|
arg.sock = sock;
|
|
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.fd = -1;
|
|
arg.resolv_timeout = resolv_timeout;
|
|
arg.connect_timeout = connect_timeout;
|
|
return rb_ensure(init_inetsock_internal, (VALUE)&arg,
|
|
inetsock_cleanup, (VALUE)&arg);
|
|
}
|
|
|
|
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)
|
|
{
|
|
rb_io_t *fptr;
|
|
union_sockaddr addr;
|
|
socklen_t len = (socklen_t)sizeof addr;
|
|
int norevlookup;
|
|
|
|
GetOpenFile(sock, fptr);
|
|
|
|
if (argc < 1 || !rsock_revlookup_flag(argv[0], &norevlookup))
|
|
norevlookup = fptr->mode & FMODE_NOREVLOOKUP;
|
|
if (getsockname(fptr->fd, &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)
|
|
{
|
|
rb_io_t *fptr;
|
|
union_sockaddr addr;
|
|
socklen_t len = (socklen_t)sizeof addr;
|
|
int norevlookup;
|
|
|
|
GetOpenFile(sock, fptr);
|
|
|
|
if (argc < 1 || !rsock_revlookup_flag(argv[0], &norevlookup))
|
|
norevlookup = fptr->mode & FMODE_NOREVLOOKUP;
|
|
if (getpeername(fptr->fd, &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");
|
|
}
|