ふと、ソケットペア上で SSL を使えるか試してみたところ、なんとかできたのだが、通信終了時の挙動が不審である。
以下のように、先に SSL を close したほうのソケットになにかデータが残るようだ。(ここで close するというのは SSL のレベルであって、ソケット (fd) は close していない。そのためソケットレベルでの読み書きはできて、読み出すと何か読み出せる、ということ。)
% cat tst.rb require 'socket' require 'openssl' def ssl_pair(so1, so2) # ADH, Anonymous Diffie-Hellman, is used # because it doesn't need certificates. th = Thread.new { ctx2 = OpenSSL::SSL::SSLContext.new ctx2.ciphers = 'ADH' ctx2.tmp_dh_callback = proc { OpenSSL::PKey::DH.new(128) } ss2 = OpenSSL::SSL::SSLSocket.new(so2, ctx2) ss2.accept ss2 } ctx1 = OpenSSL::SSL::SSLContext.new ctx1.ciphers = 'ADH' ss1 = OpenSSL::SSL::SSLSocket.new(so1, ctx1) ss1.connect ss2 = th.value return [ss1, ss2] ensure if th && th.alive? th.kill th.join end end UNIXSocket.pair {|so1, so2| 10.times {|i| ss1, ss2 = ssl_pair(so1, so2) n = 2**17 th = Thread.new { ss2.read(n) } ss1.print "x"*n th.value if i.even? print "close ss2 first." ss2.close; ss1.close else print "close ss1 first." ss1.close; ss2.close end if IO.select([so1], nil, nil, 0) len = so1.readpartial(4096).bytesize print " so1 has data: #{len}bytes." end if IO.select([so2], nil, nil, 0) len = so2.readpartial(4096).bytesize print " so2 has data: #{len}bytes." end puts } } % ruby tst.rb close ss2 first. so2 has data: 37bytes. close ss1 first. so1 has data: 37bytes. close ss2 first. so2 has data: 37bytes. close ss1 first. so1 has data: 37bytes. close ss2 first. so2 has data: 37bytes. close ss1 first. so1 has data: 37bytes. close ss2 first. so2 has data: 37bytes. close ss1 first. so1 has data: 37bytes. close ss2 first. so2 has data: 37bytes. close ss1 first. so1 has data: 37bytes.
そもそも SSL で通信終了ってどうやって扱うんだっけ、と RFC 5246 (TLS 1.2) を調べると、close_notify という Alert を送ることで通信先に EOF を伝えるようだ。(通信のどちら側でも送ってよい。)
そうすると、先に close したほうは相手からの close_notify を受け取らずに終わっているということかな。close メソッドを呼んだ後はそのオブジェクトはなんのメソッドも呼んでいないので、まぁそれはそうなるか。
データが残らないようにきれいに終わらせるには、両側とも close_notify をちゃんと受け取ってから終わらないといけない。ということは、すくなくともどちらか一方は close_notify を送った後に相手からの close_notify を受け取るようにしないといけない。(両方が相手からの close_notify を受け取った後に close_notify を送るとすると、どちらもいつまでも close_notify を送れないので通信を終了できない。)
ところが、close メソッドだと、close_notify を送った後、相手からの close_notify を受け取らずに終わってしまうため、データが残るということか。そうすると、close_notify を送っても SSL は終了しない方法が必要で、そのために SSL_shutdown() 関数があって、でも Ruby の openssl では表に出てなくて、[ruby-dev:45350] [ruby-trunk - Feature #6133] という話になるのか。
[latest]