Project

General

Profile

Actions

Feature #5180

closed

net/http の接続時に用いる IP アドレスの指定

Added by naruse (Yui NARUSE) over 12 years ago. Updated over 4 years ago.

Status:
Closed
Target version:
-
[ruby-dev:44368]

Description

通常 net/http を使う時は、Net::HTTP.start("ruby-lang.org") などとホスト名を使います。
で、Socket がホスト名から IP アドレスを引いて、コネクションが張られます。
普通の人はこれで足りるわけですが、ふつうな人はしばしば DNS で引けない IP アドレスに接続したくなります。
例えば、ホスト名は "ruby-lang.org" としたいが、IP アドレスは 127.0.0.1 とか。

以下のパッチをあてると、
Net::HTTP.start("ruby-lang.org", ipaddr: '127.0.0.1')
などとできるようになります。

diff --git a/lib/net/http.rb b/lib/net/http.rb
index 7b9ec4f..6d034e0 100644
--- a/lib/net/http.rb
+++ b/lib/net/http.rb
@@ -524,7 +524,7 @@ module Net   #:nodoc:
     # _opt_     :: optional hash
     #
     # _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, ssl_timeout,
     # 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
@@ -542,6 +542,7 @@ module Net   #:nodoc:
       port, p_addr, p_port, p_user, p_pass = *arg
       port = https_default_port if !port && opt && opt[:use_ssl]
       http = new(address, port, p_addr, p_port, p_user, p_pass)
+      http.ipaddr = opt[:ipaddr] if opt[:ipaddr]
 
       if opt
         if opt[:use_ssl]
@@ -575,6 +576,7 @@ module Net   #:nodoc:
     def initialize(address, port = nil)
       @address = address
       @port    = (port || HTTP.default_port)
+      @ipaddr = nil
       @curr_http_version = HTTPVersion
       @no_keepalive_server = false
       @close_on_empty_response = false
@@ -620,6 +622,17 @@ module Net   #:nodoc:
     # The port number to connect to.
     attr_reader :port
 
+    # 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
     # may be used, including Floats for fractional seconds. If the HTTP
     # object cannot open a connection in this many seconds, it raises a
@@ -945,7 +958,7 @@ module Net   #:nodoc:
     # without proxy
 
     def conn_address
-      address()
+      @ipaddr || address()
     end
 
     def conn_port

Related issues 1 (0 open1 closed)

Has duplicate Ruby master - Feature #15215: HTTPS server name indication (SNI): explicit server_name in Net::HTTPClosedActions

Updated by shyouhei (Shyouhei Urabe) over 12 years ago

(08/10/2011 11:46 AM), Yui NARUSE wrote:

例えば、ホスト名は "ruby-lang.org" としたいが、IP アドレスは 127.0.0.1 とか。

/etc/hostsを書け、ではだめですか。名前解決のレイヤーの要件をHTTPで解決するのは筋が悪いでしょう。

Updated by naruse (Yui NARUSE) over 12 years ago

Shyouhei Urabe wrote:

(08/10/2011 11:46 AM), Yui NARUSE wrote:

例えば、ホスト名は "ruby-lang.org" としたいが、IP アドレスは 127.0.0.1 とか。

/etc/hostsを書け、ではだめですか。名前解決のレイヤーの要件をHTTPで解決するのは筋が悪いでしょう。

127.0.0.1 の例だとそうなんですね。

別のユースケースとして、同じホスト名(というかドメイン名)を持つ複数のサーバ群に対して、
それぞれに動作確認で通信したい事があったんですが、この場合だと一定のホスト名を送りつつ 、
IP アドレスは網羅しないといけないので、/etc/hosts だとサーバーの数だけ書き換えて試すのを
繰り返さないといけないのでつらいです。

Updated by akr (Akira Tanaka) over 12 years ago

2011年8月10日15:05 Yui NARUSE :

別のユースケースとして、同じホスト名(というかドメイン名)を持つ複数のサーバ群に対して、
それぞれに動作確認で通信したい事があったんですが、この場合だと一定のホスト名を送りつつ 、
IP アドレスは網羅しないといけないので、/etc/hosts だとサーバーの数だけ書き換えて試すのを
繰り返さないといけないのでつらいです。

resolv-replace みたいに TCPSocket.open をすりかえるのはいかがですかね。

これは lexcal じゃなくて dynamic に変えたい例だなぁ (つまり local rebinding)

--
[田中 哲][たなか あきら][Tanaka Akira]

Updated by naruse (Yui NARUSE) over 12 years ago

2011年8月10日15:20 Tanaka Akira :

2011年8月10日15:05 Yui NARUSE :

別のユースケースとして、同じホスト名(というかドメイン名)を持つ複数のサーバ群に対して、
それぞれに動作確認で通信したい事があったんですが、この場合だと一定のホスト名を送りつつ 、
IP アドレスは網羅しないといけないので、/etc/hosts だとサーバーの数だけ書き換えて試すのを
繰り返さないといけないのでつらいです。

resolv-replace みたいに TCPSocket.open をすりかえるのはいかがですかね。

これは lexcal じゃなくて dynamic に変えたい例だなぁ (つまり local rebinding)

TCPSocket.open の中などでやるという方向自体はありだと思っているんですが、
ではそこで使う IPアドレスをどこから取ってくるかとなると、
グローバル変数を使うとかしないと上手くいかないんじゃないかと

この提案だとこんな感じになります。
もうちょっと使いづらくてもいいとは思いますが、そうすると start が使えなくなって
一気に複雑化するのが悩みどころ
require 'net/http'
addrs = %w/192.168.0.1 192.168.0.2 192.168.0.3/
addrs.each do |addr|
Net::HTTP.start('example.org', use_ssl: true, ipaddr: addr){|h| p h.get('/')}
end

--
NARUSE, Yui

Updated by Anonymous over 12 years ago

load balancer利用時のreal IPへの接続などのユースケースは理解できますが

Net::HTTP.start("127.0.0.1") {|h|
print h.request_get( '/index.html' , {"host"=>"ruby-lang.org"}).body
}

※startはIP指定、リクエスト時のhostヘッダにホスト名

というのがHTTP的なふつうの対処な気がします

ホストがv4/v6の両アドレスを持つ場合などにv4で接続したい、
となるときも同じような感じでしょうか

On Wed, 10 Aug 2011 15:05:24 +0900
Yui NARUSE wrote:

Issue #5180 has been updated by Yui NARUSE.

Shyouhei Urabe wrote:

(08/10/2011 11:46 AM), Yui NARUSE wrote:

例えば、ホスト名は "ruby-lang.org" としたいが、IP アドレスは 127.0.0.1 とか。

/etc/hostsを書け、ではだめですか。名前解決のレイヤーの要件をHTTPで解決するのは筋が悪いでしょう。

127.0.0.1 の例だとそうなんですね。

別のユースケースとして、同じホスト名(というかドメイン名)を持つ複数のサーバ群に対して、
それぞれに動作確認で通信したい事があったんですが、この場合だと一定のホスト名を送りつつ 、
IP アドレスは網羅しないといけないので、/etc/hosts だとサーバーの数だけ書き換えて試すのを
繰り返さないといけないのでつらいです。

Feature #5180: net/http の接続時に用いる IP アドレスの指定
http://redmine.ruby-lang.org/issues/5180

Author: Yui NARUSE
Status: Open
Priority: Normal
Assignee:
Category: lib
Target version:

通常 net/http を使う時は、Net::HTTP.start("ruby-lang.org") などとホスト名を使います。
で、Socket がホスト名から IP アドレスを引いて、コネクションが張られます。
普通の人はこれで足りるわけですが、ふつうな人はしばしば DNS で引けない IP アドレスに接続したくなります。
例えば、ホスト名は "ruby-lang.org" としたいが、IP アドレスは 127.0.0.1 とか。

以下のパッチをあてると、
Net::HTTP.start("ruby-lang.org", ipaddr: '127.0.0.1')
などとできるようになります。

diff --git a/lib/net/http.rb b/lib/net/http.rb
index 7b9ec4f..6d034e0 100644
--- a/lib/net/http.rb
+++ b/lib/net/http.rb
@@ -524,7 +524,7 @@ module Net #:nodoc:
# opt :: optional hash
#
# 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, ssl_timeout,

    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

@@ -542,6 +542,7 @@ module Net #:nodoc:
port, p_addr, p_port, p_user, p_pass = *arg
port = https_default_port if !port && opt && opt[:use_ssl]
http = new(address, port, p_addr, p_port, p_user, p_pass)

  •  http.ipaddr = opt[:ipaddr] if opt[:ipaddr]
    
     if opt
       if opt[:use_ssl]
    

@@ -575,6 +576,7 @@ module Net #:nodoc:
def initialize(address, port = nil)
@address = address
@port = (port || HTTP.default_port)

  •  @ipaddr = nil
     @curr_http_version = HTTPVersion
     @no_keepalive_server = false
     @close_on_empty_response = false
    

@@ -620,6 +622,17 @@ module Net #:nodoc:
# The port number to connect to.
attr_reader :port

  • 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

    may be used, including Floats for fractional seconds. If the HTTP

    object cannot open a connection in this many seconds, it raises a

@@ -945,7 +958,7 @@ module Net #:nodoc:
# without proxy

 def conn_address
  •  address()
    
  •  @ipaddr || address()
    

    end

    def conn_port

--
http://redmine.ruby-lang.org

Updated by naruse (Yui NARUSE) over 12 years ago

2011年8月10日18:21 tadanori kojima :

load balancer利用時のreal IPへの接続などのユースケースは理解できますが

Net::HTTP.start("127.0.0.1") {|h|
print h.request_get( '/index.html' , {"host"=>"ruby-lang.org"}).body
}

※startはIP指定、リクエスト時のhostヘッダにホスト名

というのがHTTP的なふつうの対処な気がします

基本的な HTTP に対する理屈としては同意するところなんですが、
https だと connection 張る時点でホスト名が必要なため、それだとまずいんです。

ホストがv4/v6の両アドレスを持つ場合などにv4で接続したい、
となるときも同じような感じでしょうか

そうですね。

--
NARUSE, Yui

Updated by akr (Akira Tanaka) over 12 years ago

2011年8月10日17:20 NARUSE, Yui :

TCPSocket.open の中などでやるという方向自体はありだと思っているんですが、
ではそこで使う IPアドレスをどこから取ってくるかとなると、
グローバル変数を使うとかしないと上手くいかないんじゃないかと

グローバル変数が嫌ならスレッド変数とか。

この提案だとこんな感じになります。

net/http に対処を入れるほどの話なのかなぁ。

[田中 哲][たなか あきら][Tanaka Akira]

Updated by Anonymous over 12 years ago

On Wed, 10 Aug 2011 18:33:08 +0900
"NARUSE, Yui" wrote:

2011年8月10日18:21 tadanori kojima :

load balancer利用時のreal IPへの接続などのユースケースは理解できますが

Net::HTTP.start("127.0.0.1") {|h|
print h.request_get( '/index.html' , {"host"=>"ruby-lang.org"}).body
}

※startはIP指定、リクエスト時のhostヘッダにホスト名

というのがHTTP的なふつうの対処な気がします

基本的な HTTP に対する理屈としては同意するところなんですが、
https だと connection 張る時点でホスト名が必要なため、それだとまずいんです。

意味がわかりました

でもsslってcommon nameの検証までやってたっけ?
と、net/http.rbのソースをみるとconnect内に

−・−・−・−・−・−
s.connect
if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
s.post_connection_check(@address)
end
−・−・−・−・−・−

とあるので、1.8.5だと(手元のマシンがcent5なので。。。)
−・−・−・−・−・−
diff -u net/http.rb.orig net/http.rb
--- net/http.rb.orig 2011-08-11 11:09:21.000000000 +0900
+++ net/http.rb 2011-08-11 11:39:08.000000000 +0900
@@ -470,6 +470,7 @@
@debug_output = nil
@use_ssl = false
@ssl_context = nil

  •  @common_name = nil
    

    end

    def inspect
    @@ -526,6 +527,8 @@
    false # redefined in net/https
    end

  • attr_accessor :common_name

  • Opens TCP connection and HTTP session.

    When this method is called with block, gives a HTTP object

@@ -585,7 +588,7 @@
end
s.connect
if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE

  •      s.post_connection_check(@address)
    
  •      s.post_connection_check( @common_name || @address)
       end
     end
     on_connect
    

−・−・−・−・−・−
で、
−・−・−・−・−・−
require 'net/http'
addrs = %w/192.168.0.1 192.168.0.2 192.168.0.3/
addrs.each do |addr|
Net::HTTP.start(addr, use_ssl: true, common_name: 'example.org'){|h| p h.get('/')}
end
−・−・−・−・−・−
とcommon nameを指定するほうが好みです:-)

おおもとの問題は
 接続サーバの指定(IP/Hostname)、
 HTTPのHostヘッダ(IP/Hostname、VirtualHostなどで利用)
 SSLのCN検証(IP/Hostnameとは別にCNAMEもあり得る)
と、レイヤを意識したパケットの組み立て方が必要なことでしょうか
すっきりした解決方法は浮かばないですね。。。

ホストがv4/v6の両アドレスを持つ場合などにv4で接続したい、
となるときも同じような感じでしょうか

そうですね。

--
NARUSE, Yui

--
End of mail

Updated by znz (Kazuhiro NISHIYAMA) over 12 years ago

匿名ユーザ wrote:

おおもとの問題は
 接続サーバの指定(IP/Hostname)、
 HTTPのHostヘッダ(IP/Hostname、VirtualHostなどで利用)
 SSLのCN検証(IP/Hostnameとは別にCNAMEもあり得る)
と、レイヤを意識したパケットの組み立て方が必要なことでしょうか
すっきりした解決方法は浮かばないですね。。。

ホスト名は [ruby-dev:43164] の SNI のような部分でも使っているので
元の提案では IP アドレスの方を別途指定になっているのではないかと
思いました。

Updated by Anonymous over 12 years ago

On Thu, 11 Aug 2011 15:30:06 +0900
Kazuhiro NISHIYAMA wrote:

Issue #5180 has been updated by Kazuhiro NISHIYAMA.

匿名ユーザ wrote:

おおもとの問題は
 接続サーバの指定(IP/Hostname)、
 HTTPのHostヘッダ(IP/Hostname、VirtualHostなどで利用)
 SSLのCN検証(IP/Hostnameとは別にCNAMEもあり得る)
と、レイヤを意識したパケットの組み立て方が必要なことでしょうか
すっきりした解決方法は浮かばないですね。。。

ホスト名は [ruby-dev:43164] の SNI のような部分でも使っているので
元の提案では IP アドレスの方を別途指定になっているのではないかと
思いました。

そうですね
自分はネットワーク屋なのでレイヤの低いほうから積み上げていくのが
性に合っているのですが、上位から下りていく方が通常のやり方ですね

元々は自分もhttps://www.asahi.com/のようなホスト名とCNが異なる
サイトの検証でどうしたもんか悩んだことがありました

でも、net/httpに手を入れるほどふつうか?という疑問に一票です

Updated by naruse (Yui NARUSE) over 12 years ago

(2011/08/10 18:54), Tanaka Akira wrote:

2011年8月10日17:20 NARUSE, Yui :

TCPSocket.open の中などでやるという方向自体はありだと思っているんですが、
ではそこで使う IPアドレスをどこから取ってくるかとなると、
グローバル変数を使うとかしないと上手くいかないんじゃないかと

グローバル変数が嫌ならスレッド変数とか。

require 'net/http'
addrs = %w/192.168.0.1 192.168.0.2 192.168.0.3/
def TCPSocket.open(host, serv, *rest)
TCPSocket.new(Thread.current[:ipaddr], serv, *rest)
end
addrs.map do |addr|
Thread.new do
Thread.current[:ipaddr] = addr
Net::HTTP.start('exapmle.org', use_ssl: true) do |h|
r = h.head('/')
p [addr, h.instance_variable_get(:@socket).io.peeraddr[3], r]
end
end
end.each(&:join)
class << TCPSocket
remove_method :open
end

こんな感じですかね、うーん、ここまで大げさにしないといけないのかなぁ。

この提案だとこんな感じになります。

net/http に対処を入れるほどの話なのかなぁ。

もともと conn_address は Proxy 対応のために抽象化されているので、
そこをもうちょっといじるのはそこまで突飛な話でもないと思うんですけどね。

--
NARUSE, Yui

Updated by naruse (Yui NARUSE) over 12 years ago

(2011/08/11 17:48), tadanori kojima wrote:

On Thu, 11 Aug 2011 15:30:06 +0900
Kazuhiro NISHIYAMA wrote:

Issue #5180 has been updated by Kazuhiro NISHIYAMA.
匿名ユーザ wrote:

おおもとの問題は
 接続サーバの指定(IP/Hostname)、
 HTTPのHostヘッダ(IP/Hostname、VirtualHostなどで利用)
 SSLのCN検証(IP/Hostnameとは別にCNAMEもあり得る)
と、レイヤを意識したパケットの組み立て方が必要なことでしょうか
すっきりした解決方法は浮かばないですね。。。

ホスト名は [ruby-dev:43164] の SNI のような部分でも使っているので
元の提案では IP アドレスの方を別途指定になっているのではないかと
思いました。

そうですね
自分はネットワーク屋なのでレイヤの低いほうから積み上げていくのが
性に合っているのですが、上位から下りていく方が通常のやり方ですね

まぁ、わたしがやりたいのは http(s) なので。
なお、HTTP の Host と SSL の CN が違うというケースはわたしには思いつきませんでした。

元々は自分もhttps://www.asahi.com/のようなホスト名とCNが異なる
サイトの検証でどうしたもんか悩んだことがありました

でも、net/httpに手を入れるほどふつうか?という疑問に一票です

結構 hosts ファイル書き換えってやると思うんですけれどね。

--
NARUSE, Yui

Updated by mame (Yusuke Endoh) about 12 years ago

  • Status changed from Open to Assigned
  • Assignee set to naruse (Yui NARUSE)

成瀬さん

微妙に反対意見が出てますが、net/http のメンテナの裁量で
好きにやっていい範囲だと思います。
しかし現在 net/http にはメンテナがいません。

成瀬さんがメンテナになりませんか?
おまけで、net/http 関係の issue が憑いてきます。
メンテナになるのってまつもとさんの了解が必要でしたっけ。

成瀬さんがメンテナやりたくないなら reject の方向で。
その場合は Eric Hodel あたりに声かけてみますか。

--
Yusuke Endoh

Updated by naruse (Yui NARUSE) about 12 years ago

  • Status changed from Assigned to Rejected

これは reject します。

さておき、メンテナにはなりますかね。
まつもとさんの承認は必要だったような。

Actions #15

Updated by naruse (Yui NARUSE) over 4 years ago

  • Has duplicate Feature #15215: HTTPS server name indication (SNI): explicit server_name in Net::HTTP added
Actions #16

Updated by naruse (Yui NARUSE) over 4 years ago

  • Description updated (diff)
Actions #17

Updated by naruse (Yui NARUSE) over 4 years ago

  • Status changed from Rejected to Open
Actions #18

Updated by naruse (Yui NARUSE) over 4 years ago

  • Status changed from Open to Closed

Applied in changeset git|54072e329cab7207fba133caba4fc12b45add8f9.


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

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0