Project

General

Profile

Actions

Bug #7100

closed

WEBrick::HTTPServer.new で BindAddress を指定しない場合に必ず警告が記録される

Added by sho-h (Sho Hashimoto) about 12 years ago. Updated over 11 years ago.

Status:
Closed
Target version:
ruby -v:
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]
Backport:
[ruby-dev:46189]

Description

=begin
以下のようにすると必ず警告が記録されるようです。

$ ruby -v -r webrick -e 'WEBrick::HTTPServer.new(Port: 3000)'
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]
[2012-09-04 19:20:48] INFO WEBrick 1.3.1
[2012-09-04 19:20:48] INFO ruby 1.9.3 (2012-04-20) [x86_64-linux]
[2012-09-04 19:20:48] WARN TCPServer Error: Address already in use - bind(2)

1.8 では記録されませんでした。1.9.1 以降は記録されました。

lib/webrick/utils.rb の WEBrick::Utils#create_listeners が以下のようになっており、

res = Socket::getaddrinfo(address, port,
Socket::AF_UNSPEC, # address family
Socket::SOCK_STREAM, # socket type
0, # protocol
Socket::AI_PASSIVE) # flag
last_error = nil
sockets = []
res.each{|ai|
begin
logger.debug("TCPServer.new(#{ai[3]}, #{port})") if logger
sock = TCPServer.new(ai[3], port)
...

Socket.getaddrinfo が 1.9 から複数値を返すからのようです。1.8.7 だと 0.0.0.0 の方だけでした。

$ ruby -v -r pp -r socket -e 'pp Socket::getaddrinfo(nil, 3000, Socket::AF_UNSPEC, Socket::SOCK_STREAM, 0, Socket::AI_PASSIVE)'
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]
[["AF_INET", 3000, "0.0.0.0", "0.0.0.0", 2, 1, 6],
["AF_INET6", 3000, "::", "::", 10, 1, 6]]

WEBrick::Utils#create_listeners のコメントとマッチしなくなるデメリットがあるのですが、config[:BindAddress] のデフォルト値を 0.0.0.0 か :: のどちらかにしてしまうのはいかがでしょう。
=end


Files

Updated by ChultOch5 (Sho Morita) about 12 years ago

=begin
私のところでも同様の症状が出ています。そういった警告が出る場合、WEBrick サーバーに IPv6 でアクセスできなくなってしまいます。また、環境によっては http://localhost:3000/ のように localhost を指定してもアクセス不能になってしまいます。(:BindAddress を明示的に指定すれば問題ないのですけれども…)

WEBrick のドキュメントには :BindAddress に関して

デフォルトの nil や "0.0.0.0", "::" などを指定した場合は使用可能なすべてのネットワークインターフェースに対して listen を開始します。

と書いてあるものの、実際には

  • nil を指定すると使用可能なすべての IPv4 および IPv6 ネットワークインターフェースに対して listen する。
  • "0.0.0.0" を指定すると使用可能なすべての IPv4 ネットワークインターフェースに対してのみ listen する。
  • "::" を指定すると使用可能なすべての IPv6 ネットワークインターフェースに対してのみ listen する。(一部システムでは nil と指定したのと同様に、すべての IPv4 と IPv6 ネットワークインターフェースに対して listen する)

となります。

一部のシステム(Linuxなど)では、IPv6 ワイルドカードアドレスである :: を bind すると、IPv6 ネットワークインターフェースだけではなく、IPv4 ネットワークインターフェースも bind されます。0.0.0.0 と :: を両方 bind しようとすると、後から bind した方が Address already in use(EADDRINUSE) エラーとなり失敗するため、IPv6 でのアクセスが行えなくなってしまいます。

などによると、Ruby 1.9.2 で Socket にたくさん機能が追加され、IPv6 問題がだいぶ改善されたようです。そこで追加された Socket#ipv6only! を呼び出すと IPV6_V6ONLY ソケットオプションが有効になり、0.0.0.0 と :: の両方を bind する事ができるようになります。

しかし、現在 WEBrick が使用している TCPServer は従来からある API で、IPV6_V6ONLY ソケットオプションをセットしません。新しい API である Socket.tcp_server_sockets などは、必要に応じて Socket#ipv6only! を呼び出してくれます。

ですので、TCPServer.new ではなく Socket.tcp_server_sockets を使うようにするのが良いかと思ったのですが、TCPServer.new のオブジェクトと Socket.tcp_server_sockets のオブジェクトは一部互換性の無い部分があり、置き換えてしまうと WEBrick を利用する既存のソフトウェアで互換性問題が起こるのではないか心配です。

そこで、互換性の問題が起こらないように TCPServer を使うのを維持しつつ問題を修正しようとすると、

  • :BindAddress == nil である場合に、:: を先に bind し、0.0.0.0 を後から bind (その際 EADDRINUSE が起きても無視)する。

というようにすれば良いのではないかと思います。一応パッチを作ってみました。

Index: lib/webrick/utils.rb

--- lib/webrick/utils.rb (revision 37168)
+++ lib/webrick/utils.rb (working copy)
@@ -79,6 +79,15 @@
Socket::AI_PASSIVE) # flag
last_error = nil
sockets = []

  •  # If address == nil, Socket.getaddrinfo returns 2 entries, the
    
  •  # IPv4 wildcard address "0.0.0.0" and the IPv6 wildcard address "::".
    
  •  # On some systems, if you try to bind for both "0.0.0.0" and "::",
    
  •  # the later one will fails.
    
  •  # To workaround such behaviour, try bind for "::" first, then
    
  •  # bind "0.0.0.0" and ignore EADDRINUSE error.
    
  •  if address.nil?
    
  •    res = res.sort_by{|i| i[4]}.reverse
    
  •  end
     res.each{|ai|
       begin
         logger.debug("TCPServer.new(#{ai[3]}, #{port})") if logger
    

@@ -87,7 +96,9 @@
Utils::set_close_on_exec(sock)
sockets << sock
rescue => ex

  •      logger.warn("TCPServer Error: #{ex}") if logger
    
  •      logger.warn("TCPServer Error: #{ex}") if logger and !(address.nil? and
    
  •                                                            ai[4] == Socket::AF_INET and
    
  •                                                            Errno::EADDRINUSE === ex)
         last_error  = ex
       end
     }
    

ちなみに、根本的な解決にはなりませんが、

sysctl net.ipv6.bindv6only=1

とすると、IPV6_V6ONLY ソケットオプションがデフォルトで有効になり、:BindAddress が nil でも警告は出なくなります。

=end

Updated by mame (Yusuke Endoh) almost 12 years ago

  • Status changed from Open to Assigned
  • Assignee set to akr (Akira Tanaka)
  • Target version set to 2.0.0

akr さん、どう思われますか?

--
Yusuke Endoh

Updated by akr (Akira Tanaka) almost 12 years ago

2012/11/5 mame (Yusuke Endoh) :

akr さん、どう思われますか?

IPv4-mapped IPv6 address はプラットフォームによって利用できたりできなかったりするので、
どのプラットフォームでも利用しないで動作するようにするのが動作が一貫してよいと思います。

私は、長期的には、TCPServer とかを使うのはやめて、
Socket に移行して欲しいと思っていますが、
互換性の問題を無視することもできませんよね。

というふたつの点を考えると、Socket.tcp_server_sockets でソケットを作って、
そこから TCPServer.for_fd で TCPServer のインスタンスを作るあたりかなぁ、と
思います。

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

Updated by akr (Akira Tanaka) almost 12 years ago

webrick-dont-use-ipv4-mapped-ipv6-address.patch みたいなかんじかなぁ。

Updated by mame (Yusuke Endoh) over 11 years ago

  • Target version changed from 2.0.0 to 2.6

ちょっと今からだと怖すぎるのと、BindAddress を指定するという workaround があるようなので、
すみませんが一旦 next minor に。
2.0.0-pXXX で直すかどうかは nagachika さんにお任せします。

--
Yusuke Endoh

Actions #6

Updated by akr (Akira Tanaka) over 11 years ago

  • Status changed from Assigned to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r39551.
Sho, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0