SSL_read, SSL_write での SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE と、SSL_pending の話

TLS/SSL による通信をやりたい場合、OpenSSL を使えば簡単に実装することができる。
具体的には、recv(2), send(2) を直に発行するノリで、SSL_read(3), SSL_write(3) を使えばいい……と思っていたが、そうではないらしい。

ということで、調べたことをつらつら書いてみる。なお、以下は socket が non-blocking であることを想定して書いている。

それから、OpenSSL をそこまで読み込んだわけではないので、間違っているかもしれない。ツッコミ大歓迎!

SSL_read での SSL_ERROR_WANT_READ. SSL_write での SSL_ERROR_WANT_WRITE

データの送信を行おうとしたときに TCP/IP のバッファがいっぱいで積めない場合、send は EAGAIN でエラーする。
同様に SSL_write は SSL_ERROR_WANT_WRITE を返す。この時、socket が writable になったら、全く同じ引数で SSL_write を呼ばなければならない。


WARNING
When an SSL_write() operation has to be repeated because of
SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it must be repeated with
the same arguments.

When calling SSL_write() with num=0 bytes to be sent the behaviour is
undefined.


recv の場合は、TCP/IP のバッファが空であった場合に EAGAIN でエラーする。
同様に SSL_read は SSL_ERROR_WANT_READ でエラーする。この時 SSL_write と同じで、socket が readable になったら、全く同じ引数で SSL_read を呼ばなければならない。
ここで注意しなければならないのが、select(2) で readable だったとしても、SSL_read が SSL_ERROR_WANT_READ でエラーすることがあるという点。
TLS/SSL では、データはある程度ブロックに分けられた上で、暗号化されて送受信されているため、1ブロック分全部そろっていないとデコードできない。よって、中途半端にデータを受信した場合にも SSL_ERROR_WANT_READ が返るということになる。

SSL_read での SSL_ERROR_WANT_WRITE. SSL_write での SSL_ERROR_WANT_READ

ややこしい事に、先ほどとは逆の理由でエラーすることがある。
OpenSSL では、SSL_read, SSL_write の延長上でネゴシエーション処理が実行されることがあるために、read しようとしたのに WANT_WRITE, write しようとしたのに WANT_READ が返ってきてしまう。

よって、先程の仕様とあわせると、

  • SSL_ERROR_WANT_READ: readable になってから関数を再実行
  • SSL_ERROR_WANT_WRITE: writable になってから関数を再実行

しないといけない。

すなわち……

SSL_read SSL_write
SSL_ERROR_WANT_READ でエラー select(readable), SSL_read select(readable), SSL_write
SSL_ERROR_WANT_WRITE でエラー select(writable), SSL_read select(writable), SSL_write

これはメンドクサイですね…。

SSL_pending

更に、SSL_pending という物がある。
この関数は、ssl オブジェクトのバッファから block せずに読み取り可能な byte 数を返すものである。

ここで注意しなければならないのが、socket が readable ではない場合にも ssl オブジェクトのバッファにデータが乗っていることがあるということである。*1

これを考慮すると、select で readable か判定する前に SSL_pending で SSL_read *2可能かどうかチェックする必要性が見えてくる。

ただし、次の記載が man に有って、少々気にはなる。


BUGS
(snip)

Up to OpenSSL 0.9.6, SSL_pending() does not check if the record type of
pending data is application data.

*1:ネゴシエーション処理などの関係で、らしい。

*2:SSL_pending は受信バッファなので、SSL_write は無関係