Skip to content

Commit 40d9486

Browse files
committed
Fix: Do not fast_fallback if local_port is explicitly specified
`fast fallback` cannot be used with explicitly specified local port, because concurrent binds to the same `local_host:local_port` can raise `Errno::EADDRINUSE`. This issue is more likely to occur on hosts with `IPV6_V6ONLY` disabled, because IPv6 binds can also occupy IPv4-mapped IPv6 address space.
1 parent d375bcc commit 40d9486

2 files changed

Lines changed: 15 additions & 4 deletions

File tree

ext/socket/ipsocket.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,18 @@ is_specified_ip_address(const char *hostname)
258258
inet_pton(AF_INET, hostname, &ipv4addr) == 1);
259259
}
260260

261+
static int
262+
is_local_port_fixed(const char *portp)
263+
{
264+
if (!portp) return 0;
265+
266+
char *endp;
267+
long port = strtol(portp, &endp, 10);
268+
if (endp == portp) return 0;
269+
270+
return port != 0;
271+
}
272+
261273
struct fast_fallback_inetsock_arg
262274
{
263275
VALUE self;
@@ -1320,7 +1332,7 @@ rsock_init_inetsock(
13201332
hostp = raddrinfo_host_str(remote_host, hbuf, sizeof(hbuf), &additional_flags);
13211333
portp = raddrinfo_port_str(remote_serv, pbuf, sizeof(pbuf), &additional_flags);
13221334

1323-
if (!is_specified_ip_address(hostp)) {
1335+
if (!is_specified_ip_address(hostp) && !is_local_port_fixed(portp)) {
13241336
int target_families[2] = { 0, 0 };
13251337
int resolving_family_size = 0;
13261338

ext/socket/lib/socket.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -660,12 +660,11 @@ def accept_nonblock(exception: true)
660660
# puts sock.read
661661
# }
662662
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
663-
664663
if open_timeout && (connect_timeout || resolv_timeout)
665664
raise ArgumentError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout"
666665
end
667666

668-
sock = if fast_fallback && !(host && ip_address?(host))
667+
sock = if fast_fallback && !(host && ip_address?(host)) && !(local_port && local_port.to_i != 0)
669668
tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:)
670669
else
671670
tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:)
@@ -736,7 +735,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil,
736735
if local_addrinfos.any?
737736
local_addrinfo = local_addrinfos.find { |lai| lai.afamily == addrinfo.afamily }
738737

739-
if local_addrinfo.nil? # Connecting addrinfoと同じアドレスファミリのLocal addrinfoがない
738+
if local_addrinfo.nil?
740739
if resolution_store.any_addrinfos?
741740
# Try other Addrinfo in next "while"
742741
next

0 commit comments

Comments
 (0)