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 でのアクセスが行えなくなってしまいます。
などによると、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
“::”.
“::”,
-
# 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
!(address.nil? and
== 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 を指定しない場合に必ず警告が記録される
Author: sho-h (Sho H.)
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