1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Add ipaddr optional parameter to Net::HTTP#start

to replace the address for TCP/IP connection [Feature #5180]

There're 3 layers of hostname:
* host address for TCP/IP
* TLS server name
* HTTP Host header value
To test DNS round robin or check server certificate from server local,
people sometimes want to connect server with given IP address but keep
TLS server name and HTTP Host header value.

closes [Feature #15215]
closes https://github.com/ruby/ruby/pull/1893
closes https://github.com/ruby/ruby/pull/1977
This commit is contained in:
NARUSE, Yui 2019-12-09 20:19:11 +09:00
parent 1943279426
commit 54072e329c
3 changed files with 57 additions and 8 deletions

5
NEWS
View file

@ -508,6 +508,11 @@ Net::FTP::
* Add Net::FTP#features to check available features, and Net::FTP#option to * Add Net::FTP#features to check available features, and Net::FTP#option to
enable/disable each of them. [Feature #15964] enable/disable each of them. [Feature #15964]
Net::HTTP::
* Add ipaddr optional parameter to Net::HTTP#start to replace the address for
TCP/IP connection [Feature #5180]
Net::IMAP:: Net::IMAP::
* Add Server Name Indication (SNI) support. [Feature #15594] * Add Server Name Indication (SNI) support. [Feature #15594]

View file

@ -571,7 +571,7 @@ module Net #:nodoc:
# _opt_ :: optional hash # _opt_ :: optional hash
# #
# _opt_ sets following values by its accessor. # _opt_ sets following values by its accessor.
# The keys are ca_file, ca_path, cert, cert_store, ciphers, # The keys are ipaddr, ca_file, ca_path, cert, cert_store, ciphers,
# close_on_empty_response, key, open_timeout, read_timeout, write_timeout, ssl_timeout, # close_on_empty_response, key, open_timeout, read_timeout, write_timeout, ssl_timeout,
# ssl_version, use_ssl, verify_callback, verify_depth and verify_mode. # ssl_version, use_ssl, verify_callback, verify_depth and verify_mode.
# If you set :use_ssl as true, you can use https and default value of # If you set :use_ssl as true, you can use https and default value of
@ -590,6 +590,7 @@ module Net #:nodoc:
p_addr = :ENV if arg.size < 2 p_addr = :ENV if arg.size < 2
port = https_default_port if !port && opt && opt[:use_ssl] port = https_default_port if !port && opt && opt[:use_ssl]
http = new(address, port, p_addr, p_port, p_user, p_pass) http = new(address, port, p_addr, p_port, p_user, p_pass)
http.ipaddr = opt[:ipaddr] if opt[:ipaddr]
if opt if opt
if opt[:use_ssl] if opt[:use_ssl]
@ -660,6 +661,7 @@ module Net #:nodoc:
def initialize(address, port = nil) def initialize(address, port = nil)
@address = address @address = address
@port = (port || HTTP.default_port) @port = (port || HTTP.default_port)
@ipaddr = nil
@local_host = nil @local_host = nil
@local_port = nil @local_port = nil
@curr_http_version = HTTPVersion @curr_http_version = HTTPVersion
@ -727,6 +729,17 @@ module Net #:nodoc:
attr_writer :proxy_user attr_writer :proxy_user
attr_writer :proxy_pass attr_writer :proxy_pass
# The IP address to connect to/used to connect to
def ipaddr
started? ? @socket.io.peeraddr[3] : @ipaddr
end
# Set the IP address to connect to
def ipaddr=(addr)
raise IOError, "ipaddr value changed, but session already started" if started?
@ipaddr = addr
end
# Number of seconds to wait for the connection to open. Any number # Number of seconds to wait for the connection to open. Any number
# may be used, including Floats for fractional seconds. If the HTTP # may be used, including Floats for fractional seconds. If the HTTP
# object cannot open a connection in this many seconds, it raises a # object cannot open a connection in this many seconds, it raises a
@ -934,20 +947,20 @@ module Net #:nodoc:
def connect def connect
if proxy? then if proxy? then
conn_address = proxy_address conn_addr = proxy_address
conn_port = proxy_port conn_port = proxy_port
else else
conn_address = address conn_addr = conn_address
conn_port = port conn_port = port
end end
D "opening connection to #{conn_address}:#{conn_port}..." D "opening connection to #{conn_addr}:#{conn_port}..."
s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { s = Timeout.timeout(@open_timeout, Net::OpenTimeout) {
begin begin
TCPSocket.open(conn_address, conn_port, @local_host, @local_port) TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
rescue => e rescue => e
raise e, "Failed to open TCP connection to " + raise e, "Failed to open TCP connection to " +
"#{conn_address}:#{conn_port} (#{e.message})" "#{conn_addr}:#{conn_port} (#{e.message})"
end end
} }
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
@ -984,7 +997,7 @@ module Net #:nodoc:
OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
@ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess } @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess }
D "starting SSL for #{conn_address}:#{conn_port}..." D "starting SSL for #{conn_addr}:#{conn_port}..."
s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context) s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
s.sync_close = true s.sync_close = true
# Server Name Indication (SNI) RFC 3546 # Server Name Indication (SNI) RFC 3546
@ -1161,7 +1174,7 @@ module Net #:nodoc:
# without proxy, obsolete # without proxy, obsolete
def conn_address # :nodoc: def conn_address # :nodoc:
address() @ipaddr || address()
end end
def conn_port # :nodoc: def conn_port # :nodoc:

View file

@ -50,6 +50,37 @@ class TestNetHTTPS < Test::Unit::TestCase
skip $! skip $!
end end
def test_get_SNI
http = Net::HTTP.new("localhost", config("port"))
http.ipaddr = config('host')
http.use_ssl = true
http.cert_store = TEST_STORE
certs = []
http.verify_callback = Proc.new do |preverify_ok, store_ctx|
certs << store_ctx.current_cert
preverify_ok
end
http.request_get("/") {|res|
assert_equal($test_net_http_data, res.body)
}
assert_equal(CA_CERT.to_der, certs[0].to_der)
assert_equal(SERVER_CERT.to_der, certs[1].to_der)
end
def test_get_SNI_failure
http = Net::HTTP.new("invalid_servername", config("port"))
http.ipaddr = config('host')
http.use_ssl = true
http.cert_store = TEST_STORE
certs = []
http.verify_callback = Proc.new do |preverify_ok, store_ctx|
certs << store_ctx.current_cert
preverify_ok
end
@log_tester = lambda {|_| }
assert_raise(OpenSSL::SSL::SSLError){ http.start }
end
def test_post def test_post
http = Net::HTTP.new("localhost", config("port")) http = Net::HTTP.new("localhost", config("port"))
http.use_ssl = true http.use_ssl = true