diff options
Diffstat (limited to 'ext/socket/ipsocket.c')
| -rw-r--r-- | ext/socket/ipsocket.c | 163 |
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); |
