summaryrefslogtreecommitdiff
path: root/ext/socket/ipsocket.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/socket/ipsocket.c')
-rw-r--r--ext/socket/ipsocket.c163
1 files changed, 129 insertions, 34 deletions
diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c
index 6ca0c0cc71..b5cdc60080 100644
--- a/ext/socket/ipsocket.c
+++ b/ext/socket/ipsocket.c
@@ -15,21 +15,24 @@ struct inetsock_arg
VALUE sock;
struct {
VALUE host, serv;
- struct addrinfo *res;
+ struct rb_addrinfo *res;
} remote, local;
int type;
int fd;
+ VALUE resolv_timeout;
+ VALUE connect_timeout;
};
static VALUE
-inetsock_cleanup(struct inetsock_arg *arg)
+inetsock_cleanup(VALUE v)
{
+ struct inetsock_arg *arg = (void *)v;
if (arg->remote.res) {
- freeaddrinfo(arg->remote.res);
+ rb_freeaddrinfo(arg->remote.res);
arg->remote.res = 0;
}
if (arg->local.res) {
- freeaddrinfo(arg->local.res);
+ rb_freeaddrinfo(arg->local.res);
arg->local.res = 0;
}
if (arg->fd >= 0) {
@@ -39,33 +42,63 @@ inetsock_cleanup(struct inetsock_arg *arg)
}
static VALUE
-init_inetsock_internal(struct inetsock_arg *arg)
+init_inetsock_internal(VALUE v)
{
+ struct inetsock_arg *arg = (void *)v;
+ int error = 0;
int type = arg->type;
- struct addrinfo *res;
- int fd, status = 0;
+ 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;
+
+ if (!NIL_P(connect_timeout)) {
+ tv_storage = rb_time_interval(connect_timeout);
+ tv = &tv_storage;
+ }
+
+ arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv,
+ family, SOCK_STREAM,
+ (type == INET_SERVER) ? AI_PASSIVE : 0);
+
- arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv, 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, SOCK_STREAM, 0);
+ arg->local.res = rsock_addrinfo(arg->local.host, arg->local.serv,
+ family, SOCK_STREAM, 0);
}
arg->fd = fd = -1;
- for (res = arg->remote.res; res; res = res->ai_next) {
+ 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;
@@ -79,19 +112,26 @@ init_inetsock_internal(struct inetsock_arg *arg)
syscall = "bind(2)";
}
else {
- if (arg->local.res) {
- status = bind(fd, arg->local.res->ai_addr, arg->local.res->ai_addrlen);
+ 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));
+ (type == INET_SOCKS), tv);
syscall = "connect(2)";
}
}
if (status < 0) {
+ error = errno;
close(fd);
arg->fd = fd = -1;
continue;
@@ -99,7 +139,17 @@ init_inetsock_internal(struct inetsock_arg *arg)
break;
}
if (status < 0) {
- rb_sys_fail(syscall);
+ 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;
@@ -107,8 +157,9 @@ init_inetsock_internal(struct inetsock_arg *arg)
if (type == INET_SERVER) {
status = listen(fd, SOMAXCONN);
if (status < 0) {
+ error = errno;
close(fd);
- rb_sys_fail("listen(2)");
+ rb_syserr_fail(error, "listen(2)");
}
}
@@ -118,7 +169,8 @@ init_inetsock_internal(struct inetsock_arg *arg)
VALUE
rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv,
- VALUE local_host, VALUE local_serv, int type)
+ VALUE local_host, VALUE local_serv, int type,
+ VALUE resolv_timeout, VALUE connect_timeout)
{
struct inetsock_arg arg;
arg.sock = sock;
@@ -130,6 +182,8 @@ rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_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);
}
@@ -159,6 +213,43 @@ rsock_revlookup_flag(VALUE revlookup, int *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
@@ -167,8 +258,8 @@ rsock_revlookup_flag(VALUE revlookup, int *norevlookup)
* 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 same as numeric_address.
- * Or if it is +nil+ or ommitted, obeys to +ipsocket.do_not_reverse_lookup+.
+ * 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|
@@ -184,7 +275,7 @@ static VALUE
ip_addr(int argc, VALUE *argv, VALUE sock)
{
rb_io_t *fptr;
- struct sockaddr_storage addr;
+ union_sockaddr addr;
socklen_t len = (socklen_t)sizeof addr;
int norevlookup;
@@ -192,9 +283,9 @@ ip_addr(int argc, VALUE *argv, VALUE sock)
if (argc < 1 || !rsock_revlookup_flag(argv[0], &norevlookup))
norevlookup = fptr->mode & FMODE_NOREVLOOKUP;
- if (getsockname(fptr->fd, (struct sockaddr*)&addr, &len) < 0)
+ if (getsockname(fptr->fd, &addr.addr, &len) < 0)
rb_sys_fail("getsockname(2)");
- return rsock_ipaddr((struct sockaddr*)&addr, norevlookup);
+ return rsock_ipaddr(&addr.addr, len, norevlookup);
}
/*
@@ -208,13 +299,13 @@ ip_addr(int argc, VALUE *argv, VALUE sock)
* 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 same as numeric_address.
- * Or if it is +nil+ or ommitted, obeys to +ipsocket.do_not_reverse_lookup+.
+ * 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, "221.186.184.68", "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"]
@@ -225,7 +316,7 @@ static VALUE
ip_peeraddr(int argc, VALUE *argv, VALUE sock)
{
rb_io_t *fptr;
- struct sockaddr_storage addr;
+ union_sockaddr addr;
socklen_t len = (socklen_t)sizeof addr;
int norevlookup;
@@ -233,9 +324,9 @@ ip_peeraddr(int argc, VALUE *argv, VALUE sock)
if (argc < 1 || !rsock_revlookup_flag(argv[0], &norevlookup))
norevlookup = fptr->mode & FMODE_NOREVLOOKUP;
- if (getpeername(fptr->fd, (struct sockaddr*)&addr, &len) < 0)
+ if (getpeername(fptr->fd, &addr.addr, &len) < 0)
rb_sys_fail("getpeername(2)");
- return rsock_ipaddr((struct sockaddr*)&addr, norevlookup);
+ return rsock_ipaddr(&addr.addr, len, norevlookup);
}
/*
@@ -250,7 +341,7 @@ ip_peeraddr(int argc, VALUE *argv, VALUE sock)
*
* _flags_ should be a bitwise OR of Socket::MSG_* constants.
*
- * ipaddr is same as IPSocket#{peeraddr,addr}.
+ * ipaddr is the same as IPSocket#{peeraddr,addr}.
*
* u1 = UDPSocket.new
* u1.bind("127.0.0.1", 4913)
@@ -271,6 +362,8 @@ ip_recvfrom(int argc, VALUE *argv, VALUE sock)
*
* Lookups the IP address of _host_.
*
+ * require 'socket'
+ *
* IPSocket.getaddress("localhost") #=> "127.0.0.1"
* IPSocket.getaddress("ip6-localhost") #=> "::1"
*
@@ -278,14 +371,15 @@ ip_recvfrom(int argc, VALUE *argv, VALUE sock)
static VALUE
ip_s_getaddress(VALUE obj, VALUE host)
{
- struct sockaddr_storage addr;
- struct addrinfo *res = rsock_addrinfo(host, Qnil, SOCK_STREAM, 0);
+ 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_addr, res->ai_addrlen);
- freeaddrinfo(res);
+ memcpy(&addr, res->ai->ai_addr, len);
+ rb_freeaddrinfo(res);
- return rsock_make_ipaddr((struct sockaddr*)&addr);
+ return rsock_make_ipaddr(&addr.addr, len);
}
void
@@ -297,6 +391,7 @@ rsock_init_ipsocket(void)
* 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);