はじめに
概要
本ドキュメントは、OpenSSLのSSL_get_error関数について分かりやすく解説するためのシリーズ記事の第1章です。SSL_get_errorが何をする関数か、いつ使うか、どのように結果を読み取るかを丁寧に説明します。
目的
本章では全体の位置づけと読み方、対象読者、前提知識を示します。以降の章で具体的な使い方やエラー対応例を順に説明します。
対象読者
- OpenSSLを使った通信プログラムを作るエンジニア
- SSL/TLSの動作確認や障害対応を行う方
初心者でも読めるように、専門用語は必要最小限にし具体例で補います。
前提知識
C言語や基本的なソケット通信の知識があると読みやすいですが、必須ではありません。
読み進め方
第2章で概念を説明し、第3章以降で実践的なコード例やトラブルシューティングを扱います。章ごとに独立して参照できますので必要な箇所から読むことも可能です。
SSL_get_errorとは何か
概要
SSL_get_errorは、OpenSSLなどでSSL/TLS通信中に発生した問題の種類を判定するための関数です。たとえばSSL_connectやSSL_read、SSL_writeがエラーや異常値を返した場合に、その戻り値とerrno(またはソケットの状態)を元に、詳しい原因を返します。単なる「失敗」ではなく、原因の分類ができます。
なぜ必要か
エラーの種類を知ることで、適切な対応ができます。たとえば一時的に読み込み待ちの状態か、リモートが正しく切断したか、致命的なプロトコルエラーかを判断できます。これにより再試行・再接続・ログ出力の方針を分けられます。
返される主な情報と意味
- SSL_ERROR_NONE: 問題なし(成功扱い)。
- SSL_ERROR_ZERO_RETURN: 正常なシャットダウン(リモートがTLSを終了)。
- SSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE: 非ブロッキングで操作が途中。読み書き可能になるまで待つ必要あり。
- SSL_ERROR_SYSCALL: システムコールのエラー。詳細はerrnoやソケットの状態を確認。
- SSL_ERROR_SSL: OpenSSL内部のプロトコルエラーや証明書処理の失敗。
イメージ例
SSL_readが-1を返したら、すぐに再試行せずSSL_get_errorで原因を判定します。WANT_READなら読み込み可能になるまで待ち、ZERO_RETURNなら接続終了として処理します。
注意点
返り値だけで全てを判断せず、必要に応じてerrnoやログを併せて確認してください。非ブロッキングソケットではWANT系の対応を必ず行ってください。
基本的な使い方・サンプルコード
概要
SSL_get_errorは、SSL_readやSSL_write、SSL_connect、SSL_acceptなどが0以下を返したときに呼び出します。戻り値(ret)とsslを渡すと、エラーの種類を整数で返します。返り値に応じて再試行・切断などの処理を行います。
基本的な流れ
- SSL/TLS関数を呼ぶ(例: SSL_read)。
- retが0以下ならSSL_get_error(ssl, ret)で原因を取得。
- エラー種別に応じた処理(再試行、待機、終了)を行う。
サンプル:SSL_readの基本パターン
int ret = SSL_read(ssl, buf, sizeof(buf));
if (ret > 0) {
// 正常に受信
} else {
int err = SSL_get_error(ssl, ret);
switch (err) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
// 非同期なら再試行(読み書き可能になるまで待つ)
break;
case SSL_ERROR_ZERO_RETURN:
// 相手が正常に接続を閉じた
break;
case SSL_ERROR_SYSCALL:
case SSL_ERROR_SSL:
// 致命的なエラー。詳細はERR_get_errorで取得
break;
}
}
サンプル:SSL_writeの基本パターン
int ret = SSL_write(ssl, data, len);
if (ret <= 0) {
int err = SSL_get_error(ssl, ret);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
// 再送処理(ノンブロッキングの場合)
} else {
// その他は切断やログ出し
}
}
ノンブロッキング(非同期)での注意点
- WANT_READ/WRITEが返るのは正常な状態です。ソケットの状態に応じて再試行します。
- SSLレイヤとソケットの状態がずれることがあるため、read/writeの両方で再試行ロジックを用意します。
ポイント
- SSL_ERROR_ZERO_RETURNは正常終了の合図です。
- SSL_ERROR_SSLやSSL_ERROR_SYSCALLは詳細をERR_get_errorやerrnoで確認します。
- 再試行ループは無限ループにならないようタイムアウトや最大試行回数を設けます。
代表的なエラーコードと意味
SSL_get_errorが返す主な値について、分かりやすく説明します。実際に何を確認すれば良いかも併せて触れます。
SSL_ERROR_NONE
- 意味:エラーは発生していません。操作は正常に完了しました。
- 確認点:関数は期待通りの戻り値を返しています。次の処理へ進んでください。
SSL_ERROR_ZERO_RETURN
- 意味:相手が正常に接続を閉じた(close_notifyを受信)ことを示します。
- 確認点:これ以上データは来ません。接続をクリーンに閉じる必要があります。
SSL_ERROR_WANT_READ / SSL_ERROR_WANT_WRITE
- 意味:ノンブロッキングや非同期処理で一時的に操作を再試行する必要があることを示します。
- 確認点:ソケットがブロッキングでない場合、読み取り/書き込みの準備が整うまで待機して再試行します。
- 対処:select/pollやイベントループで準備を待ち、同じSSL関数を再度呼び出します。
SSL_ERROR_SYSCALL
- 意味:システムコールレベル(read/writeなど)でエラーが発生しました。errnoやログを確認します。
- 確認点:errnoが設定されているか、通信相手の切断やネットワーク障害がないかを確認します。
- 例:FortiClient VPNのログで「SSL_get_error(): 6」と出る場合、OpenSSLではSSL_ERROR_SYSCALLを示し、システムコールの問題です。
SSL_ERROR_SSL
- 意味:OpenSSLのプロトコル処理で致命的なエラーが発生しました。ライブラリ内部で詳細エラーが記録されています。
- 確認点:ERR_get_errorやERR_error_stringでOpenSSLのエラースタックを取得し、原因を特定します。
SSL_ERROR_WANT_CONNECT / SSL_ERROR_WANT_ACCEPT
- 意味:非同期の接続やaccept処理でリトライが必要な珍しいケースです。
- 確認点:非同期接続の状態管理を確認し、準備が整ったら再試行してください。
各エラーでは、まずログとerrno(システムエラー)やOpenSSLのエラースタックを確認することが重要です。これにより原因の絞り込みが速くなります。
トラブルシューティング・実際の活用例
基本的なハンドリング(C++の実装イメージ)
SSL_read/SSL_writeでエラーが返ったらまずSSL_get_errorで分類します。WANT_READ/WANT_WRITEは一時的エラーなのでリトライし、SSL_ERROR_SSLやSSL_ERROR_SYSCALLは致命的処理を行います。
int n = SSL_read(ssl, buf, len);
int err = SSL_get_error(ssl, n);
switch(err){
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
// 非ブロッキングならselect/pollで再試行
retry(); break;
case SSL_ERROR_ZERO_RETURN:
// 相手が正常にシャットダウン
close_conn(); break;
case SSL_ERROR_SYSCALL:
// errno と OpenSSLのエラーキューを確認
log(errno, ERR_get_error()); close_conn(); break;
case SSL_ERROR_SSL:
// プロトコル/証明書の問題
ERR_print_errors_fp(logfp); close_conn(); break;
}
実際のトラブル例と対処法
- 「SSL_get_error(): 6」が出る場合
- これはSSL_ERROR_ZERO_RETURN(リモートが正常終了)を示します。途中でFINが来て接続が切れた可能性が高いので、tcpdumpやWiresharkでTCPのFIN/RSTを確認してください。中継するファイアウォールやVPN(例:FortiClient)の切断も疑ってください。
- SSL_ERROR_SYSCALLやSSL_ERROR_SSLのとき
- errnoを確認し、OpenSSLのエラーキュー(ERR_get_error/ERR_print_errors_fp)を出力します。証明書検証失敗や共通暗号の不一致が原因のことが多いです。
調査の流れ(実務的な順)
- クライアント/サーバのログとerrnoを確認
- OpenSSLのエラーキューを出力
- openssl s_clientで手動接続してハンドシェイク確認
- tcpdump/wiresharkでパケットを確認(FIN/RSTや中継の干渉)
- ファイアウォール、プロキシ、VPNの設定を点検
これらを組み合わせると原因の切り分けが早まります。ログを丁寧に残し、再現手順を確保することが最も有効です。
関連技術・注意点
他のSSL/TLS実装との違い
OpenSSL特有のAPIであるため、GnuTLSやWindowsのSChannelなどでは関数名やエラー扱いが異なります。例えば、同じ読み書きの中断でも戻り値やエラー取得手順が違うので、他実装へ移植する際はドキュメントを必ず確認してください。
非同期I/O/ノンブロッキングソケットの扱い
SSL_read/SSL_writeが失敗してSSL_get_errorがSSL_ERROR_WANT_READ/SSL_ERROR_WANT_WRITEを返すのはよくある挙動です。非ブロッキングではソケットが読み取り可能/書き込み可能になるまで待ってから同じ関数を再実行してください。select/poll/epollのようなイベント待ちと組み合わせると安定します。
SSL_ERROR_SYSCALL と SSL_ERROR_SSL の対処
これらが出たらまずシステムエラー(errno)を確認し、次にOpenSSLのエラースタックを取得します。ERR_print_errors_fpやERR_error_stringを使うと詳しい原因が分かります。時にはエラースタックが空でerrnoのみが意味を持つ場合もあります。
スレッドとバージョン差異
OpenSSL 1.1以降は内部でロックを管理しますが、古いバージョンでは明示的な初期化が必要でした。ライブラリのバージョン差で挙動が変わることがあるため、利用しているバージョンのマニュアルに従ってください。
デバッグのヒント
- ログにSSL_get_errorの戻り値とerrnoを併記する
- ERR_print_errors_fpでエラースタックを出す
- 非同期処理では状態遷移(読み待ち→書き待ち)を可視化する
運用時は実装固有の挙動に注意して、まずは小さなテスト環境で挙動確認を行ってください。
まとめ
SSL_get_errorは、SSL/TLS通信で発生した問題を正しく識別し、適切に対処するための重要な道具です。本章では、これまでの要点をやさしく整理します。
- 要点の確認
-
SSL_read/SSL_writeの戻り値を確認し、SSL_get_errorで原因を判定します。SSL_ERROR_WANT_READ/WRITEは再試行、SSL_ERROR_ZERO_RETURNは相手の正常終了、SSL_ERROR_SYSCALLは低レベルのI/O問題、SSL_ERROR_SSLはライブラリ内のエラーを示します。
-
実践的なチェックリスト
- 戻り値とエラーコードを必ずログに残す。
- 非ブロッキングソケットではWANT_READ/WRITEに対応するループを実装する。
- 証明書やホスト名の検証を怠らない。
-
問題時はopenssl s_clientで接続を確認する。
-
運用上の注意
- 詳細なデバッグにはERR_print_errors_fpやERR_error_stringを活用してください。ライブラリや証明書の更新も定期的に行うと安心です。
最後に、正しいチェックと丁寧なログが信頼性の高いSSL/TLS通信を支えます。ひとつひとつのエラーに対して適切に対処すると、安定した通信が実現します。












