Home > ブログ > SSL_MODE_AUTO_RETRYの設定

ブログ

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 Recordの構造
図1 TLS Recordの構造

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

TLS通信
図2 TLS通信

このページでは(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)のレイヤで、それぞれ扱うレイヤーが異なることに注意してください。

受信処理の動
図3 受信処理の動作

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

xxxx
図4 非Application Data処理時(SSL_MODE_AUTO_RETRYオフ)

アプリケーションが 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のread操作完了時
  • エラー発生時
  • 非Application Dataを処理した時

非Application Dataを処理した場合は、SSL_ERROR_WANT_READ が返る。

処理すべきTLS Recordがない時はBlockする。

SSL_ERROR_WANT_WRITEが返ることはない(このケースではWriteはBlockするので)。

以下の条件でreturnする。

  • Application Dataのread操作完了時
  • エラー発生時

非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) を返す。
処理すべきTLS Recordがない時はSSL_ERROR_WANT_READ。

非Application Dataを処理した場合は、SSL_ERROR_WANT_READ が返る。

I/O操作を完了できない状況ではBlockせずにSSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE(*5) を返す。
処理すべきTLS Recordがない時はSSL_ERROR_WANT_READ。

非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

タグ: ネットワーク C OpenSSL

ネットワーク関連のソフトウェア開発でしたらおまかせください。
詳細はこちら

Top

アーカイブ

タグ

Server (28) 作業実績 (21) PHP (19) ネットワーク (17) プログラミング (15) OpenSSL (10) C (8) C++ (8) PHP関連更新作業 (8) EC-CUBE (7) Webアプリ (7) laravel (6) 書籍 (5) Nginx (5) Linux (5) AWS (4) Vue.js (4) JavaScript (4) 与太話 (4) Rust (3) Symfony (2) お知らせ (2) Golang (2) OSS (1) MySQL (1) デモ (1) CreateJS (1) Apache (1)