diff --git a/stdlib/socket/0/basic_socket.rbs b/stdlib/socket/0/basic_socket.rbs
index a69132467b..63552bd49c 100644
--- a/stdlib/socket/0/basic_socket.rbs
+++ b/stdlib/socket/0/basic_socket.rbs
@@ -237,7 +237,7 @@ class BasicSocket < IO
# onoff, linger = optval.unpack "ii"
# onoff = onoff == 0 ? false : true
#
- def getsockopt: (Symbol | Integer, Symbol | Integer) -> (Integer | boolish | String)
+ def getsockopt: (interned | Integer level, interned | Integer optname) -> Socket::Option
#
+ # 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=).
+ #
+ def self.tcp_fast_fallback: () -> bool
+
+ #
+ # 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.
+ # 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.
+ # 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.
+ # If no connection is established within this time, a new connection is
+ # started every 250 ms
+ # until a connection is established or there are no more candidate
+ # addresses.
+ # (Although RFC 8305 strictly specifies sorting addresses,
+ # 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_fast_fallback=: (bool value) -> bool
+
#
+# Socket::Option represents a socket option used by BasicSocket#getsockopt and
+# BasicSocket#setsockopt. A socket option contains the socket #family, protocol
+# #level, option name #optname and option value #data.
+#
+class Socket::Option
+ #
+ # Returns a new Socket::Option object.
+ #
+ # sockopt = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [1].pack("i"))
+ # p sockopt #=> #
+ #
+ def initialize: (interned | Integer family, interned | Integer level, interned | Integer optname, String data) -> void
+
+ #
+ # Creates a new Socket::Option object which contains boolean as data. Actually 0
+ # or 1 as int is used.
+ #
+ # require 'socket'
+ #
+ # p Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true)
+ # #=> #
+ #
+ # p Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, false)
+ # #=> #
+ #
+ def self.bool: (interned | Integer family, interned | Integer level, interned | Integer optname, boolish bool) -> instance
+
+ #
+ # Creates a new Socket::Option object which contains a byte as data.
+ #
+ # p Socket::Option.byte(:INET, :SOCKET, :KEEPALIVE, 1)
+ # #=> #
+ #
+ def self.byte: (interned | Integer family, interned | Integer level, interned | Integer optname, Integer integer) -> instance
+
+ #
+ # Creates a new Socket::Option object which contains an int as data.
+ #
+ # The size and endian is dependent on the platform.
+ #
+ # p Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1)
+ # #=> #
+ #
+ def self.int: (interned | Integer family, interned | Integer level, interned | Integer optname, Integer integer) -> instance
+
+ #
+ # Creates a new Socket::Option object for IP_MULTICAST_LOOP.
+ #
+ # The size is dependent on the platform.
+ #
+ # sockopt = Socket::Option.int(:INET, :IPPROTO_IP, :IP_MULTICAST_LOOP, 1)
+ # p sockopt.int => 1
+ #
+ # p Socket::Option.ipv4_multicast_loop(10)
+ # #=> #
+ #
+ def self.ipv4_multicast_loop: (Integer integer) -> instance
+
+ #
+ # Creates a new Socket::Option object for IP_MULTICAST_TTL.
+ #
+ # The size is dependent on the platform.
+ #
+ # p Socket::Option.ipv4_multicast_ttl(10)
+ # #=> #
+ #
+ def self.ipv4_multicast_ttl: (Integer integer) -> instance
+
+ #
+ # Creates a new Socket::Option object for SOL_SOCKET/SO_LINGER.
+ #
+ # *onoff* should be an integer or a boolean.
+ #
+ # *secs* should be the number of seconds.
+ #
+ # p Socket::Option.linger(true, 10)
+ # #=> #
+ #
+ def self.linger: (boolish | Integer onoff, Integer secs) -> instance
+
+ #
+ # Returns the data in *sockopt* as an boolean value.
+ #
+ # sockopt = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1)
+ # p sockopt.bool => true
+ #
+ def bool: () -> bool
+
+ #
+ # Returns the data in *sockopt* as an byte.
+ #
+ # sockopt = Socket::Option.byte(:INET, :SOCKET, :KEEPALIVE, 1)
+ # p sockopt.byte => 1
+ #
+ def byte: () -> Integer
+
+ #
+ # returns the socket option data as a string.
+ #
+ # p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).data
+ # #=> "\x01\x00\x00\x00"
+ #
+ def data: () -> String
+
+ #
+ # returns the socket family as an integer.
+ #
+ # p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).family
+ # #=> 10
+ #
+ def family: () -> Integer
+
+ #
+ # Returns a string which shows sockopt in human-readable form.
+ #
+ # p Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [1].pack("i")).inspect
+ # #=> "#"
+ #
+ def inspect: () -> String
+
+ #
+ # Returns the data in *sockopt* as an int.
+ #
+ # The size and endian is dependent on the platform.
+ #
+ # sockopt = Socket::Option.int(:INET, :SOCKET, :KEEPALIVE, 1)
+ # p sockopt.int => 1
+ #
+ def int: () -> Integer
+
+ #
+ # Returns the ipv4_multicast_loop data in *sockopt* as an integer.
+ #
+ # sockopt = Socket::Option.ipv4_multicast_loop(10)
+ # p sockopt.ipv4_multicast_loop => 10
+ #
+ def ipv4_multicast_loop: () -> Integer
+
+ #
+ # Returns the ipv4_multicast_ttl data in *sockopt* as an integer.
+ #
+ # sockopt = Socket::Option.ipv4_multicast_ttl(10)
+ # p sockopt.ipv4_multicast_ttl => 10
+ #
+ def ipv4_multicast_ttl: () -> Integer
+
+ #
+ # returns the socket level as an integer.
+ #
+ # p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).level
+ # #=> 41
+ #
+ def level: () -> Integer
+
+ #
+ # Returns the linger data in *sockopt* as a pair of boolean and integer.
+ #
+ # sockopt = Socket::Option.linger(true, 10)
+ # p sockopt.linger => [true, 10]
+ #
+ def linger: () -> [ bool, Integer ]
+
+ #
+ # returns the socket option name as an integer.
+ #
+ # p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).optname
+ # #=> 2
+ #
+ def optname: () -> Integer
+
+ #
+ # returns the socket option data as a string.
+ #
+ # p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).data
+ # #=> "\x01\x00\x00\x00"
+ #
+ alias to_s data
+
+ #
+ # Calls String#unpack on sockopt.data.
+ #
+ # sockopt = Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [1].pack("i"))
+ # p sockopt.unpack("i") #=> [1]
+ # p sockopt.data.unpack("i") #=> [1]
+ #
+ def unpack: (String template) -> Array[untyped]
+end
diff --git a/stdlib/socket/0/socket_error.rbs b/stdlib/socket/0/socket_error.rbs
index 02095c7feb..466e575c27 100644
--- a/stdlib/socket/0/socket_error.rbs
+++ b/stdlib/socket/0/socket_error.rbs
@@ -3,3 +3,27 @@
#
class SocketError < StandardError
end
+
+#
+# Socket::ResolutionError is the error class for hostname resolution.
+#
+class Socket::ResolutionError < SocketError
+ #
+ # 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.
+ #
+ def error_code: () -> Integer
+end
diff --git a/test/stdlib/socket/BasicSocket_sockopt_test.rb b/test/stdlib/socket/BasicSocket_sockopt_test.rb
new file mode 100644
index 0000000000..8e886a1b88
--- /dev/null
+++ b/test/stdlib/socket/BasicSocket_sockopt_test.rb
@@ -0,0 +1,44 @@
+require_relative "../test_helper"
+
+require "socket"
+
+class BasicSocketSockoptTest < Test::Unit::TestCase
+ include TestHelper
+
+ library "socket"
+ testing "::BasicSocket"
+
+ def test_getsockopt
+ Socket.open(:INET, :STREAM) do |socket|
+ assert_send_type(
+ "(::Symbol, ::Symbol) -> ::Socket::Option",
+ socket, :getsockopt, :SOCKET, :KEEPALIVE
+ )
+ assert_send_type(
+ "(::Integer, ::Integer) -> ::Socket::Option",
+ socket, :getsockopt, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE
+ )
+ end
+ end
+
+ def test_setsockopt
+ Socket.open(:INET, :STREAM) do |socket|
+ assert_send_type(
+ "(::Socket::Option) -> void",
+ socket, :setsockopt, Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true)
+ )
+ assert_send_type(
+ "(::Symbol, ::Symbol, bool) -> void",
+ socket, :setsockopt, :SOCKET, :KEEPALIVE, true
+ )
+ assert_send_type(
+ "(::Symbol, ::Symbol, ::Integer) -> void",
+ socket, :setsockopt, :SOCKET, :KEEPALIVE, 1
+ )
+ assert_send_type(
+ "(::Integer, ::Integer, ::String) -> void",
+ socket, :setsockopt, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, [1].pack("i")
+ )
+ end
+ end
+end
diff --git a/test/stdlib/socket/SocketOption_test.rb b/test/stdlib/socket/SocketOption_test.rb
new file mode 100644
index 0000000000..b821e98b06
--- /dev/null
+++ b/test/stdlib/socket/SocketOption_test.rb
@@ -0,0 +1,130 @@
+require_relative "../test_helper"
+
+require "socket"
+
+class SocketOptionSingletonTest < Test::Unit::TestCase
+ include TestHelper
+
+ library "socket"
+ testing "singleton(::Socket::Option)"
+
+ def test_new
+ assert_send_type(
+ "(::Symbol, ::Symbol, ::Symbol, ::String) -> ::Socket::Option",
+ Socket::Option, :new, :INET, :SOCKET, :KEEPALIVE, [1].pack("i")
+ )
+ assert_send_type(
+ "(::String, ::Integer, ::Integer, ::String) -> ::Socket::Option",
+ Socket::Option, :new, "INET", Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, [1].pack("i")
+ )
+ end
+
+ def test_bool
+ assert_send_type(
+ "(::Symbol, ::Symbol, ::Symbol, bool) -> ::Socket::Option",
+ Socket::Option, :bool, :INET, :SOCKET, :KEEPALIVE, true
+ )
+ end
+
+ def test_byte
+ assert_send_type(
+ "(::Symbol, ::Symbol, ::Symbol, ::Integer) -> ::Socket::Option",
+ Socket::Option, :byte, :INET, :IP, :TTL, 3
+ )
+ end
+
+ def test_int
+ assert_send_type(
+ "(::Symbol, ::Symbol, ::Symbol, ::Integer) -> ::Socket::Option",
+ Socket::Option, :int, :INET, :SOCKET, :KEEPALIVE, 1
+ )
+ end
+
+ def test_ipv4_multicast_loop
+ assert_send_type(
+ "(::Integer) -> ::Socket::Option",
+ Socket::Option, :ipv4_multicast_loop, 1
+ )
+ end
+
+ def test_ipv4_multicast_ttl
+ assert_send_type(
+ "(::Integer) -> ::Socket::Option",
+ Socket::Option, :ipv4_multicast_ttl, 1
+ )
+ end
+
+ def test_linger
+ assert_send_type(
+ "(bool, ::Integer) -> ::Socket::Option",
+ Socket::Option, :linger, true, 10
+ )
+ assert_send_type(
+ "(::Integer, ::Integer) -> ::Socket::Option",
+ Socket::Option, :linger, 1, 10
+ )
+ end
+end
+
+class SocketOptionTest < Test::Unit::TestCase
+ include TestHelper
+
+ library "socket"
+ testing "::Socket::Option"
+
+ def keepalive
+ Socket::Option.new(:INET, :SOCKET, :KEEPALIVE, [1].pack("i"))
+ end
+
+ def test_bool
+ assert_send_type "() -> bool", keepalive, :bool
+ end
+
+ def test_byte
+ assert_send_type "() -> ::Integer", Socket::Option.byte(:INET, :IP, :TTL, 3), :byte
+ end
+
+ def test_data
+ assert_send_type "() -> ::String", keepalive, :data
+ end
+
+ def test_to_s
+ assert_send_type "() -> ::String", keepalive, :to_s
+ end
+
+ def test_family
+ assert_send_type "() -> ::Integer", keepalive, :family
+ end
+
+ def test_inspect
+ assert_send_type "() -> ::String", keepalive, :inspect
+ end
+
+ def test_int
+ assert_send_type "() -> ::Integer", keepalive, :int
+ end
+
+ def test_ipv4_multicast_loop
+ assert_send_type "() -> ::Integer", Socket::Option.ipv4_multicast_loop(1), :ipv4_multicast_loop
+ end
+
+ def test_ipv4_multicast_ttl
+ assert_send_type "() -> ::Integer", Socket::Option.ipv4_multicast_ttl(1), :ipv4_multicast_ttl
+ end
+
+ def test_level
+ assert_send_type "() -> ::Integer", keepalive, :level
+ end
+
+ def test_linger
+ assert_send_type "() -> [bool, ::Integer]", Socket::Option.linger(true, 10), :linger
+ end
+
+ def test_optname
+ assert_send_type "() -> ::Integer", keepalive, :optname
+ end
+
+ def test_unpack
+ assert_send_type "(::String) -> ::Array[untyped]", keepalive, :unpack, "i"
+ end
+end
diff --git a/test/stdlib/socket/SocketResolutionError_test.rb b/test/stdlib/socket/SocketResolutionError_test.rb
new file mode 100644
index 0000000000..4aa2ccebba
--- /dev/null
+++ b/test/stdlib/socket/SocketResolutionError_test.rb
@@ -0,0 +1,23 @@
+require_relative "../test_helper"
+
+require "socket"
+
+class SocketResolutionErrorTest < Test::Unit::TestCase
+ include TestHelper
+
+ library "socket"
+ testing "::Socket::ResolutionError"
+
+ def resolution_error
+ Addrinfo.getaddrinfo("nonexistent.invalid", nil)
+ rescue Socket::ResolutionError => error
+ error
+ end
+
+ def test_error_code
+ assert_send_type(
+ "() -> ::Integer",
+ resolution_error, :error_code
+ )
+ end
+end
diff --git a/test/stdlib/socket/Socket_test.rb b/test/stdlib/socket/Socket_test.rb
index f024c89456..2c103ddac3 100644
--- a/test/stdlib/socket/Socket_test.rb
+++ b/test/stdlib/socket/Socket_test.rb
@@ -66,6 +66,23 @@ def test_tcp
)
end
end
+
+ def test_tcp_fast_fallback
+ assert_send_type(
+ "() -> bool",
+ Socket, :tcp_fast_fallback
+ )
+ end
+
+ def test_tcp_fast_fallback=
+ original = Socket.tcp_fast_fallback
+ assert_send_type(
+ "(bool) -> bool",
+ Socket, :tcp_fast_fallback=, false
+ )
+ ensure
+ Socket.tcp_fast_fallback = original
+ end
end
class SocketInstanceTest < Test::Unit::TestCase