diff options
Diffstat (limited to 'ext/socket')
| -rw-r--r-- | ext/socket/depend | 75 | ||||
| -rw-r--r-- | ext/socket/extconf.rb | 1 | ||||
| -rw-r--r-- | ext/socket/getaddrinfo.c | 7 | ||||
| -rw-r--r-- | ext/socket/getnameinfo.c | 18 | ||||
| -rw-r--r-- | ext/socket/ifaddr.c | 2 | ||||
| -rw-r--r-- | ext/socket/init.c | 23 | ||||
| -rw-r--r-- | ext/socket/ipsocket.c | 1255 | ||||
| -rw-r--r-- | ext/socket/lib/socket.rb | 179 | ||||
| -rw-r--r-- | ext/socket/raddrinfo.c | 270 | ||||
| -rw-r--r-- | ext/socket/rubysocket.h | 56 | ||||
| -rw-r--r-- | ext/socket/socket.c | 84 | ||||
| -rw-r--r-- | ext/socket/sockssocket.c | 5 | ||||
| -rw-r--r-- | ext/socket/tcpserver.c | 2 | ||||
| -rw-r--r-- | ext/socket/tcpsocket.c | 47 | ||||
| -rw-r--r-- | ext/socket/udpsocket.c | 6 | ||||
| -rw-r--r-- | ext/socket/unixsocket.c | 3 |
16 files changed, 1855 insertions, 178 deletions
diff --git a/ext/socket/depend b/ext/socket/depend index 750bb0734f..77f6239a3d 100644 --- a/ext/socket/depend +++ b/ext/socket/depend @@ -151,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 @@ -192,9 +193,12 @@ 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 @@ -202,6 +206,7 @@ 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 @@ -362,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 @@ -403,9 +409,12 @@ 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 @@ -413,6 +422,7 @@ 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 @@ -573,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 @@ -614,9 +625,12 @@ 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 @@ -624,6 +638,7 @@ 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 @@ -785,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 @@ -826,9 +842,12 @@ 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 @@ -836,6 +855,7 @@ 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 @@ -996,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 @@ -1037,9 +1058,12 @@ 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 @@ -1047,6 +1071,7 @@ 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 @@ -1207,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 @@ -1248,9 +1274,12 @@ 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 @@ -1258,6 +1287,7 @@ 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 @@ -1418,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 @@ -1459,9 +1490,12 @@ 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 @@ -1469,6 +1503,7 @@ 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 @@ -1629,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 @@ -1670,9 +1706,12 @@ 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 @@ -1680,6 +1719,7 @@ 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 @@ -1840,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 @@ -1881,9 +1922,12 @@ 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 @@ -1891,6 +1935,7 @@ 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 @@ -2051,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 @@ -2092,9 +2138,12 @@ 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 @@ -2102,6 +2151,7 @@ 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 @@ -2262,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 @@ -2303,9 +2354,12 @@ 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 @@ -2313,6 +2367,7 @@ 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 @@ -2473,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 @@ -2514,9 +2570,12 @@ 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 @@ -2524,6 +2583,7 @@ 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 @@ -2684,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 @@ -2725,9 +2786,12 @@ 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 @@ -2735,6 +2799,7 @@ 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 @@ -2895,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 @@ -2936,9 +3002,12 @@ 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 @@ -2946,6 +3015,7 @@ 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 @@ -3106,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 @@ -3147,9 +3218,12 @@ 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 @@ -3157,6 +3231,7 @@ 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 diff --git a/ext/socket/extconf.rb b/ext/socket/extconf.rb index d44ce31b0a..a814e21c3a 100644 --- a/ext/socket/extconf.rb +++ b/ext/socket/extconf.rb @@ -704,6 +704,7 @@ SRC have_func("pthread_create") have_func("pthread_detach") + have_func("pthread_attr_setdetachstate") $VPATH << '$(topdir)' << '$(top_srcdir)' create_makefile("socket") diff --git a/ext/socket/getaddrinfo.c b/ext/socket/getaddrinfo.c index bf0d90129f..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;\ }\ 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 83af8c5b0e..b761d601c3 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -473,7 +473,7 @@ rsock_socket(int domain, int type, int proto) /* emulate blocking connect behavior on EINTR or non-blocking socket */ static int -wait_connectable(VALUE self, VALUE timeout) +wait_connectable(VALUE self, VALUE timeout, const struct sockaddr *sockaddr, int len) { int sockerr; socklen_t sockerrlen; @@ -514,7 +514,10 @@ wait_connectable(VALUE self, VALUE timeout) VALUE result = rb_io_wait(self, RB_INT2NUM(RUBY_IO_READABLE|RUBY_IO_WRITABLE), timeout); if (result == Qfalse) { - rb_raise(rb_eIOTimeoutError, "Connect timed out!"); + 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); @@ -603,7 +606,7 @@ rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, V #ifdef EINPROGRESS case EINPROGRESS: #endif - return wait_connectable(self, timeout); + return wait_connectable(self, timeout, sockaddr, len); } } return status; @@ -786,7 +789,17 @@ rsock_getfamily(rb_io_t *fptr) * call-seq: * error_code -> integer * - * Returns the raw error code occurred at name resolution. + * 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) @@ -802,7 +815,7 @@ rsock_init_socket_init(void) */ rb_eSocket = rb_define_class("SocketError", rb_eStandardError); /* - * ResolutionError is the error class for socket name resolution. + * 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); diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index 3de8445435..931a1a629c 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -23,8 +23,25 @@ struct inetsock_arg int type; 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) { @@ -45,6 +62,13 @@ inetsock_cleanup(VALUE v) } 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; @@ -54,12 +78,18 @@ init_inetsock_internal(VALUE v) 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; + VALUE open_timeout = arg->open_timeout; + VALUE timeout; + VALUE starts_at; + + 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 @@ -67,7 +97,7 @@ 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); } VALUE io = Qnil; @@ -122,8 +152,18 @@ 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(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), connect_timeout); + status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), timeout); syscall = "connect(2)"; } } @@ -169,9 +209,19 @@ init_inetsock_internal(VALUE v) return io; } +#if FAST_FALLBACK_INIT_INETSOCK_IMPL == 0 + VALUE -rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout) +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.self = self; arg.io = Qnil; @@ -184,10 +234,1203 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca 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); } +#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 @@ -366,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 f23ca8085d..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) @@ -599,6 +599,7 @@ class Socket < BasicSocket __accept_nonblock(exception) end + # :stopdoc: RESOLUTION_DELAY = 0.05 private_constant :RESOLUTION_DELAY @@ -614,14 +615,9 @@ class Socket < BasicSocket HOSTNAME_RESOLUTION_QUEUE_UPDATED = 0 private_constant :HOSTNAME_RESOLUTION_QUEUE_UPDATED - IPV6_ADRESS_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_ADRESS_FORMAT - - @tcp_fast_fallback = true - - class << self - attr_accessor :tcp_fast_fallback - end + 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| ... } @@ -633,8 +629,13 @@ class Socket < BasicSocket # 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+. + # 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. @@ -644,6 +645,7 @@ class Socket < BasicSocket # # [: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. @@ -657,29 +659,15 @@ class Socket < BasicSocket # sock.close_write # puts sock.read # } - # - # === 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. - def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket - sock = if fast_fallback && !(host && ip_address?(host)) - tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:) + 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:) + tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) end if block_given? @@ -693,9 +681,10 @@ class Socket < BasicSocket end end - def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil) + # :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: resolv_timeout) + 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 = [] @@ -708,14 +697,17 @@ class Socket < BasicSocket 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, family_name, :STREAM, timeout: resolv_timeout) + 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 @@ -732,7 +724,6 @@ class Socket < BasicSocket thread } ) - user_specified_resolv_timeout_at = resolv_timeout ? now + resolv_timeout : Float::INFINITY end @@ -744,7 +735,7 @@ class Socket < BasicSocket if local_addrinfos.any? local_addrinfo = local_addrinfos.find { |lai| lai.afamily == addrinfo.afamily } - if local_addrinfo.nil? # Connecting addrinfoと同じアドレスファミリのLocal addrinfoがない + if local_addrinfo.nil? if resolution_store.any_addrinfos? # Try other Addrinfo in next "while" next @@ -766,9 +757,16 @@ class Socket < BasicSocket 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: connect_timeout) : - addrinfo.connect(timeout: connect_timeout) + addrinfo.connect_from(local_addrinfo, timeout:) : + addrinfo.connect(timeout:) end if result == :wait_writable @@ -802,7 +800,10 @@ class Socket < BasicSocket ends_at = if resolution_store.any_addrinfos? - resolution_delay_expires_at || connection_attempt_delay_expires_at + [(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 @@ -834,15 +835,14 @@ class Socket < BasicSocket 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? || - resolution_store.any_addrinfos? || - connecting_sockets.any? || - resolution_store.any_unresolved_family? - user_specified_connect_timeout_at = nil if connecting_sockets.empty? + if writable_sockets.any? || connecting_sockets.any? # Try other writable socket in next "while" - # Or exit this "while" and try other connection attempt # 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 @@ -853,20 +853,19 @@ class Socket < BasicSocket 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_CONNECT_TIME) + 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 writable_sockets.any? || - resolution_store.any_addrinfos? || - connecting_sockets.any? || - resolution_store.any_unresolved_family? - user_specified_connect_timeout_at = nil if connecting_sockets.empty? - # Try other writable socket in next "while" - # Or exit this "while" and try other connection attempt + 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 @@ -883,7 +882,11 @@ class Socket < BasicSocket unless (Socket.const_defined?(:EAI_ADDRFAMILY)) && (result.is_a?(Socket::ResolutionError)) && (result.error_code == Socket::EAI_ADDRFAMILY) - last_error = result + 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) @@ -901,14 +904,22 @@ class Socket < BasicSocket 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? - raise last_error + 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 Errno::ETIMEDOUT, 'user specified timeout' + raise(IO::TimeoutError, "user specified timeout for #{host}:#{port}") end end end @@ -923,17 +934,21 @@ class Socket < BasicSocket 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:) + 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 @@ -941,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 @@ -964,7 +987,7 @@ class Socket < BasicSocket private_class_method :tcp_without_fast_fallback def self.ip_address?(hostname) - hostname.match?(IPV6_ADRESS_FORMAT) || hostname.match?(/\A([0-9]{1,3}\.){3}[0-9]{1,3}\z/) + hostname.match?(IPV6_ADDRESS_FORMAT) || hostname.match?(/\A([0-9]{1,3}\.){3}[0-9]{1,3}\z/) end private_class_method :ip_address? @@ -1039,8 +1062,8 @@ class Socket < BasicSocket private_constant :HostnameResolutionResult class HostnameResolutionStore - PRIORITY_ON_V6 = [:ipv6, :ipv4] - PRIORITY_ON_V4 = [:ipv4, :ipv6] + PRIORITY_ON_V6 = [:ipv6, :ipv4].freeze + PRIORITY_ON_V4 = [:ipv4, :ipv6].freeze def initialize(family_names) @family_names = family_names @@ -1089,7 +1112,7 @@ class Socket < BasicSocket end def resolved_successfully?(family) - resolved?(family) && !!@error_dict[family] + resolved?(family) && !@error_dict[family] end def resolved_all_families? @@ -1102,7 +1125,6 @@ class Socket < BasicSocket end private_constant :HostnameResolutionStore - # :stopdoc: def self.ip_sockets_port0(ai_list, reuseaddr) sockets = [] begin @@ -1135,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) @@ -1565,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. @@ -1611,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 @@ -1646,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 diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 7ec700075e..6cdf5c6abc 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -293,10 +293,22 @@ rb_freeaddrinfo(struct rb_addrinfo *ai) xfree(ai); } +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) +rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai, int _timeout) { return getaddrinfo(hostp, portp, hints, ai); } @@ -334,7 +346,7 @@ fork_safe_getaddrinfo(void *arg) } static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai) +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); @@ -352,13 +364,14 @@ struct getaddrinfo_arg char *node, *service; struct addrinfo hints; struct addrinfo *ai; - int err, gai_errno, refcount, done, cancelled; + 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) +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); @@ -374,7 +387,7 @@ allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addr if (hostp) { arg->node = buf + hostp_offset; - strcpy(arg->node, hostp); + memcpy(arg->node, hostp, portp_offset - hostp_offset); } else { arg->node = NULL; @@ -382,7 +395,7 @@ allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addr if (portp) { arg->service = buf + portp_offset; - strcpy(arg->service, portp); + memcpy(arg->service, portp, bufsize - portp_offset); } else { arg->service = NULL; @@ -392,7 +405,8 @@ allocate_getaddrinfo_arg(const char *hostp, const char *portp, const struct addr arg->ai = NULL; arg->refcount = 2; - arg->done = arg->cancelled = 0; + arg->done = arg->cancelled = arg->timedout = 0; + arg->timeout = timeout; rb_nativethread_lock_initialize(&arg->lock); rb_native_cond_initialize(&arg->cond); @@ -451,7 +465,19 @@ wait_getaddrinfo(void *ptr) struct getaddrinfo_arg *arg = (struct getaddrinfo_arg *)ptr; rb_nativethread_lock_lock(&arg->lock); while (!arg->done && !arg->cancelled) { - rb_native_cond_wait(&arg->cond, &arg->lock); + 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; @@ -469,17 +495,53 @@ cancel_getaddrinfo(void *ptr) rb_nativethread_lock_unlock(&arg->lock); } -static int -do_pthread_create(pthread_t *th, void *(*start_routine) (void *), void *arg) +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, 0, start_routine, arg); + 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; } @@ -490,28 +552,27 @@ fork_safe_do_getaddrinfo(void *ptr) } static int -rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hints, struct addrinfo **ai) +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; + int err = 0, gai_errno = 0, timedout = 0; start: retry = 0; - arg = allocate_getaddrinfo_arg(hostp, portp, hints); + arg = allocate_getaddrinfo_arg(hostp, portp, hints, timeout); if (!arg) { return EAI_MEMORY; } pthread_t th; - if (do_pthread_create(&th, fork_safe_do_getaddrinfo, arg) != 0) { + if (raddrinfo_pthread_create(&th, fork_safe_do_getaddrinfo, arg) != 0) { int err = errno; free_getaddrinfo_arg(arg); errno = err; return EAI_SYSTEM; } - pthread_detach(th); rb_thread_call_without_gvl2(wait_getaddrinfo, arg, cancel_getaddrinfo, arg); @@ -525,6 +586,7 @@ start: } else if (arg->cancelled) { retry = 1; + timedout = arg->timedout; } else { // If already interrupted, rb_thread_call_without_gvl2 may return without calling wait_getaddrinfo. @@ -538,6 +600,16 @@ start: 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(); @@ -552,6 +624,10 @@ start: #endif +#define GETNAMEINFO_WONT_BLOCK(host, serv, flags) \ + ((!(host) || ((flags) & NI_NUMERICHOST)) && \ + (!(serv) || ((flags) & NI_NUMERICSERV))) + #if GETADDRINFO_IMPL == 0 int @@ -559,7 +635,7 @@ 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, hostlen, serv, servlen, flags); + return getnameinfo(sa, salen, host, (socklen_t)hostlen, serv, (socklen_t)servlen, flags); } #elif GETADDRINFO_IMPL == 1 @@ -589,6 +665,10 @@ rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) { + 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; @@ -715,7 +795,11 @@ rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, { int retry; struct getnameinfo_arg *arg; - int err, gni_errno = 0; + 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; @@ -726,13 +810,12 @@ start: } pthread_t th; - if (do_pthread_create(&th, do_getnameinfo, arg) != 0) { + if (raddrinfo_pthread_create(&th, do_getnameinfo, arg) != 0) { int err = errno; free_getnameinfo_arg(arg); errno = err; return EAI_SYSTEM; } - pthread_detach(th); rb_thread_call_without_gvl2(wait_getnameinfo, arg, cancel_getnameinfo, arg); @@ -822,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; @@ -861,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; @@ -914,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) { @@ -941,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; @@ -950,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; @@ -976,7 +1059,8 @@ rsock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_h } if (!resolved) { - error = rb_getaddrinfo(hostp, portp, hints, &ai); + 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; @@ -1009,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; @@ -1017,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 @@ -1137,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)); @@ -1211,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 @@ -1248,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) { @@ -1260,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 @@ -1274,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; } @@ -1299,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"); @@ -1309,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) { @@ -1323,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); @@ -1435,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; @@ -1451,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); } @@ -1555,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); @@ -1567,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 @@ -1580,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); } @@ -2169,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; @@ -2937,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; } @@ -3024,6 +3109,99 @@ 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 */ diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index b4daa66071..2ec3ab335a 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -138,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" @@ -291,8 +292,8 @@ 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 @@ -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 { @@ -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); @@ -461,12 +503,12 @@ 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 487a293c4e..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) { @@ -963,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); } @@ -1181,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); @@ -1277,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; } @@ -1333,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); @@ -1854,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) { @@ -1982,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); @@ -2025,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 1e94186cd0..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 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 a984933c9f..b2bc925538 100644 --- a/ext/socket/udpsocket.c +++ b/ext/socket/udpsocket.c @@ -84,7 +84,7 @@ udp_connect(VALUE self, VALUE host, VALUE port) { struct udp_arg arg = {.io = self}; - arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0); + 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) { @@ -129,7 +129,7 @@ udp_bind(VALUE self, VALUE host, VALUE port) { struct udp_arg arg = {.io = self}; - arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0); + 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) { @@ -212,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 31e2acee04..2ec9376074 100644 --- a/ext/socket/unixsocket.c +++ b/ext/socket/unixsocket.c @@ -42,11 +42,12 @@ 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 |
