diff options
Diffstat (limited to 'ext/socket')
| -rw-r--r-- | ext/socket/ancdata.c | 14 | ||||
| -rw-r--r-- | ext/socket/basicsocket.c | 6 | ||||
| -rw-r--r-- | ext/socket/depend | 375 | ||||
| -rw-r--r-- | ext/socket/extconf.rb | 8 | ||||
| -rw-r--r-- | ext/socket/getaddrinfo.c | 10 | ||||
| -rw-r--r-- | ext/socket/getnameinfo.c | 18 | ||||
| -rw-r--r-- | ext/socket/ifaddr.c | 2 | ||||
| -rw-r--r-- | ext/socket/init.c | 136 | ||||
| -rw-r--r-- | ext/socket/ipsocket.c | 1325 | ||||
| -rw-r--r-- | ext/socket/lib/socket.rb | 530 | ||||
| -rw-r--r-- | ext/socket/mkconstants.rb | 46 | ||||
| -rw-r--r-- | ext/socket/raddrinfo.c | 774 | ||||
| -rw-r--r-- | ext/socket/rubysocket.h | 66 | ||||
| -rw-r--r-- | ext/socket/socket.c | 103 | ||||
| -rw-r--r-- | ext/socket/sockssocket.c | 7 | ||||
| -rw-r--r-- | ext/socket/tcpserver.c | 2 | ||||
| -rw-r--r-- | ext/socket/tcpsocket.c | 47 | ||||
| -rw-r--r-- | ext/socket/udpsocket.c | 57 | ||||
| -rw-r--r-- | ext/socket/unixsocket.c | 43 |
19 files changed, 3225 insertions, 344 deletions
diff --git a/ext/socket/ancdata.c b/ext/socket/ancdata.c index 7406177de2..f1e9e42524 100644 --- a/ext/socket/ancdata.c +++ b/ext/socket/ancdata.c @@ -1555,6 +1555,10 @@ bsock_recvmsg_internal(VALUE sock, ss = rb_recvmsg(fptr->fd, &mh, flags); + if (ss == 0 && !rsock_is_dgram(fptr)) { + return Qnil; + } + if (ss == -1) { int e; if (!nonblock && rb_io_maybe_wait_readable(errno, fptr->self, RUBY_IO_TIMEOUT_DEFAULT)) { @@ -1639,14 +1643,14 @@ bsock_recvmsg_internal(VALUE sock, rb_obj_reveal(dat_str, rb_cString); } - ret = rb_ary_new3(3, dat_str, - rsock_io_socket_addrinfo(sock, mh.msg_name, mh.msg_namelen), #if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) - INT2NUM(mh.msg_flags) + VALUE msg_flags = INT2NUM(mh.msg_flags); #else - Qnil + VALUE msg_flags = Qnil; #endif - ); + ret = rb_ary_new3(3, dat_str, + rsock_io_socket_addrinfo(sock, mh.msg_name, mh.msg_namelen), + msg_flags); #if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) family = rsock_getfamily(fptr); diff --git a/ext/socket/basicsocket.c b/ext/socket/basicsocket.c index 54c369f6fc..2fcae8eb54 100644 --- a/ext/socket/basicsocket.c +++ b/ext/socket/basicsocket.c @@ -124,7 +124,7 @@ bsock_close_read(VALUE sock) rb_io_t *fptr; GetOpenFile(sock, fptr); - shutdown(fptr->fd, 0); + shutdown(fptr->fd, SHUT_RD); if (!(fptr->mode & FMODE_WRITABLE)) { return rb_io_close(sock); } @@ -157,7 +157,7 @@ bsock_close_write(VALUE sock) if (!(fptr->mode & FMODE_READABLE)) { return rb_io_close(sock); } - shutdown(fptr->fd, 1); + shutdown(fptr->fd, SHUT_WR); fptr->mode &= ~FMODE_WRITABLE; return Qnil; @@ -597,7 +597,7 @@ rsock_bsock_send(int argc, VALUE *argv, VALUE socket) rb_io_wait(socket, RB_INT2NUM(RUBY_IO_WRITABLE), Qnil); #endif - ssize_t n = (ssize_t)BLOCKING_REGION_FD(func, &arg); + ssize_t n = (ssize_t)rb_io_blocking_region(fptr, func, &arg); if (n >= 0) return SSIZET2NUM(n); diff --git a/ext/socket/depend b/ext/socket/depend index 1c02f574bd..77f6239a3d 100644 --- a/ext/socket/depend +++ b/ext/socket/depend @@ -13,6 +13,7 @@ constdefs.c: constdefs.h ancdata.o: $(RUBY_EXTCONF_H) ancdata.o: $(arch_hdrdir)/ruby/config.h ancdata.o: $(hdrdir)/ruby/assert.h +ancdata.o: $(hdrdir)/ruby/atomic.h ancdata.o: $(hdrdir)/ruby/backward.h ancdata.o: $(hdrdir)/ruby/backward/2/assume.h ancdata.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -150,6 +151,7 @@ ancdata.o: $(hdrdir)/ruby/internal/intern/re.h ancdata.o: $(hdrdir)/ruby/internal/intern/ruby.h ancdata.o: $(hdrdir)/ruby/internal/intern/select.h ancdata.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ancdata.o: $(hdrdir)/ruby/internal/intern/set.h ancdata.o: $(hdrdir)/ruby/internal/intern/signal.h ancdata.o: $(hdrdir)/ruby/internal/intern/sprintf.h ancdata.o: $(hdrdir)/ruby/internal/intern/string.h @@ -169,6 +171,7 @@ ancdata.o: $(hdrdir)/ruby/internal/special_consts.h ancdata.o: $(hdrdir)/ruby/internal/static_assert.h ancdata.o: $(hdrdir)/ruby/internal/stdalign.h ancdata.o: $(hdrdir)/ruby/internal/stdbool.h +ancdata.o: $(hdrdir)/ruby/internal/stdckdint.h ancdata.o: $(hdrdir)/ruby/internal/symbol.h ancdata.o: $(hdrdir)/ruby/internal/value.h ancdata.o: $(hdrdir)/ruby/internal/value_type.h @@ -183,27 +186,50 @@ ancdata.o: $(hdrdir)/ruby/ruby.h ancdata.o: $(hdrdir)/ruby/st.h ancdata.o: $(hdrdir)/ruby/subst.h ancdata.o: $(hdrdir)/ruby/thread.h +ancdata.o: $(hdrdir)/ruby/thread_native.h ancdata.o: $(hdrdir)/ruby/util.h +ancdata.o: $(hdrdir)/ruby/version.h +ancdata.o: $(top_srcdir)/ccan/check_type/check_type.h +ancdata.o: $(top_srcdir)/ccan/container_of/container_of.h +ancdata.o: $(top_srcdir)/ccan/list/list.h +ancdata.o: $(top_srcdir)/ccan/str/str.h +ancdata.o: $(top_srcdir)/encindex.h +ancdata.o: $(top_srcdir)/id_table.h ancdata.o: $(top_srcdir)/internal.h ancdata.o: $(top_srcdir)/internal/array.h +ancdata.o: $(top_srcdir)/internal/basic_operators.h +ancdata.o: $(top_srcdir)/internal/box.h ancdata.o: $(top_srcdir)/internal/compilers.h ancdata.o: $(top_srcdir)/internal/error.h ancdata.o: $(top_srcdir)/internal/gc.h +ancdata.o: $(top_srcdir)/internal/imemo.h ancdata.o: $(top_srcdir)/internal/io.h +ancdata.o: $(top_srcdir)/internal/sanitizers.h ancdata.o: $(top_srcdir)/internal/serial.h +ancdata.o: $(top_srcdir)/internal/set_table.h ancdata.o: $(top_srcdir)/internal/static_assert.h ancdata.o: $(top_srcdir)/internal/string.h ancdata.o: $(top_srcdir)/internal/thread.h ancdata.o: $(top_srcdir)/internal/vm.h ancdata.o: $(top_srcdir)/internal/warnings.h +ancdata.o: $(top_srcdir)/method.h +ancdata.o: $(top_srcdir)/node.h +ancdata.o: $(top_srcdir)/ruby_assert.h +ancdata.o: $(top_srcdir)/ruby_atomic.h +ancdata.o: $(top_srcdir)/rubyparser.h ancdata.o: $(top_srcdir)/shape.h +ancdata.o: $(top_srcdir)/thread_pthread.h +ancdata.o: $(top_srcdir)/vm_core.h +ancdata.o: $(top_srcdir)/vm_opts.h ancdata.o: ancdata.c ancdata.o: constdefs.h ancdata.o: rubysocket.h ancdata.o: sockport.h +ancdata.o: {$(VPATH)}id.h basicsocket.o: $(RUBY_EXTCONF_H) basicsocket.o: $(arch_hdrdir)/ruby/config.h basicsocket.o: $(hdrdir)/ruby/assert.h +basicsocket.o: $(hdrdir)/ruby/atomic.h basicsocket.o: $(hdrdir)/ruby/backward.h basicsocket.o: $(hdrdir)/ruby/backward/2/assume.h basicsocket.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -341,6 +367,7 @@ basicsocket.o: $(hdrdir)/ruby/internal/intern/re.h basicsocket.o: $(hdrdir)/ruby/internal/intern/ruby.h basicsocket.o: $(hdrdir)/ruby/internal/intern/select.h basicsocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +basicsocket.o: $(hdrdir)/ruby/internal/intern/set.h basicsocket.o: $(hdrdir)/ruby/internal/intern/signal.h basicsocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h basicsocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -360,6 +387,7 @@ basicsocket.o: $(hdrdir)/ruby/internal/special_consts.h basicsocket.o: $(hdrdir)/ruby/internal/static_assert.h basicsocket.o: $(hdrdir)/ruby/internal/stdalign.h basicsocket.o: $(hdrdir)/ruby/internal/stdbool.h +basicsocket.o: $(hdrdir)/ruby/internal/stdckdint.h basicsocket.o: $(hdrdir)/ruby/internal/symbol.h basicsocket.o: $(hdrdir)/ruby/internal/value.h basicsocket.o: $(hdrdir)/ruby/internal/value_type.h @@ -374,27 +402,50 @@ basicsocket.o: $(hdrdir)/ruby/ruby.h basicsocket.o: $(hdrdir)/ruby/st.h basicsocket.o: $(hdrdir)/ruby/subst.h basicsocket.o: $(hdrdir)/ruby/thread.h +basicsocket.o: $(hdrdir)/ruby/thread_native.h basicsocket.o: $(hdrdir)/ruby/util.h +basicsocket.o: $(hdrdir)/ruby/version.h +basicsocket.o: $(top_srcdir)/ccan/check_type/check_type.h +basicsocket.o: $(top_srcdir)/ccan/container_of/container_of.h +basicsocket.o: $(top_srcdir)/ccan/list/list.h +basicsocket.o: $(top_srcdir)/ccan/str/str.h +basicsocket.o: $(top_srcdir)/encindex.h +basicsocket.o: $(top_srcdir)/id_table.h basicsocket.o: $(top_srcdir)/internal.h basicsocket.o: $(top_srcdir)/internal/array.h +basicsocket.o: $(top_srcdir)/internal/basic_operators.h +basicsocket.o: $(top_srcdir)/internal/box.h basicsocket.o: $(top_srcdir)/internal/compilers.h basicsocket.o: $(top_srcdir)/internal/error.h basicsocket.o: $(top_srcdir)/internal/gc.h +basicsocket.o: $(top_srcdir)/internal/imemo.h basicsocket.o: $(top_srcdir)/internal/io.h +basicsocket.o: $(top_srcdir)/internal/sanitizers.h basicsocket.o: $(top_srcdir)/internal/serial.h +basicsocket.o: $(top_srcdir)/internal/set_table.h basicsocket.o: $(top_srcdir)/internal/static_assert.h basicsocket.o: $(top_srcdir)/internal/string.h basicsocket.o: $(top_srcdir)/internal/thread.h basicsocket.o: $(top_srcdir)/internal/vm.h basicsocket.o: $(top_srcdir)/internal/warnings.h +basicsocket.o: $(top_srcdir)/method.h +basicsocket.o: $(top_srcdir)/node.h +basicsocket.o: $(top_srcdir)/ruby_assert.h +basicsocket.o: $(top_srcdir)/ruby_atomic.h +basicsocket.o: $(top_srcdir)/rubyparser.h basicsocket.o: $(top_srcdir)/shape.h +basicsocket.o: $(top_srcdir)/thread_pthread.h +basicsocket.o: $(top_srcdir)/vm_core.h +basicsocket.o: $(top_srcdir)/vm_opts.h basicsocket.o: basicsocket.c basicsocket.o: constdefs.h basicsocket.o: rubysocket.h basicsocket.o: sockport.h +basicsocket.o: {$(VPATH)}id.h constants.o: $(RUBY_EXTCONF_H) constants.o: $(arch_hdrdir)/ruby/config.h constants.o: $(hdrdir)/ruby/assert.h +constants.o: $(hdrdir)/ruby/atomic.h constants.o: $(hdrdir)/ruby/backward.h constants.o: $(hdrdir)/ruby/backward/2/assume.h constants.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -532,6 +583,7 @@ constants.o: $(hdrdir)/ruby/internal/intern/re.h constants.o: $(hdrdir)/ruby/internal/intern/ruby.h constants.o: $(hdrdir)/ruby/internal/intern/select.h constants.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +constants.o: $(hdrdir)/ruby/internal/intern/set.h constants.o: $(hdrdir)/ruby/internal/intern/signal.h constants.o: $(hdrdir)/ruby/internal/intern/sprintf.h constants.o: $(hdrdir)/ruby/internal/intern/string.h @@ -551,6 +603,7 @@ constants.o: $(hdrdir)/ruby/internal/special_consts.h constants.o: $(hdrdir)/ruby/internal/static_assert.h constants.o: $(hdrdir)/ruby/internal/stdalign.h constants.o: $(hdrdir)/ruby/internal/stdbool.h +constants.o: $(hdrdir)/ruby/internal/stdckdint.h constants.o: $(hdrdir)/ruby/internal/symbol.h constants.o: $(hdrdir)/ruby/internal/value.h constants.o: $(hdrdir)/ruby/internal/value_type.h @@ -565,28 +618,51 @@ constants.o: $(hdrdir)/ruby/ruby.h constants.o: $(hdrdir)/ruby/st.h constants.o: $(hdrdir)/ruby/subst.h constants.o: $(hdrdir)/ruby/thread.h +constants.o: $(hdrdir)/ruby/thread_native.h constants.o: $(hdrdir)/ruby/util.h +constants.o: $(hdrdir)/ruby/version.h +constants.o: $(top_srcdir)/ccan/check_type/check_type.h +constants.o: $(top_srcdir)/ccan/container_of/container_of.h +constants.o: $(top_srcdir)/ccan/list/list.h +constants.o: $(top_srcdir)/ccan/str/str.h +constants.o: $(top_srcdir)/encindex.h +constants.o: $(top_srcdir)/id_table.h constants.o: $(top_srcdir)/internal.h constants.o: $(top_srcdir)/internal/array.h +constants.o: $(top_srcdir)/internal/basic_operators.h +constants.o: $(top_srcdir)/internal/box.h constants.o: $(top_srcdir)/internal/compilers.h constants.o: $(top_srcdir)/internal/error.h constants.o: $(top_srcdir)/internal/gc.h +constants.o: $(top_srcdir)/internal/imemo.h constants.o: $(top_srcdir)/internal/io.h +constants.o: $(top_srcdir)/internal/sanitizers.h constants.o: $(top_srcdir)/internal/serial.h +constants.o: $(top_srcdir)/internal/set_table.h constants.o: $(top_srcdir)/internal/static_assert.h constants.o: $(top_srcdir)/internal/string.h constants.o: $(top_srcdir)/internal/thread.h constants.o: $(top_srcdir)/internal/vm.h constants.o: $(top_srcdir)/internal/warnings.h +constants.o: $(top_srcdir)/method.h +constants.o: $(top_srcdir)/node.h +constants.o: $(top_srcdir)/ruby_assert.h +constants.o: $(top_srcdir)/ruby_atomic.h +constants.o: $(top_srcdir)/rubyparser.h constants.o: $(top_srcdir)/shape.h +constants.o: $(top_srcdir)/thread_pthread.h +constants.o: $(top_srcdir)/vm_core.h +constants.o: $(top_srcdir)/vm_opts.h constants.o: constants.c constants.o: constdefs.c constants.o: constdefs.h constants.o: rubysocket.h constants.o: sockport.h +constants.o: {$(VPATH)}id.h ifaddr.o: $(RUBY_EXTCONF_H) ifaddr.o: $(arch_hdrdir)/ruby/config.h ifaddr.o: $(hdrdir)/ruby/assert.h +ifaddr.o: $(hdrdir)/ruby/atomic.h ifaddr.o: $(hdrdir)/ruby/backward.h ifaddr.o: $(hdrdir)/ruby/backward/2/assume.h ifaddr.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -724,6 +800,7 @@ ifaddr.o: $(hdrdir)/ruby/internal/intern/re.h ifaddr.o: $(hdrdir)/ruby/internal/intern/ruby.h ifaddr.o: $(hdrdir)/ruby/internal/intern/select.h ifaddr.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ifaddr.o: $(hdrdir)/ruby/internal/intern/set.h ifaddr.o: $(hdrdir)/ruby/internal/intern/signal.h ifaddr.o: $(hdrdir)/ruby/internal/intern/sprintf.h ifaddr.o: $(hdrdir)/ruby/internal/intern/string.h @@ -743,6 +820,7 @@ ifaddr.o: $(hdrdir)/ruby/internal/special_consts.h ifaddr.o: $(hdrdir)/ruby/internal/static_assert.h ifaddr.o: $(hdrdir)/ruby/internal/stdalign.h ifaddr.o: $(hdrdir)/ruby/internal/stdbool.h +ifaddr.o: $(hdrdir)/ruby/internal/stdckdint.h ifaddr.o: $(hdrdir)/ruby/internal/symbol.h ifaddr.o: $(hdrdir)/ruby/internal/value.h ifaddr.o: $(hdrdir)/ruby/internal/value_type.h @@ -757,27 +835,50 @@ ifaddr.o: $(hdrdir)/ruby/ruby.h ifaddr.o: $(hdrdir)/ruby/st.h ifaddr.o: $(hdrdir)/ruby/subst.h ifaddr.o: $(hdrdir)/ruby/thread.h +ifaddr.o: $(hdrdir)/ruby/thread_native.h ifaddr.o: $(hdrdir)/ruby/util.h +ifaddr.o: $(hdrdir)/ruby/version.h +ifaddr.o: $(top_srcdir)/ccan/check_type/check_type.h +ifaddr.o: $(top_srcdir)/ccan/container_of/container_of.h +ifaddr.o: $(top_srcdir)/ccan/list/list.h +ifaddr.o: $(top_srcdir)/ccan/str/str.h +ifaddr.o: $(top_srcdir)/encindex.h +ifaddr.o: $(top_srcdir)/id_table.h ifaddr.o: $(top_srcdir)/internal.h ifaddr.o: $(top_srcdir)/internal/array.h +ifaddr.o: $(top_srcdir)/internal/basic_operators.h +ifaddr.o: $(top_srcdir)/internal/box.h ifaddr.o: $(top_srcdir)/internal/compilers.h ifaddr.o: $(top_srcdir)/internal/error.h ifaddr.o: $(top_srcdir)/internal/gc.h +ifaddr.o: $(top_srcdir)/internal/imemo.h ifaddr.o: $(top_srcdir)/internal/io.h +ifaddr.o: $(top_srcdir)/internal/sanitizers.h ifaddr.o: $(top_srcdir)/internal/serial.h +ifaddr.o: $(top_srcdir)/internal/set_table.h ifaddr.o: $(top_srcdir)/internal/static_assert.h ifaddr.o: $(top_srcdir)/internal/string.h ifaddr.o: $(top_srcdir)/internal/thread.h ifaddr.o: $(top_srcdir)/internal/vm.h ifaddr.o: $(top_srcdir)/internal/warnings.h +ifaddr.o: $(top_srcdir)/method.h +ifaddr.o: $(top_srcdir)/node.h +ifaddr.o: $(top_srcdir)/ruby_assert.h +ifaddr.o: $(top_srcdir)/ruby_atomic.h +ifaddr.o: $(top_srcdir)/rubyparser.h ifaddr.o: $(top_srcdir)/shape.h +ifaddr.o: $(top_srcdir)/thread_pthread.h +ifaddr.o: $(top_srcdir)/vm_core.h +ifaddr.o: $(top_srcdir)/vm_opts.h ifaddr.o: constdefs.h ifaddr.o: ifaddr.c ifaddr.o: rubysocket.h ifaddr.o: sockport.h +ifaddr.o: {$(VPATH)}id.h init.o: $(RUBY_EXTCONF_H) init.o: $(arch_hdrdir)/ruby/config.h init.o: $(hdrdir)/ruby/assert.h +init.o: $(hdrdir)/ruby/atomic.h init.o: $(hdrdir)/ruby/backward.h init.o: $(hdrdir)/ruby/backward/2/assume.h init.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -915,6 +1016,7 @@ init.o: $(hdrdir)/ruby/internal/intern/re.h init.o: $(hdrdir)/ruby/internal/intern/ruby.h init.o: $(hdrdir)/ruby/internal/intern/select.h init.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +init.o: $(hdrdir)/ruby/internal/intern/set.h init.o: $(hdrdir)/ruby/internal/intern/signal.h init.o: $(hdrdir)/ruby/internal/intern/sprintf.h init.o: $(hdrdir)/ruby/internal/intern/string.h @@ -934,6 +1036,7 @@ init.o: $(hdrdir)/ruby/internal/special_consts.h init.o: $(hdrdir)/ruby/internal/static_assert.h init.o: $(hdrdir)/ruby/internal/stdalign.h init.o: $(hdrdir)/ruby/internal/stdbool.h +init.o: $(hdrdir)/ruby/internal/stdckdint.h init.o: $(hdrdir)/ruby/internal/symbol.h init.o: $(hdrdir)/ruby/internal/value.h init.o: $(hdrdir)/ruby/internal/value_type.h @@ -948,27 +1051,50 @@ init.o: $(hdrdir)/ruby/ruby.h init.o: $(hdrdir)/ruby/st.h init.o: $(hdrdir)/ruby/subst.h init.o: $(hdrdir)/ruby/thread.h +init.o: $(hdrdir)/ruby/thread_native.h init.o: $(hdrdir)/ruby/util.h +init.o: $(hdrdir)/ruby/version.h +init.o: $(top_srcdir)/ccan/check_type/check_type.h +init.o: $(top_srcdir)/ccan/container_of/container_of.h +init.o: $(top_srcdir)/ccan/list/list.h +init.o: $(top_srcdir)/ccan/str/str.h +init.o: $(top_srcdir)/encindex.h +init.o: $(top_srcdir)/id_table.h init.o: $(top_srcdir)/internal.h init.o: $(top_srcdir)/internal/array.h +init.o: $(top_srcdir)/internal/basic_operators.h +init.o: $(top_srcdir)/internal/box.h init.o: $(top_srcdir)/internal/compilers.h init.o: $(top_srcdir)/internal/error.h init.o: $(top_srcdir)/internal/gc.h +init.o: $(top_srcdir)/internal/imemo.h init.o: $(top_srcdir)/internal/io.h +init.o: $(top_srcdir)/internal/sanitizers.h init.o: $(top_srcdir)/internal/serial.h +init.o: $(top_srcdir)/internal/set_table.h init.o: $(top_srcdir)/internal/static_assert.h init.o: $(top_srcdir)/internal/string.h init.o: $(top_srcdir)/internal/thread.h init.o: $(top_srcdir)/internal/vm.h init.o: $(top_srcdir)/internal/warnings.h +init.o: $(top_srcdir)/method.h +init.o: $(top_srcdir)/node.h +init.o: $(top_srcdir)/ruby_assert.h +init.o: $(top_srcdir)/ruby_atomic.h +init.o: $(top_srcdir)/rubyparser.h init.o: $(top_srcdir)/shape.h +init.o: $(top_srcdir)/thread_pthread.h +init.o: $(top_srcdir)/vm_core.h +init.o: $(top_srcdir)/vm_opts.h init.o: constdefs.h init.o: init.c init.o: rubysocket.h init.o: sockport.h +init.o: {$(VPATH)}id.h ipsocket.o: $(RUBY_EXTCONF_H) ipsocket.o: $(arch_hdrdir)/ruby/config.h ipsocket.o: $(hdrdir)/ruby/assert.h +ipsocket.o: $(hdrdir)/ruby/atomic.h ipsocket.o: $(hdrdir)/ruby/backward.h ipsocket.o: $(hdrdir)/ruby/backward/2/assume.h ipsocket.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -1106,6 +1232,7 @@ ipsocket.o: $(hdrdir)/ruby/internal/intern/re.h ipsocket.o: $(hdrdir)/ruby/internal/intern/ruby.h ipsocket.o: $(hdrdir)/ruby/internal/intern/select.h ipsocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +ipsocket.o: $(hdrdir)/ruby/internal/intern/set.h ipsocket.o: $(hdrdir)/ruby/internal/intern/signal.h ipsocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h ipsocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1125,6 +1252,7 @@ ipsocket.o: $(hdrdir)/ruby/internal/special_consts.h ipsocket.o: $(hdrdir)/ruby/internal/static_assert.h ipsocket.o: $(hdrdir)/ruby/internal/stdalign.h ipsocket.o: $(hdrdir)/ruby/internal/stdbool.h +ipsocket.o: $(hdrdir)/ruby/internal/stdckdint.h ipsocket.o: $(hdrdir)/ruby/internal/symbol.h ipsocket.o: $(hdrdir)/ruby/internal/value.h ipsocket.o: $(hdrdir)/ruby/internal/value_type.h @@ -1139,27 +1267,50 @@ ipsocket.o: $(hdrdir)/ruby/ruby.h ipsocket.o: $(hdrdir)/ruby/st.h ipsocket.o: $(hdrdir)/ruby/subst.h ipsocket.o: $(hdrdir)/ruby/thread.h +ipsocket.o: $(hdrdir)/ruby/thread_native.h ipsocket.o: $(hdrdir)/ruby/util.h +ipsocket.o: $(hdrdir)/ruby/version.h +ipsocket.o: $(top_srcdir)/ccan/check_type/check_type.h +ipsocket.o: $(top_srcdir)/ccan/container_of/container_of.h +ipsocket.o: $(top_srcdir)/ccan/list/list.h +ipsocket.o: $(top_srcdir)/ccan/str/str.h +ipsocket.o: $(top_srcdir)/encindex.h +ipsocket.o: $(top_srcdir)/id_table.h ipsocket.o: $(top_srcdir)/internal.h ipsocket.o: $(top_srcdir)/internal/array.h +ipsocket.o: $(top_srcdir)/internal/basic_operators.h +ipsocket.o: $(top_srcdir)/internal/box.h ipsocket.o: $(top_srcdir)/internal/compilers.h ipsocket.o: $(top_srcdir)/internal/error.h ipsocket.o: $(top_srcdir)/internal/gc.h +ipsocket.o: $(top_srcdir)/internal/imemo.h ipsocket.o: $(top_srcdir)/internal/io.h +ipsocket.o: $(top_srcdir)/internal/sanitizers.h ipsocket.o: $(top_srcdir)/internal/serial.h +ipsocket.o: $(top_srcdir)/internal/set_table.h ipsocket.o: $(top_srcdir)/internal/static_assert.h ipsocket.o: $(top_srcdir)/internal/string.h ipsocket.o: $(top_srcdir)/internal/thread.h ipsocket.o: $(top_srcdir)/internal/vm.h ipsocket.o: $(top_srcdir)/internal/warnings.h +ipsocket.o: $(top_srcdir)/method.h +ipsocket.o: $(top_srcdir)/node.h +ipsocket.o: $(top_srcdir)/ruby_assert.h +ipsocket.o: $(top_srcdir)/ruby_atomic.h +ipsocket.o: $(top_srcdir)/rubyparser.h ipsocket.o: $(top_srcdir)/shape.h +ipsocket.o: $(top_srcdir)/thread_pthread.h +ipsocket.o: $(top_srcdir)/vm_core.h +ipsocket.o: $(top_srcdir)/vm_opts.h ipsocket.o: constdefs.h ipsocket.o: ipsocket.c ipsocket.o: rubysocket.h ipsocket.o: sockport.h +ipsocket.o: {$(VPATH)}id.h option.o: $(RUBY_EXTCONF_H) option.o: $(arch_hdrdir)/ruby/config.h option.o: $(hdrdir)/ruby/assert.h +option.o: $(hdrdir)/ruby/atomic.h option.o: $(hdrdir)/ruby/backward.h option.o: $(hdrdir)/ruby/backward/2/assume.h option.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -1297,6 +1448,7 @@ option.o: $(hdrdir)/ruby/internal/intern/re.h option.o: $(hdrdir)/ruby/internal/intern/ruby.h option.o: $(hdrdir)/ruby/internal/intern/select.h option.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +option.o: $(hdrdir)/ruby/internal/intern/set.h option.o: $(hdrdir)/ruby/internal/intern/signal.h option.o: $(hdrdir)/ruby/internal/intern/sprintf.h option.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1316,6 +1468,7 @@ option.o: $(hdrdir)/ruby/internal/special_consts.h option.o: $(hdrdir)/ruby/internal/static_assert.h option.o: $(hdrdir)/ruby/internal/stdalign.h option.o: $(hdrdir)/ruby/internal/stdbool.h +option.o: $(hdrdir)/ruby/internal/stdckdint.h option.o: $(hdrdir)/ruby/internal/symbol.h option.o: $(hdrdir)/ruby/internal/value.h option.o: $(hdrdir)/ruby/internal/value_type.h @@ -1330,27 +1483,50 @@ option.o: $(hdrdir)/ruby/ruby.h option.o: $(hdrdir)/ruby/st.h option.o: $(hdrdir)/ruby/subst.h option.o: $(hdrdir)/ruby/thread.h +option.o: $(hdrdir)/ruby/thread_native.h option.o: $(hdrdir)/ruby/util.h +option.o: $(hdrdir)/ruby/version.h +option.o: $(top_srcdir)/ccan/check_type/check_type.h +option.o: $(top_srcdir)/ccan/container_of/container_of.h +option.o: $(top_srcdir)/ccan/list/list.h +option.o: $(top_srcdir)/ccan/str/str.h +option.o: $(top_srcdir)/encindex.h +option.o: $(top_srcdir)/id_table.h option.o: $(top_srcdir)/internal.h option.o: $(top_srcdir)/internal/array.h +option.o: $(top_srcdir)/internal/basic_operators.h +option.o: $(top_srcdir)/internal/box.h option.o: $(top_srcdir)/internal/compilers.h option.o: $(top_srcdir)/internal/error.h option.o: $(top_srcdir)/internal/gc.h +option.o: $(top_srcdir)/internal/imemo.h option.o: $(top_srcdir)/internal/io.h +option.o: $(top_srcdir)/internal/sanitizers.h option.o: $(top_srcdir)/internal/serial.h +option.o: $(top_srcdir)/internal/set_table.h option.o: $(top_srcdir)/internal/static_assert.h option.o: $(top_srcdir)/internal/string.h option.o: $(top_srcdir)/internal/thread.h option.o: $(top_srcdir)/internal/vm.h option.o: $(top_srcdir)/internal/warnings.h +option.o: $(top_srcdir)/method.h +option.o: $(top_srcdir)/node.h +option.o: $(top_srcdir)/ruby_assert.h +option.o: $(top_srcdir)/ruby_atomic.h +option.o: $(top_srcdir)/rubyparser.h option.o: $(top_srcdir)/shape.h +option.o: $(top_srcdir)/thread_pthread.h +option.o: $(top_srcdir)/vm_core.h +option.o: $(top_srcdir)/vm_opts.h option.o: constdefs.h option.o: option.c option.o: rubysocket.h option.o: sockport.h +option.o: {$(VPATH)}id.h raddrinfo.o: $(RUBY_EXTCONF_H) raddrinfo.o: $(arch_hdrdir)/ruby/config.h raddrinfo.o: $(hdrdir)/ruby/assert.h +raddrinfo.o: $(hdrdir)/ruby/atomic.h raddrinfo.o: $(hdrdir)/ruby/backward.h raddrinfo.o: $(hdrdir)/ruby/backward/2/assume.h raddrinfo.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -1488,6 +1664,7 @@ raddrinfo.o: $(hdrdir)/ruby/internal/intern/re.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/ruby.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/select.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +raddrinfo.o: $(hdrdir)/ruby/internal/intern/set.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/signal.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/sprintf.h raddrinfo.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1507,6 +1684,7 @@ raddrinfo.o: $(hdrdir)/ruby/internal/special_consts.h raddrinfo.o: $(hdrdir)/ruby/internal/static_assert.h raddrinfo.o: $(hdrdir)/ruby/internal/stdalign.h raddrinfo.o: $(hdrdir)/ruby/internal/stdbool.h +raddrinfo.o: $(hdrdir)/ruby/internal/stdckdint.h raddrinfo.o: $(hdrdir)/ruby/internal/symbol.h raddrinfo.o: $(hdrdir)/ruby/internal/value.h raddrinfo.o: $(hdrdir)/ruby/internal/value_type.h @@ -1521,27 +1699,50 @@ raddrinfo.o: $(hdrdir)/ruby/ruby.h raddrinfo.o: $(hdrdir)/ruby/st.h raddrinfo.o: $(hdrdir)/ruby/subst.h raddrinfo.o: $(hdrdir)/ruby/thread.h +raddrinfo.o: $(hdrdir)/ruby/thread_native.h raddrinfo.o: $(hdrdir)/ruby/util.h +raddrinfo.o: $(hdrdir)/ruby/version.h +raddrinfo.o: $(top_srcdir)/ccan/check_type/check_type.h +raddrinfo.o: $(top_srcdir)/ccan/container_of/container_of.h +raddrinfo.o: $(top_srcdir)/ccan/list/list.h +raddrinfo.o: $(top_srcdir)/ccan/str/str.h +raddrinfo.o: $(top_srcdir)/encindex.h +raddrinfo.o: $(top_srcdir)/id_table.h raddrinfo.o: $(top_srcdir)/internal.h raddrinfo.o: $(top_srcdir)/internal/array.h +raddrinfo.o: $(top_srcdir)/internal/basic_operators.h +raddrinfo.o: $(top_srcdir)/internal/box.h raddrinfo.o: $(top_srcdir)/internal/compilers.h raddrinfo.o: $(top_srcdir)/internal/error.h raddrinfo.o: $(top_srcdir)/internal/gc.h +raddrinfo.o: $(top_srcdir)/internal/imemo.h raddrinfo.o: $(top_srcdir)/internal/io.h +raddrinfo.o: $(top_srcdir)/internal/sanitizers.h raddrinfo.o: $(top_srcdir)/internal/serial.h +raddrinfo.o: $(top_srcdir)/internal/set_table.h raddrinfo.o: $(top_srcdir)/internal/static_assert.h raddrinfo.o: $(top_srcdir)/internal/string.h raddrinfo.o: $(top_srcdir)/internal/thread.h raddrinfo.o: $(top_srcdir)/internal/vm.h raddrinfo.o: $(top_srcdir)/internal/warnings.h +raddrinfo.o: $(top_srcdir)/method.h +raddrinfo.o: $(top_srcdir)/node.h +raddrinfo.o: $(top_srcdir)/ruby_assert.h +raddrinfo.o: $(top_srcdir)/ruby_atomic.h +raddrinfo.o: $(top_srcdir)/rubyparser.h raddrinfo.o: $(top_srcdir)/shape.h +raddrinfo.o: $(top_srcdir)/thread_pthread.h +raddrinfo.o: $(top_srcdir)/vm_core.h +raddrinfo.o: $(top_srcdir)/vm_opts.h raddrinfo.o: constdefs.h raddrinfo.o: raddrinfo.c raddrinfo.o: rubysocket.h raddrinfo.o: sockport.h +raddrinfo.o: {$(VPATH)}id.h socket.o: $(RUBY_EXTCONF_H) socket.o: $(arch_hdrdir)/ruby/config.h socket.o: $(hdrdir)/ruby/assert.h +socket.o: $(hdrdir)/ruby/atomic.h socket.o: $(hdrdir)/ruby/backward.h socket.o: $(hdrdir)/ruby/backward/2/assume.h socket.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -1679,6 +1880,7 @@ socket.o: $(hdrdir)/ruby/internal/intern/re.h socket.o: $(hdrdir)/ruby/internal/intern/ruby.h socket.o: $(hdrdir)/ruby/internal/intern/select.h socket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +socket.o: $(hdrdir)/ruby/internal/intern/set.h socket.o: $(hdrdir)/ruby/internal/intern/signal.h socket.o: $(hdrdir)/ruby/internal/intern/sprintf.h socket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1698,6 +1900,7 @@ socket.o: $(hdrdir)/ruby/internal/special_consts.h socket.o: $(hdrdir)/ruby/internal/static_assert.h socket.o: $(hdrdir)/ruby/internal/stdalign.h socket.o: $(hdrdir)/ruby/internal/stdbool.h +socket.o: $(hdrdir)/ruby/internal/stdckdint.h socket.o: $(hdrdir)/ruby/internal/symbol.h socket.o: $(hdrdir)/ruby/internal/value.h socket.o: $(hdrdir)/ruby/internal/value_type.h @@ -1712,27 +1915,50 @@ socket.o: $(hdrdir)/ruby/ruby.h socket.o: $(hdrdir)/ruby/st.h socket.o: $(hdrdir)/ruby/subst.h socket.o: $(hdrdir)/ruby/thread.h +socket.o: $(hdrdir)/ruby/thread_native.h socket.o: $(hdrdir)/ruby/util.h +socket.o: $(hdrdir)/ruby/version.h +socket.o: $(top_srcdir)/ccan/check_type/check_type.h +socket.o: $(top_srcdir)/ccan/container_of/container_of.h +socket.o: $(top_srcdir)/ccan/list/list.h +socket.o: $(top_srcdir)/ccan/str/str.h +socket.o: $(top_srcdir)/encindex.h +socket.o: $(top_srcdir)/id_table.h socket.o: $(top_srcdir)/internal.h socket.o: $(top_srcdir)/internal/array.h +socket.o: $(top_srcdir)/internal/basic_operators.h +socket.o: $(top_srcdir)/internal/box.h socket.o: $(top_srcdir)/internal/compilers.h socket.o: $(top_srcdir)/internal/error.h socket.o: $(top_srcdir)/internal/gc.h +socket.o: $(top_srcdir)/internal/imemo.h socket.o: $(top_srcdir)/internal/io.h +socket.o: $(top_srcdir)/internal/sanitizers.h socket.o: $(top_srcdir)/internal/serial.h +socket.o: $(top_srcdir)/internal/set_table.h socket.o: $(top_srcdir)/internal/static_assert.h socket.o: $(top_srcdir)/internal/string.h socket.o: $(top_srcdir)/internal/thread.h socket.o: $(top_srcdir)/internal/vm.h socket.o: $(top_srcdir)/internal/warnings.h +socket.o: $(top_srcdir)/method.h +socket.o: $(top_srcdir)/node.h +socket.o: $(top_srcdir)/ruby_assert.h +socket.o: $(top_srcdir)/ruby_atomic.h +socket.o: $(top_srcdir)/rubyparser.h socket.o: $(top_srcdir)/shape.h +socket.o: $(top_srcdir)/thread_pthread.h +socket.o: $(top_srcdir)/vm_core.h +socket.o: $(top_srcdir)/vm_opts.h socket.o: constdefs.h socket.o: rubysocket.h socket.o: socket.c socket.o: sockport.h +socket.o: {$(VPATH)}id.h sockssocket.o: $(RUBY_EXTCONF_H) sockssocket.o: $(arch_hdrdir)/ruby/config.h sockssocket.o: $(hdrdir)/ruby/assert.h +sockssocket.o: $(hdrdir)/ruby/atomic.h sockssocket.o: $(hdrdir)/ruby/backward.h sockssocket.o: $(hdrdir)/ruby/backward/2/assume.h sockssocket.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -1870,6 +2096,7 @@ sockssocket.o: $(hdrdir)/ruby/internal/intern/re.h sockssocket.o: $(hdrdir)/ruby/internal/intern/ruby.h sockssocket.o: $(hdrdir)/ruby/internal/intern/select.h sockssocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +sockssocket.o: $(hdrdir)/ruby/internal/intern/set.h sockssocket.o: $(hdrdir)/ruby/internal/intern/signal.h sockssocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h sockssocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -1889,6 +2116,7 @@ sockssocket.o: $(hdrdir)/ruby/internal/special_consts.h sockssocket.o: $(hdrdir)/ruby/internal/static_assert.h sockssocket.o: $(hdrdir)/ruby/internal/stdalign.h sockssocket.o: $(hdrdir)/ruby/internal/stdbool.h +sockssocket.o: $(hdrdir)/ruby/internal/stdckdint.h sockssocket.o: $(hdrdir)/ruby/internal/symbol.h sockssocket.o: $(hdrdir)/ruby/internal/value.h sockssocket.o: $(hdrdir)/ruby/internal/value_type.h @@ -1903,27 +2131,50 @@ sockssocket.o: $(hdrdir)/ruby/ruby.h sockssocket.o: $(hdrdir)/ruby/st.h sockssocket.o: $(hdrdir)/ruby/subst.h sockssocket.o: $(hdrdir)/ruby/thread.h +sockssocket.o: $(hdrdir)/ruby/thread_native.h sockssocket.o: $(hdrdir)/ruby/util.h +sockssocket.o: $(hdrdir)/ruby/version.h +sockssocket.o: $(top_srcdir)/ccan/check_type/check_type.h +sockssocket.o: $(top_srcdir)/ccan/container_of/container_of.h +sockssocket.o: $(top_srcdir)/ccan/list/list.h +sockssocket.o: $(top_srcdir)/ccan/str/str.h +sockssocket.o: $(top_srcdir)/encindex.h +sockssocket.o: $(top_srcdir)/id_table.h sockssocket.o: $(top_srcdir)/internal.h sockssocket.o: $(top_srcdir)/internal/array.h +sockssocket.o: $(top_srcdir)/internal/basic_operators.h +sockssocket.o: $(top_srcdir)/internal/box.h sockssocket.o: $(top_srcdir)/internal/compilers.h sockssocket.o: $(top_srcdir)/internal/error.h sockssocket.o: $(top_srcdir)/internal/gc.h +sockssocket.o: $(top_srcdir)/internal/imemo.h sockssocket.o: $(top_srcdir)/internal/io.h +sockssocket.o: $(top_srcdir)/internal/sanitizers.h sockssocket.o: $(top_srcdir)/internal/serial.h +sockssocket.o: $(top_srcdir)/internal/set_table.h sockssocket.o: $(top_srcdir)/internal/static_assert.h sockssocket.o: $(top_srcdir)/internal/string.h sockssocket.o: $(top_srcdir)/internal/thread.h sockssocket.o: $(top_srcdir)/internal/vm.h sockssocket.o: $(top_srcdir)/internal/warnings.h +sockssocket.o: $(top_srcdir)/method.h +sockssocket.o: $(top_srcdir)/node.h +sockssocket.o: $(top_srcdir)/ruby_assert.h +sockssocket.o: $(top_srcdir)/ruby_atomic.h +sockssocket.o: $(top_srcdir)/rubyparser.h sockssocket.o: $(top_srcdir)/shape.h +sockssocket.o: $(top_srcdir)/thread_pthread.h +sockssocket.o: $(top_srcdir)/vm_core.h +sockssocket.o: $(top_srcdir)/vm_opts.h sockssocket.o: constdefs.h sockssocket.o: rubysocket.h sockssocket.o: sockport.h sockssocket.o: sockssocket.c +sockssocket.o: {$(VPATH)}id.h tcpserver.o: $(RUBY_EXTCONF_H) tcpserver.o: $(arch_hdrdir)/ruby/config.h tcpserver.o: $(hdrdir)/ruby/assert.h +tcpserver.o: $(hdrdir)/ruby/atomic.h tcpserver.o: $(hdrdir)/ruby/backward.h tcpserver.o: $(hdrdir)/ruby/backward/2/assume.h tcpserver.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -2061,6 +2312,7 @@ tcpserver.o: $(hdrdir)/ruby/internal/intern/re.h tcpserver.o: $(hdrdir)/ruby/internal/intern/ruby.h tcpserver.o: $(hdrdir)/ruby/internal/intern/select.h tcpserver.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +tcpserver.o: $(hdrdir)/ruby/internal/intern/set.h tcpserver.o: $(hdrdir)/ruby/internal/intern/signal.h tcpserver.o: $(hdrdir)/ruby/internal/intern/sprintf.h tcpserver.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2080,6 +2332,7 @@ tcpserver.o: $(hdrdir)/ruby/internal/special_consts.h tcpserver.o: $(hdrdir)/ruby/internal/static_assert.h tcpserver.o: $(hdrdir)/ruby/internal/stdalign.h tcpserver.o: $(hdrdir)/ruby/internal/stdbool.h +tcpserver.o: $(hdrdir)/ruby/internal/stdckdint.h tcpserver.o: $(hdrdir)/ruby/internal/symbol.h tcpserver.o: $(hdrdir)/ruby/internal/value.h tcpserver.o: $(hdrdir)/ruby/internal/value_type.h @@ -2094,27 +2347,50 @@ tcpserver.o: $(hdrdir)/ruby/ruby.h tcpserver.o: $(hdrdir)/ruby/st.h tcpserver.o: $(hdrdir)/ruby/subst.h tcpserver.o: $(hdrdir)/ruby/thread.h +tcpserver.o: $(hdrdir)/ruby/thread_native.h tcpserver.o: $(hdrdir)/ruby/util.h +tcpserver.o: $(hdrdir)/ruby/version.h +tcpserver.o: $(top_srcdir)/ccan/check_type/check_type.h +tcpserver.o: $(top_srcdir)/ccan/container_of/container_of.h +tcpserver.o: $(top_srcdir)/ccan/list/list.h +tcpserver.o: $(top_srcdir)/ccan/str/str.h +tcpserver.o: $(top_srcdir)/encindex.h +tcpserver.o: $(top_srcdir)/id_table.h tcpserver.o: $(top_srcdir)/internal.h tcpserver.o: $(top_srcdir)/internal/array.h +tcpserver.o: $(top_srcdir)/internal/basic_operators.h +tcpserver.o: $(top_srcdir)/internal/box.h tcpserver.o: $(top_srcdir)/internal/compilers.h tcpserver.o: $(top_srcdir)/internal/error.h tcpserver.o: $(top_srcdir)/internal/gc.h +tcpserver.o: $(top_srcdir)/internal/imemo.h tcpserver.o: $(top_srcdir)/internal/io.h +tcpserver.o: $(top_srcdir)/internal/sanitizers.h tcpserver.o: $(top_srcdir)/internal/serial.h +tcpserver.o: $(top_srcdir)/internal/set_table.h tcpserver.o: $(top_srcdir)/internal/static_assert.h tcpserver.o: $(top_srcdir)/internal/string.h tcpserver.o: $(top_srcdir)/internal/thread.h tcpserver.o: $(top_srcdir)/internal/vm.h tcpserver.o: $(top_srcdir)/internal/warnings.h +tcpserver.o: $(top_srcdir)/method.h +tcpserver.o: $(top_srcdir)/node.h +tcpserver.o: $(top_srcdir)/ruby_assert.h +tcpserver.o: $(top_srcdir)/ruby_atomic.h +tcpserver.o: $(top_srcdir)/rubyparser.h tcpserver.o: $(top_srcdir)/shape.h +tcpserver.o: $(top_srcdir)/thread_pthread.h +tcpserver.o: $(top_srcdir)/vm_core.h +tcpserver.o: $(top_srcdir)/vm_opts.h tcpserver.o: constdefs.h tcpserver.o: rubysocket.h tcpserver.o: sockport.h tcpserver.o: tcpserver.c +tcpserver.o: {$(VPATH)}id.h tcpsocket.o: $(RUBY_EXTCONF_H) tcpsocket.o: $(arch_hdrdir)/ruby/config.h tcpsocket.o: $(hdrdir)/ruby/assert.h +tcpsocket.o: $(hdrdir)/ruby/atomic.h tcpsocket.o: $(hdrdir)/ruby/backward.h tcpsocket.o: $(hdrdir)/ruby/backward/2/assume.h tcpsocket.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -2252,6 +2528,7 @@ tcpsocket.o: $(hdrdir)/ruby/internal/intern/re.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/ruby.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/select.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +tcpsocket.o: $(hdrdir)/ruby/internal/intern/set.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/signal.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h tcpsocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2271,6 +2548,7 @@ tcpsocket.o: $(hdrdir)/ruby/internal/special_consts.h tcpsocket.o: $(hdrdir)/ruby/internal/static_assert.h tcpsocket.o: $(hdrdir)/ruby/internal/stdalign.h tcpsocket.o: $(hdrdir)/ruby/internal/stdbool.h +tcpsocket.o: $(hdrdir)/ruby/internal/stdckdint.h tcpsocket.o: $(hdrdir)/ruby/internal/symbol.h tcpsocket.o: $(hdrdir)/ruby/internal/value.h tcpsocket.o: $(hdrdir)/ruby/internal/value_type.h @@ -2285,27 +2563,50 @@ tcpsocket.o: $(hdrdir)/ruby/ruby.h tcpsocket.o: $(hdrdir)/ruby/st.h tcpsocket.o: $(hdrdir)/ruby/subst.h tcpsocket.o: $(hdrdir)/ruby/thread.h +tcpsocket.o: $(hdrdir)/ruby/thread_native.h tcpsocket.o: $(hdrdir)/ruby/util.h +tcpsocket.o: $(hdrdir)/ruby/version.h +tcpsocket.o: $(top_srcdir)/ccan/check_type/check_type.h +tcpsocket.o: $(top_srcdir)/ccan/container_of/container_of.h +tcpsocket.o: $(top_srcdir)/ccan/list/list.h +tcpsocket.o: $(top_srcdir)/ccan/str/str.h +tcpsocket.o: $(top_srcdir)/encindex.h +tcpsocket.o: $(top_srcdir)/id_table.h tcpsocket.o: $(top_srcdir)/internal.h tcpsocket.o: $(top_srcdir)/internal/array.h +tcpsocket.o: $(top_srcdir)/internal/basic_operators.h +tcpsocket.o: $(top_srcdir)/internal/box.h tcpsocket.o: $(top_srcdir)/internal/compilers.h tcpsocket.o: $(top_srcdir)/internal/error.h tcpsocket.o: $(top_srcdir)/internal/gc.h +tcpsocket.o: $(top_srcdir)/internal/imemo.h tcpsocket.o: $(top_srcdir)/internal/io.h +tcpsocket.o: $(top_srcdir)/internal/sanitizers.h tcpsocket.o: $(top_srcdir)/internal/serial.h +tcpsocket.o: $(top_srcdir)/internal/set_table.h tcpsocket.o: $(top_srcdir)/internal/static_assert.h tcpsocket.o: $(top_srcdir)/internal/string.h tcpsocket.o: $(top_srcdir)/internal/thread.h tcpsocket.o: $(top_srcdir)/internal/vm.h tcpsocket.o: $(top_srcdir)/internal/warnings.h +tcpsocket.o: $(top_srcdir)/method.h +tcpsocket.o: $(top_srcdir)/node.h +tcpsocket.o: $(top_srcdir)/ruby_assert.h +tcpsocket.o: $(top_srcdir)/ruby_atomic.h +tcpsocket.o: $(top_srcdir)/rubyparser.h tcpsocket.o: $(top_srcdir)/shape.h +tcpsocket.o: $(top_srcdir)/thread_pthread.h +tcpsocket.o: $(top_srcdir)/vm_core.h +tcpsocket.o: $(top_srcdir)/vm_opts.h tcpsocket.o: constdefs.h tcpsocket.o: rubysocket.h tcpsocket.o: sockport.h tcpsocket.o: tcpsocket.c +tcpsocket.o: {$(VPATH)}id.h udpsocket.o: $(RUBY_EXTCONF_H) udpsocket.o: $(arch_hdrdir)/ruby/config.h udpsocket.o: $(hdrdir)/ruby/assert.h +udpsocket.o: $(hdrdir)/ruby/atomic.h udpsocket.o: $(hdrdir)/ruby/backward.h udpsocket.o: $(hdrdir)/ruby/backward/2/assume.h udpsocket.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -2443,6 +2744,7 @@ udpsocket.o: $(hdrdir)/ruby/internal/intern/re.h udpsocket.o: $(hdrdir)/ruby/internal/intern/ruby.h udpsocket.o: $(hdrdir)/ruby/internal/intern/select.h udpsocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +udpsocket.o: $(hdrdir)/ruby/internal/intern/set.h udpsocket.o: $(hdrdir)/ruby/internal/intern/signal.h udpsocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h udpsocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2462,6 +2764,7 @@ udpsocket.o: $(hdrdir)/ruby/internal/special_consts.h udpsocket.o: $(hdrdir)/ruby/internal/static_assert.h udpsocket.o: $(hdrdir)/ruby/internal/stdalign.h udpsocket.o: $(hdrdir)/ruby/internal/stdbool.h +udpsocket.o: $(hdrdir)/ruby/internal/stdckdint.h udpsocket.o: $(hdrdir)/ruby/internal/symbol.h udpsocket.o: $(hdrdir)/ruby/internal/value.h udpsocket.o: $(hdrdir)/ruby/internal/value_type.h @@ -2476,27 +2779,50 @@ udpsocket.o: $(hdrdir)/ruby/ruby.h udpsocket.o: $(hdrdir)/ruby/st.h udpsocket.o: $(hdrdir)/ruby/subst.h udpsocket.o: $(hdrdir)/ruby/thread.h +udpsocket.o: $(hdrdir)/ruby/thread_native.h udpsocket.o: $(hdrdir)/ruby/util.h +udpsocket.o: $(hdrdir)/ruby/version.h +udpsocket.o: $(top_srcdir)/ccan/check_type/check_type.h +udpsocket.o: $(top_srcdir)/ccan/container_of/container_of.h +udpsocket.o: $(top_srcdir)/ccan/list/list.h +udpsocket.o: $(top_srcdir)/ccan/str/str.h +udpsocket.o: $(top_srcdir)/encindex.h +udpsocket.o: $(top_srcdir)/id_table.h udpsocket.o: $(top_srcdir)/internal.h udpsocket.o: $(top_srcdir)/internal/array.h +udpsocket.o: $(top_srcdir)/internal/basic_operators.h +udpsocket.o: $(top_srcdir)/internal/box.h udpsocket.o: $(top_srcdir)/internal/compilers.h udpsocket.o: $(top_srcdir)/internal/error.h udpsocket.o: $(top_srcdir)/internal/gc.h +udpsocket.o: $(top_srcdir)/internal/imemo.h udpsocket.o: $(top_srcdir)/internal/io.h +udpsocket.o: $(top_srcdir)/internal/sanitizers.h udpsocket.o: $(top_srcdir)/internal/serial.h +udpsocket.o: $(top_srcdir)/internal/set_table.h udpsocket.o: $(top_srcdir)/internal/static_assert.h udpsocket.o: $(top_srcdir)/internal/string.h udpsocket.o: $(top_srcdir)/internal/thread.h udpsocket.o: $(top_srcdir)/internal/vm.h udpsocket.o: $(top_srcdir)/internal/warnings.h +udpsocket.o: $(top_srcdir)/method.h +udpsocket.o: $(top_srcdir)/node.h +udpsocket.o: $(top_srcdir)/ruby_assert.h +udpsocket.o: $(top_srcdir)/ruby_atomic.h +udpsocket.o: $(top_srcdir)/rubyparser.h udpsocket.o: $(top_srcdir)/shape.h +udpsocket.o: $(top_srcdir)/thread_pthread.h +udpsocket.o: $(top_srcdir)/vm_core.h +udpsocket.o: $(top_srcdir)/vm_opts.h udpsocket.o: constdefs.h udpsocket.o: rubysocket.h udpsocket.o: sockport.h udpsocket.o: udpsocket.c +udpsocket.o: {$(VPATH)}id.h unixserver.o: $(RUBY_EXTCONF_H) unixserver.o: $(arch_hdrdir)/ruby/config.h unixserver.o: $(hdrdir)/ruby/assert.h +unixserver.o: $(hdrdir)/ruby/atomic.h unixserver.o: $(hdrdir)/ruby/backward.h unixserver.o: $(hdrdir)/ruby/backward/2/assume.h unixserver.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -2634,6 +2960,7 @@ unixserver.o: $(hdrdir)/ruby/internal/intern/re.h unixserver.o: $(hdrdir)/ruby/internal/intern/ruby.h unixserver.o: $(hdrdir)/ruby/internal/intern/select.h unixserver.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +unixserver.o: $(hdrdir)/ruby/internal/intern/set.h unixserver.o: $(hdrdir)/ruby/internal/intern/signal.h unixserver.o: $(hdrdir)/ruby/internal/intern/sprintf.h unixserver.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2653,6 +2980,7 @@ unixserver.o: $(hdrdir)/ruby/internal/special_consts.h unixserver.o: $(hdrdir)/ruby/internal/static_assert.h unixserver.o: $(hdrdir)/ruby/internal/stdalign.h unixserver.o: $(hdrdir)/ruby/internal/stdbool.h +unixserver.o: $(hdrdir)/ruby/internal/stdckdint.h unixserver.o: $(hdrdir)/ruby/internal/symbol.h unixserver.o: $(hdrdir)/ruby/internal/value.h unixserver.o: $(hdrdir)/ruby/internal/value_type.h @@ -2667,27 +2995,50 @@ unixserver.o: $(hdrdir)/ruby/ruby.h unixserver.o: $(hdrdir)/ruby/st.h unixserver.o: $(hdrdir)/ruby/subst.h unixserver.o: $(hdrdir)/ruby/thread.h +unixserver.o: $(hdrdir)/ruby/thread_native.h unixserver.o: $(hdrdir)/ruby/util.h +unixserver.o: $(hdrdir)/ruby/version.h +unixserver.o: $(top_srcdir)/ccan/check_type/check_type.h +unixserver.o: $(top_srcdir)/ccan/container_of/container_of.h +unixserver.o: $(top_srcdir)/ccan/list/list.h +unixserver.o: $(top_srcdir)/ccan/str/str.h +unixserver.o: $(top_srcdir)/encindex.h +unixserver.o: $(top_srcdir)/id_table.h unixserver.o: $(top_srcdir)/internal.h unixserver.o: $(top_srcdir)/internal/array.h +unixserver.o: $(top_srcdir)/internal/basic_operators.h +unixserver.o: $(top_srcdir)/internal/box.h unixserver.o: $(top_srcdir)/internal/compilers.h unixserver.o: $(top_srcdir)/internal/error.h unixserver.o: $(top_srcdir)/internal/gc.h +unixserver.o: $(top_srcdir)/internal/imemo.h unixserver.o: $(top_srcdir)/internal/io.h +unixserver.o: $(top_srcdir)/internal/sanitizers.h unixserver.o: $(top_srcdir)/internal/serial.h +unixserver.o: $(top_srcdir)/internal/set_table.h unixserver.o: $(top_srcdir)/internal/static_assert.h unixserver.o: $(top_srcdir)/internal/string.h unixserver.o: $(top_srcdir)/internal/thread.h unixserver.o: $(top_srcdir)/internal/vm.h unixserver.o: $(top_srcdir)/internal/warnings.h +unixserver.o: $(top_srcdir)/method.h +unixserver.o: $(top_srcdir)/node.h +unixserver.o: $(top_srcdir)/ruby_assert.h +unixserver.o: $(top_srcdir)/ruby_atomic.h +unixserver.o: $(top_srcdir)/rubyparser.h unixserver.o: $(top_srcdir)/shape.h +unixserver.o: $(top_srcdir)/thread_pthread.h +unixserver.o: $(top_srcdir)/vm_core.h +unixserver.o: $(top_srcdir)/vm_opts.h unixserver.o: constdefs.h unixserver.o: rubysocket.h unixserver.o: sockport.h unixserver.o: unixserver.c +unixserver.o: {$(VPATH)}id.h unixsocket.o: $(RUBY_EXTCONF_H) unixsocket.o: $(arch_hdrdir)/ruby/config.h unixsocket.o: $(hdrdir)/ruby/assert.h +unixsocket.o: $(hdrdir)/ruby/atomic.h unixsocket.o: $(hdrdir)/ruby/backward.h unixsocket.o: $(hdrdir)/ruby/backward/2/assume.h unixsocket.o: $(hdrdir)/ruby/backward/2/attributes.h @@ -2825,6 +3176,7 @@ unixsocket.o: $(hdrdir)/ruby/internal/intern/re.h unixsocket.o: $(hdrdir)/ruby/internal/intern/ruby.h unixsocket.o: $(hdrdir)/ruby/internal/intern/select.h unixsocket.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +unixsocket.o: $(hdrdir)/ruby/internal/intern/set.h unixsocket.o: $(hdrdir)/ruby/internal/intern/signal.h unixsocket.o: $(hdrdir)/ruby/internal/intern/sprintf.h unixsocket.o: $(hdrdir)/ruby/internal/intern/string.h @@ -2844,6 +3196,7 @@ unixsocket.o: $(hdrdir)/ruby/internal/special_consts.h unixsocket.o: $(hdrdir)/ruby/internal/static_assert.h unixsocket.o: $(hdrdir)/ruby/internal/stdalign.h unixsocket.o: $(hdrdir)/ruby/internal/stdbool.h +unixsocket.o: $(hdrdir)/ruby/internal/stdckdint.h unixsocket.o: $(hdrdir)/ruby/internal/symbol.h unixsocket.o: $(hdrdir)/ruby/internal/value.h unixsocket.o: $(hdrdir)/ruby/internal/value_type.h @@ -2858,22 +3211,44 @@ unixsocket.o: $(hdrdir)/ruby/ruby.h unixsocket.o: $(hdrdir)/ruby/st.h unixsocket.o: $(hdrdir)/ruby/subst.h unixsocket.o: $(hdrdir)/ruby/thread.h +unixsocket.o: $(hdrdir)/ruby/thread_native.h unixsocket.o: $(hdrdir)/ruby/util.h +unixsocket.o: $(hdrdir)/ruby/version.h +unixsocket.o: $(top_srcdir)/ccan/check_type/check_type.h +unixsocket.o: $(top_srcdir)/ccan/container_of/container_of.h +unixsocket.o: $(top_srcdir)/ccan/list/list.h +unixsocket.o: $(top_srcdir)/ccan/str/str.h +unixsocket.o: $(top_srcdir)/encindex.h +unixsocket.o: $(top_srcdir)/id_table.h unixsocket.o: $(top_srcdir)/internal.h unixsocket.o: $(top_srcdir)/internal/array.h +unixsocket.o: $(top_srcdir)/internal/basic_operators.h +unixsocket.o: $(top_srcdir)/internal/box.h unixsocket.o: $(top_srcdir)/internal/compilers.h unixsocket.o: $(top_srcdir)/internal/error.h unixsocket.o: $(top_srcdir)/internal/gc.h +unixsocket.o: $(top_srcdir)/internal/imemo.h unixsocket.o: $(top_srcdir)/internal/io.h +unixsocket.o: $(top_srcdir)/internal/sanitizers.h unixsocket.o: $(top_srcdir)/internal/serial.h +unixsocket.o: $(top_srcdir)/internal/set_table.h unixsocket.o: $(top_srcdir)/internal/static_assert.h unixsocket.o: $(top_srcdir)/internal/string.h unixsocket.o: $(top_srcdir)/internal/thread.h unixsocket.o: $(top_srcdir)/internal/vm.h unixsocket.o: $(top_srcdir)/internal/warnings.h +unixsocket.o: $(top_srcdir)/method.h +unixsocket.o: $(top_srcdir)/node.h +unixsocket.o: $(top_srcdir)/ruby_assert.h +unixsocket.o: $(top_srcdir)/ruby_atomic.h +unixsocket.o: $(top_srcdir)/rubyparser.h unixsocket.o: $(top_srcdir)/shape.h +unixsocket.o: $(top_srcdir)/thread_pthread.h +unixsocket.o: $(top_srcdir)/vm_core.h +unixsocket.o: $(top_srcdir)/vm_opts.h unixsocket.o: constdefs.h unixsocket.o: rubysocket.h unixsocket.o: sockport.h unixsocket.o: unixsocket.c +unixsocket.o: {$(VPATH)}id.h # AUTOGENERATED DEPENDENCIES END diff --git a/ext/socket/extconf.rb b/ext/socket/extconf.rb index 4ca0983c68..a814e21c3a 100644 --- a/ext/socket/extconf.rb +++ b/ext/socket/extconf.rb @@ -327,6 +327,8 @@ end net/if_dl.h arpa/nameser.h resolv.h + pthread.h + sched.h ].each {|h| if have_header(h, headers) headers << h @@ -699,5 +701,11 @@ SRC "not needed" end end + + have_func("pthread_create") + have_func("pthread_detach") + have_func("pthread_attr_setdetachstate") + + $VPATH << '$(topdir)' << '$(top_srcdir)' create_makefile("socket") end diff --git a/ext/socket/getaddrinfo.c b/ext/socket/getaddrinfo.c index 95a2feb3be..9a65490b1d 100644 --- a/ext/socket/getaddrinfo.c +++ b/ext/socket/getaddrinfo.c @@ -62,9 +62,6 @@ #endif #include <unistd.h> #else -#if defined(_MSC_VER) && _MSC_VER <= 1200 -#include <windows.h> -#endif #include <winsock2.h> #include <ws2tcpip.h> #include <io.h> @@ -171,9 +168,7 @@ static const char *const ai_errlist[] = { #define GET_CANONNAME(ai, str) \ if (pai->ai_flags & AI_CANONNAME) {\ - if (((ai)->ai_canonname = (char *)malloc(strlen(str) + 1)) != NULL) {\ - strcpy((ai)->ai_canonname, (str));\ - } else {\ + if (((ai)->ai_canonname = strdup(str)) == NULL) {\ error = EAI_MEMORY;\ goto free;\ }\ @@ -219,8 +214,7 @@ freeaddrinfo(struct addrinfo *ai) do { next = ai->ai_next; - if (ai->ai_canonname) - free(ai->ai_canonname); + free(ai->ai_canonname); /* no need to free(ai->ai_addr) */ free(ai); } while ((ai = next) != NULL); diff --git a/ext/socket/getnameinfo.c b/ext/socket/getnameinfo.c index ae5284fab6..98da8c1647 100644 --- a/ext/socket/getnameinfo.c +++ b/ext/socket/getnameinfo.c @@ -55,9 +55,6 @@ #endif #endif #ifdef _WIN32 -#if defined(_MSC_VER) && _MSC_VER <= 1200 -#include <windows.h> -#endif #include <winsock2.h> #include <ws2tcpip.h> #define snprintf _snprintf @@ -158,16 +155,14 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho /* what we should do? */ } else if (flags & NI_NUMERICSERV) { snprintf(numserv, sizeof(numserv), "%d", ntohs(port)); - if (strlen(numserv) + 1 > servlen) + if (strlcpy(serv, numserv, servlen) >= servlen) return ENI_MEMORY; - strcpy(serv, numserv); } else { #if defined(HAVE_GETSERVBYPORT) struct servent *sp = getservbyport(port, (flags & NI_DGRAM) ? "udp" : "tcp"); if (sp) { - if (strlen(sp->s_name) + 1 > servlen) + if (strlcpy(serv, sp->s_name, servlen) >= servlen) return ENI_MEMORY; - strcpy(serv, sp->s_name); } else return ENI_NOSERVNAME; #else @@ -202,9 +197,8 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho if (inet_ntop(afd->a_af, addr, numaddr, sizeof(numaddr)) == NULL) return ENI_SYSTEM; - if (strlen(numaddr) > hostlen) + if (strlcpy(host, numaddr, hostlen) >= hostlen) return ENI_MEMORY; - strcpy(host, numaddr); } else { #ifdef INET6 hp = getipnodebyaddr(addr, afd->a_addrlen, afd->a_af, &h_error); @@ -218,13 +212,12 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho p = strchr(hp->h_name, '.'); if (p) *p = '\0'; } - if (strlen(hp->h_name) + 1 > hostlen) { + if (strlcpy(host, hp->h_name, hostlen) >= hostlen) { #ifdef INET6 freehostent(hp); #endif return ENI_MEMORY; } - strcpy(host, hp->h_name); #ifdef INET6 freehostent(hp); #endif @@ -234,9 +227,8 @@ getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, socklen_t ho if (inet_ntop(afd->a_af, addr, numaddr, sizeof(numaddr)) == NULL) return ENI_NOHOSTNAME; - if (strlen(numaddr) > hostlen) + if (strlcpy(host, numaddr, hostlen) >= hostlen) return ENI_MEMORY; - strcpy(host, numaddr); } } return SUCCESS; diff --git a/ext/socket/ifaddr.c b/ext/socket/ifaddr.c index ab163dcc8f..3596c40a11 100644 --- a/ext/socket/ifaddr.c +++ b/ext/socket/ifaddr.c @@ -177,6 +177,8 @@ ifaddr_ifindex(VALUE self) * ifaddr.flags => integer * * Returns the flags of _ifaddr_. + * + * The value is bitwise-or of Socket::IFF_* constants such as Socket::IFF_LOOPBACK. */ static VALUE diff --git a/ext/socket/init.c b/ext/socket/init.c index 557d4374a5..b761d601c3 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -27,6 +27,7 @@ VALUE rb_cSocket; VALUE rb_cAddrinfo; VALUE rb_eSocket; +VALUE rb_eResolution; #ifdef SOCKS VALUE rb_cSOCKSSocket; @@ -34,9 +35,10 @@ VALUE rb_cSOCKSSocket; int rsock_do_not_reverse_lookup = 1; static VALUE sym_wait_readable; +static ID id_error_code; void -rsock_raise_socket_error(const char *reason, int error) +rsock_raise_resolution_error(const char *reason, int error) { #ifdef EAI_SYSTEM int e; @@ -48,10 +50,14 @@ rsock_raise_socket_error(const char *reason, int error) VALUE msg = rb_sprintf("%s: ", reason); if (!enc) enc = rb_default_internal_encoding(); rb_str_concat(msg, rb_w32_conv_from_wchar(gai_strerrorW(error), enc)); - rb_exc_raise(rb_exc_new_str(rb_eSocket, msg)); #else - rb_raise(rb_eSocket, "%s: %s", reason, gai_strerror(error)); + VALUE msg = rb_sprintf("%s: %s", reason, gai_strerror(error)); #endif + + StringValue(msg); + VALUE self = rb_class_new_instance(1, &msg, rb_eResolution); + rb_ivar_set(self, id_error_code, INT2NUM(error)); + rb_exc_raise(self); } #if defined __APPLE__ @@ -101,6 +107,7 @@ rsock_send_blocking(void *data) } struct recvfrom_arg { + rb_io_t *fptr; int fd, flags; VALUE str; size_t length; @@ -116,6 +123,7 @@ recvfrom_blocking(void *data) ssize_t ret; ret = recvfrom(arg->fd, RSTRING_PTR(arg->str), arg->length, arg->flags, &arg->buf.addr, &arg->alen); + if (ret != -1 && len0 < arg->alen) arg->alen = len0; @@ -144,7 +152,19 @@ recvfrom_locktmp(VALUE v) { struct recvfrom_arg *arg = (struct recvfrom_arg *)v; - return rb_thread_io_blocking_region(recvfrom_blocking, arg, arg->fd); + return rb_io_blocking_region(arg->fptr, recvfrom_blocking, arg); +} + +int +rsock_is_dgram(rb_io_t *fptr) +{ + int socktype; + socklen_t optlen = (socklen_t)sizeof(socktype); + int ret = getsockopt(fptr->fd, SOL_SOCKET, SO_TYPE, (void*)&socktype, &optlen); + if (ret == -1) { + rb_sys_fail("getsockopt(SO_TYPE)"); + } + return socktype == SOCK_DGRAM; } VALUE @@ -173,6 +193,7 @@ rsock_s_recvfrom(VALUE socket, int argc, VALUE *argv, enum sock_recv_type from) rb_raise(rb_eIOError, "recv for buffered IO"); } + arg.fptr = fptr; arg.fd = fptr->fd; arg.alen = (socklen_t)sizeof(arg.buf); arg.str = str; @@ -185,8 +206,12 @@ rsock_s_recvfrom(VALUE socket, int argc, VALUE *argv, enum sock_recv_type from) rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), Qnil); #endif - slen = (long)rb_str_locktmp_ensure(str, recvfrom_locktmp, (VALUE)&arg); + rb_str_locktmp(str); + slen = (long)rb_ensure(recvfrom_locktmp, (VALUE)&arg, rb_str_unlocktmp, str); + if (slen == 0 && !rsock_is_dgram(fptr)) { + return Qnil; + } if (slen >= 0) break; if (!rb_io_maybe_wait_readable(errno, socket, RUBY_IO_TIMEOUT_DEFAULT)) @@ -259,6 +284,10 @@ rsock_s_recvfrom_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str, if (slen != -1 && len0 < alen) alen = len0; + if (slen == 0 && !rsock_is_dgram(fptr)) { + return Qnil; + } + if (slen < 0) { int e = errno; switch (e) { @@ -392,7 +421,7 @@ rsock_write_nonblock(VALUE sock, VALUE str, VALUE ex) if (e == EWOULDBLOCK || e == EAGAIN) { if (ex == Qfalse) return sym_wait_writable; rb_readwrite_syserr_fail(RB_IO_WAIT_WRITABLE, e, - "write would block"); + "write would block"); } rb_syserr_fail_path(e, fptr->pathv); } @@ -433,9 +462,9 @@ rsock_socket(int domain, int type, int proto) fd = rsock_socket0(domain, type, proto); if (fd < 0) { - if (rb_gc_for_fd(errno)) { - fd = rsock_socket0(domain, type, proto); - } + if (rb_gc_for_fd(errno)) { + fd = rsock_socket0(domain, type, proto); + } } if (0 <= fd) rb_update_max_fd(fd); @@ -444,10 +473,11 @@ rsock_socket(int domain, int type, int proto) /* emulate blocking connect behavior on EINTR or non-blocking socket */ static int -wait_connectable(int fd, struct timeval *timeout) +wait_connectable(VALUE self, VALUE timeout, const struct sockaddr *sockaddr, int len) { - int sockerr, revents; + int sockerr; socklen_t sockerrlen; + int fd = rb_io_descriptor(self); sockerrlen = (socklen_t)sizeof(sockerr); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen) < 0) @@ -481,7 +511,16 @@ wait_connectable(int fd, struct timeval *timeout) * * Note: rb_wait_for_single_fd already retries on EINTR/ERESTART */ - revents = rb_wait_for_single_fd(fd, RB_WAITFD_IN|RB_WAITFD_OUT, timeout); + VALUE result = rb_io_wait(self, RB_INT2NUM(RUBY_IO_READABLE|RUBY_IO_WRITABLE), timeout); + + if (result == Qfalse) { + VALUE rai = rsock_addrinfo_new((struct sockaddr *)sockaddr, len, PF_UNSPEC, 0, 0, Qnil, Qnil); + VALUE addr_str = rsock_addrinfo_inspect_sockaddr(rai); + VALUE message = rb_sprintf("user specified timeout for %" PRIsVALUE, addr_str); + rb_raise(rb_eIOTimeoutError, "%" PRIsVALUE, message); + } + + int revents = RB_NUM2INT(result); if (revents < 0) return -1; @@ -492,16 +531,10 @@ wait_connectable(int fd, struct timeval *timeout) switch (sockerr) { case 0: - /* - * be defensive in case some platforms set SO_ERROR on the original, - * interrupted connect() - */ - - /* when the connection timed out, no errno is set and revents is 0. */ - if (timeout && revents == 0) { - errno = ETIMEDOUT; - return -1; - } + /* + * be defensive in case some platforms set SO_ERROR on the original, + * interrupted connect() + */ case EINTR: #ifdef ERESTART case ERESTART: @@ -549,19 +582,19 @@ socks_connect_blocking(void *data) #endif int -rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks, struct timeval *timeout) +rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, VALUE timeout) { - int status; + int descriptor = rb_io_descriptor(self); rb_blocking_function_t *func = connect_blocking; - struct connect_arg arg; + struct connect_arg arg = {.fd = descriptor, .sockaddr = sockaddr, .len = len}; + + rb_io_t *fptr; + RB_IO_POINTER(self, fptr); - arg.fd = fd; - arg.sockaddr = sockaddr; - arg.len = len; #if defined(SOCKS) && !defined(SOCKS5) if (socks) func = socks_connect_blocking; #endif - status = (int)BLOCKING_REGION_FD(func, &arg); + int status = (int)rb_io_blocking_region(fptr, func, &arg); if (status < 0) { switch (errno) { @@ -573,7 +606,7 @@ rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks, struc #ifdef EINPROGRESS case EINPROGRESS: #endif - return wait_connectable(fd, timeout); + return wait_connectable(self, timeout, sockaddr, len); } } return status; @@ -681,9 +714,9 @@ rsock_s_accept(VALUE klass, VALUE io, struct sockaddr *sockaddr, socklen_t *len) RB_IO_POINTER(io, fptr); struct accept_arg accept_arg = { - .fd = fptr->fd, - .sockaddr = sockaddr, - .len = len + .fd = fptr->fd, + .sockaddr = sockaddr, + .len = len }; int retry = 0, peer; @@ -692,7 +725,7 @@ rsock_s_accept(VALUE klass, VALUE io, struct sockaddr *sockaddr, socklen_t *len) #ifdef RSOCK_WAIT_BEFORE_BLOCKING rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), Qnil); #endif - peer = (int)BLOCKING_REGION_FD(accept_blocking, &accept_arg); + peer = (int)rb_io_blocking_region(fptr, accept_blocking, &accept_arg); if (peer < 0) { int error = errno; @@ -730,10 +763,10 @@ rsock_getfamily(rb_io_t *fptr) if (cached) { switch (cached) { #ifdef AF_UNIX - case FMODE_UNIX: return AF_UNIX; + case FMODE_UNIX: return AF_UNIX; #endif - case FMODE_INET: return AF_INET; - case FMODE_INET6: return AF_INET6; + case FMODE_INET: return AF_INET; + case FMODE_INET6: return AF_INET6; } } @@ -752,6 +785,28 @@ rsock_getfamily(rb_io_t *fptr) return ss.addr.sa_family; } +/* + * call-seq: + * error_code -> integer + * + * Returns the raw error code indicating the cause of the hostname resolution failure. + * + * begin + * Addrinfo.getaddrinfo("ruby-lang.org", nil) + * rescue Socket::ResolutionError => e + * if e.error_code == Socket::EAI_AGAIN + * puts "Temporary failure in name resolution." + * end + * end + * + * Note that error codes depend on the operating system. + */ +static VALUE +sock_resolv_error_code(VALUE self) +{ + return rb_attr_get(self, id_error_code); +} + void rsock_init_socket_init(void) { @@ -759,6 +814,11 @@ rsock_init_socket_init(void) * SocketError is the error class for socket. */ rb_eSocket = rb_define_class("SocketError", rb_eStandardError); + /* + * Socket::ResolutionError is the error class for hostname resolution. + */ + rb_eResolution = rb_define_class_under(rb_cSocket, "ResolutionError", rb_eSocket); + rb_define_method(rb_eResolution, "error_code", sock_resolv_error_code, 0); rsock_init_ipsocket(); rsock_init_tcpsocket(); rsock_init_tcpserver(); @@ -772,6 +832,8 @@ rsock_init_socket_init(void) rsock_init_sockifaddr(); rsock_init_socket_constants(); + id_error_code = rb_intern_const("error_code"); + #undef rb_intern sym_wait_readable = ID2SYM(rb_intern("wait_readable")); diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index 0c13620258..931a1a629c 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -9,20 +9,39 @@ ************************************************/ #include "rubysocket.h" +#include <stdio.h> struct inetsock_arg { - VALUE sock; + VALUE self; + VALUE io; + struct { VALUE host, serv; struct rb_addrinfo *res; } remote, local; int type; - int fd; VALUE resolv_timeout; VALUE connect_timeout; + VALUE open_timeout; }; +void +rsock_raise_user_specified_timeout(struct addrinfo *ai, VALUE host, VALUE port) +{ + VALUE message; + + if (ai && ai->ai_addr) { + VALUE rai = rsock_addrinfo_new((struct sockaddr *)ai->ai_addr, (socklen_t)ai->ai_addrlen, PF_UNSPEC, 0, 0, Qnil, Qnil); + VALUE addr_str = rsock_addrinfo_inspect_sockaddr(rai); + message = rb_sprintf("user specified timeout for %" PRIsVALUE, addr_str); + } else { + message = rb_sprintf("user specified timeout for %" PRIsVALUE " port %" PRIsVALUE, host, port); + } + + rb_raise(rb_eIOTimeoutError, "%" PRIsVALUE, message); +} + static VALUE inetsock_cleanup(VALUE v) { @@ -35,35 +54,42 @@ inetsock_cleanup(VALUE v) rb_freeaddrinfo(arg->local.res); arg->local.res = 0; } - if (arg->fd >= 0) { - close(arg->fd); + if (arg->io != Qnil) { + rb_io_close(arg->io); + arg->io = Qnil; } return Qnil; } static VALUE +current_clocktime(void) +{ + VALUE clock_monotnic_const = rb_const_get(rb_mProcess, rb_intern("CLOCK_MONOTONIC")); + return rb_funcall(rb_mProcess, rb_intern("clock_gettime"), 1, clock_monotnic_const); +} + +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 status = 0, local = 0; int family = AF_UNSPEC; const char *syscall = 0; + VALUE resolv_timeout = arg->resolv_timeout; VALUE connect_timeout = arg->connect_timeout; - struct timeval tv_storage; - struct timeval *tv = NULL; + VALUE open_timeout = arg->open_timeout; + VALUE timeout; + VALUE starts_at; - if (!NIL_P(connect_timeout)) { - tv_storage = rb_time_interval(connect_timeout); - tv = &tv_storage; - } + timeout = NIL_P(open_timeout) ? resolv_timeout : open_timeout; + starts_at = current_clocktime(); arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv, family, SOCK_STREAM, - (type == INET_SERVER) ? AI_PASSIVE : 0); - + (type == INET_SERVER) ? AI_PASSIVE : 0, timeout); /* * Maybe also accept a local address @@ -71,10 +97,11 @@ init_inetsock_internal(VALUE v) 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); + family, SOCK_STREAM, 0, timeout); } - arg->fd = fd = -1; + 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) @@ -96,12 +123,14 @@ init_inetsock_internal(VALUE v) } status = rsock_socket(res->ai_family,res->ai_socktype,res->ai_protocol); syscall = "socket(2)"; - fd = status; - if (fd < 0) { + if (status < 0) { error = errno; continue; } - arg->fd = fd; + + int fd = status; + io = arg->io = rsock_init_sock(arg->self, fd); + if (type == INET_SERVER) { #if !defined(_WIN32) && !defined(__CYGWIN__) status = 1; @@ -123,21 +152,33 @@ init_inetsock_internal(VALUE v) syscall = "bind(2)"; } + if (NIL_P(open_timeout)) { + timeout = connect_timeout; + } else { + VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at); + timeout = rb_funcall(open_timeout, '-', 1, elapsed); + if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) { + rsock_raise_user_specified_timeout(res, arg->remote.host, arg->remote.serv); + } + } + if (status >= 0) { - status = rsock_connect(fd, res->ai_addr, res->ai_addrlen, - (type == INET_SOCKS), tv); + status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), timeout); syscall = "connect(2)"; } } if (status < 0) { error = errno; - close(fd); - arg->fd = fd = -1; + arg->io = Qnil; + rb_io_close(io); + io = Qnil; continue; - } else + } else { break; + } } + if (status < 0) { VALUE host, port; @@ -152,28 +193,38 @@ init_inetsock_internal(VALUE v) rsock_syserr_fail_host_port(error, syscall, host, port); } - arg->fd = -1; + // Don't close the socket in `inetsock_cleanup` if we are returning it: + arg->io = Qnil; - if (type == INET_SERVER) { - status = listen(fd, SOMAXCONN); + if (type == INET_SERVER && io != Qnil) { + status = listen(rb_io_descriptor(io), SOMAXCONN); if (status < 0) { error = errno; - close(fd); + rb_io_close(io); rb_syserr_fail(error, "listen(2)"); } } /* create new instance */ - return rsock_init_sock(arg->sock, fd); + return io; } +#if FAST_FALLBACK_INIT_INETSOCK_IMPL == 0 + 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) +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 open_timeout, + VALUE _fast_fallback, VALUE _test_mode_settings) { + if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) { + rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"); + } + struct inetsock_arg arg; - arg.sock = sock; + arg.self = self; + arg.io = Qnil; arg.remote.host = remote_host; arg.remote.serv = remote_serv; arg.remote.res = 0; @@ -181,13 +232,1205 @@ rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv, 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; + arg.open_timeout = open_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); +} + +static int +is_local_port_fixed(const char *portp) +{ + if (!portp) return 0; + + char *endp; + errno = 0; + long port = strtol(portp, &endp, 10); + + if (endp == portp) return 0; + if (errno == ERANGE) return 0; + + return port > 0; +} + +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; + VALUE open_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, + struct timeval *user_specified_open_timeout_at) +{ + if (any_addrinfos(resolution_store)) { + struct timeval *delay; + delay = resolution_delay ? resolution_delay : connection_attempt_delay; + + if (user_specified_open_timeout_at && + timercmp(user_specified_open_timeout_at, delay, <)) { + return user_specified_open_timeout_at; + } + return delay; + } + + if (user_specified_open_timeout_at) return user_specified_open_timeout_at; + + 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 open_timeout = arg->open_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 timeval user_specified_open_timeout_storage; + struct timeval *user_specified_open_timeout_at = NULL; + struct timespec now = current_clocktime_ts(); + VALUE starts_at = current_clocktime(); + + if (!NIL_P(open_timeout)) { + struct timeval open_timeout_tv = rb_time_interval(open_timeout); + user_specified_open_timeout_storage = add_ts_to_tv(open_timeout_tv, now); + user_specified_open_timeout_at = &user_specified_open_timeout_storage; + } + + /* start of hostname resolution */ + if (arg->family_size == 1) { + arg->wait = -1; + arg->getaddrinfo_shared = NULL; + + int family = arg->families[0]; + VALUE t = NIL_P(open_timeout) ? resolv_timeout : open_timeout; + + arg->remote.res = rsock_addrinfo( + arg->remote.host, + arg->remote.serv, + family, + SOCK_STREAM, + 0, + t + ); + + 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); + } + } + + 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 { + VALUE timeout = Qnil; + + if (!NIL_P(open_timeout)) { + VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at); + timeout = rb_funcall(open_timeout, '-', 1, elapsed); + + if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) { + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); + } + } + if (NIL_P(timeout)) { + 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; + } + 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, + user_specified_open_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 (is_timeout_tv(user_specified_open_timeout_at, now)) { + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); + } + + 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))) { + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); + } + } + } + + 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 open_timeout, + VALUE fast_fallback, VALUE test_mode_settings) +{ + if (!NIL_P(open_timeout) && (!NIL_P(resolv_timeout) || !NIL_P(connect_timeout))) { + rb_raise(rb_eArgError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"); + } + + if (type == INET_CLIENT && FAST_FALLBACK_INIT_INETSOCK_IMPL == 1 && RTEST(fast_fallback)) { + struct rb_addrinfo *local_res = NULL; + char *hostp, *portp, *local_portp; + char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV], local_pbuf[NI_MAXSERV]; + int additional_flags = 0; + int local_flags = 0; + hostp = raddrinfo_host_str(remote_host, hbuf, sizeof(hbuf), &additional_flags); + portp = raddrinfo_port_str(remote_serv, pbuf, sizeof(pbuf), &additional_flags); + local_portp = raddrinfo_port_str(local_serv, local_pbuf, sizeof(local_pbuf), &local_flags); + + if (!is_specified_ip_address(hostp) && !is_local_port_fixed(local_portp)) { + 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)) { + VALUE t = NIL_P(open_timeout) ? resolv_timeout : open_timeout; + local_res = rsock_addrinfo( + local_host, + local_serv, + AF_UNSPEC, + SOCK_STREAM, + 0, + t + ); + + struct addrinfo *tmp_p = local_res->ai; + for (; 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.open_timeout = open_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; + arg.open_timeout = open_timeout; + + return rb_ensure(init_inetsock_internal, (VALUE)&arg, + inetsock_cleanup, (VALUE)&arg); +} + +#endif + static ID id_numeric, id_hostname; int @@ -274,16 +1517,13 @@ ip_inspect(VALUE sock) 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) + 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); } @@ -315,16 +1555,13 @@ ip_addr(int argc, VALUE *argv, VALUE sock) 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) + 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); } @@ -372,7 +1609,7 @@ 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); + struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0, Qnil); socklen_t len = res->ai->ai_addrlen; /* just take the first one */ diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index eecdc7d4b8..36fcceaee9 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -62,7 +62,7 @@ class Addrinfo break when :wait_writable sock.wait_writable(timeout) or - raise Errno::ETIMEDOUT, 'user specified timeout' + raise Errno::ETIMEDOUT, "user specified timeout for #{self.ip_address}:#{self.ip_port}" end while true else sock.connect(self) @@ -333,9 +333,10 @@ class BasicSocket < IO # _flags_ is zero or more of the +MSG_+ options. # The result, _mesg_, is the data received. # - # When recvfrom(2) returns 0, Socket#recv_nonblock returns - # an empty string as data. - # The meaning depends on the socket: EOF on TCP, empty packet on UDP, etc. + # When recvfrom(2) returns 0, Socket#recv_nonblock returns nil. + # In most cases it means the connection was closed, but for UDP connections + # it may mean an empty packet was received, as the underlying API makes + # it impossible to distinguish these two cases. # # === Parameters # * +maxlen+ - the number of bytes to receive from the socket @@ -480,9 +481,10 @@ class Socket < BasicSocket # The second element, _sender_addrinfo_, contains protocol-specific address # information of the sender. # - # When recvfrom(2) returns 0, Socket#recvfrom_nonblock returns - # an empty string as data. - # The meaning depends on the socket: EOF on TCP, empty packet on UDP, etc. + # When recvfrom(2) returns 0, Socket#recv_nonblock returns nil. + # In most cases it means the connection was closed, but for UDP connections + # it may mean an empty packet was received, as the underlying API makes + # it impossible to distinguish these two cases. # # === Parameters # * +maxlen+ - the maximum number of bytes to receive from the socket @@ -597,19 +599,54 @@ class Socket < BasicSocket __accept_nonblock(exception) end + # :stopdoc: + RESOLUTION_DELAY = 0.05 + private_constant :RESOLUTION_DELAY + + CONNECTION_ATTEMPT_DELAY = 0.25 + private_constant :CONNECTION_ATTEMPT_DELAY + + ADDRESS_FAMILIES = { + ipv6: Socket::AF_INET6, + ipv4: Socket::AF_INET + }.freeze + private_constant :ADDRESS_FAMILIES + + HOSTNAME_RESOLUTION_QUEUE_UPDATED = 0 + private_constant :HOSTNAME_RESOLUTION_QUEUE_UPDATED + + IPV6_ADDRESS_FORMAT = /\A(?i:(?:(?:[0-9A-F]{1,4}:){7}(?:[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){6}(?:[0-9A-F]{1,4}|:(?:[0-9A-F]{1,4}:){1,5}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){5}(?:(?::[0-9A-F]{1,4}){1,2}|:(?:[0-9A-F]{1,4}:){1,4}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){4}(?:(?::[0-9A-F]{1,4}){1,3}|:(?:[0-9A-F]{1,4}:){1,3}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){3}(?:(?::[0-9A-F]{1,4}){1,4}|:(?:[0-9A-F]{1,4}:){1,2}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){2}(?:(?::[0-9A-F]{1,4}){1,5}|:(?:[0-9A-F]{1,4}:)[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){1}(?:(?::[0-9A-F]{1,4}){1,6}|:(?:[0-9A-F]{1,4}:){0,5}[0-9A-F]{1,4}|:)|(?:::(?:[0-9A-F]{1,4}:){0,7}[0-9A-F]{1,4}|::)))(?:%.+)?\z/ + private_constant :IPV6_ADDRESS_FORMAT + # :startdoc: + # :call-seq: # Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) {|socket| ... } # Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) # # creates a new socket object connected to host:port using TCP/IP. # + # Starting from Ruby 3.4, this method operates according to the + # Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305]) + # algorithm by default. + # + # For details on Happy Eyeballs Version 2, + # see {Socket.tcp_fast_fallback=}[rdoc-ref:Socket.tcp_fast_fallback=]. + # + # To make it behave the same as in Ruby 3.3 and earlier, + # explicitly specify the option fast_fallback:false. + # Or, setting Socket.tcp_fast_fallback=false will disable + # Happy Eyeballs Version 2 not only for this method but for all Socket globally. + # # If local_host:local_port is given, # the socket is bound to it. # # The optional last argument _opts_ is options represented by a hash. # _opts_ may have following options: # - # [:connect_timeout] specify the timeout in seconds. + # [:resolv_timeout] Specifies the timeout in seconds from when the hostname resolution starts. + # [:connect_timeout] This method sequentially attempts connecting to all candidate destination addresses.<br>The +connect_timeout+ specifies the timeout in seconds from the start of the connection attempt to the last candidate.<br>By default, all connection attempts continue until the timeout occurs.<br>When +fast_fallback:false+ is explicitly specified,<br>a timeout is set for each connection attempt and any connection attempt that exceeds its timeout will be canceled. + # [:open_timeout] Specifies the timeout in seconds from the start of the method execution.<br>If this timeout is reached while there are still addresses that have not yet been attempted for connection, no further attempts will be made.<br>If this option is specified together with other timeout options, an +ArgumentError+ will be raised. + # [:fast_fallback] Enables the Happy Eyeballs Version 2 algorithm (enabled by default). # # If a block is given, the block is called with the socket. # The value of the block is returned. @@ -622,17 +659,296 @@ class Socket < BasicSocket # sock.close_write # puts sock.read # } - # - def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil) # :yield: socket + def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket + if open_timeout && (connect_timeout || resolv_timeout) + raise ArgumentError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout" + end + + sock = if fast_fallback && !(host && ip_address?(host)) && !(local_port && local_port.to_i != 0) + tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) + else + tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) + end + + if block_given? + begin + yield sock + ensure + sock.close + end + else + sock + end + end + + # :stopdoc: + def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil) + if local_host || local_port + local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, timeout: open_timeout || resolv_timeout) + resolving_family_names = local_addrinfos.map { |lai| ADDRESS_FAMILIES.key(lai.afamily) }.uniq + else + local_addrinfos = [] + resolving_family_names = ADDRESS_FAMILIES.keys + end + + hostname_resolution_threads = [] + resolution_store = HostnameResolutionStore.new(resolving_family_names) + connecting_sockets = {} + is_windows_environment ||= (RUBY_PLATFORM =~ /mswin|mingw|cygwin/) + + now = current_clock_time + starts_at = now + resolution_delay_expires_at = nil + connection_attempt_delay_expires_at = nil + user_specified_connect_timeout_at = nil + user_specified_open_timeout_at = open_timeout ? now + open_timeout : nil + last_error = nil + last_error_from_thread = false + + if resolving_family_names.size == 1 + family_name = resolving_family_names.first + addrinfos = Addrinfo.getaddrinfo(host, port, ADDRESS_FAMILIES[:family_name], :STREAM, timeout: open_timeout || resolv_timeout) + resolution_store.add_resolved(family_name, addrinfos) + hostname_resolution_result = nil + hostname_resolution_notifier = nil + user_specified_resolv_timeout_at = nil + else + hostname_resolution_result = HostnameResolutionResult.new(resolving_family_names.size) + hostname_resolution_notifier = hostname_resolution_result.notifier + + hostname_resolution_threads.concat( + resolving_family_names.map { |family| + thread_args = [family, host, port, hostname_resolution_result] + thread = Thread.new(*thread_args) { |*thread_args| resolve_hostname(*thread_args) } + Thread.pass + thread + } + ) + user_specified_resolv_timeout_at = resolv_timeout ? now + resolv_timeout : Float::INFINITY + end + + loop do + if resolution_store.any_addrinfos? && + !resolution_delay_expires_at && + !connection_attempt_delay_expires_at + while (addrinfo = resolution_store.get_addrinfo) + if local_addrinfos.any? + local_addrinfo = local_addrinfos.find { |lai| lai.afamily == addrinfo.afamily } + + if local_addrinfo.nil? + if resolution_store.any_addrinfos? + # Try other Addrinfo in next "while" + next + elsif connecting_sockets.any? || resolution_store.any_unresolved_family? + # Exit this "while" and wait for connections to be established or hostname resolution in next loop + # Or exit this "while" and wait for hostname resolution in next loop + break + else + raise SocketError.new 'no appropriate local address' + end + end + end + + begin + if resolution_store.any_addrinfos? || + connecting_sockets.any? || + resolution_store.any_unresolved_family? + socket = Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol) + socket.bind(local_addrinfo) if local_addrinfo + result = socket.connect_nonblock(addrinfo, exception: false) + else + timeout = + if open_timeout + t = open_timeout - (current_clock_time - starts_at) + t.negative? ? 0 : t + else + connect_timeout + end + result = socket = local_addrinfo ? + addrinfo.connect_from(local_addrinfo, timeout:) : + addrinfo.connect(timeout:) + end + + if result == :wait_writable + connection_attempt_delay_expires_at = now + CONNECTION_ATTEMPT_DELAY + if resolution_store.empty_addrinfos? + user_specified_connect_timeout_at = connect_timeout ? now + connect_timeout : Float::INFINITY + end + + connecting_sockets[socket] = addrinfo + break + else + return socket # connection established + end + rescue SystemCallError => e + socket&.close + last_error = e + + if resolution_store.any_addrinfos? + # Try other Addrinfo in next "while" + next + elsif connecting_sockets.any? || resolution_store.any_unresolved_family? + # Exit this "while" and wait for connections to be established or hostname resolution in next loop + # Or exit this "while" and wait for hostname resolution in next loop + break + else + raise last_error + end + end + end + end + + ends_at = + if resolution_store.any_addrinfos? + [(resolution_delay_expires_at || connection_attempt_delay_expires_at), + user_specified_open_timeout_at].compact.min + elsif user_specified_open_timeout_at + user_specified_open_timeout_at + else + [user_specified_resolv_timeout_at, user_specified_connect_timeout_at].compact.max + end + + hostname_resolved, writable_sockets, except_sockets = IO.select( + hostname_resolution_notifier, + connecting_sockets.keys, + # Use errorfds to wait for non-blocking connect failures on Windows + is_windows_environment ? connecting_sockets.keys : nil, + second_to_timeout(current_clock_time, ends_at), + ) + now = current_clock_time + resolution_delay_expires_at = nil if expired?(now, resolution_delay_expires_at) + connection_attempt_delay_expires_at = nil if expired?(now, connection_attempt_delay_expires_at) + + if writable_sockets&.any? + while (writable_socket = writable_sockets.pop) + is_connected = is_windows_environment || ( + sockopt = writable_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR) + sockopt.int.zero? + ) + + if is_connected + connecting_sockets.delete writable_socket + return writable_socket + else + failed_ai = connecting_sockets.delete writable_socket + writable_socket.close + ip_address = failed_ai.ipv6? ? "[#{failed_ai.ip_address}]" : failed_ai.ip_address + last_error = SystemCallError.new("connect(2) for #{ip_address}:#{failed_ai.ip_port}", sockopt.int) + + if writable_sockets.any? || connecting_sockets.any? + # Try other writable socket in next "while" + # Or exit this "while" and wait for connections to be established or hostname resolution in next loop + elsif resolution_store.any_addrinfos? || resolution_store.any_unresolved_family? + # Exit this "while" and try other connection attempt + # Or exit this "while" and wait for hostname resolution in next loop + connection_attempt_delay_expires_at = nil + user_specified_connect_timeout_at = nil + else + raise last_error + end + end + end + end + + if except_sockets&.any? + except_sockets.each do |except_socket| + failed_ai = connecting_sockets.delete except_socket + sockopt = except_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR) + except_socket.close + ip_address = failed_ai.ipv6? ? "[#{failed_ai.ip_address}]" : failed_ai.ip_address + last_error = SystemCallError.new("connect(2) for #{ip_address}:#{failed_ai.ip_port}", sockopt.int) + + if except_sockets.any? || connecting_sockets.any? + # Cleanup other except socket in next "each" + # Or exit this "while" and wait for connections to be established or hostname resolution in next loop + elsif resolution_store.any_addrinfos? || resolution_store.any_unresolved_family? + # Exit this "while" and try other connection attempt + # Or exit this "while" and wait for hostname resolution in next loop + connection_attempt_delay_expires_at = nil + user_specified_connect_timeout_at = nil + else + raise last_error + end + end + end + + if hostname_resolved&.any? + while (family_and_result = hostname_resolution_result.get) + family_name, result = family_and_result + + if result.is_a? Exception + resolution_store.add_error(family_name, result) + + unless (Socket.const_defined?(:EAI_ADDRFAMILY)) && + (result.is_a?(Socket::ResolutionError)) && + (result.error_code == Socket::EAI_ADDRFAMILY) + other = family_name == :ipv6 ? :ipv4 : :ipv6 + if !resolution_store.resolved?(other) || !resolution_store.resolved_successfully?(other) + last_error = result + last_error_from_thread = true + end + end + else + resolution_store.add_resolved(family_name, result) + end + end + + if resolution_store.resolved?(:ipv4) + if resolution_store.resolved?(:ipv6) + hostname_resolution_notifier = nil + resolution_delay_expires_at = nil + user_specified_resolv_timeout_at = nil + elsif resolution_store.resolved_successfully?(:ipv4) + resolution_delay_expires_at = now + RESOLUTION_DELAY + end + end + end + + if expired?(now, user_specified_open_timeout_at) + raise(IO::TimeoutError, "user specified timeout for #{host}:#{port}") + end + + if resolution_store.empty_addrinfos? + if connecting_sockets.empty? && resolution_store.resolved_all_families? + if last_error_from_thread + raise last_error.class, last_error.message, cause: last_error + else + raise last_error + end + end + + if (expired?(now, user_specified_resolv_timeout_at) || resolution_store.resolved_all_families?) && + (expired?(now, user_specified_connect_timeout_at) || connecting_sockets.empty?) + raise(IO::TimeoutError, "user specified timeout for #{host}:#{port}") + end + end + end + ensure + hostname_resolution_threads.each do |thread| + thread.exit + end + + hostname_resolution_result&.close + + connecting_sockets.each_key do |connecting_socket| + connecting_socket.close + end + end + private_class_method :tcp_with_fast_fallback + + def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) last_error = nil ret = nil local_addr_list = nil if local_host != nil || local_port != nil - local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil) + local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil, timeout: open_timeout || resolv_timeout) end - Addrinfo.foreach(host, port, nil, :STREAM, timeout: resolv_timeout) {|ai| + timeout = open_timeout ? open_timeout : resolv_timeout + starts_at = current_clock_time + + Addrinfo.foreach(host, port, nil, :STREAM, timeout:) {|ai| if local_addr_list local_addr = local_addr_list.find {|local_ai| local_ai.afamily == ai.afamily } next unless local_addr @@ -640,9 +956,17 @@ class Socket < BasicSocket local_addr = nil end begin + timeout = + if open_timeout + t = open_timeout - (current_clock_time - starts_at) + t.negative? ? 0 : t + else + connect_timeout + end + sock = local_addr ? - ai.connect_from(local_addr, timeout: connect_timeout) : - ai.connect(timeout: connect_timeout) + ai.connect_from(local_addr, timeout:) : + ai.connect(timeout:) rescue SystemCallError last_error = $! next @@ -657,18 +981,150 @@ class Socket < BasicSocket raise SocketError, "no appropriate local address" end end - if block_given? - begin - yield ret - ensure - ret.close + + ret + end + private_class_method :tcp_without_fast_fallback + + def self.ip_address?(hostname) + hostname.match?(IPV6_ADDRESS_FORMAT) || hostname.match?(/\A([0-9]{1,3}\.){3}[0-9]{1,3}\z/) + end + private_class_method :ip_address? + + def self.resolve_hostname(family, host, port, hostname_resolution_result) + begin + resolved_addrinfos = Addrinfo.getaddrinfo(host, port, ADDRESS_FAMILIES[family], :STREAM) + hostname_resolution_result.add(family, resolved_addrinfos) + rescue => e + hostname_resolution_result.add(family, e) + end + end + private_class_method :resolve_hostname + + def self.current_clock_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + private_class_method :current_clock_time + + def self.second_to_timeout(started_at, ends_at) + return nil if ends_at == Float::INFINITY || ends_at.nil? + + remaining = (ends_at - started_at) + remaining.negative? ? 0 : remaining + end + private_class_method :second_to_timeout + + def self.expired?(started_at, ends_at) + second_to_timeout(started_at, ends_at)&.zero? + end + private_class_method :expired? + + class HostnameResolutionResult + def initialize(size) + @size = size + @taken_count = 0 + @rpipe, @wpipe = IO.pipe + @results = [] + @mutex = Mutex.new + end + + def notifier + [@rpipe] + end + + def add(family, result) + @mutex.synchronize do + @results.push [family, result] + @wpipe.putc HOSTNAME_RESOLUTION_QUEUE_UPDATED end - else - ret + end + + def get + return nil if @results.empty? + + res = nil + + @mutex.synchronize do + @rpipe.getbyte + res = @results.shift + end + + @taken_count += 1 + close if @taken_count == @size + res + end + + def close + @rpipe.close + @wpipe.close end end + private_constant :HostnameResolutionResult + + class HostnameResolutionStore + PRIORITY_ON_V6 = [:ipv6, :ipv4].freeze + PRIORITY_ON_V4 = [:ipv4, :ipv6].freeze + + def initialize(family_names) + @family_names = family_names + @addrinfo_dict = {} + @error_dict = {} + @last_family = nil + end + + def add_resolved(family_name, addrinfos) + @addrinfo_dict[family_name] = addrinfos + end + + def add_error(family_name, error) + @addrinfo_dict[family_name] = [] + @error_dict[family_name] = error + end + + def get_addrinfo + precedences = + case @last_family + when :ipv4, nil then PRIORITY_ON_V6 + when :ipv6 then PRIORITY_ON_V4 + end + + precedences.each do |family_name| + addrinfo = @addrinfo_dict[family_name]&.shift + next unless addrinfo + + @last_family = family_name + return addrinfo + end + + nil + end + + def empty_addrinfos? + @addrinfo_dict.all? { |_, addrinfos| addrinfos.empty? } + end + + def any_addrinfos? + !empty_addrinfos? + end + + def resolved?(family) + @addrinfo_dict.has_key? family + end + + def resolved_successfully?(family) + resolved?(family) && !@error_dict[family] + end + + def resolved_all_families? + (@family_names - @addrinfo_dict.keys).empty? + end + + def any_unresolved_family? + !resolved_all_families? + end + end + private_constant :HostnameResolutionStore - # :stopdoc: def self.ip_sockets_port0(ai_list, reuseaddr) sockets = [] begin @@ -701,9 +1157,7 @@ class Socket < BasicSocket end sockets end - class << self - private :ip_sockets_port0 - end + private_class_method :ip_sockets_port0 def self.tcp_server_sockets_port0(host) ai_list = Addrinfo.getaddrinfo(host, 0, nil, :STREAM, nil, Socket::AI_PASSIVE) @@ -1131,13 +1585,18 @@ class Socket < BasicSocket end end - class << self - private - - def unix_socket_abstract_name?(path) - /linux/ =~ RUBY_PLATFORM && /\A(\0|\z)/ =~ path + # :stopdoc: + if RUBY_PLATFORM.include?("linux") + def self.unix_socket_abstract_name?(path) + path.empty? or path.start_with?("\0") + end + else + def self.unix_socket_abstract_name?(path) + false end end + private_class_method :unix_socket_abstract_name? + # :startdoc: # creates a UNIX socket server on _path_. # It calls the block for each socket accepted. @@ -1177,7 +1636,7 @@ class Socket < BasicSocket # Returns 0 if successful, otherwise an exception is raised. # # === Parameter - # # +remote_sockaddr+ - the +struct+ sockaddr contained in a string or Addrinfo object + # * +remote_sockaddr+ - the +struct+ sockaddr contained in a string or Addrinfo object # # === Example: # # Pull down Google's web page @@ -1212,7 +1671,7 @@ class Socket < BasicSocket # return the symbol +:wait_writable+ instead. # # === See - # # Socket#connect + # * Socket#connect def connect_nonblock(addr, exception: true) __connect_nonblock(addr, exception) end @@ -1229,9 +1688,10 @@ class UDPSocket < IPSocket # The first element of the results, _mesg_, is the data received. # The second element, _sender_inet_addr_, is an array to represent the sender address. # - # When recvfrom(2) returns 0, - # Socket#recvfrom_nonblock returns an empty string as data. - # It means an empty packet. + # When recvfrom(2) returns 0, Socket#recv_nonblock returns nil. + # In most cases it means the connection was closed, but it may also mean + # an empty packet was received, as the underlying API makes + # it impossible to distinguish these two cases. # # === Parameters # * +maxlen+ - the number of bytes to receive from the socket diff --git a/ext/socket/mkconstants.rb b/ext/socket/mkconstants.rb index 5e6c0668f6..4271e40cd8 100644 --- a/ext/socket/mkconstants.rb +++ b/ext/socket/mkconstants.rb @@ -51,7 +51,10 @@ DATA.each_line {|s| next end h[name] = default_value - COMMENTS[name] = comment + if comment + # Stop unintentional references + COMMENTS[name] = comment.gsub(/\b(Data|Kernel|Process|Set|Socket|Time)\b/, '\\\\\\&') + end } DEFS = h.to_a @@ -73,15 +76,11 @@ def each_name(pat) } end -erb_new = lambda do |src, safe, trim| - if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+ - ERB.new(src, trim_mode: trim) - else - ERB.new(src, safe, trim) - end +erb_new = lambda do |src, trim| + ERB.new(src, trim_mode: trim) end -erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_const_decls") +erb_new.call(<<'EOS', '%').def_method(Object, "gen_const_decls") % each_const {|guard, name, default_value| #if !defined(<%=name%>) # if defined(HAVE_CONST_<%=name.upcase%>) @@ -95,7 +94,7 @@ erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_const_decls") % } EOS -erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_const_defs_in_guard(name, default_value)") +erb_new.call(<<'EOS', '%').def_method(Object, "gen_const_defs_in_guard(name, default_value)") #if defined(<%=name%>) /* <%= COMMENTS[name] %> */ rb_define_const(rb_cSocket, <%=c_str name%>, INTEGER2NUM(<%=name%>)); @@ -104,7 +103,7 @@ erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_const_defs_in_guard(name #endif EOS -erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_const_defs") +erb_new.call(<<'EOS', '%').def_method(Object, "gen_const_defs") % each_const {|guard, name, default_value| % if guard #if <%=guard%> @@ -154,7 +153,7 @@ def each_names_with_len(pat, prefix_optional=nil) } end -erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_name_to_int_decl(funcname, pat, prefix_optional, guard=nil)") +erb_new.call(<<'EOS', '%').def_method(Object, "gen_name_to_int_decl(funcname, pat, prefix_optional, guard=nil)") %if guard #ifdef <%=guard%> int <%=funcname%>(const char *str, long len, int *valp); @@ -164,7 +163,7 @@ int <%=funcname%>(const char *str, long len, int *valp); %end EOS -erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_name_to_int_func_in_guard(funcname, pat, prefix_optional, guard=nil)") +erb_new.call(<<'EOS', '%').def_method(Object, "gen_name_to_int_func_in_guard(funcname, pat, prefix_optional, guard=nil)") int <%=funcname%>(const char *str, long len, int *valp) { @@ -186,7 +185,7 @@ int } EOS -erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_name_to_int_func(funcname, pat, prefix_optional, guard=nil)") +erb_new.call(<<'EOS', '%').def_method(Object, "gen_name_to_int_func(funcname, pat, prefix_optional, guard=nil)") %if guard #ifdef <%=guard%> <%=gen_name_to_int_func_in_guard(funcname, pat, prefix_optional, guard)%> @@ -215,7 +214,7 @@ def reverse_each_name_with_prefix_optional(pat, prefix_pat) end end -erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_int_to_name_hash(hash_var, pat, prefix_pat)") +erb_new.call(<<'EOS', '%').def_method(Object, "gen_int_to_name_hash(hash_var, pat, prefix_pat)") <%=hash_var%> = st_init_numtable(); % reverse_each_name_with_prefix_optional(pat, prefix_pat) {|n,s| #ifdef <%=n%> @@ -224,7 +223,7 @@ erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_int_to_name_hash(hash_va % } EOS -erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_int_to_name_func(func_name, hash_var)") +erb_new.call(<<'EOS', '%').def_method(Object, "gen_int_to_name_func(func_name, hash_var)") ID <%=func_name%>(int val) { @@ -235,7 +234,7 @@ ID } EOS -erb_new.call(<<'EOS', nil, '%').def_method(Object, "gen_int_to_name_decl(func_name, hash_var)") +erb_new.call(<<'EOS', '%').def_method(Object, "gen_int_to_name_decl(func_name, hash_var)") ID <%=func_name%>(int val); EOS @@ -284,7 +283,7 @@ def_intern('rsock_intern_udp_optname', /\AUDP_/, "UDP_") def_intern('rsock_intern_scm_optname', /\ASCM_/, "SCM_") def_intern('rsock_intern_local_optname', /\ALOCAL_/, "LOCAL_") -result = erb_new.call(<<'EOS', nil, '%').result(binding) +result = erb_new.call(<<'EOS', '%').result(binding) /* autogenerated file */ <%= INTERN_DEFS.map {|vardef, gen_hash, decl, func| vardef }.join("\n") %> @@ -327,7 +326,7 @@ init_constants(void) EOS -header_result = erb_new.call(<<'EOS', nil, '%').result(binding) +header_result = erb_new.call(<<'EOS', '%').result(binding) /* autogenerated file */ <%= gen_const_decls %> <%= NAME_TO_INT_DEFS.map {|decl, func| decl }.join("\n") %> @@ -423,8 +422,8 @@ AF_ISDN nil Integrated Services Digital Network PF_ISDN nil Integrated Services Digital Network AF_NATM nil Native ATM access PF_NATM nil Native ATM access -AF_SYSTEM -PF_SYSTEM +AF_SYSTEM nil Kernel event messages +PF_SYSTEM nil Kernel event messages AF_NETBIOS nil NetBIOS PF_NETBIOS nil NetBIOS AF_PPP nil Point-to-Point Protocol @@ -440,8 +439,8 @@ PF_PACKET nil Direct link-layer access AF_E164 nil CCITT (ITU-T) E.164 recommendation PF_XTP nil eXpress Transfer Protocol -PF_RTIP -PF_PIP +PF_RTIP nil Help Identify RTIP packets +PF_PIP nil Help Identify PIP packets AF_KEY nil Key management protocol, originally developed for usage with IPsec PF_KEY nil Key management protocol, originally developed for usage with IPsec AF_NETLINK nil Kernel user interface device @@ -666,6 +665,7 @@ SO_SETFIB nil Set the associated routing table for the socket (FreeBSD SO_RTABLE nil Set the routing table for this socket (OpenBSD) SO_INCOMING_CPU nil Receive the cpu attached to the socket (Linux 3.19) SO_INCOMING_NAPI_ID nil Receive the napi ID attached to a RX queue (Linux 4.12) +SO_CONNECT_TIME nil Returns the number of seconds a socket has been connected. This option is only valid for connection-oriented protocols (Windows) SOPRI_INTERACTIVE nil Interactive socket priority SOPRI_NORMAL nil Normal socket priority @@ -745,6 +745,7 @@ SHUT_RDWR 2 Shut down the both sides of the socket IPV6_JOIN_GROUP nil Join a group membership IPV6_LEAVE_GROUP nil Leave a group membership +IPV6_MTU_DISCOVER nil Path MTU discovery IPV6_MULTICAST_HOPS nil IP6 multicast hops IPV6_MULTICAST_IF nil IP6 multicast interface IPV6_MULTICAST_LOOP nil IP6 multicast loopback @@ -759,6 +760,7 @@ IPV6_NEXTHOP nil Next hop address IPV6_PATHMTU nil Retrieve current path MTU IPV6_PKTINFO nil Receive packet information with datagram IPV6_RECVDSTOPTS nil Receive all IP6 options for response +IPV6_RECVERR nil Enable extended reliable error message passing IPV6_RECVHOPLIMIT nil Receive hop limit with datagram IPV6_RECVHOPOPTS nil Receive hop-by-hop options IPV6_RECVPKTINFO nil Receive destination IP address and incoming interface diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 269edc4dad..6cdf5c6abc 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -10,6 +10,22 @@ #include "rubysocket.h" +// GETADDRINFO_IMPL == 0 : call getaddrinfo/getnameinfo directly +// GETADDRINFO_IMPL == 1 : call getaddrinfo/getnameinfo without gvl (but uncancellable) +// GETADDRINFO_IMPL == 2 : call getaddrinfo/getnameinfo in a dedicated pthread +// (and if the call is interrupted, the pthread is detached) + +#ifndef GETADDRINFO_IMPL +# ifdef GETADDRINFO_EMU +# define GETADDRINFO_IMPL 0 +# elif !defined(HAVE_PTHREAD_CREATE) || !defined(HAVE_PTHREAD_DETACH) || defined(__MINGW32__) || defined(__MINGW64__) +# define GETADDRINFO_IMPL 1 +# else +# define GETADDRINFO_IMPL 2 +# include "ruby/thread_native.h" +# endif +#endif + #if defined(INET6) && (defined(LOOKUP_ORDER_HACK_INET) || defined(LOOKUP_ORDER_HACK_INET6)) #define LOOKUP_ORDERS (sizeof(lookup_order_table) / sizeof(lookup_order_table[0])) static const int lookup_order_table[] = { @@ -173,32 +189,6 @@ parse_numeric_port(const char *service, int *portp) } #endif -#ifndef GETADDRINFO_EMU -struct getaddrinfo_arg -{ - const char *node; - const char *service; - const struct addrinfo *hints; - struct addrinfo **res; -}; - -static void * -nogvl_getaddrinfo(void *arg) -{ - int ret; - struct getaddrinfo_arg *ptr = arg; - ret = getaddrinfo(ptr->node, ptr->service, ptr->hints, ptr->res); -#ifdef __linux__ - /* On Linux (mainly Ubuntu 13.04) /etc/nsswitch.conf has mdns4 and - * it cause getaddrinfo to return EAI_SYSTEM/ENOENT. [ruby-list:49420] - */ - if (ret == EAI_SYSTEM && errno == ENOENT) - ret = EAI_NONAME; -#endif - return (void *)(VALUE)ret; -} -#endif - static int numeric_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, @@ -287,8 +277,9 @@ numeric_getaddrinfo(const char *node, const char *service, void rb_freeaddrinfo(struct rb_addrinfo *ai) { - if (!ai->allocated_by_malloc) - freeaddrinfo(ai->ai); + if (!ai->allocated_by_malloc) { + if (ai->ai) freeaddrinfo(ai->ai); + } else { struct addrinfo *ai1, *ai2; ai1 = ai->ai; @@ -302,7 +293,353 @@ rb_freeaddrinfo(struct rb_addrinfo *ai) xfree(ai); } -#ifndef GETADDRINFO_EMU +static int +rsock_value_timeout_to_msec(VALUE timeout) +{ + double seconds = NUM2DBL(timeout); + if (seconds < 0) rb_raise(rb_eArgError, "timeout must not be negative"); + + double msec = seconds * 1000.0; + if (msec > UINT_MAX) rb_raise(rb_eArgError, "timeout too large"); + + return (unsigned int)(msec + 0.5); +} + +#if GETADDRINFO_IMPL == 0 + +static int +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int _timeout) +{ + return getaddrinfo(hostp, portp, hints, ai); +} + +#elif GETADDRINFO_IMPL == 1 + +struct getaddrinfo_arg +{ + const char *node; + const char *service; + const struct addrinfo *hints; + struct addrinfo **res; +}; + +static void * +nogvl_getaddrinfo(void *arg) +{ + int ret; + struct getaddrinfo_arg *ptr = arg; + ret = getaddrinfo(ptr->node, ptr->service, ptr->hints, ptr->res); +#ifdef __linux__ + /* On Linux (mainly Ubuntu 13.04) /etc/nsswitch.conf has mdns4 and + * it cause getaddrinfo to return EAI_SYSTEM/ENOENT. [ruby-list:49420] + */ + if (ret == EAI_SYSTEM && errno == ENOENT) + ret = EAI_NONAME; +#endif + return (void *)(VALUE)ret; +} + +static void * +fork_safe_getaddrinfo(void *arg) +{ + return rb_thread_prevent_fork(nogvl_getaddrinfo, arg); +} + +static int +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int _timeout) +{ + struct getaddrinfo_arg arg; + MEMZERO(&arg, struct getaddrinfo_arg, 1); + arg.node = hostp; + arg.service = portp; + arg.hints = hints; + arg.res = ai; + return (int)(VALUE)rb_thread_call_without_gvl(fork_safe_getaddrinfo, &arg, RUBY_UBF_IO, 0); +} + +#elif GETADDRINFO_IMPL == 2 + +struct getaddrinfo_arg +{ + char *node, *service; + struct addrinfo hints; + struct addrinfo *ai; + int err, gai_errno, refcount, done, cancelled, timedout; + rb_nativethread_lock_t lock; + rb_nativethread_cond_t cond; + int timeout; +}; + +static struct getaddrinfo_arg * +allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addrinfo *hints, int timeout) +{ + size_t hostp_offset = sizeof(struct getaddrinfo_arg); + size_t portp_offset = hostp_offset + (hostp ? strlen(hostp) + 1 : 0); + size_t bufsize = portp_offset + (portp ? strlen(portp) + 1 : 0); + + char *buf = malloc(bufsize); + if (!buf) { + rb_gc(); + buf = malloc(bufsize); + if (!buf) return NULL; + } + struct getaddrinfo_arg *arg = (struct getaddrinfo_arg *)buf; + + if (hostp) { + arg->node = buf + hostp_offset; + memcpy(arg->node, hostp, portp_offset - hostp_offset); + } + else { + arg->node = NULL; + } + + if (portp) { + arg->service = buf + portp_offset; + memcpy(arg->service, portp, bufsize - portp_offset); + } + else { + arg->service = NULL; + } + + arg->hints = *hints; + arg->ai = NULL; + + arg->refcount = 2; + arg->done = arg->cancelled = arg->timedout = 0; + arg->timeout = timeout; + + rb_nativethread_lock_initialize(&arg->lock); + rb_native_cond_initialize(&arg->cond); + + return arg; +} + +static void +free_getaddrinfo_arg(struct getaddrinfo_arg *arg) +{ + rb_native_cond_destroy(&arg->cond); + rb_nativethread_lock_destroy(&arg->lock); + free(arg); +} + +static void * +do_getaddrinfo(void *ptr) +{ + struct getaddrinfo_arg *arg = (struct getaddrinfo_arg *)ptr; + + int err, gai_errno; + err = getaddrinfo(arg->node, arg->service, &arg->hints, &arg->ai); + gai_errno = errno; +#ifdef __linux__ + /* On Linux (mainly Ubuntu 13.04) /etc/nsswitch.conf has mdns4 and + * it cause getaddrinfo to return EAI_SYSTEM/ENOENT. [ruby-list:49420] + */ + if (err == EAI_SYSTEM && errno == ENOENT) + err = EAI_NONAME; +#endif + + int need_free = 0; + rb_nativethread_lock_lock(&arg->lock); + { + arg->err = err; + arg->gai_errno = gai_errno; + if (arg->cancelled) { + if (arg->ai) freeaddrinfo(arg->ai); + } + else { + arg->done = 1; + rb_native_cond_signal(&arg->cond); + } + if (--arg->refcount == 0) need_free = 1; + } + rb_nativethread_lock_unlock(&arg->lock); + + if (need_free) free_getaddrinfo_arg(arg); + + return 0; +} + +static void * +wait_getaddrinfo(void *ptr) +{ + struct getaddrinfo_arg *arg = (struct getaddrinfo_arg *)ptr; + rb_nativethread_lock_lock(&arg->lock); + while (!arg->done && !arg->cancelled) { + long msec = arg->timeout; + if (msec == 0) { + arg->cancelled = 1; + arg->timedout = 1; + } else if (msec > 0) { + rb_native_cond_timedwait(&arg->cond, &arg->lock, msec); + if (!arg->done) { + arg->cancelled = 1; + arg->timedout = 1; + } + } else { + rb_native_cond_wait(&arg->cond, &arg->lock); + } + } + rb_nativethread_lock_unlock(&arg->lock); + return 0; +} + +static void +cancel_getaddrinfo(void *ptr) +{ + struct getaddrinfo_arg *arg = (struct getaddrinfo_arg *)ptr; + rb_nativethread_lock_lock(&arg->lock); + { + arg->cancelled = 1; + rb_native_cond_signal(&arg->cond); + } + rb_nativethread_lock_unlock(&arg->lock); +} + +int +raddrinfo_pthread_create(pthread_t *th, void *(*start_routine) (void *), void *arg) +{ + int limit = 3, ret; + int saved_errno; +#ifdef HAVE_PTHREAD_ATTR_SETDETACHSTATE + pthread_attr_t attr; + pthread_attr_t *attr_p = &attr; + int err; + int init_retries = 0; + int init_retries_max = 3; +retry_attr_init: + if ((err = pthread_attr_init(attr_p)) != 0) { + if (err == ENOMEM && init_retries < init_retries_max) { + init_retries++; + rb_gc(); + goto retry_attr_init; + } + return err; + } + if ((err = pthread_attr_setdetachstate(attr_p, PTHREAD_CREATE_DETACHED)) != 0) { + saved_errno = errno; + pthread_attr_destroy(attr_p); + errno = saved_errno; + return err; // EINVAL - shouldn't happen + } +#else + pthread_attr_t *attr_p = NULL; +#endif + do { + // It is said that pthread_create may fail spuriously, so we follow the JDK and retry several times. + // + // https://bugs.openjdk.org/browse/JDK-8268605 + // https://github.com/openjdk/jdk/commit/e35005d5ce383ddd108096a3079b17cb0bcf76f1 + ret = pthread_create(th, attr_p, start_routine, arg); + } while (ret == EAGAIN && limit-- > 0); +#ifdef HAVE_PTHREAD_ATTR_SETDETACHSTATE + saved_errno = errno; + pthread_attr_destroy(attr_p); + if (ret != 0) { + errno = saved_errno; + } +#else + if (ret == 0) { + pthread_detach(th); // this can race with shutdown routine of thread in some glibc versions + } +#endif + return ret; +} + +static void * +fork_safe_do_getaddrinfo(void *ptr) +{ + return rb_thread_prevent_fork(do_getaddrinfo, ptr); +} + +static int +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int timeout) +{ + int retry; + struct getaddrinfo_arg *arg; + int err = 0, gai_errno = 0, timedout = 0; + +start: + retry = 0; + + arg = allocate_getaddrinfo_arg(hostp, portp, hints, timeout); + if (!arg) { + return EAI_MEMORY; + } + + pthread_t th; + if (raddrinfo_pthread_create(&th, fork_safe_do_getaddrinfo, arg) != 0) { + int err = errno; + free_getaddrinfo_arg(arg); + errno = err; + return EAI_SYSTEM; + } + + rb_thread_call_without_gvl2(wait_getaddrinfo, arg, cancel_getaddrinfo, arg); + + int need_free = 0; + rb_nativethread_lock_lock(&arg->lock); + { + if (arg->done) { + err = arg->err; + gai_errno = arg->gai_errno; + if (err == 0) *ai = arg->ai; + } + else if (arg->cancelled) { + retry = 1; + timedout = arg->timedout; + } + else { + // If already interrupted, rb_thread_call_without_gvl2 may return without calling wait_getaddrinfo. + // In this case, it could be !arg->done && !arg->cancelled. + arg->cancelled = 1; // to make do_getaddrinfo call freeaddrinfo + retry = 1; + } + if (--arg->refcount == 0) need_free = 1; + } + rb_nativethread_lock_unlock(&arg->lock); + + if (need_free) free_getaddrinfo_arg(arg); + + if (timedout) { + if (arg->ai) { + rsock_raise_user_specified_timeout(arg->ai, Qnil, Qnil); + } else { + VALUE host = rb_str_new_cstr(hostp); + VALUE port = rb_str_new_cstr(portp); + rsock_raise_user_specified_timeout(NULL, host, port); + } + } + + // If the current thread is interrupted by asynchronous exception, the following raises the exception. + // But if the current thread is interrupted by timer thread, the following returns; we need to manually retry. + rb_thread_check_ints(); + if (retry) goto start; + + /* Because errno is threadlocal, the errno value we got from the call to getaddrinfo() in the thread + * (in case of EAI_SYSTEM return value) is not propagated to the caller of _this_ function. Set errno + * explicitly, as round-tripped through struct getaddrinfo_arg, to deal with that */ + if (gai_errno) errno = gai_errno; + return err; +} + +#endif + +#define GETNAMEINFO_WONT_BLOCK(host, serv, flags) \ + ((!(host) || ((flags) & NI_NUMERICHOST)) && \ + (!(serv) || ((flags) & NI_NUMERICSERV))) + +#if GETADDRINFO_IMPL == 0 + +int +rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, + char *serv, size_t servlen, int flags) +{ + return getnameinfo(sa, salen, host, (socklen_t)hostlen, serv, (socklen_t)servlen, flags); +} + +#elif GETADDRINFO_IMPL == 1 + struct getnameinfo_arg { const struct sockaddr *sa; @@ -323,16 +660,15 @@ nogvl_getnameinfo(void *arg) ptr->serv, (socklen_t)ptr->servlen, ptr->flags); } -#endif - int rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, - char *host, size_t hostlen, - char *serv, size_t servlen, int flags) + char *host, size_t hostlen, + char *serv, size_t servlen, int flags) { -#ifdef GETADDRINFO_EMU - return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); -#else + if (GETNAMEINFO_WONT_BLOCK(host, serv, flags)) { + return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); + } + struct getnameinfo_arg arg; int ret; arg.sa = sa; @@ -344,17 +680,190 @@ rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, arg.flags = flags; ret = (int)(VALUE)rb_thread_call_without_gvl(nogvl_getnameinfo, &arg, RUBY_UBF_IO, 0); return ret; -#endif +} + +#elif GETADDRINFO_IMPL == 2 + +struct getnameinfo_arg +{ + struct sockaddr *sa; + socklen_t salen; + int flags; + char *host; + size_t hostlen; + char *serv; + size_t servlen; + int err, gni_errno, refcount, done, cancelled; + rb_nativethread_lock_t lock; + rb_nativethread_cond_t cond; +}; + +static struct getnameinfo_arg * +allocate_getnameinfo_arg(const struct sockaddr *sa, socklen_t salen, size_t hostlen, size_t servlen, int flags) +{ + size_t sa_offset = sizeof(struct getnameinfo_arg); + size_t host_offset = sa_offset + salen; + size_t serv_offset = host_offset + hostlen; + size_t bufsize = serv_offset + servlen; + + char *buf = malloc(bufsize); + if (!buf) { + rb_gc(); + buf = malloc(bufsize); + if (!buf) return NULL; + } + struct getnameinfo_arg *arg = (struct getnameinfo_arg *)buf; + + arg->sa = (struct sockaddr *)(buf + sa_offset); + memcpy(arg->sa, sa, salen); + arg->salen = salen; + arg->host = buf + host_offset; + arg->hostlen = hostlen; + arg->serv = buf + serv_offset; + arg->servlen = servlen; + arg->flags = flags; + + arg->refcount = 2; + arg->done = arg->cancelled = 0; + + rb_nativethread_lock_initialize(&arg->lock); + rb_native_cond_initialize(&arg->cond); + + return arg; } static void +free_getnameinfo_arg(struct getnameinfo_arg *arg) +{ + rb_native_cond_destroy(&arg->cond); + rb_nativethread_lock_destroy(&arg->lock); + + free(arg); +} + +static void * +do_getnameinfo(void *ptr) +{ + struct getnameinfo_arg *arg = (struct getnameinfo_arg *)ptr; + + int err, gni_errno; + err = getnameinfo(arg->sa, arg->salen, arg->host, (socklen_t)arg->hostlen, arg->serv, (socklen_t)arg->servlen, arg->flags); + gni_errno = errno; + + int need_free = 0; + rb_nativethread_lock_lock(&arg->lock); + arg->err = err; + arg->gni_errno = gni_errno; + if (!arg->cancelled) { + arg->done = 1; + rb_native_cond_signal(&arg->cond); + } + if (--arg->refcount == 0) need_free = 1; + rb_nativethread_lock_unlock(&arg->lock); + + if (need_free) free_getnameinfo_arg(arg); + + return 0; +} + +static void * +wait_getnameinfo(void *ptr) +{ + struct getnameinfo_arg *arg = (struct getnameinfo_arg *)ptr; + rb_nativethread_lock_lock(&arg->lock); + while (!arg->done && !arg->cancelled) { + rb_native_cond_wait(&arg->cond, &arg->lock); + } + rb_nativethread_lock_unlock(&arg->lock); + return 0; +} + +static void +cancel_getnameinfo(void *ptr) +{ + struct getnameinfo_arg *arg = (struct getnameinfo_arg *)ptr; + rb_nativethread_lock_lock(&arg->lock); + arg->cancelled = 1; + rb_native_cond_signal(&arg->cond); + rb_nativethread_lock_unlock(&arg->lock); +} + +int +rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, + char *serv, size_t servlen, int flags) +{ + int retry; + struct getnameinfo_arg *arg; + int err = 0, gni_errno = 0; + + if (GETNAMEINFO_WONT_BLOCK(host, serv, flags)) { + return getnameinfo(sa, salen, host, (socklen_t)hostlen, serv, (socklen_t)servlen, flags); + } + +start: + retry = 0; + + arg = allocate_getnameinfo_arg(sa, salen, hostlen, servlen, flags); + if (!arg) { + return EAI_MEMORY; + } + + pthread_t th; + if (raddrinfo_pthread_create(&th, do_getnameinfo, arg) != 0) { + int err = errno; + free_getnameinfo_arg(arg); + errno = err; + return EAI_SYSTEM; + } + + rb_thread_call_without_gvl2(wait_getnameinfo, arg, cancel_getnameinfo, arg); + + int need_free = 0; + rb_nativethread_lock_lock(&arg->lock); + if (arg->done) { + err = arg->err; + gni_errno = arg->gni_errno; + if (err == 0) { + if (host) memcpy(host, arg->host, hostlen); + if (serv) memcpy(serv, arg->serv, servlen); + } + } + else if (arg->cancelled) { + retry = 1; + } + else { + // If already interrupted, rb_thread_call_without_gvl2 may return without calling wait_getnameinfo. + // In this case, it could be !arg->done && !arg->cancelled. + arg->cancelled = 1; + retry = 1; + } + if (--arg->refcount == 0) need_free = 1; + rb_nativethread_lock_unlock(&arg->lock); + + if (need_free) free_getnameinfo_arg(arg); + + // If the current thread is interrupted by asynchronous exception, the following raises the exception. + // But if the current thread is interrupted by timer thread, the following returns; we need to manually retry. + rb_thread_check_ints(); + if (retry) goto start; + + /* Make sure we copy the thread-local errno value from the getnameinfo thread back to this thread, so + * calling code sees the correct errno */ + if (gni_errno) errno = gni_errno; + return err; +} + +#endif + +static void make_ipaddr0(struct sockaddr *addr, socklen_t addrlen, char *buf, size_t buflen) { int error; error = rb_getnameinfo(addr, addrlen, buf, buflen, NULL, 0, NI_NUMERICHOST); if (error) { - rsock_raise_socket_error("getnameinfo", error); + rsock_raise_resolution_error("getnameinfo", error); } } @@ -396,8 +905,8 @@ str_is_number(const char *p) ((ptr)[0] == name[0] && \ rb_strlen_lit(name) == (len) && memcmp(ptr, name, len) == 0) -static char* -host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr) +char* +raddrinfo_host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr) { if (NIL_P(host)) { return NULL; @@ -435,8 +944,8 @@ host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr) } } -static char* -port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr) +char* +raddrinfo_port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr) { if (NIL_P(port)) { return 0; @@ -488,7 +997,7 @@ rb_scheduler_getaddrinfo(VALUE scheduler, VALUE host, const char *service, for(i=0; i<len; i++) { ip_address = rb_ary_entry(ip_addresses_array, i); - hostp = host_str(ip_address, _hbuf, sizeof(_hbuf), &_additional_flags); + hostp = raddrinfo_host_str(ip_address, _hbuf, sizeof(_hbuf), &_additional_flags); error = numeric_getaddrinfo(hostp, service, hints, &ai); if (error == 0) { if (!res_allocated) { @@ -515,7 +1024,7 @@ rb_scheduler_getaddrinfo(VALUE scheduler, VALUE host, const char *service, } struct rb_addrinfo* -rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack) +rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, VALUE timeout) { struct rb_addrinfo* res = NULL; struct addrinfo *ai; @@ -524,8 +1033,8 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV]; int additional_flags = 0; - hostp = host_str(host, hbuf, sizeof(hbuf), &additional_flags); - portp = port_str(port, pbuf, sizeof(pbuf), &additional_flags); + hostp = raddrinfo_host_str(host, hbuf, sizeof(hbuf), &additional_flags); + portp = raddrinfo_port_str(port, pbuf, sizeof(pbuf), &additional_flags); if (socktype_hack && hints->ai_socktype == 0 && str_is_number(portp)) { hints->ai_socktype = SOCK_DGRAM; @@ -550,17 +1059,8 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h } if (!resolved) { -#ifdef GETADDRINFO_EMU - error = getaddrinfo(hostp, portp, hints, &ai); -#else - struct getaddrinfo_arg arg; - MEMZERO(&arg, struct getaddrinfo_arg, 1); - arg.node = hostp; - arg.service = portp; - arg.hints = hints; - arg.res = &ai; - error = (int)(VALUE)rb_thread_call_without_gvl(nogvl_getaddrinfo, &arg, RUBY_UBF_IO, 0); -#endif + int t = NIL_P(timeout) ? -1 : rsock_value_timeout_to_msec(timeout); + error = rb_getaddrinfo(hostp, portp, hints, &ai, t); if (error == 0) { res = (struct rb_addrinfo *)xmalloc(sizeof(struct rb_addrinfo)); res->allocated_by_malloc = 0; @@ -573,7 +1073,7 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h if (hostp && hostp[strlen(hostp)-1] == '\n') { rb_raise(rb_eSocket, "newline at the end of hostname"); } - rsock_raise_socket_error("getaddrinfo", error); + rsock_raise_resolution_error("getaddrinfo", error); } return res; @@ -593,7 +1093,7 @@ rsock_fd_family(int fd) } struct rb_addrinfo* -rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags) +rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, VALUE timeout) { struct addrinfo hints; @@ -601,7 +1101,7 @@ rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags) hints.ai_family = family; hints.ai_socktype = socktype; hints.ai_flags = flags; - return rsock_getaddrinfo(host, port, &hints, 1); + return rsock_getaddrinfo(host, port, &hints, 1, timeout); } VALUE @@ -632,7 +1132,7 @@ rsock_ipaddr(struct sockaddr *sockaddr, socklen_t sockaddrlen, int norevlookup) error = rb_getnameinfo(sockaddr, sockaddrlen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf), NI_NUMERICHOST | NI_NUMERICSERV); if (error) { - rsock_raise_socket_error("getnameinfo", error); + rsock_raise_resolution_error("getnameinfo", error); } addr2 = rb_str_new2(hbuf); if (addr1 == Qnil) { @@ -721,7 +1221,7 @@ make_hostent_internal(VALUE v) hostp = addr->ai_canonname; } else { - hostp = host_str(host, hbuf, sizeof(hbuf), NULL); + hostp = raddrinfo_host_str(host, hbuf, sizeof(hbuf), NULL); } rb_ary_push(ary, rb_str_new2(hostp)); @@ -795,6 +1295,7 @@ addrinfo_memsize(const void *ptr) static const rb_data_type_t addrinfo_type = { "socket/addrinfo", {addrinfo_mark, addrinfo_free, addrinfo_memsize,}, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_WB_PROTECTED, }; static VALUE @@ -832,7 +1333,7 @@ alloc_addrinfo(void) } static void -init_addrinfo(rb_addrinfo_t *rai, struct sockaddr *sa, socklen_t len, +init_addrinfo(VALUE self, rb_addrinfo_t *rai, struct sockaddr *sa, socklen_t len, int pfamily, int socktype, int protocol, VALUE canonname, VALUE inspectname) { @@ -844,8 +1345,8 @@ init_addrinfo(rb_addrinfo_t *rai, struct sockaddr *sa, socklen_t len, rai->pfamily = pfamily; rai->socktype = socktype; rai->protocol = protocol; - rai->canonname = canonname; - rai->inspectname = inspectname; + RB_OBJ_WRITE(self, &rai->canonname, canonname); + RB_OBJ_WRITE(self, &rai->inspectname, inspectname); } VALUE @@ -858,7 +1359,7 @@ rsock_addrinfo_new(struct sockaddr *addr, socklen_t len, a = addrinfo_s_allocate(rb_cAddrinfo); DATA_PTR(a) = rai = alloc_addrinfo(); - init_addrinfo(rai, addr, len, family, socktype, protocol, canonname, inspectname); + init_addrinfo(a, rai, addr, len, family, socktype, protocol, canonname, inspectname); return a; } @@ -883,7 +1384,7 @@ call_getaddrinfo(VALUE node, VALUE service, hints.ai_flags = NUM2INT(flags); } - res = rsock_getaddrinfo(node, service, &hints, socktype_hack); + res = rsock_getaddrinfo(node, service, &hints, socktype_hack, timeout); if (res == NULL) rb_raise(rb_eSocket, "host not found"); @@ -893,7 +1394,7 @@ call_getaddrinfo(VALUE node, VALUE service, static VALUE make_inspectname(VALUE node, VALUE service, struct addrinfo *res); static void -init_addrinfo_getaddrinfo(rb_addrinfo_t *rai, VALUE node, VALUE service, +init_addrinfo_getaddrinfo(VALUE self, rb_addrinfo_t *rai, VALUE node, VALUE service, VALUE family, VALUE socktype, VALUE protocol, VALUE flags, VALUE inspectnode, VALUE inspectservice) { @@ -907,7 +1408,7 @@ init_addrinfo_getaddrinfo(rb_addrinfo_t *rai, VALUE node, VALUE service, OBJ_FREEZE(canonname); } - init_addrinfo(rai, res->ai->ai_addr, res->ai->ai_addrlen, + init_addrinfo(self, rai, res->ai->ai_addr, res->ai->ai_addrlen, NUM2INT(family), NUM2INT(socktype), NUM2INT(protocol), canonname, inspectname); @@ -1019,7 +1520,7 @@ addrinfo_list_new(VALUE node, VALUE service, VALUE family, VALUE socktype, VALUE #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN static void -init_unix_addrinfo(rb_addrinfo_t *rai, VALUE path, int socktype) +init_unix_addrinfo(VALUE self, rb_addrinfo_t *rai, VALUE path, int socktype) { struct sockaddr_un un; socklen_t len; @@ -1035,7 +1536,7 @@ init_unix_addrinfo(rb_addrinfo_t *rai, VALUE path, int socktype) memcpy((void*)&un.sun_path, RSTRING_PTR(path), RSTRING_LEN(path)); len = rsock_unix_sockaddr_len(path); - init_addrinfo(rai, (struct sockaddr *)&un, len, + init_addrinfo(self, rai, (struct sockaddr *)&un, len, PF_UNIX, socktype, 0, Qnil, Qnil); } @@ -1139,7 +1640,7 @@ addrinfo_initialize(int argc, VALUE *argv, VALUE self) flags |= AI_NUMERICSERV; #endif - init_addrinfo_getaddrinfo(rai, numericnode, service, + init_addrinfo_getaddrinfo(self, rai, numericnode, service, INT2NUM(i_pfamily ? i_pfamily : af), INT2NUM(i_socktype), INT2NUM(i_protocol), INT2NUM(flags), nodename, service); @@ -1151,7 +1652,7 @@ addrinfo_initialize(int argc, VALUE *argv, VALUE self) { VALUE path = rb_ary_entry(sockaddr_ary, 1); StringValue(path); - init_unix_addrinfo(rai, path, SOCK_STREAM); + init_unix_addrinfo(self, rai, path, SOCK_STREAM); break; } #endif @@ -1164,7 +1665,7 @@ addrinfo_initialize(int argc, VALUE *argv, VALUE self) StringValue(sockaddr_arg); sockaddr_ptr = (struct sockaddr *)RSTRING_PTR(sockaddr_arg); sockaddr_len = RSTRING_SOCKLEN(sockaddr_arg); - init_addrinfo(rai, sockaddr_ptr, sockaddr_len, + init_addrinfo(self, rai, sockaddr_ptr, sockaddr_len, i_pfamily, i_socktype, i_protocol, canonname, inspectname); } @@ -1266,11 +1767,11 @@ rsock_inspect_sockaddr(struct sockaddr *sockaddr_arg, socklen_t socklen, VALUE r * RFC 4007: IPv6 Scoped Address Architecture * draft-ietf-ipv6-scope-api-00.txt: Scoped Address Extensions to the IPv6 Basic Socket API */ - error = getnameinfo(&sockaddr->addr, socklen, - hbuf, (socklen_t)sizeof(hbuf), NULL, 0, - NI_NUMERICHOST|NI_NUMERICSERV); + error = rb_getnameinfo(&sockaddr->addr, socklen, + hbuf, (socklen_t)sizeof(hbuf), NULL, 0, + NI_NUMERICHOST|NI_NUMERICSERV); if (error) { - rsock_raise_socket_error("getnameinfo", error); + rsock_raise_resolution_error("getnameinfo", error); } if (addr->sin6_port == 0) { rb_str_cat2(ret, hbuf); @@ -1634,11 +2135,11 @@ addrinfo_mdump(VALUE self) { char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV]; int error; - error = getnameinfo(&rai->addr.addr, rai->sockaddr_len, - hbuf, (socklen_t)sizeof(hbuf), pbuf, (socklen_t)sizeof(pbuf), - NI_NUMERICHOST|NI_NUMERICSERV); + error = rb_getnameinfo(&rai->addr.addr, rai->sockaddr_len, + hbuf, (socklen_t)sizeof(hbuf), pbuf, (socklen_t)sizeof(pbuf), + NI_NUMERICHOST|NI_NUMERICSERV); if (error) { - rsock_raise_socket_error("getnameinfo", error); + rsock_raise_resolution_error("getnameinfo", error); } sockaddr = rb_assoc_new(rb_str_new_cstr(hbuf), rb_str_new_cstr(pbuf)); break; @@ -1753,7 +2254,7 @@ addrinfo_mload(VALUE self, VALUE ary) } DATA_PTR(self) = rai = alloc_addrinfo(); - init_addrinfo(rai, &ss.addr, len, + init_addrinfo(self, rai, &ss.addr, len, pfamily, socktype, protocol, canonname, inspectname); return self; @@ -1980,11 +2481,11 @@ addrinfo_getnameinfo(int argc, VALUE *argv, VALUE self) if (rai->socktype == SOCK_DGRAM) flags |= NI_DGRAM; - error = getnameinfo(&rai->addr.addr, rai->sockaddr_len, - hbuf, (socklen_t)sizeof(hbuf), pbuf, (socklen_t)sizeof(pbuf), - flags); + error = rb_getnameinfo(&rai->addr.addr, rai->sockaddr_len, + hbuf, (socklen_t)sizeof(hbuf), pbuf, (socklen_t)sizeof(pbuf), + flags); if (error) { - rsock_raise_socket_error("getnameinfo", error); + rsock_raise_resolution_error("getnameinfo", error); } return rb_assoc_new(rb_str_new2(hbuf), rb_str_new2(pbuf)); @@ -2521,7 +3022,7 @@ addrinfo_s_unix(int argc, VALUE *argv, VALUE self) addr = addrinfo_s_allocate(rb_cAddrinfo); DATA_PTR(addr) = rai = alloc_addrinfo(); - init_unix_addrinfo(rai, path, socktype); + init_unix_addrinfo(self, rai, path, socktype); return addr; } @@ -2608,18 +3109,111 @@ rsock_io_socket_addrinfo(VALUE io, struct sockaddr *addr, socklen_t len) UNREACHABLE_RETURN(Qnil); } +#if FAST_FALLBACK_INIT_INETSOCK_IMPL == 1 + +void +free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shared **shared) +{ + xfree((*shared)->node); + (*shared)->node = NULL; + xfree((*shared)->service); + (*shared)->service = NULL; + rb_nativethread_lock_destroy(&(*shared)->lock); + free(*shared); + *shared = NULL; +} + +static void * +do_fast_fallback_getaddrinfo(void *ptr) +{ + struct fast_fallback_getaddrinfo_entry *entry = (struct fast_fallback_getaddrinfo_entry *)ptr; + struct fast_fallback_getaddrinfo_shared *shared = entry->shared; + int err = 0, shared_need_free = 0; + struct addrinfo *ai = NULL; + + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &set, NULL); + + err = numeric_getaddrinfo(shared->node, shared->service, &entry->hints, &entry->ai); + + if (err != 0) { + err = getaddrinfo(shared->node, shared->service, &entry->hints, &entry->ai); + #ifdef __linux__ + /* On Linux (mainly Ubuntu 13.04) /etc/nsswitch.conf has mdns4 and + * it cause getaddrinfo to return EAI_SYSTEM/ENOENT. [ruby-list:49420] + */ + if (err == EAI_SYSTEM && errno == ENOENT) + err = EAI_NONAME; + #endif + } + + /* for testing HEv2 */ + if (entry->test_sleep_ms > 0) { + struct timespec sleep_ts; + sleep_ts.tv_sec = entry->test_sleep_ms / 1000; + sleep_ts.tv_nsec = (entry->test_sleep_ms % 1000) * 1000000L; + if (sleep_ts.tv_nsec >= 1000000000L) { + sleep_ts.tv_sec += sleep_ts.tv_nsec / 1000000000L; + sleep_ts.tv_nsec = sleep_ts.tv_nsec % 1000000000L; + } + nanosleep(&sleep_ts, NULL); + } + if (entry->test_ecode != 0) { + err = entry->test_ecode; + if (entry->ai) { + freeaddrinfo(entry->ai); + entry->ai = NULL; + } + } + + rb_nativethread_lock_lock(&shared->lock); + { + entry->err = err; + const char notification = entry->family == AF_INET6 ? + IPV6_HOSTNAME_RESOLVED : IPV4_HOSTNAME_RESOLVED; + + if (shared->notify != -1 && (write(shared->notify, ¬ification, 1)) < 0) { + entry->err = errno; + entry->has_syserr = true; + } + if (--(entry->refcount) == 0) { + ai = entry->ai; + entry->ai = NULL; + } + if (--(shared->refcount) == 0) shared_need_free = 1; + } + rb_nativethread_lock_unlock(&shared->lock); + + if (ai) freeaddrinfo(ai); + if (shared_need_free && shared) { + free_fast_fallback_getaddrinfo_shared(&shared); + } + + return 0; +} + +void * +fork_safe_do_fast_fallback_getaddrinfo(void *ptr) +{ + return rb_thread_prevent_fork(do_fast_fallback_getaddrinfo, ptr); +} + +#endif + /* * Addrinfo class */ void rsock_init_addrinfo(void) { + id_timeout = rb_intern("timeout"); + /* * The Addrinfo class maps <tt>struct addrinfo</tt> to ruby. This * structure identifies an Internet host and a service. */ - id_timeout = rb_intern("timeout"); - rb_cAddrinfo = rb_define_class("Addrinfo", rb_cObject); rb_define_alloc_func(rb_cAddrinfo, addrinfo_s_allocate); rb_define_method(rb_cAddrinfo, "initialize", addrinfo_initialize, -1); diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 5f803ba0da..2ec3ab335a 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -35,6 +35,7 @@ #ifdef _WIN32 # include <winsock2.h> # include <ws2tcpip.h> +# include <mswsock.h> # include <iphlpapi.h> # if defined(_MSC_VER) # undef HAVE_TYPE_STRUCT_SOCKADDR_DL @@ -137,6 +138,7 @@ #include "internal.h" #include "internal/array.h" +#include "internal/compilers.h" #include "internal/error.h" #include "internal/gc.h" #include "internal/io.h" @@ -285,19 +287,18 @@ extern VALUE rb_cAddrinfo; extern VALUE rb_cSockOpt; extern VALUE rb_eSocket; +extern VALUE rb_eResolution; #ifdef SOCKS extern VALUE rb_cSOCKSSocket; # ifndef SOCKS5 -void SOCKSinit(); -int Rconnect(); +void SOCKSinit(char *); +int Rconnect(int, const struct sockaddr *, socklen_t); # endif #endif #include "constdefs.h" -#define BLOCKING_REGION_FD(func, arg) (long)rb_thread_io_blocking_region((func), (arg), (arg)->fd) - #define SockAddrStringValue(v) rsock_sockaddr_string_value(&(v)) #define SockAddrStringValuePtr(v) rsock_sockaddr_string_value_ptr(&(v)) #define SockAddrStringValueWithAddrinfo(v, rai_ret) rsock_sockaddr_string_value_with_addrinfo(&(v), &(rai_ret)) @@ -307,7 +308,7 @@ VALUE rsock_sockaddr_string_value_with_addrinfo(volatile VALUE *v, VALUE *ai_ret VALUE rb_check_sockaddr_string_type(VALUE); -NORETURN(void rsock_raise_socket_error(const char *, int)); +NORETURN(void rsock_raise_resolution_error(const char *, int)); int rsock_family_arg(VALUE domain); int rsock_socktype_arg(VALUE type); @@ -326,8 +327,8 @@ void rb_freeaddrinfo(struct rb_addrinfo *ai); VALUE rsock_freeaddrinfo(VALUE arg); int rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags); int rsock_fd_family(int fd); -struct rb_addrinfo *rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags); -struct rb_addrinfo *rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack); +struct rb_addrinfo *rsock_addrinfo(VALUE host, VALUE port, int family, int socktype, int flags, VALUE timeout); +struct rb_addrinfo *rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_hack, VALUE timeout); VALUE rsock_fd_socket_addrinfo(int fd, struct sockaddr *addr, socklen_t len); VALUE rsock_io_socket_addrinfo(VALUE io, struct sockaddr *addr, socklen_t len); @@ -354,7 +355,7 @@ int rsock_socket(int domain, int type, int proto); int rsock_detect_cloexec(int fd); VALUE rsock_init_sock(VALUE sock, int fd); VALUE rsock_sock_s_socketpair(int argc, VALUE *argv, VALUE klass); -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); +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, VALUE open_timeout, VALUE fast_fallback, VALUE test_mode_settings); VALUE rsock_init_unixsock(VALUE sock, VALUE path, int server); struct rsock_send_arg { @@ -379,7 +380,7 @@ VALUE rsock_s_recvfrom_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str, VALUE ex, enum sock_recv_type from); VALUE rsock_s_recvfrom(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from); -int rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks, struct timeval *timeout); +int rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, VALUE timeout); VALUE rsock_s_accept(VALUE klass, VALUE io, struct sockaddr *sockaddr, socklen_t *len); VALUE rsock_s_accept_nonblock(VALUE klass, VALUE ex, rb_io_t *fptr, @@ -413,6 +414,47 @@ ssize_t rsock_recvmsg(int socket, struct msghdr *message, int flags); void rsock_discard_cmsg_resource(struct msghdr *mh, int msg_peek_p); #endif +char *raddrinfo_host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr); +char *raddrinfo_port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr); + +#ifndef FAST_FALLBACK_INIT_INETSOCK_IMPL +# if !defined(HAVE_PTHREAD_CREATE) || !defined(HAVE_PTHREAD_DETACH) || defined(__MINGW32__) || defined(__MINGW64__) +# define FAST_FALLBACK_INIT_INETSOCK_IMPL 0 +# else +# include "ruby/thread_native.h" +# define FAST_FALLBACK_INIT_INETSOCK_IMPL 1 +# define IPV6_HOSTNAME_RESOLVED '1' +# define IPV4_HOSTNAME_RESOLVED '2' +# define SELECT_CANCELLED '3' + +struct fast_fallback_getaddrinfo_entry +{ + int family, err, refcount; + struct addrinfo hints; + struct addrinfo *ai; + struct fast_fallback_getaddrinfo_shared *shared; + int has_syserr; + long test_sleep_ms; + int test_ecode; +}; + +struct fast_fallback_getaddrinfo_shared +{ + int notify, refcount; + char *node, *service; + rb_nativethread_lock_t lock; + struct fast_fallback_getaddrinfo_entry getaddrinfo_entries[FLEX_ARY_LEN]; +}; + +int raddrinfo_pthread_create(pthread_t *th, void *(*start_routine) (void *), void *arg); +void *fork_safe_do_fast_fallback_getaddrinfo(void *ptr); +void free_fast_fallback_getaddrinfo_entry(struct fast_fallback_getaddrinfo_entry **entry); +void free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shared **shared); +# endif +#endif + +NORETURN(void rsock_raise_user_specified_timeout(struct addrinfo *ai, VALUE host, VALUE port)); + void rsock_init_basicsocket(void); void rsock_init_ipsocket(void); void rsock_init_tcpsocket(void); @@ -459,12 +501,14 @@ VALUE rsock_write_nonblock(VALUE sock, VALUE buf, VALUE ex); void rsock_make_fd_nonblock(int fd); +int rsock_is_dgram(rb_io_t *fptr); + +extern ID tcp_fast_fallback; + #if !defined HAVE_INET_NTOP && ! defined _WIN32 const char *inet_ntop(int, const void *, char *, size_t); #elif defined __MINGW32__ # define inet_ntop(f,a,n,l) rb_w32_inet_ntop(f,a,n,l) -#elif defined _MSC_VER && RUBY_MSVCRT_VERSION < 90 -const char *WSAAPI inet_ntop(int, const void *, char *, size_t); #endif #endif diff --git a/ext/socket/socket.c b/ext/socket/socket.c index eb74f7a936..a8e5ae8119 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -14,6 +14,8 @@ static VALUE sym_wait_writable; static VALUE sock_s_unpack_sockaddr_in(VALUE, VALUE); +ID tcp_fast_fallback; + void rsock_sys_fail_host_port(const char *mesg, VALUE host, VALUE port) { @@ -387,22 +389,20 @@ rsock_sock_s_socketpair(int argc, VALUE *argv, VALUE klass) * * connect function in Microsoft's Winsock functions reference */ static VALUE -sock_connect(VALUE sock, VALUE addr) +sock_connect(VALUE self, VALUE addr) { VALUE rai; - rb_io_t *fptr; - int fd, n; SockAddrStringValueWithAddrinfo(addr, rai); addr = rb_str_new4(addr); - GetOpenFile(sock, fptr); - fd = fptr->fd; - n = rsock_connect(fd, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr), 0, NULL); - if (n < 0) { + + int result = rsock_connect(self, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr), 0, RUBY_IO_TIMEOUT_DEFAULT); + + if (result < 0) { rsock_sys_fail_raddrinfo_or_sockaddr("connect(2)", addr, rai); } - return INT2FIX(n); + return INT2FIX(result); } /* :nodoc: */ @@ -965,7 +965,7 @@ sock_s_gethostbyname(VALUE obj, VALUE host) { rb_warn("Socket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead."); struct rb_addrinfo *res = - rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME); + rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, Qnil); return rsock_make_hostent(host, res, sock_sockaddr); } @@ -1183,7 +1183,7 @@ sock_s_getaddrinfo(int argc, VALUE *argv, VALUE _) norevlookup = rsock_do_not_reverse_lookup; } - res = rsock_getaddrinfo(host, port, &hints, 0); + res = rsock_getaddrinfo(host, port, &hints, 0, Qnil); ret = make_addrinfo(res, norevlookup); rb_freeaddrinfo(res); @@ -1279,7 +1279,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) hints.ai_socktype = (fl & NI_DGRAM) ? SOCK_DGRAM : SOCK_STREAM; /* af */ hints.ai_family = NIL_P(af) ? PF_UNSPEC : rsock_family_arg(af); - res = rsock_getaddrinfo(host, port, &hints, 0); + res = rsock_getaddrinfo(host, port, &hints, 0, Qnil); sap = res->ai->ai_addr; salen = res->ai->ai_addrlen; } @@ -1313,7 +1313,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) saved_errno = errno; if (res) rb_freeaddrinfo(res); errno = saved_errno; - rsock_raise_socket_error("getnameinfo", error); + rsock_raise_resolution_error("getnameinfo", error); UNREACHABLE_RETURN(Qnil); } @@ -1335,7 +1335,7 @@ sock_s_getnameinfo(int argc, VALUE *argv, VALUE _) static VALUE sock_s_pack_sockaddr_in(VALUE self, VALUE port, VALUE host) { - struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0); + struct rb_addrinfo *res = rsock_addrinfo(host, port, AF_UNSPEC, 0, 0, Qnil); VALUE addr = rb_str_new((char*)res->ai->ai_addr, res->ai->ai_addrlen); rb_freeaddrinfo(res); @@ -1654,8 +1654,7 @@ socket_s_ip_address_list(VALUE self) finish: save_errno = errno; - if (lc.lifc_buf != NULL) - xfree(lc.lifc_req); + xfree(lc.lifc_req); if (fd != -1) close(fd); errno = save_errno; @@ -1857,6 +1856,67 @@ socket_s_ip_address_list(VALUE self) #define socket_s_ip_address_list rb_f_notimplement #endif +/* + * call-seq: + * Socket.tcp_fast_fallback -> true or false + * + * Returns whether Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305]), + * which is provided starting from Ruby 3.4 when using TCPSocket.new and Socket.tcp, + * is enabled or disabled. + * + * If true, it is enabled for TCPSocket.new and Socket.tcp. + * (Note: Happy Eyeballs Version 2 is not provided when using TCPSocket.new on Windows.) + * + * If false, Happy Eyeballs Version 2 is disabled. + * + * For details on Happy Eyeballs Version 2, + * see {Socket.tcp_fast_fallback=}[rdoc-ref:Socket.tcp_fast_fallback=]. + */ +VALUE socket_s_tcp_fast_fallback(VALUE self) { + return rb_ivar_get(rb_cSocket, tcp_fast_fallback); +} + +/* + * call-seq: + * Socket.tcp_fast_fallback= -> true or false + * + * Enable or disable Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305]) + * globally, which is provided starting from Ruby 3.4 when using TCPSocket.new and Socket.tcp. + * + * When set to true, the feature is enabled for both `TCPSocket.new` and `Socket.tcp`. + * (Note: This feature is not available when using TCPSocket.new on Windows.) + * + * When set to false, the behavior reverts to that of Ruby 3.3 or earlier. + * + * The default value is true if no value is explicitly set by calling this method. + * However, when the environment variable RUBY_TCP_NO_FAST_FALLBACK=1 is set, + * the default is false. + * + * To control the setting on a per-method basis, use the fast_fallback keyword argument for each method. + * + * === Happy Eyeballs Version 2 + * Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305]) + * is an algorithm designed to improve client socket connectivity.<br> + * It aims for more reliable and efficient connections by performing hostname resolution + * and connection attempts in parallel, instead of serially. + * + * Starting from Ruby 3.4, this method operates as follows with this algorithm: + * + * 1. Start resolving both IPv6 and IPv4 addresses concurrently. + * 2. Start connecting to the one of the addresses that are obtained first.<br>If IPv4 addresses are obtained first, + * the method waits 50 ms for IPv6 name resolution to prioritize IPv6 connections. + * 3. After starting a connection attempt, wait 250 ms for the connection to be established.<br> + * If no connection is established within this time, a new connection is started every 250 ms<br> + * until a connection is established or there are no more candidate addresses.<br> + * (Although RFC 8305 strictly specifies sorting addresses,<br> + * this method only alternates between IPv6 / IPv4 addresses due to the performance concerns) + * 4. Once a connection is established, all remaining connection attempts are canceled. + */ +VALUE socket_s_tcp_fast_fallback_set(VALUE self, VALUE value) { + rb_ivar_set(rb_cSocket, tcp_fast_fallback, value); + return value; +} + void Init_socket(void) { @@ -1985,6 +2045,16 @@ Init_socket(void) rsock_init_socket_init(); + const char *tcp_no_fast_fallback_config = getenv("RUBY_TCP_NO_FAST_FALLBACK"); + VALUE fast_fallback_default; + if (tcp_no_fast_fallback_config == NULL || strcmp(tcp_no_fast_fallback_config, "0") == 0) { + fast_fallback_default = Qtrue; + } else { + fast_fallback_default = Qfalse; + } + tcp_fast_fallback = rb_intern_const("tcp_fast_fallback"); + rb_ivar_set(rb_cSocket, tcp_fast_fallback, fast_fallback_default); + rb_define_method(rb_cSocket, "initialize", sock_initialize, -1); rb_define_method(rb_cSocket, "connect", sock_connect, 1); @@ -2028,6 +2098,9 @@ Init_socket(void) rb_define_singleton_method(rb_cSocket, "ip_address_list", socket_s_ip_address_list, 0); + rb_define_singleton_method(rb_cSocket, "tcp_fast_fallback", socket_s_tcp_fast_fallback, 0); + rb_define_singleton_method(rb_cSocket, "tcp_fast_fallback=", socket_s_tcp_fast_fallback_set, 1); + #undef rb_intern sym_wait_writable = ID2SYM(rb_intern("wait_writable")); } diff --git a/ext/socket/sockssocket.c b/ext/socket/sockssocket.c index f263ac3804..30860ea257 100644 --- a/ext/socket/sockssocket.c +++ b/ext/socket/sockssocket.c @@ -30,11 +30,12 @@ socks_init(VALUE sock, VALUE host, VALUE port) static int init = 0; if (init == 0) { - SOCKSinit("ruby"); + char progname[] = "ruby"; + SOCKSinit(progname); init = 1; } - return rsock_init_inetsock(sock, host, port, Qnil, Qnil, INET_SOCKS, Qnil, Qnil); + return rsock_init_inetsock(sock, host, port, Qnil, Qnil, INET_SOCKS, Qnil, Qnil, Qnil, Qfalse, Qnil); } #ifdef SOCKS5 @@ -48,7 +49,7 @@ socks_s_close(VALUE sock) rb_io_t *fptr; GetOpenFile(sock, fptr); - shutdown(fptr->fd, 2); + shutdown(fptr->fd, SHUT_RDWR); return rb_io_close(sock); } #endif diff --git a/ext/socket/tcpserver.c b/ext/socket/tcpserver.c index 04e5a0bb51..0069f3c703 100644 --- a/ext/socket/tcpserver.c +++ b/ext/socket/tcpserver.c @@ -36,7 +36,7 @@ tcp_svr_init(int argc, VALUE *argv, VALUE sock) VALUE hostname, port; rb_scan_args(argc, argv, "011", &hostname, &port); - return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil); + return rsock_init_inetsock(sock, hostname, port, Qnil, Qnil, INET_SERVER, Qnil, Qnil, Qnil, Qfalse, Qnil); } /* diff --git a/ext/socket/tcpsocket.c b/ext/socket/tcpsocket.c index 03787272f3..7ce536e0af 100644 --- a/ext/socket/tcpsocket.c +++ b/ext/socket/tcpsocket.c @@ -12,13 +12,31 @@ /* * call-seq: - * TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, connect_timeout: nil) + * TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, resolv_timeout: nil, connect_timeout: nil, open_timeout: nil, fast_fallback: true) * * Opens a TCP connection to +remote_host+ on +remote_port+. If +local_host+ * and +local_port+ are specified, then those parameters are used on the local * end to establish the connection. * - * [:connect_timeout] specify the timeout in seconds. + * Starting from Ruby 3.4, this method operates according to the + * Happy Eyeballs Version 2 ({RFC 8305}[https://datatracker.ietf.org/doc/html/rfc8305]) + * algorithm by default, except on Windows. + * + * For details on Happy Eyeballs Version 2, + * see {Socket.tcp_fast_fallback=}[rdoc-ref:Socket.tcp_fast_fallback=]. + * + * To make it behave the same as in Ruby 3.3 and earlier, + * explicitly specify the option fast_fallback:false. + * Or, setting Socket.tcp_fast_fallback=false will disable + * Happy Eyeballs Version 2 not only for this method but for all Socket globally. + * + * When using TCPSocket.new on Windows, Happy Eyeballs Version 2 is not provided, + * and it behaves the same as in Ruby 3.3 and earlier. + * + * [:resolv_timeout] Specifies the timeout in seconds from when the hostname resolution starts. + * [:connect_timeout] This method sequentially attempts connecting to all candidate destination addresses.<br>The +connect_timeout+ specifies the timeout in seconds from the start of the connection attempt to the last candidate.<br>By default, all connection attempts continue until the timeout occurs.<br>When +fast_fallback:false+ is explicitly specified,<br>a timeout is set for each connection attempt and any connection attempt that exceeds its timeout will be canceled. + * [:open_timeout] Specifies the timeout in seconds from the start of the method execution.<br>If this timeout is reached while there are still addresses that have not yet been attempted for connection, no further attempts will be made.<br>If this option is specified together with other timeout options, an +ArgumentError+ will be raised. + * [:fast_fallback] Enables the Happy Eyeballs Version 2 algorithm (enabled by default). */ static VALUE tcp_init(int argc, VALUE *argv, VALUE sock) @@ -26,28 +44,43 @@ tcp_init(int argc, VALUE *argv, VALUE sock) VALUE remote_host, remote_serv; VALUE local_host, local_serv; VALUE opt; - static ID keyword_ids[2]; - VALUE kwargs[2]; + static ID keyword_ids[5]; + VALUE kwargs[5]; VALUE resolv_timeout = Qnil; VALUE connect_timeout = Qnil; + VALUE open_timeout = Qnil; + VALUE fast_fallback = Qnil; + VALUE test_mode_settings = Qnil; if (!keyword_ids[0]) { CONST_ID(keyword_ids[0], "resolv_timeout"); CONST_ID(keyword_ids[1], "connect_timeout"); + CONST_ID(keyword_ids[2], "open_timeout"); + CONST_ID(keyword_ids[3], "fast_fallback"); + CONST_ID(keyword_ids[4], "test_mode_settings"); } rb_scan_args(argc, argv, "22:", &remote_host, &remote_serv, &local_host, &local_serv, &opt); if (!NIL_P(opt)) { - rb_get_kwargs(opt, keyword_ids, 0, 2, kwargs); + rb_get_kwargs(opt, keyword_ids, 0, 5, kwargs); if (kwargs[0] != Qundef) { resolv_timeout = kwargs[0]; } if (kwargs[1] != Qundef) { connect_timeout = kwargs[1]; } + if (kwargs[2] != Qundef) { open_timeout = kwargs[2]; } + if (kwargs[3] != Qundef) { fast_fallback = kwargs[3]; } + if (kwargs[4] != Qundef) { test_mode_settings = kwargs[4]; } + } + + if (fast_fallback == Qnil) { + fast_fallback = rb_ivar_get(rb_cSocket, tcp_fast_fallback); + if (fast_fallback == Qnil) fast_fallback = Qtrue; } return rsock_init_inetsock(sock, remote_host, remote_serv, local_host, local_serv, INET_CLIENT, - resolv_timeout, connect_timeout); + resolv_timeout, connect_timeout, open_timeout, + fast_fallback, test_mode_settings); } static VALUE @@ -80,7 +113,7 @@ tcp_s_gethostbyname(VALUE obj, VALUE host) { rb_warn("TCPSocket.gethostbyname is deprecated; use Addrinfo.getaddrinfo instead."); struct rb_addrinfo *res = - rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME); + rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, AI_CANONNAME, Qnil); return rsock_make_hostent(host, res, tcp_sockaddr); } diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c index 5224e48a96..b2bc925538 100644 --- a/ext/socket/udpsocket.c +++ b/ext/socket/udpsocket.c @@ -45,22 +45,18 @@ udp_init(int argc, VALUE *argv, VALUE sock) struct udp_arg { + VALUE io; struct rb_addrinfo *res; - rb_io_t *fptr; }; static VALUE udp_connect_internal(VALUE v) { struct udp_arg *arg = (void *)v; - rb_io_t *fptr; - int fd; struct addrinfo *res; - rb_io_check_closed(fptr = arg->fptr); - fd = fptr->fd; for (res = arg->res->ai; res; res = res->ai_next) { - if (rsock_connect(fd, res->ai_addr, res->ai_addrlen, 0, NULL) >= 0) { + if (rsock_connect(arg->io, res->ai_addr, res->ai_addrlen, 0, RUBY_IO_TIMEOUT_DEFAULT) >= 0) { return Qtrue; } } @@ -84,16 +80,17 @@ udp_connect_internal(VALUE v) * */ static VALUE -udp_connect(VALUE sock, VALUE host, VALUE port) +udp_connect(VALUE self, VALUE host, VALUE port) { - struct udp_arg arg; - VALUE ret; + struct udp_arg arg = {.io = self}; + + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, Qnil); + + int result = (int)rb_ensure(udp_connect_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); + if (!result) { + rsock_sys_fail_host_port("connect(2)", host, port); + } - GetOpenFile(sock, arg.fptr); - arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0); - ret = rb_ensure(udp_connect_internal, (VALUE)&arg, - rsock_freeaddrinfo, (VALUE)arg.res); - if (!ret) rsock_sys_fail_host_port("connect(2)", host, port); return INT2FIX(0); } @@ -101,14 +98,13 @@ static VALUE udp_bind_internal(VALUE v) { struct udp_arg *arg = (void *)v; - rb_io_t *fptr; - int fd; struct addrinfo *res; - rb_io_check_closed(fptr = arg->fptr); - fd = fptr->fd; + rb_io_t *fptr; + RB_IO_POINTER(arg->io, fptr); + for (res = arg->res->ai; res; res = res->ai_next) { - if (bind(fd, res->ai_addr, res->ai_addrlen) < 0) { + if (bind(fptr->fd, res->ai_addr, res->ai_addrlen) < 0) { continue; } return Qtrue; @@ -129,22 +125,23 @@ udp_bind_internal(VALUE v) * */ static VALUE -udp_bind(VALUE sock, VALUE host, VALUE port) +udp_bind(VALUE self, VALUE host, VALUE port) { - struct udp_arg arg; - VALUE ret; + struct udp_arg arg = {.io = self}; + + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0, Qnil); + + VALUE result = rb_ensure(udp_bind_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); + if (!result) { + rsock_sys_fail_host_port("bind(2)", host, port); + } - GetOpenFile(sock, arg.fptr); - arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0); - ret = rb_ensure(udp_bind_internal, (VALUE)&arg, - rsock_freeaddrinfo, (VALUE)arg.res); - if (!ret) rsock_sys_fail_host_port("bind(2)", host, port); return INT2FIX(0); } struct udp_send_arg { - struct rb_addrinfo *res; rb_io_t *fptr; + struct rb_addrinfo *res; struct rsock_send_arg sarg; }; @@ -166,7 +163,7 @@ udp_send_internal(VALUE v) rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_WRITABLE), Qnil); #endif - ssize_t n = (ssize_t)BLOCKING_REGION_FD(rsock_sendto_blocking, &arg->sarg); + ssize_t n = (ssize_t)rb_io_blocking_region(fptr, rsock_sendto_blocking, &arg->sarg); if (n >= 0) return RB_SSIZE2NUM(n); @@ -215,7 +212,7 @@ udp_send(int argc, VALUE *argv, VALUE sock) GetOpenFile(sock, arg.fptr); arg.sarg.fd = arg.fptr->fd; arg.sarg.flags = NUM2INT(flags); - arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0); + arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0, Qnil); ret = rb_ensure(udp_send_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!ret) rsock_sys_fail_host_port("sendto(2)", host, port); diff --git a/ext/socket/unixsocket.c b/ext/socket/unixsocket.c index a8475e3e60..2ec9376074 100644 --- a/ext/socket/unixsocket.c +++ b/ext/socket/unixsocket.c @@ -14,15 +14,14 @@ struct unixsock_arg { struct sockaddr_un *sockaddr; socklen_t sockaddrlen; - int fd; + VALUE io; }; static VALUE unixsock_connect_internal(VALUE a) { struct unixsock_arg *arg = (struct unixsock_arg *)a; - return (VALUE)rsock_connect(arg->fd, (struct sockaddr*)arg->sockaddr, - arg->sockaddrlen, 0, NULL); + return (VALUE)rsock_connect(arg->io, (struct sockaddr*)arg->sockaddr, arg->sockaddrlen, 0, RUBY_IO_TIMEOUT_DEFAULT); } static VALUE @@ -43,15 +42,16 @@ unixsock_path_value(VALUE path) } } #endif + path = rb_get_path(path); #ifdef _WIN32 /* UNIXSocket requires UTF-8 per spec. */ path = rb_str_export_to_enc(path, rb_utf8_encoding()); #endif - return rb_get_path(path); + return path; } VALUE -rsock_init_unixsock(VALUE sock, VALUE path, int server) +rsock_init_unixsock(VALUE self, VALUE path, int server) { struct sockaddr_un sockaddr; socklen_t sockaddrlen; @@ -73,43 +73,46 @@ rsock_init_unixsock(VALUE sock, VALUE path, int server) rsock_sys_fail_path("socket(2)", path); } + VALUE io = rsock_init_sock(self, fd); + RB_IO_POINTER(io, fptr); + if (server) { status = bind(fd, (struct sockaddr*)&sockaddr, sockaddrlen); } else { - int prot; + int error_tag; struct unixsock_arg arg; arg.sockaddr = &sockaddr; arg.sockaddrlen = sockaddrlen; - arg.fd = fd; - status = (int)rb_protect(unixsock_connect_internal, (VALUE)&arg, &prot); - if (prot) { - close(fd); - rb_jump_tag(prot); + arg.io = io; + + status = (int)rb_protect(unixsock_connect_internal, (VALUE)&arg, &error_tag); + + if (error_tag) { + rb_io_close(io); + rb_jump_tag(error_tag); } } if (status < 0) { int e = errno; - close(fd); + rb_io_close(io); rsock_syserr_fail_path(e, "connect(2)", path); } if (server) { if (listen(fd, SOMAXCONN) < 0) { int e = errno; - close(fd); + rb_io_close(io); rsock_syserr_fail_path(e, "listen(2)", path); } } - rsock_init_sock(sock, fd); if (server) { - GetOpenFile(sock, fptr); fptr->pathv = rb_str_new_frozen(path); } - return sock; + return io; } /* @@ -125,9 +128,9 @@ rsock_init_unixsock(VALUE sock, VALUE path, int server) * */ static VALUE -unix_init(VALUE sock, VALUE path) +unix_init(VALUE self, VALUE path) { - return rsock_init_unixsock(sock, path, 0); + return rsock_init_unixsock(self, path, 0); } /* @@ -288,7 +291,7 @@ unix_send_io(VALUE sock, VALUE val) #endif arg.fd = fptr->fd; - while ((int)BLOCKING_REGION_FD(sendmsg_blocking, &arg) == -1) { + while ((int)rb_io_blocking_region(fptr, sendmsg_blocking, &arg) == -1) { if (!rb_io_wait_writable(arg.fd)) rsock_sys_fail_path("sendmsg(2)", fptr->pathv); } @@ -390,7 +393,7 @@ retry: #endif arg.fd = fptr->fd; - while ((int)BLOCKING_REGION_FD(recvmsg_blocking, &arg) == -1) { + while ((int)rb_io_blocking_region(fptr, recvmsg_blocking, &arg) == -1) { int e = errno; if (e == EMSGSIZE && !(gc_reason & GC_REASON_EMSGSIZE)) { /* FreeBSD gets here when we're out of FDs */ |
