diff options
-rw-r--r-- | ext/socket/lib/socket.rb | 512 | ||||
-rw-r--r-- | ext/socket/mkconstants.rb | 1 | ||||
-rw-r--r-- | ext/socket/rubysocket.h | 1 | ||||
-rw-r--r-- | include/ruby/win32.h | 1 | ||||
-rw-r--r-- | test/ruby/test_complex.rb | 2 | ||||
-rw-r--r-- | test/socket/test_socket.rb | 239 |
6 files changed, 3 insertions, 753 deletions
diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index e953077fe6..de542223c6 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -599,30 +599,6 @@ class Socket < BasicSocket __accept_nonblock(exception) end - RESOLUTION_DELAY = 0.05 - private_constant :RESOLUTION_DELAY - - CONNECTION_ATTEMPT_DELAY = 0.25 - private_constant :CONNECTION_ATTEMPT_DELAY - - ADDRESS_FAMILIES = { - ipv6: Socket::AF_INET6, - ipv4: Socket::AF_INET - }.freeze - private_constant :ADDRESS_FAMILIES - - HOSTNAME_RESOLUTION_QUEUE_UPDATED = 0 - private_constant :HOSTNAME_RESOLUTION_QUEUE_UPDATED - - IPV6_ADRESS_FORMAT = /(?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}|:(?:[0-9A-F]{1,4}:){1,5}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){5}(?::[0-9A-F]{1,4}::[0-9A-F]{1,4}|:(?:[0-9A-F]{1,4}:){1,4}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){4}(?::[0-9A-F]{1,4}::[0-9A-F]{1,4}|:(?:[0-9A-F]{1,4}:){1,3}[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){3}(?::[0-9A-F]{1,4}::[0-9A-F]{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}::[0-9A-F]{1,4}|:(?:[0-9A-F]{1,4}:)[0-9A-F]{1,4}|:)|(?:[0-9A-F]{1,4}:){1}(?::[0-9A-F]{1,4}::[0-9A-F]{1,4}|::(?:[0-9A-F]{1,4}:){1,5}[0-9A-F]{1,4}|:)|::(?:[0-9A-F]{1,4}::[0-9A-F]{1,4}|:(?:[0-9A-F]{1,4}:){1,6}[0-9A-F]{1,4}|:))(?:%.+)?/ - private_constant :IPV6_ADRESS_FORMAT - - @tcp_fast_fallback = true - - class << self - attr_accessor :tcp_fast_fallback - end - # :call-seq: # Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) {|socket| ... } # Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) @@ -648,491 +624,8 @@ class Socket < BasicSocket # sock.close_write # puts sock.read # } - def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, fast_fallback: tcp_fast_fallback, &block) # :yield: socket - unless fast_fallback - return tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, &block) - end - - # Happy Eyeballs' states - # - :start - # - :v6c - # - :v4w - # - :v4c - # - :v46c - # - :v46w - # - :success - # - :failure - # - :timeout - - specified_family_name = nil - hostname_resolution_threads = [] - hostname_resolution_queue = nil - hostname_resolution_waiting = nil - hostname_resolution_expires_at = nil - selectable_addrinfos = SelectableAddrinfos.new - connecting_sockets = ConnectingSockets.new - local_addrinfos = [] - connection_attempt_delay_expires_at = nil - connection_attempt_started_at = nil - state = :start - connected_socket = nil - last_error = nil - is_windows_environment ||= (RUBY_PLATFORM =~ /mswin|mingw|cygwin/) - - ret = loop do - case state - when :start - specified_family_name, next_state = host && specified_family_name_and_next_state(host) - - if local_host && local_port - specified_family_name, next_state = specified_family_name_and_next_state(local_host) unless specified_family_name - local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, ADDRESS_FAMILIES[specified_family_name], :STREAM, timeout: resolv_timeout) - end - - if specified_family_name - addrinfos = Addrinfo.getaddrinfo(host, port, ADDRESS_FAMILIES[specified_family_name], :STREAM, timeout: resolv_timeout) - selectable_addrinfos.add(specified_family_name, addrinfos) - hostname_resolution_queue = NoHostnameResolutionQueue.new - state = next_state - next - end - - resolving_family_names = ADDRESS_FAMILIES.keys - hostname_resolution_queue = HostnameResolutionQueue.new(resolving_family_names.size) - hostname_resolution_waiting = hostname_resolution_queue.waiting_pipe - hostname_resolution_started_at = current_clocktime if resolv_timeout - hostname_resolution_args = [host, port, hostname_resolution_queue] - - hostname_resolution_threads.concat( - resolving_family_names.map { |family| - thread_args = [family, *hostname_resolution_args] - thread = Thread.new(*thread_args) { |*thread_args| hostname_resolution(*thread_args) } - Thread.pass - thread - } - ) - - hostname_resolution_retry_count = resolving_family_names.size - 1 - hostname_resolution_expires_at = hostname_resolution_started_at + resolv_timeout if resolv_timeout - - while hostname_resolution_retry_count >= 0 - remaining = resolv_timeout ? second_to_timeout(hostname_resolution_started_at + resolv_timeout) : nil - hostname_resolved, _, = IO.select(hostname_resolution_waiting, nil, nil, remaining) - - unless hostname_resolved - state = :timeout - break - end - - family_name, res = hostname_resolution_queue.get - - if res.is_a? Exception - unless (Socket.const_defined?(:EAI_ADDRFAMILY)) && - (res.is_a?(Socket::ResolutionError)) && - (res.error_code == Socket::EAI_ADDRFAMILY) - last_error = res - end - - if hostname_resolution_retry_count.zero? - state = :failure - break - end - hostname_resolution_retry_count -= 1 - else - state = case family_name - when :ipv6 then :v6c - when :ipv4 then hostname_resolution_queue.closed? ? :v4c : :v4w - end - selectable_addrinfos.add(family_name, res) - last_error = nil - break - end - end - - next - when :v4w - ipv6_resolved, _, = IO.select(hostname_resolution_waiting, nil, nil, RESOLUTION_DELAY) - - if ipv6_resolved - family_name, res = hostname_resolution_queue.get - selectable_addrinfos.add(family_name, res) unless res.is_a? Exception - state = :v46c - else - state = :v4c - end - - next - when :v4c, :v6c, :v46c - connection_attempt_started_at = current_clocktime unless connection_attempt_started_at - addrinfo = selectable_addrinfos.get - - if local_addrinfos.any? - local_addrinfo = local_addrinfos.find { |lai| lai.afamily == addrinfo.afamily } - - if local_addrinfo.nil? - if selectable_addrinfos.empty? && connecting_sockets.empty? && hostname_resolution_queue.closed? - last_error = SocketError.new 'no appropriate local address' - state = :failure - elsif selectable_addrinfos.any? - # Try other Addrinfo in next loop - else - if resolv_timeout && hostname_resolution_queue.opened? && - (current_clocktime >= hostname_resolution_expires_at) - state = :timeout - else - # Wait for connection to be established or hostname resolution in next loop - connection_attempt_delay_expires_at = nil - state = :v46w - end - end - next - end - end - - connection_attempt_delay_expires_at = current_clocktime + CONNECTION_ATTEMPT_DELAY - - begin - result = if specified_family_name && selectable_addrinfos.empty? && - connecting_sockets.empty? && hostname_resolution_queue.closed? - local_addrinfo ? - addrinfo.connect_from(local_addrinfo, timeout: connect_timeout) : - addrinfo.connect(timeout: connect_timeout) - else - socket = Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol) - socket.bind(local_addrinfo) if local_addrinfo - socket.connect_nonblock(addrinfo, exception: false) - end - - case result - when 0 - connected_socket = socket - state = :success - when Socket - connected_socket = result - state = :success - when :wait_writable - connecting_sockets.add(socket, addrinfo) - state = :v46w - end - rescue SystemCallError => e - last_error = e - socket.close if socket && !socket.closed? - - if selectable_addrinfos.empty? && connecting_sockets.empty? && hostname_resolution_queue.closed? - state = :failure - elsif selectable_addrinfos.any? - # Try other Addrinfo in next loop - else - if resolv_timeout && hostname_resolution_queue.opened? && - (current_clocktime >= hostname_resolution_expires_at) - state = :timeout - else - # Wait for connection to be established or hostname resolution in next loop - connection_attempt_delay_expires_at = nil - state = :v46w - end - end - end - - next - when :v46w - if connect_timeout && second_to_timeout(connection_attempt_started_at + connect_timeout).zero? - state = :timeout - next - end - - remaining = second_to_timeout(connection_attempt_delay_expires_at) - hostname_resolution_waiting = hostname_resolution_waiting && hostname_resolution_queue.closed? ? nil : hostname_resolution_waiting - hostname_resolved, connectable_sockets, = IO.select(hostname_resolution_waiting, connecting_sockets.all, nil, remaining) - - if connectable_sockets&.any? - while (connectable_socket = connectable_sockets.pop) - is_connected = - if is_windows_environment - sockopt = connectable_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_CONNECT_TIME) - sockopt.unpack('i').first >= 0 - else - sockopt = connectable_socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR) - sockopt.int.zero? - end - - if is_connected - connected_socket = connectable_socket - connecting_sockets.delete connectable_socket - connectable_sockets.each do |other_connectable_socket| - other_connectable_socket.close unless other_connectable_socket.closed? - end - state = :success - break - else - failed_ai = connecting_sockets.delete connectable_socket - inspected_ip_address = failed_ai.ipv6? ? "[#{failed_ai.ip_address}]" : failed_ai.ip_address - last_error = SystemCallError.new("connect(2) for #{inspected_ip_address}:#{failed_ai.ip_port}", sockopt.int) - connectable_socket.close unless connectable_socket.closed? - - next if connectable_sockets.any? - - if selectable_addrinfos.empty? && connecting_sockets.empty? && hostname_resolution_queue.closed? - state = :failure - elsif selectable_addrinfos.any? - # Wait for connection attempt delay timeout in next loop - else - if resolv_timeout && hostname_resolution_queue.opened? && - (current_clocktime >= hostname_resolution_expires_at) - state = :timeout - else - # Wait for connection to be established or hostname resolution in next loop - connection_attempt_delay_expires_at = nil - end - end - end - end - elsif hostname_resolved&.any? - family_name, res = hostname_resolution_queue.get - selectable_addrinfos.add(family_name, res) unless res.is_a? Exception - else - if selectable_addrinfos.empty? && connecting_sockets.empty? && hostname_resolution_queue.closed? - state = :failure - elsif selectable_addrinfos.any? - # Try other Addrinfo in next loop - state = :v46c - else - if resolv_timeout && hostname_resolution_queue.opened? && - (current_clocktime >= hostname_resolution_expires_at) - state = :timeout - else - # Wait for connection to be established or hostname resolution in next loop - connection_attempt_delay_expires_at = nil - end - end - end - - next - when :success - break connected_socket - when :failure - raise last_error - when :timeout - raise Errno::ETIMEDOUT, 'user specified timeout' - end - end - - if block_given? - begin - yield ret - ensure - ret.close - end - else - ret - end - ensure - if fast_fallback - hostname_resolution_threads.each do |thread| - thread&.exit - end - - hostname_resolution_queue&.close_all - - connecting_sockets.each do |connecting_socket| - connecting_socket.close unless connecting_socket.closed? - end - end - end - - def self.specified_family_name_and_next_state(hostname) - if hostname.match?(IPV6_ADRESS_FORMAT) then [:ipv6, :v6c] - elsif hostname.match?(/^([0-9]{1,3}\.){3}[0-9]{1,3}$/) then [:ipv4, :v4c] - end - end - private_class_method :specified_family_name_and_next_state - - def self.hostname_resolution(family, host, port, hostname_resolution_queue) - begin - resolved_addrinfos = Addrinfo.getaddrinfo(host, port, ADDRESS_FAMILIES[family], :STREAM) - hostname_resolution_queue.add_resolved(family, resolved_addrinfos) - rescue => e - hostname_resolution_queue.add_error(family, e) - end - end - private_class_method :hostname_resolution - - def self.second_to_timeout(ends_at) - return 0 unless ends_at - - remaining = (ends_at - current_clocktime) - remaining.negative? ? 0 : remaining - end - private_class_method :second_to_timeout - - def self.current_clocktime - Process.clock_gettime(Process::CLOCK_MONOTONIC) - end - private_class_method :current_clocktime - - class SelectableAddrinfos - PRIORITY_ON_V6 = [:ipv6, :ipv4] - PRIORITY_ON_V4 = [:ipv4, :ipv6] - - def initialize - @addrinfo_dict = {} - @last_family = nil - end - - def add(family_name, addrinfos) - @addrinfo_dict[family_name] = addrinfos - end - - def get - return nil if empty? - - if @addrinfo_dict.size == 1 - @addrinfo_dict.each { |_, addrinfos| return addrinfos.shift } - end - - precedences = case @last_family - when :ipv4, nil then PRIORITY_ON_V6 - when :ipv6 then PRIORITY_ON_V4 - end - - precedences.each do |family_name| - addrinfo = @addrinfo_dict[family_name]&.shift - next unless addrinfo - - @last_family = family_name - return addrinfo - end - end - - def empty? - @addrinfo_dict.all? { |_, addrinfos| addrinfos.empty? } - end - - def any? - !empty? - end - end - private_constant :SelectableAddrinfos - - class NoHostnameResolutionQueue - def waiting_pipe - nil - end - - def add_resolved(_, _) - raise StandardError, "This #{self.class} cannot respond to:" - end - - def add_error(_, _) - raise StandardError, "This #{self.class} cannot respond to:" - end - - def get - nil - end - - def opened? - false - end - - def closed? - true - end - - def close_all - # Do nothing - end - end - private_constant :NoHostnameResolutionQueue - - class HostnameResolutionQueue - def initialize(size) - @size = size - @taken_count = 0 - @rpipe, @wpipe = IO.pipe - @queue = Queue.new - @mutex = Mutex.new - end - - def waiting_pipe - [@rpipe] - end - - def add_resolved(family, resolved_addrinfos) - @mutex.synchronize do - @queue.push [family, resolved_addrinfos] - @wpipe.putc HOSTNAME_RESOLUTION_QUEUE_UPDATED - end - end - - def add_error(family, error) - @mutex.synchronize do - @queue.push [family, error] - @wpipe.putc HOSTNAME_RESOLUTION_QUEUE_UPDATED - end - end - - def get - return nil if @queue.empty? - - res = nil - - @mutex.synchronize do - @rpipe.getbyte - res = @queue.pop - end - - @taken_count += 1 - close_all if @taken_count == @size - res - end - - def closed? - @rpipe.closed? - end - - def opened? - !closed? - end - - def close_all - @queue.close unless @queue.closed? - @rpipe.close unless @rpipe.closed? - @wpipe.close unless @wpipe.closed? - end - end - private_constant :HostnameResolutionQueue - - class ConnectingSockets - def initialize - @socket_dict = {} - end - - def all - @socket_dict.keys - end - - def add(socket, addrinfo) - @socket_dict[socket] = addrinfo - end - - def delete(socket) - @socket_dict.delete socket - end - - def empty? - @socket_dict.empty? - end - - def each - @socket_dict.keys.each do |socket| - yield socket - end - end - end - private_constant :ConnectingSockets - - def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, &block) + # + def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil) # :yield: socket last_error = nil ret = nil @@ -1176,7 +669,6 @@ class Socket < BasicSocket ret end end - private_class_method :tcp_without_fast_fallback # :stopdoc: def self.ip_sockets_port0(ai_list, reuseaddr) diff --git a/ext/socket/mkconstants.rb b/ext/socket/mkconstants.rb index 04719fff28..9150c3ff67 100644 --- a/ext/socket/mkconstants.rb +++ b/ext/socket/mkconstants.rb @@ -669,7 +669,6 @@ SO_SETFIB nil Set the associated routing table for the socket (FreeBSD SO_RTABLE nil Set the routing table for this socket (OpenBSD) SO_INCOMING_CPU nil Receive the cpu attached to the socket (Linux 3.19) SO_INCOMING_NAPI_ID nil Receive the napi ID attached to a RX queue (Linux 4.12) -SO_CONNECT_TIME nil Returns the number of seconds a socket has been connected. This option is only valid for connection-oriented protocols (Windows) SOPRI_INTERACTIVE nil Interactive socket priority SOPRI_NORMAL nil Normal socket priority diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index f486db4262..283735b12c 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -35,7 +35,6 @@ #ifdef _WIN32 # include <winsock2.h> # include <ws2tcpip.h> -# include <mswsock.h> # include <iphlpapi.h> # if defined(_MSC_VER) # undef HAVE_TYPE_STRUCT_SOCKADDR_DL diff --git a/include/ruby/win32.h b/include/ruby/win32.h index 27a3467606..dfb56f4182 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -35,7 +35,6 @@ extern "C++" { /* template without extern "C++" */ #endif #include <winsock2.h> #include <ws2tcpip.h> -#include <mswsock.h> #if !defined(_MSC_VER) || _MSC_VER >= 1400 #include <iphlpapi.h> #endif diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb index 3a9bad1416..8817a62873 100644 --- a/test/ruby/test_complex.rb +++ b/test/ruby/test_complex.rb @@ -980,7 +980,7 @@ class Complex_Test < Test::Unit::TestCase } end - def assert_complex_with_exception(error, *args, message: nil) + def assert_complex_with_exception(error, *args, message: "") assert_raise(error, message) do Complex(*args, exception: true) end diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb index 6a057e866f..598a05d123 100644 --- a/test/socket/test_socket.rb +++ b/test/socket/test_socket.rb @@ -778,243 +778,4 @@ class TestSocket < Test::Unit::TestCase end end - def test_tcp_socket_v6_hostname_resolved_earlier - opts = %w[-rsocket -W1] - assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}" - - begin; - begin - server = TCPServer.new("::1", 0) - rescue Errno::EADDRNOTAVAIL # IPv6 is not supported - exit - end - - server_thread = Thread.new { server.accept } - port = server.addr[1] - - Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| - case family - when Socket::AF_INET6 then [Addrinfo.tcp("::1", port)] - when Socket::AF_INET then sleep(10); [Addrinfo.tcp("127.0.0.1", port)] - end - end - - socket = Socket.tcp("localhost", port) - assert_true(socket.remote_address.ipv6?) - server_thread.value.close - server.close - socket.close if socket && !socket.closed? - end; - end - - def test_tcp_socket_v4_hostname_resolved_earlier - opts = %w[-rsocket -W1] - assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}" - - begin; - server = TCPServer.new("127.0.0.1", 0) - port = server.addr[1] - - Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| - case family - when Socket::AF_INET6 then sleep(10); [Addrinfo.tcp("::1", port)] - when Socket::AF_INET then [Addrinfo.tcp("127.0.0.1", port)] - end - end - - server_thread = Thread.new { server.accept } - socket = Socket.tcp("localhost", port) - assert_true(socket.remote_address.ipv4?) - server_thread.value.close - server.close - socket.close if socket && !socket.closed? - end; - end - - def test_tcp_socket_v6_hostname_resolved_in_resolution_delay - opts = %w[-rsocket -W1] - assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}" - - begin; - begin - server = TCPServer.new("::1", 0) - rescue Errno::EADDRNOTAVAIL # IPv6 is not supported - exit - end - - port = server.addr[1] - delay_time = 0.025 # Socket::RESOLUTION_DELAY (private) is 0.05 - - Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| - case family - when Socket::AF_INET6 then sleep(delay_time); [Addrinfo.tcp("::1", port)] - when Socket::AF_INET then [Addrinfo.tcp("127.0.0.1", port)] - end - end - - server_thread = Thread.new { server.accept } - socket = Socket.tcp("localhost", port) - assert_true(socket.remote_address.ipv6?) - server_thread.value.close - server.close - socket.close if socket && !socket.closed? - end; - end - - def test_tcp_socket_v6_hostname_resolved_earlier_and_v6_server_is_not_listening - opts = %w[-rsocket -W1] - assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}" - - begin; - ipv4_address = "127.0.0.1" - ipv4_server = Socket.new(Socket::AF_INET, :STREAM) - ipv4_server.bind(Socket.pack_sockaddr_in(0, ipv4_address)) - port = ipv4_server.connect_address.ip_port - - Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| - case family - when Socket::AF_INET6 then [Addrinfo.tcp("::1", port)] - when Socket::AF_INET then sleep(0.001); [Addrinfo.tcp(ipv4_address, port)] - end - end - - ipv4_server_thread = Thread.new { ipv4_server.listen(1); ipv4_server.accept } - socket = Socket.tcp("localhost", port) - assert_equal(ipv4_address, socket.remote_address.ip_address) - - accepted, _ = ipv4_server_thread.value - accepted.close - ipv4_server.close - socket.close if socket && !socket.closed? - end; - end - - def test_tcp_socket_resolv_timeout - opts = %w[-rsocket -W1] - assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}" - - begin; - Addrinfo.define_singleton_method(:getaddrinfo) { |*_| sleep } - port = TCPServer.new("localhost", 0).addr[1] - - assert_raise(Errno::ETIMEDOUT) do - Socket.tcp("localhost", port, resolv_timeout: 0.01) - end - end; - end - - def test_tcp_socket_resolv_timeout_with_connection_failure - opts = %w[-rsocket -W1] - assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}" - - begin; - server = TCPServer.new("127.0.0.1", 12345) - _, port, = server.addr - - Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| - if family == Socket::AF_INET6 - sleep - else - [Addrinfo.tcp("127.0.0.1", port)] - end - end - - server.close - - assert_raise(Errno::ETIMEDOUT) do - Socket.tcp("localhost", port, resolv_timeout: 0.01) - end - end; - end - - def test_tcp_socket_one_hostname_resolution_succeeded_at_least - opts = %w[-rsocket -W1] - assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}" - - begin; - begin - server = TCPServer.new("::1", 0) - rescue Errno::EADDRNOTAVAIL # IPv6 is not supported - exit - end - - port = server.addr[1] - - Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| - case family - when Socket::AF_INET6 then [Addrinfo.tcp("::1", port)] - when Socket::AF_INET then sleep(0.001); raise SocketError - end - end - - server_thread = Thread.new { server.accept } - socket = nil - - assert_nothing_raised do - socket = Socket.tcp("localhost", port) - end - - server_thread.value.close - server.close - socket.close if socket && !socket.closed? - end; - end - - def test_tcp_socket_all_hostname_resolution_failed - opts = %w[-rsocket -W1] - assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}" - - begin; - Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| - case family - when Socket::AF_INET6 then raise SocketError - when Socket::AF_INET then sleep(0.001); raise SocketError, "Last hostname resolution error" - end - end - port = TCPServer.new("localhost", 0).addr[1] - - assert_raise_with_message(SocketError, "Last hostname resolution error") do - Socket.tcp("localhost", port) - end - end; - end - - def test_tcp_socket_v6_address_passed - opts = %w[-rsocket -W1] - assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}" - - begin; - begin - server = TCPServer.new("::1", 0) - rescue Errno::EADDRNOTAVAIL # IPv6 is not supported - exit - end - - _, port, = server.addr - - Addrinfo.define_singleton_method(:getaddrinfo) do |*_| - [Addrinfo.tcp("::1", port)] - end - - server_thread = Thread.new { server.accept } - socket = Socket.tcp("::1", port) - - assert_true(socket.remote_address.ipv6?) - server_thread.value.close - server.close - socket.close if socket && !socket.closed? - end; - end - - def test_tcp_socket_fast_fallback_is_false - server = TCPServer.new("127.0.0.1", 0) - _, port, = server.addr - server_thread = Thread.new { server.accept } - socket = Socket.tcp("127.0.0.1", port, fast_fallback: false) - - assert_true(socket.remote_address.ipv4?) - server_thread.value.close - server.close - socket.close if socket && !socket.closed? - end end if defined?(Socket) |