ふと、ソケットペア上で 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]