仙石浩明の日記

2006年6月2日

SSL_pending

SSL_pending をマニュアルで調べると、

SSL_pending - obtain number of readable bytes buffered in an SSL object

SSL_pending() returns the number of bytes which are available inside ssl for immediate read.

と書いてある。つまり OpenSSL ライブラリ内に受信可能データがある場合、 そのデータバイト数を返す関数である。

なぜこんな関数が必要かというと、 データを送信しようとして SSL_write を呼び出したときも、 TCP/IP レベルでは送信だけでなく、受信も行なわれるからだ。 SSL のような暗号通信の場合、 送信は「垂れ流し」では済まず、ハンドシェークを行なう必要があるからだが、 この時、受信しようと思っていなかったデータ、 つまり通信相手が送信したデータまで読み込んでしまう場合がある。

すると、SSL_write を呼んでいるのに、 OpenSSL の受信バッファに、意図せずデータが溜まってしまう。 こうなってしまうと、select(2) や epoll(2) では検知できない。 select(2) や epoll(2) は、 I/O レベルでの受信データの有無を調べるシステムコールであり、 それより上のレベルである OpenSSL ライブラリの受信バッファのことは 関知しないからだ。

stone では、SSL_write で全てのデータを送り終わったとき、 SSL_pending を呼び出して受信すべきデータがないか確認している。 これを行なっているのが、 中継元/中継先との送受信を行なう stone の中核関数 doReadWritePair である。

select 版 stone の場合、 doReadWritePair を呼び出すのは doReadWrite である。 この関数は、 中継元/中継先との通信を行なうソケットディスクリプタのみを監視する select ループである。 epoll 版と異なり、select 版では、 select で監視するソケットの数が増えるとパフォーマンスが落ちるので、 送受信が継続しているときはメインの select ループとは別のスレッドを作成して、 パフォーマンスの低下を回避している。 送受信が途絶える (0.1 秒以上送受信が行なわれない) と、 この doReadWrite select ループを抜け、スレッドを終了して、 メイン select ループでソケットを監視する状態に戻る。

stone 2.3a (正確に言うと、stone.c 2.2.2.6 ~ 2.2.2.23) では、 SSL_pending で受信すべきデータを検知したとき、 SSL_read を行なってデータを受信した直後に、 この doReadWrite select ループを抜けてしまっていた。 本来なら、データを受信したのだから 直ちにそれを中継するために送信しなければならないのであるが、 ループを抜けてしまったために、いったんスレッドを終了し、 メイン select ループで送信可能か確認し、 再びスレッドを生成して doReadWrite select ループに入る、 という無駄が生じてしまっていた。

したがって、SSL_pending でデータが検知されることが多発するような場合は、 転送速度が著しく遅くなる。 例えば、stone の受信バッファを意図的に小さくすることによって OpenSSL 内のバッファに受信データが残りやすくして SSL 通信を受信してみる:

-X 512
-z key=key.pem
-z cert=cert.pem
localhost:22 localhost:12346/ssl --

「-X 512」オプションによって、受信バッファのサイズを 512 バイト (デフォルトは 2048 バイト) にしている。 12346番ポートで SSL 接続を受付け、 それを復号した上で、22番ポートへ中継する設定である。 続いて、

localhost:12346/ssl localhost:12347 --

という設定で stone を走らせて、12347番ポートで受付けた接続を、 SSL で暗号化した上で 12346番ポートへ転送するようにする。 上記二つの stone を実行することによって、 12347番ポートへ接続すると、 後者の stone によっていったん SSL で暗号化された上で、 前者の stone で復号されて 22番ポートへつながる。 つまり 12347番ポートで ssh 通信が行なえる。

前者の stone として stone 2.3a select 版を使うと、 ssh 転送が異様に遅くなる。

% scp -P 12347 /boot/linuz-2.6.16.18 localhost:/tmp/
...
linuz-2.6.16.18   100% 1211KB  11.3KB/s  01:47

延々 2 分近くかかってしまうが、 最新の stone.c 2.2.2.25 を使って select 版を作ると、 1 秒以内に転送が終わる。 また、stone 2.3a select 版であっても「-X 512」オプションを指定しなければ、 SSL_pending でデータが検知されるケースがほとんどなくなるので、 1 秒以内に転送が終わる。 なお、stone 2.3a epoll 版には、doReadWrite ループがなく、 全てメインループで処理を行なうので、 このような問題はない。

Filed under: stone 開発日記 — hiroaki_sengoku @ 18:51

No Comments »

No comments yet.

RSS feed for comments on this post.

Leave a comment