From: "ChultOch5 (Sho Morita)" Date: 2012-10-13T00:07:37+09:00 Subject: [ruby-dev:46217] [ruby-trunk - Bug #7100] WEBrick::HTTPServer.new で BindAddress を指定しない場合に必ず警告が記録される Issue #7100 has been updated by ChultOch5 (Sho Morita). =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 でのアクセスが行えなくなってしまいます。 * http://www.a-k-r.org/pub/socket-rubykaigi2009.pdf * http://codezine.jp/article/detail/5395 * NEWS-1.9.2 などによると、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 ---------------------------------------- Bug #7100: WEBrick::HTTPServer.new で BindAddress を指定しない場合に必ず警告が記録される https://bugs.ruby-lang.org/issues/7100#change-30447 Author: sho-h (Sho Hashimoto) Status: Open Priority: Low Assignee: Category: Target version: ruby -v: ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux] =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 -- http://bugs.ruby-lang.org/