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 (17) プログラミング (15) 作業実績 (15) PHP (10) ネットワーク (9) C (7) C++ (7) Nginx (5) OpenSSL (5) Webアプリ (5) Linux (4) laravel (4) EC-CUBE (4) 書籍 (4) AWS (3) JavaScript (3) Rust (3) Golang (2) Vue.js (2) デモ (1) Apache (1) お知らせ (1) Symfony (1) MySQL (1) CreateJS (1) OSS (1)