SSL_MODE_AUTO_RETRYの設定
OpenSSLを使ったプログラミングをしていると、SSL_CTX_set_mode() / SSL_CTX_clear_mode()で指定できる SSL_MODE_AUTO_RETRY という設定があります。今回はこれがどのようなものなのかを説明します。
SSL_MODE_AUTO_RETRYは何をするのか
SSL_MODE_AUTO_RETRY は SSL_read()等のread系関数(*1)の動作に影響します。
TLS通信で扱うデータには、アプリケーションが送信したデータを暗号化した Application Data と Handshakeメッセージ等のTLSプロトコルが扱う非Application Data があります。非Application Data は SSL_read()/SSL_write() 等のI/O処理を行った際に、アプリケーションからは見えないように(透過的に)送受信されるようになっています。
SSL_read()は本来、アプリケーションが Application Data を読み込むために呼び出す関数ですが、この際、非Application Data を受信していた場合、非Application Dataの処理(TLSプロトコルの処理)も SSL_read() の中で一緒に行われます。 SSL_read()呼び出し時に非Application Data が処理された場合、SSL_read() はエラー(SSL_ERROR_WANT_READ)を返して、もう一度 read処理を行う必要があることを呼び出し側に示すことがあります。
SSL_ERROR_WANT_READは、今は渡せるデータがないので、後でもう一度readをしてくれという意味で、NonBlocking I/O におけるread()のEWOULDBLOCKに相当するものと考えてください。ここでは、「Application Dataを処理しようとしたが、非Application Dataがあったのでそちらを処理した。今回渡せるデータはない。もう一回readしてくれ」という意味になります。
SSL_MODE_AUTO_RETRYがオフの場合、SSL_read()等のread系関数は、非Application Dataを処理した場合、次のデータを読み込もうとはせずに、一旦、SSL_ERROR_WANT_READでエラーリターンします。
SSL_MODE_AUTO_RETRYをオンにしておくと、非Application Dataが処理された際、エラーリターンする代わりに、Application Dataを処理するまで後続のTLS Recordを処理しようとします。
SSL_MODE_AUTO_RETRYの挙動は上記のとおりなのですが、これが何のために必要なのかを理解するには、TLSでどのようにデータが流れているかを知る必要があります。そこで一旦話の腰を折って、TLSでどのようにデータが送信されているかを簡単に説明します。 openssl の SSL_read関連のmanを読む時にも何が書いてあるか理解しやすくなるはずです。
TLSでのデータの流れ
TLSではTCP層の上にTLS Recordレイヤー(TLS層)があり、そこにTLS Recordが流れます。 TLS Recordは図1のようになっており、ContentType,ProtocolVersion,Lengthのヘッダー情報の後にメッセージデータが続きます。

暗号化されたアプリケーションデータやTLSプロトコルのメッセージ(Handshakeメッセージ等)はこのTLS Recordに格納されて通信相手に伝達されます(図2)。

このページでは(opensslのmanにならって)、TLS Recordを大きく以下の二種類に分類します。
- Application Data:
アプリケーション間で送受信するデータ(ContentType:23のTLS Record) - 非Application Data:
TLSプロトコルで使用されるメッセージ(ContentType:23以外のTLS Record)。
Handshakeメッセージ等。
アプリケーションがデータを送信すると(図2右側)、TLSレイヤーで暗号化されて TLS Record(Application Data) に分割されて送信されます。 受信側ではTLSレイヤーで Application Data を受信すると復号されてアプリケーションに渡されます(図2)。
なお、復号は TLS Record の単位で行われます。TLS Record は TCP で伝達されています(*2)。TCPはストリーム型の通信なので、受信側で TLS Record 単位の受信になるとは限りません。TLS Record の途中までしか受信できていなかった場合は、残りのデータが届くまで アプリケーションに復号したデータを渡すことはできません。OpenSSLの場合、SSL_read()はこの状況ではブロックするか(Blocking I/O時)、SSL_ERROR_WANT_READを返します(NonBlocking I/O時)。
以上で説明は終わりですが、以下を覚えておいてください。
- データは TLS Record で送信される
- TLS Record には Application Data と非Application Data がある
- TLS Record は TCP で伝送されるので、受信側で TLS Record単位で受信できるとは限らない
SSL_MODE_AUTO_RETRYがなぜ必要か
TLS Record に関する説明をしたところで、これを踏まえて SSL_read() による受信時の動作をもう少し詳細にみていきます。
select()/poll() で I/O を多重化しているケースを考えます。この場合、アプリケーションは select()/poll() で TCPソケットの受信待ちをして、データを受信したら SSL_read() でデータを読み込みます(図3)。
図3中の OpenSSL の部分が図2の TLS層にあたります。 アプリケーションが SSL_read() を呼び出すと、OpenSSL は TCPソケットから TLS Record を受信して、Application Data ならデータを復号してアプリケーション側に渡します。
select()/poll() で監視するTCPソケットはKernel内のTCPスタックで、SSL_read()で扱うTLS接続はTLS(OpenSSL)のレイヤで、それぞれ扱うレイヤーが異なることに注意してください。

一方、TLS の Renegotiation が発生して Handshakeメッセージ(非Application Data)の TLS Record が来ていた場合は、図4のようになります。

アプリケーションが SSL_read() を呼び出して、OpenSSL が TLS Record を処理しようとしたところ、非Application Data(Handshake Message)なので、OpenSSL はプロトコル処理(ハンドシェイク)を行います(*3)。
ハンドシェイク完了後に、SSL_MODE_AUTO_RETRY がオフなら SSL_read() は SSL_ERROR_WANT_READ を返します。アプリケーションは SSL_ERROR_WANT_READ を受けて、自分でリトライ(再度select()/poll())する必要があります。
「なぜ、非Application Data だった場合、次の TLS Record を自動で処理してくれないんだ?」と思うかもしれません。Blocking I/O環境で、次の TLS Record を自動で処理しようとした場合、Record がまだなければ受信待ちで Block します(*4)。select()/poll() で I/O を多重化していた場合は、select()/poll() が受信可能と返してきたから SSL_read() したのに read操作が Block してしまい、他の I/O を止めてしまうという状況になってしまうのです。このため、非Application Data 処理時は一旦 SSL_ERROR_WANT_READ を返して、アプリケーション側で再度 select()/poll() させることで、意図しない Block を避けるようになっています。
select()/poll() は TCP層を扱うのに対し、SSL_read() は TLS層を扱います。それぞれ扱っているレイヤーが異なるのが、わざわざ SSL_ERROR_WANT_READ を返すようになっている理由です。
一方、select()/poll() は使わずに単純に逐次的に read/write するだけのアプリケーションでは read/write 操作が Block しても問題ありません。 その場合は、SSL_MODE_AUTO_RETRY をオンにすれば、SSL_read() は非Application Dataを処理してもエラーリターンせず、Application Data を処理するまで、後続の TLS Record を処理してくれます(Auto Retry)。 この状況では、SSL_ERROR_WANT_READ のリターンをケアしなくてよくなるので SSL_MODE_AUTO_RETRY をオンにしておいた方が便利でしょう。
まとめると、SSL_MODE_AUTO_RETRY は何のために存在するかという問いについては、
read操作の Block を許容できる環境においては、SSL_MODE_AUTO_RETRY をオンにすれば、read系関数は SSL_ERROR_WANT_READ を返すことはなくなるので、リトライ処理を自分で書かなくて便利である(TCPソケットの Block I/Oと同じ感じで read/write 処理をかける)。
ということになります。
まとめ
ここまでBlocking I/Oのケースばかりで、NonBlocking I/Oとの組み合わせには触れていなかったので、NonBlocking I/Oの時も含めてSSL_read()の動作を表1にまとめておきます。
表1 SSL_read()の動作
SSL_MODE_AUTO_RETRY Off | SSL_MODE_AUTO_RETRY On | |
---|---|---|
Blocking I/O |
以下の条件でreturnする。
非Application Dataを処理した場合は、SSL_ERROR_WANT_READ が返る。 処理すべきTLS Recordがない時はBlockする。 SSL_ERROR_WANT_WRITEが返ることはない(このケースではWriteはBlockするので)。 |
以下の条件でreturnする。
非Application Dataを処理した場合は、SSL_ERROR_WANT_READでリターンせず、 Application Dataを処理できるまで次のTLS Recordを処理していく(Auto Retry)。 処理すべきTLS Recordがない/なくなった場合はBlockする。 SSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE が返ることはない。 |
NonBlocking I/O |
I/O操作を完了できない状況ではBlockせずにSSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE(*5) を返す。 非Application Dataを処理した場合は、SSL_ERROR_WANT_READ が返る。 |
I/O操作を完了できない状況ではBlockせずにSSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE(*5) を返す。 非Application Dataを処理した場合は、SSL_ERROR_WANT_READでリターンせず、 処理できるTLS RecordがなくなるかApplication Dataが処理されるまで、非Application Dataを処理する(Auto Retry)。 |
Blocking / NonBlocking 関係なく、SSL_MODE_AUTO_RETRY の動作としては以下のとおりです。
SSL_MODE_AUTO_RETRYがオフの場合、SSL_read()等のread系関数は、非Application Dataを処理した場合、次のデータを読み込もうとはせずに、一旦、SSL_ERROR_WANT_READでエラーリターンします。
SSL_MODE_AUTO_RETRYをオンにしておくと、非Application Dataが処理された際、エラーリターンする代わりに、Application Dataを処理するまで後続のTLS Recordを処理しようとします。
Auto Retry で後続の TLS Record を処理しようとして Record を読めなかった際に、Blocking I/O と NonBlocking I/O で動作が異なります。Blocking I/O なら Block して待ち、NonBlocking I/O なら SSL_ERROR_WANT_READ を返します。
SSL_MODE_AUTO_RETRY はオン/オフどちらを選択すればいいかについては、以下のような基準になります。
ブロッキングI/Oの場合、select()/poll() で I/O の多重化を行っている場合は、SSL_MODE_AUTO_RETRY をオフにしておく必要があります。
オンになっていると、SSL_read() は非Application Data を受けた時に、Auto Retry により Application Data を処理するまで後続の TLS Record を処理しようとしますが、後続の Record がまだない場合、受信待ちで Block してしまいます。このため、select()/poll() で受信可能と言われたにも関わらず、SSL_read() したら Block してしまい、多重化している他のI/Oを処理できなくなってしまう状況が発生します。これを防ぐために、SSL_MODE_AUTO_RETRY はオフにして SSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE のエラーが返った場合、自分でリトライ(再度、select()/poll() を呼び出す)する必要があります。
一方、select()/poll() を使わずに逐次的に read/write するだけなら SSL_MODE_AUTO_RETRY をオンにしておけばよいでしょう。このケースでは、I/O が Block しても問題ない作りのはずですし、SSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE をケアする必要がなくなるので、リトライ処理を書く必要がなく楽になります。
ノンブロッキングI/Oの場合は、どちらにせよ SSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE を処理しないといけないので、SSL_MODE_AUTO_RETRY はどちらでもいいでしょう。
以上で SSL_MODE_AUTO_RETRY に関する解説は終わりです。
おまけ: SSL_MODE_AUTO_RETRY のデフォルト設定
SSL_MODE_AUTO_RETRY のデフォルト設定はもともとオフでしたが、Ver. 1.1.1からオンに変わったようです。
*) SSL_MODE_AUTO_RETRY is enabled by default. Applications that use blocking I/O in combination with something like select() or poll() will hang. This can be turned off again using SSL_CTX_clear_mode(). Many applications do not properly handle non-application data records, and TLS 1.3 sends more of such records. Setting SSL_MODE_AUTO_RETRY works around the problems in those applications, but can also break some. It's recommended to read the manpages about SSL_read(), SSL_write(), SSL_get_error(), SSL_shutdown(), SSL_CTX_set_mode() and SSL_CTX_set_read_ahead() again.
opensslのCHANGESファイルより引用
- 多くのアプリケーションは非Application Data の処理を適切に行っていない。
- (OpenSSL 1.1.1からサポートされた)TLS1.3 では多くの非Application Data を送信するようになった。
- SSL_MODE_AUTO_RETRY がオフのままだと、これらのアプリケーションでは SSL_ERROR_WANT_READ が頻発して、まともに動かなくなるだろう。
という理由からデフォルトをオンに変更したようです。SSL_MODE_AUTO_RETRYを明示的に指定していないアプリケーションでは、OpenSSLのバージョンによって挙動が変わるかもしれません。
https://wiki.openssl.org/index.php/TLS1.3の Non-application data records も参考になります。
[参考]
- man 3ssl SSL_CTX_set_mode
- man 3ssl SSL_get_error
- man 3ssl SSL_read
- man 3ssl SSL_write
(*1) man SSL_read の記述に従って、SSL_read(), SSL_read_ex(), SSL_peek(), SSL_peek_ex()をread系関数とします。このページのSSL_read()に関する記述は他のread系関数に関しても同様です。
(*2) トランスポート層にUDPを使うDTLSというものもありますがここでは置いておきます。
(*3) 繰り返しになりますが、TLSプロトコルの処理はread/write時に透過的に行われます
(*4) Application DataのTLS Recordが途中までしか受信できていない場合も、データを復号できないので同じくBlockします。
(*5) RenegotiationでHandshakeが発生する可能性があり、その場合、SSL_read()内でwrite操作も発生します。NonBlocking I/Oでは、このwirte操作でBufferがいっぱいで書き込みできなかった場合はSSL_ERROR_WANT_WRITEが発生します。read操作なのに SSL_ERROR_WANT_WRITE も発生しうるというのがややこしいところです。ただし、SSL_read()でSSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE いずれが返った場合もやるべきことは同じです(再度selectしたり時間をおいて再度SSL_read()する)。
投稿日:2022/03/29 16:20