Project

General

Profile

Feature #5180

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

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

Status:
Closed
Priority:
Normal
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

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

Updated by shyouhei (Shyouhei Urabe) over 8 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 8 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 8 years ago

2011年8月10日15:05 Yui NARUSE naruse@airemix.jp:

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

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

# これは lexcal じゃなくて dynamic に変えたい例だなぁ (つまり local rebinding)
--
[田中 哲][たなか あきら][Tanaka Akira]

Updated by naruse (Yui NARUSE) over 8 years ago

2011年8月10日15:20 Tanaka Akira akr@fsij.org:

2011年8月10日15:05 Yui NARUSE naruse@airemix.jp:

別のユースケースとして、同じホスト名(というかドメイン名)を持つ複数のサーバ群に対して、
それぞれに動作確認で通信したい事があったんですが、この場合だと一定のホスト名を送りつつ 、
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 naruse@airemix.jp

Updated by Anonymous over 8 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 naruse@airemix.jp 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 8 years ago

2011年8月10日18:21 tadanori kojima tadanori.kojima@i2ts.com:

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 naruse@airemix.jp

Updated by akr (Akira Tanaka) over 8 years ago

2011年8月10日17:20 NARUSE, Yui naruse@airemix.jp:

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

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

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

net/http に対処を入れるほどの話なのかなぁ。
--
[田中 哲][たなか あきら][Tanaka Akira]

Updated by Anonymous over 8 years ago

On Wed, 10 Aug 2011 18:33:08 +0900
"NARUSE, Yui" naruse@airemix.jp wrote:

2011年8月10日18:21 tadanori kojima tadanori.kojima@i2ts.com:

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 naruse@airemix.jp

--
End of mail

Updated by znz (Kazuhiro NISHIYAMA) over 8 years ago

匿名ユーザ wrote:

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

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

Updated by Anonymous over 8 years ago

On Thu, 11 Aug 2011 15:30:06 +0900
Kazuhiro NISHIYAMA redmine@ruby-lang.org 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 8 years ago

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

2011年8月10日17:20 NARUSE, Yui naruse@airemix.jp:

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 naruse@airemix.jp

Updated by naruse (Yui NARUSE) over 8 years ago

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

On Thu, 11 Aug 2011 15:30:06 +0900
Kazuhiro NISHIYAMA redmine@ruby-lang.org 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 naruse@airemix.jp

Updated by mame (Yusuke Endoh) about 8 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 mame@tsg.ne.jp

Updated by naruse (Yui NARUSE) about 8 years ago

  • Status changed from Assigned to Rejected

これは reject します。

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

#15

Updated by naruse (Yui NARUSE) 4 months ago

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

Updated by naruse (Yui NARUSE) 4 months ago

  • Description updated (diff)
#17

Updated by naruse (Yui NARUSE) 4 months ago

  • Status changed from Rejected to Open
#18

Updated by naruse (Yui NARUSE) 4 months 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

Also available in: Atom PDF