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 | 22 | ||||
| -rw-r--r-- | ext/socket/ipsocket.c | 586 | ||||
| -rw-r--r-- | ext/socket/lib/socket.rb | 187 | ||||
| -rw-r--r-- | ext/socket/raddrinfo.c | 226 | ||||
| -rw-r--r-- | ext/socket/rubysocket.h | 39 | ||||
| -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 | 60 | ||||
| -rw-r--r-- | ext/socket/udpsocket.c | 6 | ||||
| -rw-r--r-- | ext/socket/unixsocket.c | 3 |
16 files changed, 846 insertions, 477 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..6091385561 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,9 @@ 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); + rb_raise(rb_eIOTimeoutError, "user specified timeout for %" PRIsVALUE, addr_str); } int revents = RB_NUM2INT(result); @@ -603,7 +605,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 +788,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 +814,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 f3c00b518d..e1943b8496 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_exc_raise(rb_exc_new_str(rb_eIOTimeoutError, 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)"; } } @@ -172,8 +212,16 @@ init_inetsock_internal(VALUE v) #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, VALUE _fast_fallback, VALUE _test_mode_settings) +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; @@ -186,6 +234,7 @@ 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); } @@ -209,6 +258,21 @@ is_specified_ip_address(const char *hostname) 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; @@ -221,46 +285,34 @@ struct fast_fallback_inetsock_arg int type; VALUE resolv_timeout; VALUE connect_timeout; + VALUE open_timeout; const char *hostp, *portp; int *families; int family_size; int additional_flags; - int cancelled; - rb_nativethread_lock_t *lock; 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(void) +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) + sizeof(struct fast_fallback_getaddrinfo_shared) + (family_size == 1 ? 0 : 2) * sizeof(struct fast_fallback_getaddrinfo_entry) ); return shared; } -static struct fast_fallback_getaddrinfo_entry * -allocate_fast_fallback_getaddrinfo_entry(void) -{ - struct fast_fallback_getaddrinfo_entry *entry; - - entry = (struct fast_fallback_getaddrinfo_entry *)calloc( - 1, - sizeof(struct fast_fallback_getaddrinfo_entry) - ); - - return entry; -} - static void allocate_fast_fallback_getaddrinfo_hints(struct addrinfo *hints, int family, int remote_addrinfo_hints, int additional_flags) { @@ -282,55 +334,21 @@ allocate_connection_attempt_fds(int additional_capacity) } static int -reallocate_connection_attempt_fds(int *fds, int current_capacity, int additional_capacity) +reallocate_connection_attempt_fds(int **fds, int current_capacity, int additional_capacity) { int new_capacity = current_capacity + additional_capacity; + int *new_fds; - if (realloc(fds, new_capacity * sizeof(int)) == NULL) { + 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; + for (int i = current_capacity; i < new_capacity; i++) (*fds)[i] = -1; return new_capacity; } -struct wait_fast_fallback_arg -{ - int status, nfds; - fd_set *readfds, *writefds; - struct timeval *delay; - int *cancelled; -}; - -static void * -wait_fast_fallback(void *ptr) -{ - struct wait_fast_fallback_arg *arg = (struct wait_fast_fallback_arg *)ptr; - int status; - status = select(arg->nfds, arg->readfds, arg->writefds, NULL, arg->delay); - arg->status = status; - if (errno == EINTR) *arg->cancelled = true; - return 0; -} - -static void -cancel_fast_fallback(void *ptr) -{ - if (!ptr) return; - - struct fast_fallback_getaddrinfo_shared *arg = (struct fast_fallback_getaddrinfo_shared *)ptr; - - rb_nativethread_lock_lock(arg->lock); - { - *arg->cancelled = true; - char notification = SELECT_CANCELLED; - if ((write(arg->notify, ¬ification, 1)) < 0) { - rb_syserr_fail(errno, "write(2)"); - } - } - rb_nativethread_lock_unlock(arg->lock); -} - struct hostname_resolution_result { struct addrinfo *ai; @@ -342,7 +360,7 @@ struct hostname_resolution_store { struct hostname_resolution_result v6; struct hostname_resolution_result v4; - int is_all_finised; + int is_all_finished; }; static int @@ -427,12 +445,22 @@ select_expires_at( 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_connect_timeout_at, + struct timeval *user_specified_open_timeout_at) +{ if (any_addrinfos(resolution_store)) { - return resolution_delay ? resolution_delay : connection_attempt_delay; + 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) { @@ -521,7 +549,8 @@ in_progress_fds(int fds_size) } static void -remove_connection_attempt_fd(int *fds, int *fds_size, int removing_fd) { +remove_connection_attempt_fd(int *fds, int *fds_size, int removing_fd) +{ int i, j; for (i = 0; i < *fds_size; i++) { @@ -550,6 +579,7 @@ init_fast_fallback_inetsock_internal(VALUE 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; @@ -565,19 +595,16 @@ init_fast_fallback_inetsock_internal(VALUE v) pthread_t threads[arg->family_size]; char resolved_type[2]; ssize_t resolved_type_size; - int hostname_resolution_waiter = 0, hostname_resolution_notifier = 0; + int hostname_resolution_waiter = -1, hostname_resolution_notifier = -1; int pipefd[2]; - fd_set readfds, writefds; - struct wait_fast_fallback_arg wait_arg; + int nfds = 0; struct timeval *ends_at = NULL; struct timeval delay = (struct timeval){ -1, -1 }; - wait_arg.nfds = 0; - wait_arg.writefds = NULL; - wait_arg.status = 0; + struct timeval *delay_p = NULL; struct hostname_resolution_store resolution_store; - resolution_store.is_all_finised = false; + resolution_store.is_all_finished = false; resolution_store.v6.ai = NULL; resolution_store.v6.finished = false; resolution_store.v6.has_error = false; @@ -586,7 +613,6 @@ init_fast_fallback_inetsock_internal(VALUE v) 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); @@ -600,17 +626,32 @@ init_fast_fallback_inetsock_internal(VALUE v) 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 + 0, + t ); if (family == AF_INET6) { @@ -622,8 +663,7 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.v4.finished = true; resolution_store.v6.finished = true; } - resolution_store.is_all_finised = true; - wait_arg.readfds = NULL; + resolution_store.is_all_finished = true; } else { if (pipe(pipefd) != 0) rb_syserr_fail(errno, "pipe(2)"); hostname_resolution_waiter = pipefd[0]; @@ -632,30 +672,21 @@ init_fast_fallback_inetsock_internal(VALUE v) 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]; - wait_arg.readfds = &readfds; - arg->getaddrinfo_shared = allocate_fast_fallback_getaddrinfo_shared(); + arg->getaddrinfo_shared = allocate_fast_fallback_getaddrinfo_shared(arg->family_size); if (!arg->getaddrinfo_shared) rb_syserr_fail(errno, "calloc(3)"); - arg->getaddrinfo_shared->lock = calloc(1, sizeof(rb_nativethread_lock_t)); - if (!arg->getaddrinfo_shared->lock) rb_syserr_fail(errno, "calloc(3)"); - rb_nativethread_lock_initialize(arg->getaddrinfo_shared->lock); + rb_nativethread_lock_initialize(&arg->getaddrinfo_shared->lock); + arg->getaddrinfo_shared->notify = hostname_resolution_notifier; - arg->getaddrinfo_shared->node = arg->hostp ? strdup(arg->hostp) : NULL; - arg->getaddrinfo_shared->service = strdup(arg->portp); + 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; - arg->getaddrinfo_shared->notify = hostname_resolution_notifier; - arg->getaddrinfo_shared->wait = hostname_resolution_waiter; - arg->getaddrinfo_shared->connection_attempt_fds = arg->connection_attempt_fds; - arg->getaddrinfo_shared->connection_attempt_fds_size = arg->connection_attempt_fds_size; - arg->getaddrinfo_shared->cancelled = &arg->cancelled; - wait_arg.cancelled = &arg->cancelled; for (int i = 0; i < arg->family_size; i++) { - arg->getaddrinfo_entries[i] = allocate_fast_fallback_getaddrinfo_entry(); - if (!(arg->getaddrinfo_entries[i])) rb_syserr_fail(errno, "calloc(3)"); + 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]; @@ -695,10 +726,9 @@ init_fast_fallback_inetsock_internal(VALUE v) } } - if (raddrinfo_pthread_create(&threads[i], do_fast_fallback_getaddrinfo, arg->getaddrinfo_entries[i]) != 0) { + 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); } - pthread_detach(threads[i]); } if (NIL_P(resolv_timeout)) { @@ -722,7 +752,7 @@ init_fast_fallback_inetsock_internal(VALUE v) 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_finised) break; + if (resolution_store.is_all_finished) break; if (local_status < 0) { host = arg->local.host; @@ -748,7 +778,7 @@ init_fast_fallback_inetsock_internal(VALUE v) if (!local_ai) { if (any_addrinfos(&resolution_store)) continue; if (in_progress_fds(arg->connection_attempt_fds_size)) break; - if (!resolution_store.is_all_finised) break; + if (!resolution_store.is_all_finished) break; /* Use a different family local address if no choice, this * will cause EAFNOSUPPORT. */ @@ -765,7 +795,7 @@ init_fast_fallback_inetsock_internal(VALUE v) if (any_addrinfos(&resolution_store)) continue; if (in_progress_fds(arg->connection_attempt_fds_size)) break; - if (!resolution_store.is_all_finised) break; + if (!resolution_store.is_all_finished) break; if (local_status < 0) { host = arg->local.host; @@ -801,7 +831,7 @@ init_fast_fallback_inetsock_internal(VALUE v) if (any_addrinfos(&resolution_store)) continue; if (in_progress_fds(arg->connection_attempt_fds_size)) break; - if (!resolution_store.is_all_finised) break; + if (!resolution_store.is_all_finished) break; if (local_status < 0) { host = arg->local.host; @@ -822,19 +852,31 @@ init_fast_fallback_inetsock_internal(VALUE v) if (any_addrinfos(&resolution_store) || in_progress_fds(arg->connection_attempt_fds_size) || - !resolution_store.is_all_finised) { + !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 { - 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; + 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); } - VALUE 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); } @@ -847,14 +889,13 @@ init_fast_fallback_inetsock_internal(VALUE v) if (errno == EINPROGRESS) { if (current_capacity == arg->connection_attempt_fds_size) { current_capacity = reallocate_connection_attempt_fds( - arg->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)++; - wait_arg.writefds = &writefds; set_timeout_tv(&connection_attempt_delay_strage, 250, now); connection_attempt_delay_expires_at = &connection_attempt_delay_strage; @@ -883,7 +924,7 @@ init_fast_fallback_inetsock_internal(VALUE v) if (any_addrinfos(&resolution_store)) continue; if (in_progress_fds(arg->connection_attempt_fds_size)) break; - if (!resolution_store.is_all_finised) break; + if (!resolution_store.is_all_finished) break; if (local_status < 0) { host = arg->local.host; @@ -907,47 +948,51 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_delay_expires_at, connection_attempt_delay_expires_at, user_specified_resolv_timeout_at, - user_specified_connect_timeout_at + user_specified_connect_timeout_at, + user_specified_open_timeout_at ); if (ends_at) { delay = tv_to_timeout(ends_at, now); - wait_arg.delay = &delay; + delay_p = &delay; } else { - wait_arg.delay = NULL; + 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; + } } - if (arg->connection_attempt_fds_size) { - FD_ZERO(wait_arg.writefds); + 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; - FD_SET(cfd, wait_arg.writefds); + rb_fd_set(cfd, &arg->writefds); } if (n > 0) n++; - wait_arg.nfds = n; - } else { - wait_arg.writefds = NULL; + nfds = n; } - FD_ZERO(wait_arg.readfds); - FD_SET(hostname_resolution_waiter, wait_arg.readfds); - if ((hostname_resolution_waiter + 1) > wait_arg.nfds) { - wait_arg.nfds = hostname_resolution_waiter + 1; - } + rb_fd_zero(&arg->readfds); + if (arg->family_size > 1) { + rb_fd_set(hostname_resolution_waiter, &arg->readfds); - rb_thread_call_without_gvl2( - wait_fast_fallback, - &wait_arg, - cancel_fast_fallback, - arg->getaddrinfo_shared - ); - rb_thread_check_ints(); - if (errno == EINTR || arg->cancelled) break; + if ((hostname_resolution_waiter + 1) > nfds) { + nfds = hostname_resolution_waiter + 1; + } + } - status = wait_arg.status; - syscall = "select(2)"; + 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)) { @@ -961,14 +1006,39 @@ init_fast_fallback_inetsock_internal(VALUE v) if (status > 0) { /* check for connection */ - for (int i = 0; i < arg->connection_attempt_fds_size; i++) { - int fd = arg->connection_attempt_fds[i]; - if (fd < 0 || !FD_ISSET(fd, wait_arg.writefds)) continue; + 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); - int err; - socklen_t len = sizeof(err); + 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 (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) == 0) { if (err == 0) { /* success */ remove_connection_attempt_fd( arg->connection_attempt_fds, @@ -977,47 +1047,42 @@ init_fast_fallback_inetsock_internal(VALUE v) ); connected_fd = fd; break; - }; - - /* fail */ - errno = err; - close(fd); - remove_connection_attempt_fd( - arg->connection_attempt_fds, - &arg->connection_attempt_fds_size, - fd - ); - continue; + } 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; - last_error.type = SYSCALL_ERROR; - last_error.ecode = errno; + if (connected_fd >= 0) break; - if (any_addrinfos(&resolution_store) || - in_progress_fds(arg->connection_attempt_fds_size) || - !resolution_store.is_all_finised) { 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; } - } else { - 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); - } } /* check for hostname resolution */ - if (!resolution_store.is_all_finised && FD_ISSET(hostname_resolution_waiter, wait_arg.readfds)) { + if (!resolution_store.is_all_finished && rb_fd_isset(hostname_resolution_waiter, &arg->readfds)) { while (true) { resolved_type_size = read( hostname_resolution_waiter, @@ -1033,15 +1098,17 @@ init_fast_fallback_inetsock_internal(VALUE v) if (arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err && arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err != EAI_ADDRFAMILY) { - last_error.type = RESOLUTION_ERROR; - last_error.ecode = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->err; - syscall = "getaddrinfo(3)"; + 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_finised = true; + resolution_store.is_all_finished = true; resolution_delay_expires_at = NULL; user_specified_resolv_timeout_at = NULL; break; @@ -1050,16 +1117,18 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.v4.finished = true; if (arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err) { - last_error.type = RESOLUTION_ERROR; - last_error.ecode = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->err; - syscall = "getaddrinfo(3)"; + 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_finised = true; + resolution_store.is_all_finished = true; resolution_delay_expires_at = NULL; user_specified_resolv_timeout_at = NULL; break; @@ -1083,12 +1152,63 @@ init_fast_fallback_inetsock_internal(VALUE v) } } - status = wait_arg.status = 0; + 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_finised) { + resolution_store.is_all_finished) { if (local_status < 0) { host = arg->local.host; serv = arg->local.serv; @@ -1104,46 +1224,14 @@ init_fast_fallback_inetsock_internal(VALUE v) } if ((is_timeout_tv(user_specified_resolv_timeout_at, now) || - resolution_store.is_all_finised) && + resolution_store.is_all_finished) && (is_timeout_tv(user_specified_connect_timeout_at, now) || !in_progress_fds(arg->connection_attempt_fds_size))) { - VALUE errno_module = rb_const_get(rb_cObject, rb_intern("Errno")); - VALUE etimedout_error = rb_const_get(errno_module, rb_intern("ETIMEDOUT")); - rb_raise(etimedout_error, "user specified timeout"); - } - } - - if (!resolution_store.is_all_finised) { - if (!resolution_store.v6.finished && arg->getaddrinfo_entries[IPV6_ENTRY_POS]->has_syserr) { - resolution_store.v6.ai = arg->getaddrinfo_entries[IPV6_ENTRY_POS]->ai; - resolution_store.v6.finished = true; - - if (resolution_store.v4.finished) { - resolution_store.is_all_finised = true; - wait_arg.readfds = NULL; - 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.ai = arg->getaddrinfo_entries[IPV4_ENTRY_POS]->ai; - resolution_store.v4.finished = true; - - if (resolution_store.v6.finished) { - resolution_store.is_all_finised = true; - wait_arg.readfds = NULL; - 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; - } + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); } } } - rb_thread_check_ints(); - if (NIL_P(arg->io)) { /* create new instance */ arg->io = rsock_init_sock(arg->self, connected_fd); @@ -1167,27 +1255,40 @@ fast_fallback_inetsock_cleanup(VALUE v) 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; - int need_free[2] = { 0, 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); + rb_nativethread_lock_lock(&getaddrinfo_shared->lock); { for (int i = 0; i < arg->family_size; i++) { - if (arg->getaddrinfo_entries[i] && --(arg->getaddrinfo_entries[i]->refcount) == 0) { - need_free[i] = 1; + 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); + rb_nativethread_lock_unlock(&getaddrinfo_shared->lock); for (int i = 0; i < arg->family_size; i++) { - if (need_free[i]) free_fast_fallback_getaddrinfo_entry(&arg->getaddrinfo_entries[i]); + if (ais[i]) freeaddrinfo(ais[i]); + } + if (getaddrinfo_shared && shared_need_free) { + free_fast_fallback_getaddrinfo_shared(&getaddrinfo_shared); } - if (shared_need_free) free_fast_fallback_getaddrinfo_shared(&getaddrinfo_shared); } int connection_attempt_fd; @@ -1204,6 +1305,9 @@ fast_fallback_inetsock_cleanup(VALUE v) } } + 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; @@ -1213,17 +1317,27 @@ fast_fallback_inetsock_cleanup(VALUE v) } 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 fast_fallback, VALUE test_mode_settings) +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; - char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV]; + char *hostp, *portp, *local_portp; + char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV], local_pbuf[NI_MAXSERV]; int additional_flags = 0; - hostp = host_str(remote_host, hbuf, sizeof(hbuf), &additional_flags); - portp = port_str(remote_serv, pbuf, sizeof(pbuf), &additional_flags); + 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)) { + if (!is_specified_ip_address(hostp) && !is_local_port_fixed(local_portp)) { int target_families[2] = { 0, 0 }; int resolving_family_size = 0; @@ -1231,16 +1345,18 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca * 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 + 0, + t ); struct addrinfo *tmp_p = local_res->ai; - for (tmp_p; tmp_p != NULL; tmp_p = tmp_p->ai_next) { + 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++; @@ -1270,10 +1386,10 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca 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; - fast_fallback_arg.cancelled = false; int resolving_families[resolving_family_size]; int resolving_family_index = 0; @@ -1287,6 +1403,9 @@ rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE loca 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); } @@ -1304,6 +1423,7 @@ 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); @@ -1489,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..465b74964f 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,39 +904,47 @@ 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 ensure - hostname_resolution_threads.each do |thread| - thread.exit - end + hostname_resolution_threads.each(&:exit).each(&:join) hostname_resolution_result&.close - connecting_sockets.each_key do |connecting_socket| - connecting_socket.close - end + connecting_sockets.each_key(&:close) 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 +952,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 +983,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 +1058,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 +1108,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 +1121,6 @@ class Socket < BasicSocket end private_constant :HostnameResolutionStore - # :stopdoc: def self.ip_sockets_port0(ai_list, reuseaddr) sockets = [] begin @@ -1135,9 +1153,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 +1581,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 +1632,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 +1667,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 a7cf1211ba..f1fbcc35de 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; @@ -473,13 +499,49 @@ 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,16 +552,16 @@ 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; } @@ -511,7 +573,6 @@ start: 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,12 @@ start: if (need_free) free_getaddrinfo_arg(arg); + if (timedout) { + 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 +620,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 +631,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 +661,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 +791,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; @@ -732,7 +812,6 @@ start: errno = err; return EAI_SYSTEM; } - pthread_detach(th); rb_thread_call_without_gvl2(wait_getnameinfo, arg, cancel_getnameinfo, arg); @@ -823,7 +902,7 @@ str_is_number(const char *p) rb_strlen_lit(name) == (len) && memcmp(ptr, name, len) == 0) char* -host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr) +raddrinfo_host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr) { if (NIL_P(host)) { return NULL; @@ -862,7 +941,7 @@ host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr) } char* -port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr) +raddrinfo_port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr) { if (NIL_P(port)) { return 0; @@ -914,7 +993,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 +1020,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 +1029,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 +1055,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 +1089,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 +1097,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 +1217,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 +1291,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 +1329,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 +1341,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 +1355,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 +1380,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 +1390,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 +1404,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 +1516,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 +1532,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 +1636,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 +1648,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 +1661,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 +2250,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 +3018,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; } @@ -3029,34 +3110,27 @@ rsock_io_socket_addrinfo(VALUE io, struct sockaddr *addr, socklen_t len) void free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shared **shared) { - free((*shared)->node); + xfree((*shared)->node); (*shared)->node = NULL; - free((*shared)->service); + xfree((*shared)->service); (*shared)->service = NULL; - close((*shared)->notify); - close((*shared)->wait); - rb_nativethread_lock_destroy((*shared)->lock); + rb_nativethread_lock_destroy(&(*shared)->lock); free(*shared); *shared = NULL; } -void -free_fast_fallback_getaddrinfo_entry(struct fast_fallback_getaddrinfo_entry **entry) -{ - if ((*entry)->ai) { - freeaddrinfo((*entry)->ai); - (*entry)->ai = NULL; - } - free(*entry); - *entry = NULL; -} - -void * +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, need_free = 0, shared_need_free = 0; + 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); @@ -3090,31 +3164,25 @@ do_fast_fallback_getaddrinfo(void *ptr) } } - rb_nativethread_lock_lock(shared->lock); + rb_nativethread_lock_lock(&shared->lock); { entry->err = err; - if (*shared->cancelled) { - if (entry->ai) { - freeaddrinfo(entry->ai); - entry->ai = NULL; - } - } else { - const char notification = entry->family == AF_INET6 ? - IPV6_HOSTNAME_RESOLVED : IPV4_HOSTNAME_RESOLVED; + const char notification = entry->family == AF_INET6 ? + IPV6_HOSTNAME_RESOLVED : IPV4_HOSTNAME_RESOLVED; - if ((write(shared->notify, ¬ification, strlen(¬ification))) < 0) { - entry->err = errno; - entry->has_syserr = true; - } + 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 (--(entry->refcount) == 0) need_free = 1; if (--(shared->refcount) == 0) shared_need_free = 1; } - rb_nativethread_lock_unlock(shared->lock); + rb_nativethread_lock_unlock(&shared->lock); - if (need_free && entry) { - free_fast_fallback_getaddrinfo_entry(&entry); - } + if (ai) freeaddrinfo(ai); if (shared_need_free && shared) { free_fast_fallback_getaddrinfo_shared(&shared); } @@ -3122,6 +3190,12 @@ do_fast_fallback_getaddrinfo(void *ptr) return 0; } +void * +fork_safe_do_fast_fallback_getaddrinfo(void *ptr) +{ + return rb_thread_prevent_fork(do_fast_fallback_getaddrinfo, ptr); +} + #endif /* diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 97f3bc55d7..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 fast_fallback, VALUE test_mode_settings); +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,8 +414,8 @@ 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 *host_str(VALUE host, char *hbuf, size_t hbuflen, int *flags_ptr); -char *port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr); +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__) @@ -426,14 +427,6 @@ char *port_str(VALUE port, char *pbuf, size_t pbuflen, int *flags_ptr); # define IPV4_HOSTNAME_RESOLVED '2' # define SELECT_CANCELLED '3' -struct fast_fallback_getaddrinfo_shared -{ - int wait, notify, refcount, connection_attempt_fds_size; - int *connection_attempt_fds, *cancelled; - char *node, *service; - rb_nativethread_lock_t *lock; -}; - struct fast_fallback_getaddrinfo_entry { int family, err, refcount; @@ -445,13 +438,23 @@ struct fast_fallback_getaddrinfo_entry 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 *do_fast_fallback_getaddrinfo(void *ptr); +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); @@ -500,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 1031812bef..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, Qfalse, 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 8206fe46a9..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, Qfalse, 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 3c03e89e0d..7ce536e0af 100644 --- a/ext/socket/tcpsocket.c +++ b/ext/socket/tcpsocket.c @@ -12,7 +12,7 @@ /* * call-seq: - * TCPSocket.new(remote_host, remote_port, local_host=nil, local_port=nil, resolv_timeout: nil, connect_timeout: nil, fast_fallback: true) + * 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 @@ -22,33 +22,21 @@ * 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+. + * 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. * - * Happy Eyeballs Version 2 is not provided on Windows, + * 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). - * - * === 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 except on Windows: - * - * 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. */ static VALUE tcp_init(int argc, VALUE *argv, VALUE sock) @@ -56,35 +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[4]; - VALUE kwargs[4]; + static ID keyword_ids[5]; + VALUE kwargs[5]; VALUE resolv_timeout = Qnil; VALUE connect_timeout = Qnil; - VALUE fast_fallback = Qtrue; + 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], "fast_fallback"); - CONST_ID(keyword_ids[3], "test_mode_settings"); + 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, 4, 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) { fast_fallback = kwargs[2]; } - if (kwargs[3] != Qundef) { test_mode_settings = kwargs[3]; } + 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, fast_fallback, - test_mode_settings); + resolv_timeout, connect_timeout, open_timeout, + fast_fallback, test_mode_settings); } static VALUE @@ -117,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 |
