Skip to content

Commit 2fafaad

Browse files
simideivid-rodriguez
authored andcommitted
Merge pull request #2662 from sonalkr132/ipv6-fallback
Fallback to IPv4 when IPv6 is unreachable (cherry picked from commit 533a242)
1 parent a660c91 commit 2fafaad

File tree

6 files changed

+76
-0
lines changed

6 files changed

+76
-0
lines changed

Manifest.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ lib/rubygems/config_file.rb
354354
lib/rubygems/core_ext/kernel_gem.rb
355355
lib/rubygems/core_ext/kernel_require.rb
356356
lib/rubygems/core_ext/kernel_warn.rb
357+
lib/rubygems/core_ext/tcpsocket_init.rb
357358
lib/rubygems/defaults.rb
358359
lib/rubygems/dependency.rb
359360
lib/rubygems/dependency_installer.rb

lib/rubygems/config_file.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class Gem::ConfigFile
4545
DEFAULT_UPDATE_SOURCES = true
4646
DEFAULT_CONCURRENT_DOWNLOADS = 8
4747
DEFAULT_CERT_EXPIRATION_LENGTH_DAYS = 365
48+
DEFAULT_IPV4_FALLBACK_ENABLED = false
4849

4950
##
5051
# For Ruby packagers to set configuration defaults. Set in
@@ -140,6 +141,12 @@ class Gem::ConfigFile
140141

141142
attr_accessor :cert_expiration_length_days
142143

144+
##
145+
# == Experimental ==
146+
# Fallback to IPv4 when IPv6 is not reachable or slow (default: false)
147+
148+
attr_accessor :ipv4_fallback_enabled
149+
143150
##
144151
# Path name of directory or file of openssl client certificate, used for remote https connection with client authentication
145152

@@ -175,6 +182,7 @@ def initialize(args)
175182
@update_sources = DEFAULT_UPDATE_SOURCES
176183
@concurrent_downloads = DEFAULT_CONCURRENT_DOWNLOADS
177184
@cert_expiration_length_days = DEFAULT_CERT_EXPIRATION_LENGTH_DAYS
185+
@ipv4_fallback_enabled = ENV['IPV4_FALLBACK_ENABLED'] == 'true' || DEFAULT_IPV4_FALLBACK_ENABLED
178186

179187
operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS)
180188
platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS)
@@ -203,6 +211,7 @@ def initialize(args)
203211
@disable_default_gem_server = @hash[:disable_default_gem_server] if @hash.key? :disable_default_gem_server
204212
@sources = @hash[:sources] if @hash.key? :sources
205213
@cert_expiration_length_days = @hash[:cert_expiration_length_days] if @hash.key? :cert_expiration_length_days
214+
@ipv4_fallback_enabled = @hash[:ipv4_fallback_enabled] if @hash.key? :ipv4_fallback_enabled
206215

207216
@ssl_verify_mode = @hash[:ssl_verify_mode] if @hash.key? :ssl_verify_mode
208217
@ssl_ca_cert = @hash[:ssl_ca_cert] if @hash.key? :ssl_ca_cert
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require 'socket'
2+
3+
module CoreExtensions
4+
module TCPSocketExt
5+
def self.prepended(base)
6+
base.prepend Initializer
7+
end
8+
9+
module Initializer
10+
CONNECTION_TIMEOUT = 5
11+
IPV4_DELAY_SECONDS = 0.1
12+
13+
def initialize(host, serv, *rest)
14+
mutex = Mutex.new
15+
addrs = []
16+
cond_var = ConditionVariable.new
17+
18+
Addrinfo.foreach(host, serv, nil, :STREAM) do |addr|
19+
Thread.report_on_exception = false if defined? Thread.report_on_exception = ()
20+
21+
Thread.new(addr) do
22+
# give head start to ipv6 addresses
23+
sleep IPV4_DELAY_SECONDS if addr.ipv4?
24+
25+
# raises Errno::ECONNREFUSED when ip:port is unreachable
26+
Socket.tcp(addr.ip_address, serv, connect_timeout: CONNECTION_TIMEOUT).close
27+
mutex.synchronize do
28+
addrs << addr.ip_address
29+
cond_var.signal
30+
end
31+
end
32+
end
33+
34+
mutex.synchronize do
35+
timeout_time = CONNECTION_TIMEOUT + Time.now.to_f
36+
while addrs.empty? && (remaining_time = timeout_time - Time.now.to_f) > 0
37+
cond_var.wait(mutex, remaining_time)
38+
end
39+
40+
host = addrs.shift unless addrs.empty?
41+
end
42+
43+
super(host, serv, *rest)
44+
end
45+
end
46+
end
47+
end
48+
49+
TCPSocket.prepend CoreExtensions::TCPSocketExt

lib/rubygems/remote_fetcher.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def self.fetcher
7878
# fetching the gem.
7979

8080
def initialize(proxy=nil, dns=nil, headers={})
81+
require 'rubygems/core_ext/tcpsocket_init' if Gem.configuration.ipv4_fallback_enabled
8182
require 'net/http'
8283
require 'stringio'
8384
require 'uri'

test/rubygems/test_gem_config_file.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def test_initialize
4141
assert_equal true, @cfg.verbose
4242
assert_equal [@gem_repo], Gem.sources
4343
assert_equal 365, @cfg.cert_expiration_length_days
44+
assert_equal false, @cfg.ipv4_fallback_enabled
4445

4546
File.open @temp_conf, 'w' do |fp|
4647
fp.puts ":backtrace: true"
@@ -56,6 +57,7 @@ def test_initialize
5657
fp.puts ":ssl_verify_mode: 0"
5758
fp.puts ":ssl_ca_cert: /etc/ssl/certs"
5859
fp.puts ":cert_expiration_length_days: 28"
60+
fp.puts ":ipv4_fallback_enabled: true"
5961
end
6062

6163
util_config_file
@@ -70,6 +72,14 @@ def test_initialize
7072
assert_equal 0, @cfg.ssl_verify_mode
7173
assert_equal '/etc/ssl/certs', @cfg.ssl_ca_cert
7274
assert_equal 28, @cfg.cert_expiration_length_days
75+
assert_equal true, @cfg.ipv4_fallback_enabled
76+
end
77+
78+
def test_initialize_ipv4_fallback_enabled_env
79+
ENV['IPV4_FALLBACK_ENABLED'] = 'true'
80+
util_config_file %W[--config-file #{@temp_conf}]
81+
82+
assert_equal true, @cfg.ipv4_fallback_enabled
7383
end
7484

7585
def test_initialize_handle_arguments_config_file

test/rubygems/test_gem_remote_fetcher.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,12 @@ def test_nil_ca_cert
962962
end
963963
end
964964

965+
def test_tcpsocketext_require
966+
with_configured_fetcher(":ipv4_fallback_enabled: true") do |fetcher|
967+
refute require('rubygems/core_ext/tcpsocket_init')
968+
end
969+
end
970+
965971
def with_configured_fetcher(config_str = nil, &block)
966972
if config_str
967973
temp_conf = File.join @tempdir, '.gemrc'

0 commit comments

Comments
 (0)