зеркало из https://github.com/github/ruby.git
This commit is contained in:
Родитель
a0f7de814a
Коммит
d4e24021d3
|
@ -599,6 +599,30 @@ class Socket < BasicSocket
|
||||||
__accept_nonblock(exception)
|
__accept_nonblock(exception)
|
||||||
end
|
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:
|
# :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]) {|socket| ... }
|
||||||
# Socket.tcp(host, port, local_host=nil, local_port=nil, [opts])
|
# Socket.tcp(host, port, local_host=nil, local_port=nil, [opts])
|
||||||
|
@ -624,8 +648,491 @@ class Socket < BasicSocket
|
||||||
# sock.close_write
|
# sock.close_write
|
||||||
# puts sock.read
|
# 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
|
||||||
def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil) # :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)
|
||||||
last_error = nil
|
last_error = nil
|
||||||
ret = nil
|
ret = nil
|
||||||
|
|
||||||
|
@ -669,6 +1176,7 @@ class Socket < BasicSocket
|
||||||
ret
|
ret
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
private_class_method :tcp_without_fast_fallback
|
||||||
|
|
||||||
# :stopdoc:
|
# :stopdoc:
|
||||||
def self.ip_sockets_port0(ai_list, reuseaddr)
|
def self.ip_sockets_port0(ai_list, reuseaddr)
|
||||||
|
|
|
@ -669,6 +669,7 @@ SO_SETFIB nil Set the associated routing table for the socket (FreeBSD
|
||||||
SO_RTABLE nil Set the routing table for this socket (OpenBSD)
|
SO_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_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_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_INTERACTIVE nil Interactive socket priority
|
||||||
SOPRI_NORMAL nil Normal socket priority
|
SOPRI_NORMAL nil Normal socket priority
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# include <winsock2.h>
|
# include <winsock2.h>
|
||||||
# include <ws2tcpip.h>
|
# include <ws2tcpip.h>
|
||||||
|
# include <mswsock.h>
|
||||||
# include <iphlpapi.h>
|
# include <iphlpapi.h>
|
||||||
# if defined(_MSC_VER)
|
# if defined(_MSC_VER)
|
||||||
# undef HAVE_TYPE_STRUCT_SOCKADDR_DL
|
# undef HAVE_TYPE_STRUCT_SOCKADDR_DL
|
||||||
|
|
|
@ -35,6 +35,7 @@ extern "C++" { /* template without extern "C++" */
|
||||||
#endif
|
#endif
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
|
#include <mswsock.h>
|
||||||
#if !defined(_MSC_VER) || _MSC_VER >= 1400
|
#if !defined(_MSC_VER) || _MSC_VER >= 1400
|
||||||
#include <iphlpapi.h>
|
#include <iphlpapi.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -778,4 +778,243 @@ class TestSocket < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
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)
|
end if defined?(Socket)
|
||||||
|
|
Загрузка…
Ссылка в новой задаче