mirror of
https://github.com/ruby/ruby.git
synced 2025-08-25 05:55:46 +02:00

To verify the behavior of HEv2, some tests were prepared. But unexpected failures occur in certain environments. This happens in environments where "localhost" resolves only to an IPv4 address during tests that verify connections to IPv6. For example, the following situation can occur: - The server process is bound to ::1. - The client socket always resolves "localhost" to 127.0.0.1 and attempts to connect to 127.0.0.1. - Since no server is bound to 127.0.0.1, an ECONNREFUSED error is raised. In such situations, the behavior of `TCPSocket.new` remains unchanged from before the introduction of HEv2. (The failures occur because tests explicitly binding to ::1 were added to verify HEv2 behavior.) This change ensures that the affected tests are skipped in environments of this kind.
410 lines
12 KiB
Ruby
410 lines
12 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
begin
|
|
require "socket"
|
|
require "test/unit"
|
|
rescue LoadError
|
|
end
|
|
|
|
|
|
class TestSocket_TCPSocket < Test::Unit::TestCase
|
|
def test_inspect
|
|
TCPServer.open("localhost", 0) {|server|
|
|
assert_match(/AF_INET/, server.inspect)
|
|
TCPSocket.open("localhost", server.addr[1]) {|client|
|
|
assert_match(/AF_INET/, client.inspect)
|
|
}
|
|
}
|
|
end
|
|
|
|
def test_initialize_failure
|
|
# These addresses are chosen from TEST-NET-1, TEST-NET-2, and TEST-NET-3.
|
|
# [RFC 5737]
|
|
# They are chosen because probably they are not used as a host address.
|
|
# Anyway the addresses are used for bind() and should be failed.
|
|
# So no packets should be generated.
|
|
test_ip_addresses = [
|
|
'192.0.2.1', '192.0.2.42', # TEST-NET-1
|
|
'198.51.100.1', '198.51.100.42', # TEST-NET-2
|
|
'203.0.113.1', '203.0.113.42', # TEST-NET-3
|
|
]
|
|
begin
|
|
list = Socket.ip_address_list
|
|
rescue NotImplementedError
|
|
return
|
|
end
|
|
test_ip_addresses -= list.reject {|ai| !ai.ipv4? }.map {|ai| ai.ip_address }
|
|
if test_ip_addresses.empty?
|
|
return
|
|
end
|
|
client_addr = test_ip_addresses.first
|
|
client_port = 8000
|
|
|
|
server_addr = '127.0.0.1'
|
|
server_port = 80
|
|
|
|
begin
|
|
# Since client_addr is not an IP address of this host,
|
|
# bind() in TCPSocket.new should fail as EADDRNOTAVAIL.
|
|
t = TCPSocket.new(server_addr, server_port, client_addr, client_port)
|
|
flunk "expected SystemCallError"
|
|
rescue SystemCallError => e
|
|
assert_match "for \"#{client_addr}\" port #{client_port}", e.message
|
|
end
|
|
ensure
|
|
t.close if t && !t.closed?
|
|
end
|
|
|
|
def test_initialize_resolv_timeout
|
|
TCPServer.open("localhost", 0) do |svr|
|
|
th = Thread.new {
|
|
c = svr.accept
|
|
c.close
|
|
}
|
|
addr = svr.addr
|
|
s = TCPSocket.new(addr[3], addr[1], resolv_timeout: 10)
|
|
th.join
|
|
ensure
|
|
s.close()
|
|
end
|
|
end
|
|
|
|
def test_initialize_connect_timeout
|
|
assert_raise(IO::TimeoutError, Errno::ENETUNREACH, Errno::EACCES) do
|
|
TCPSocket.new("192.0.2.1", 80, connect_timeout: 0)
|
|
end
|
|
end
|
|
|
|
def test_recvfrom
|
|
TCPServer.open("localhost", 0) {|svr|
|
|
th = Thread.new {
|
|
c = svr.accept
|
|
c.write "foo"
|
|
c.close
|
|
}
|
|
addr = svr.addr
|
|
TCPSocket.open(addr[3], addr[1]) {|sock|
|
|
assert_equal(["foo", nil], sock.recvfrom(0x10000))
|
|
}
|
|
th.join
|
|
}
|
|
end
|
|
|
|
def test_encoding
|
|
TCPServer.open("localhost", 0) {|svr|
|
|
th = Thread.new {
|
|
c = svr.accept
|
|
c.write "foo\r\n"
|
|
c.close
|
|
}
|
|
addr = svr.addr
|
|
TCPSocket.open(addr[3], addr[1]) {|sock|
|
|
assert_equal(true, sock.binmode?)
|
|
s = sock.gets
|
|
assert_equal("foo\r\n", s)
|
|
assert_equal(Encoding.find("ASCII-8BIT"), s.encoding)
|
|
}
|
|
th.join
|
|
}
|
|
end
|
|
|
|
def test_accept_nonblock
|
|
TCPServer.open("localhost", 0) {|svr|
|
|
assert_raise(IO::WaitReadable) { svr.accept_nonblock }
|
|
assert_equal :wait_readable, svr.accept_nonblock(exception: false)
|
|
assert_raise(IO::WaitReadable) { svr.accept_nonblock(exception: true) }
|
|
}
|
|
end
|
|
|
|
def test_accept_multithread
|
|
attempts_count = 5
|
|
server_threads_count = 3
|
|
client_threads_count = 3
|
|
|
|
attempts_count.times do
|
|
server_threads = Array.new(server_threads_count) do
|
|
Thread.new do
|
|
TCPServer.open("localhost", 0) do |server|
|
|
accept_threads = Array.new(client_threads_count) do
|
|
Thread.new { server.accept.close }
|
|
end
|
|
client_threads = Array.new(client_threads_count) do
|
|
Thread.new { TCPSocket.open(server.addr[3], server.addr[1]) {} }
|
|
end
|
|
client_threads.each(&:join)
|
|
accept_threads.each(&:join)
|
|
end
|
|
end
|
|
end
|
|
|
|
server_threads.each(&:join)
|
|
end
|
|
end
|
|
|
|
def test_initialize_v6_hostname_resolved_earlier
|
|
# pend "to suppress the output of test failure logs in CI temporarily"
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
begin
|
|
# Verify that "localhost" can be resolved to an IPv6 address
|
|
Socket.getaddrinfo("localhost", 0, Socket::AF_INET6)
|
|
server = TCPServer.new("::1", 0)
|
|
rescue Socket::ResolutionError, Errno::EADDRNOTAVAIL # IPv6 is not supported
|
|
return
|
|
end
|
|
|
|
server_thread = Thread.new { server.accept }
|
|
port = server.addr[1]
|
|
|
|
socket = TCPSocket.new(
|
|
"localhost",
|
|
port,
|
|
fast_fallback: true,
|
|
test_mode_settings: { delay: { ipv4: 1000 } }
|
|
)
|
|
assert_true(socket.remote_address.ipv6?)
|
|
server_thread.value.close
|
|
server.close
|
|
socket.close if socket && !socket.closed?
|
|
end
|
|
|
|
def test_initialize_v4_hostname_resolved_earlier
|
|
# pend "to suppress the output of test failure logs in CI temporarily"
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
server = TCPServer.new("127.0.0.1", 0)
|
|
port = server.addr[1]
|
|
|
|
server_thread = Thread.new { server.accept }
|
|
socket = TCPSocket.new(
|
|
"localhost",
|
|
port,
|
|
fast_fallback: true,
|
|
test_mode_settings: { delay: { ipv6: 1000 } }
|
|
)
|
|
|
|
assert_true(socket.remote_address.ipv4?)
|
|
server_thread.value.close
|
|
server.close
|
|
socket.close if socket && !socket.closed?
|
|
end
|
|
|
|
def test_initialize_v6_hostname_resolved_in_resolution_delay
|
|
# pend "to suppress the output of test failure logs in CI temporarily"
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
begin
|
|
# Verify that "localhost" can be resolved to an IPv6 address
|
|
Socket.getaddrinfo("localhost", 0, Socket::AF_INET6)
|
|
server = TCPServer.new("::1", 0)
|
|
rescue Socket::ResolutionError, Errno::EADDRNOTAVAIL # IPv6 is not supported
|
|
return
|
|
end
|
|
|
|
port = server.addr[1]
|
|
delay_time = 25 # Socket::RESOLUTION_DELAY (private) is 50ms
|
|
|
|
server_thread = Thread.new { server.accept }
|
|
socket = TCPSocket.new(
|
|
"localhost",
|
|
port,
|
|
fast_fallback: true,
|
|
test_mode_settings: { delay: { ipv6: delay_time } }
|
|
)
|
|
assert_true(socket.remote_address.ipv6?)
|
|
server_thread.value.close
|
|
server.close
|
|
socket.close if socket && !socket.closed?
|
|
end
|
|
|
|
def test_initialize_v6_hostname_resolved_earlier_and_v6_server_is_not_listening
|
|
# pend "to suppress the output of test failure logs in CI temporarily"
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
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
|
|
|
|
ipv4_server_thread = Thread.new { ipv4_server.listen(1); ipv4_server.accept }
|
|
socket = TCPSocket.new(
|
|
"localhost",
|
|
port,
|
|
fast_fallback: true,
|
|
test_mode_settings: { delay: { ipv4: 10 } }
|
|
)
|
|
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
|
|
|
|
def test_initialize_v6_hostname_resolved_later_and_v6_server_is_not_listening
|
|
# pend "to suppress the output of test failure logs in CI temporarily"
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
ipv4_server = Socket.new(Socket::AF_INET, :STREAM)
|
|
ipv4_server.bind(Socket.pack_sockaddr_in(0, "127.0.0.1"))
|
|
port = ipv4_server.connect_address.ip_port
|
|
|
|
ipv4_server_thread = Thread.new { ipv4_server.listen(1); ipv4_server.accept }
|
|
socket = TCPSocket.new(
|
|
"localhost",
|
|
port,
|
|
fast_fallback: true,
|
|
test_mode_settings: { delay: { ipv6: 25 } }
|
|
)
|
|
|
|
assert_equal(
|
|
socket.remote_address.ipv4?,
|
|
true
|
|
)
|
|
accepted, _ = ipv4_server_thread.value
|
|
accepted.close
|
|
ipv4_server.close
|
|
socket.close if socket && !socket.closed?
|
|
end
|
|
|
|
def test_initialize_v6_hostname_resolution_failed_and_v4_hostname_resolution_is_success
|
|
# pend "to suppress the output of test failure logs in CI temporarily"
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
server = TCPServer.new("127.0.0.1", 0)
|
|
port = server.addr[1]
|
|
|
|
server_thread = Thread.new { server.accept }
|
|
socket = TCPSocket.new(
|
|
"localhost",
|
|
port,
|
|
fast_fallback: true,
|
|
test_mode_settings: { delay: { ipv4: 10 }, error: { ipv6: Socket::EAI_FAIL } }
|
|
)
|
|
|
|
assert_true(socket.remote_address.ipv4?)
|
|
server_thread.value.close
|
|
server.close
|
|
socket.close if socket && !socket.closed?
|
|
end
|
|
|
|
def test_initialize_resolv_timeout_with_connection_failure
|
|
# pend "to suppress the output of test failure logs in CI temporarily"
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
begin
|
|
server = TCPServer.new("::1", 0)
|
|
rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
|
|
return
|
|
end
|
|
|
|
port = server.connect_address.ip_port
|
|
server.close
|
|
|
|
assert_raise(Errno::ETIMEDOUT) do
|
|
TCPSocket.new(
|
|
"localhost",
|
|
port,
|
|
resolv_timeout: 0.01,
|
|
fast_fallback: true,
|
|
test_mode_settings: { delay: { ipv4: 1000 } }
|
|
)
|
|
end
|
|
end
|
|
|
|
def test_initialize_with_hostname_resolution_failure_after_connection_failure
|
|
# pend "to suppress the output of test failure logs in CI temporarily"
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
begin
|
|
server = TCPServer.new("::1", 0)
|
|
rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
|
|
return
|
|
end
|
|
|
|
port = server.connect_address.ip_port
|
|
server.close
|
|
|
|
assert_raise(Socket::ResolutionError) do
|
|
TCPSocket.new(
|
|
"localhost",
|
|
port,
|
|
fast_fallback: true,
|
|
test_mode_settings: { delay: { ipv4: 100 }, error: { ipv4: Socket::EAI_FAIL } }
|
|
)
|
|
end
|
|
end
|
|
|
|
def test_initialize_with_connection_failure_after_hostname_resolution_failure
|
|
# pend "to suppress the output of test failure logs in CI temporarily"
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
server = TCPServer.new("127.0.0.1", 0)
|
|
port = server.connect_address.ip_port
|
|
server.close
|
|
|
|
assert_raise(Errno::ECONNREFUSED) do
|
|
TCPSocket.new(
|
|
"localhost",
|
|
port,
|
|
fast_fallback: true,
|
|
test_mode_settings: { delay: { ipv4: 100 }, error: { ipv6: Socket::EAI_FAIL } }
|
|
)
|
|
end
|
|
end
|
|
|
|
def test_initialize_v6_connected_socket_with_v6_address
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
begin
|
|
server = TCPServer.new("::1", 0)
|
|
rescue Errno::EADDRNOTAVAIL # IPv6 is not supported
|
|
return
|
|
end
|
|
|
|
server_thread = Thread.new { server.accept }
|
|
port = server.addr[1]
|
|
|
|
socket = TCPSocket.new("::1", port)
|
|
assert_true(socket.remote_address.ipv6?)
|
|
ensure
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
server_thread.value.close
|
|
server.close
|
|
socket.close if socket && !socket.closed?
|
|
end
|
|
|
|
def test_initialize_v4_connected_socket_with_v4_address
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
server = TCPServer.new("127.0.0.1", 0)
|
|
server_thread = Thread.new { server.accept }
|
|
port = server.addr[1]
|
|
|
|
socket = TCPSocket.new("127.0.0.1", port)
|
|
assert_true(socket.remote_address.ipv4?)
|
|
ensure
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
server_thread.value.close
|
|
server.close
|
|
socket.close if socket && !socket.closed?
|
|
end
|
|
|
|
def test_initialize_fast_fallback_is_false
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
server = TCPServer.new("127.0.0.1", 0)
|
|
_, port, = server.addr
|
|
server_thread = Thread.new { server.accept }
|
|
|
|
socket = TCPSocket.new("127.0.0.1", port, fast_fallback: false)
|
|
assert_true(socket.remote_address.ipv4?)
|
|
ensure
|
|
return if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
|
|
|
server_thread.value.close
|
|
server.close
|
|
socket.close if socket && !socket.closed?
|
|
end
|
|
end if defined?(TCPSocket)
|