天泣記

2012-12-13 (Thu)

#1

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


田中哲