SSHがキーストロークごとに100パケットを送信する問題をLLMとともに解決した話

SSHはターミナルエミュレーターを通じてリモートサーバーに安全に接続するためのプロトコルであり、ブラウザなどを用いた通信に比べると圧倒的にシンプルな送受信を行っているのではないかと考えてしまいます。しかしあるプログラマーがSSHを使用している際にキーストロークごとに100パケットが送信されるという現象に気づき、その理由について調査し対策した経緯を自身のブログで公開しました。
Why does SSH send 100 packets per keystroke? · eieio.games
https://eieio.games/blog/ssh-sends-100-packets-per-keystroke/
ゲームプログラマーのNolen Royalty氏はSSH上で動作するゲームを開発していました。ゲームは80x60のターミナルエミュレーター上で実行され、1秒間に10回の画面更新を行うというものです。同時接続は少なくとも2000人を想定しているため、サーバーから見れば1秒間に1億近いセルを更新する必要があることを意味しています。Royalty氏はパフォーマンスを重視していたので数百ものボットをSSHで接続して常に状況を監視していましたが、テストハーネスに破壊的変更を加えてしまった際に奇妙な現象に遭遇しました。なお、「破壊的変更(Breaking Change)」とは既存の機能や動作が動かなくなる変更のことを指します。
普段はサーバーからボットに対してゲームデータを定期的に送信するのですが、破壊的変更の影響によりサーバーは「画面が小さすぎます」というエラーメッセージを単発でボットに送信するようになりました。するとゲームのCPU使用率と帯域幅使用率が100%から半分の50%になったそうです。Royalty氏は一瞬パフォーマンスが向上したのかとぬか喜びしたものの、状況が判明すると今度は「ボットに対してゲームデータの送信が行われなかったのであればCPU使用率は0%になるはずなのに、どうして50%なのか?」という疑問を覚えたとのこと。
まずはテストハーネスをデバッグするためにゲームデータのトラフィックをtcpdumpで記録し、テストハーネスに破壊的変更を加えた前後で差異があるかを確認しました。
# 破壊的変更を加えた後の22番ポートのトラフィックを記録
timeout 30s tcpdump -i eth0 'port 22' -w with-breaking-change.pcap
# 破壊的変更を元に戻して22番ポートのトラフィックを記録
timeout 30s tcpdump -i eth0 'port 22' -w without-breaking-change.pcap
すると破壊的変更の後はSSH経由でゲームのレンダリングが不可能になっていることがわかりました。つまり変更後の記録である「with-breaking-change.pcap」にはゲームのレンダリングデータが含まれていないので接続のオーバーヘッドを示すパケットのみが含まれていることになります。状況を整理するためデバッグに使用していたClaude Codeに対して記録したトラフィックデータの内容を要約するよう質問しました。
Wanna take a look yourself? I put with-breaking-change.pcap in this directory
--
Wow! Here's what I found:
Packet Size Distribution (413,703 total packets):
274,907 packets (66%): Exactly 36 bytes
138,778 packets (34%): 0 bytes (TCP ACKs)
18 packets (<0.1%): 72 bytes
Claude Codeの出力結果をもとに記録時間を短くして分析を深めることにより「謎のパケット」が約20ミリ秒間隔で到着していることが判明しました。何が起こっているのかはRoyalty氏にもClaude Codeにもわからなかったのでパケットの正体について幾つかの仮説を立てました。
・SSHフロー制御メッセージ
・ターミナルエミュレーターのサイズをポーリングしている、もしくは何らかのステータスチェック
・bubbleteaかwishで何か奇妙なことが起こっている
手がかりとなりそうな点として「パケットの送受信がサーバー側からではなくクライアント側から開始されている」という状況があったため、Royalty氏は試しに通常のSSHセッションで同様の記録を行ってみることにしました。
# クライアントPCのターミナルエミュレーター画面で実行
sudo tcpdump -ien0 'port 22'
# クライアントPCの別のターミナルエミュレーター画面で実行
ssh $some_vm_of_mine
接続開始時のパケット送受信が止んだのを待ってからリモートサーバーにキーストロークを一回送信しtcpdumpの出力を確認したところ、まったく同一の現象が発生していることがわかりました。つまりこの現象はゲームによって発生しているのではなくSSHが本来示す特性であるということになります。
「ssh -vvv」を実行したところ何が起こっているのかがかなり明確に示されました。
debug3: obfuscate_keystroke_timing: starting: interval ~20ms
debug3: obfuscate_keystroke_timing: stopping: chaff time expired (49 chaff packets sent)
debug3: obfuscate_keystroke_timing: starting: interval ~20ms
debug3: obfuscate_keystroke_timing: stopping: chaff time expired (101 chaff packets sent)
「20ミリ秒のインターバル」という決定的な証拠が得られたことから、先ほど見られた「謎のパケット」と状況が完全に一致することが判明しました。メッセージの他の部分にもかなり重要な情報があり、最初のキーストロークには49個の「チャフ」パケットが含まれ、2回目のキーストロークには101個の「チャフ」パケットが送られていたことがわかりました。「チャフ」パケットというのは2023年にキーストロークタイミングの難読化を目的としてSSHに追加したもので、打鍵する文字によって入力速度が異なることを悪用してどの文字を入力したかを知られてしまうのを防止する目的があります。

大量の「チャフ」パケットが送信されることによりキーストロークを伝えるパケットのタイミングが知られる恐れがなくなるので、プライバシーを重要視する通常のSSHセッションでは「チャフ」パケットの存在は非常に重要です。しかしながらレイテンシーを抑える必要のあるネットゲームの観点からは「チャフ」パケットの存在はオーバーヘッドでしかありません。
「キーストロークの難読化」はSSHクライアント側で無効化するようテストハーネスを更新したところ、狙い通りにCPU使用率は劇的に低下しつつボットは有効なデータを受け取っていました。ただし現実問題としてプレイヤーに毎回謎めいたオプションを付けてSSHを起動してもらうのは無理があります。「サーバー側でキーストロークの難読化を無効化できないか」とClaude Codeに尋ねてみても、最初は「おそらく無理だ」と回答したとのこと。

しかし幸運にもSSHのキーストローク難読化に関する資料を見つけることができたため、サーバーが依存していたGoのSSHライブラリーで関連するコードを発見できたそうです。キーストローク難読化に関わるコードからキーワードを見つけSSHライブラリを検索すると実装されたコミットを特定することができ、無効化するのは簡単であることが判明しました。そこでRoyalty氏は当該リポジトリをクローンした上でClaudeにキーストローク難読化の無効化を指示し、さらにサーバープログラムの依存関係を書き換えてクローンしたライブラリを使用するよう指示したとのことです。改良したサーバーを用いてテストハーネスを再実行したところ非常に良好な結果が得られました。
Total CPU 29.90% -> 11.64%
Syscalls 3.10s -> 0.66s
Crypto 1.6s -> 0.11s
Bandwidth ~6.5 Mbit/sec -> ~3 Mbit/sec
結果を見てClaudeも大興奮したとのこと。

最後にRoyalty氏は「LLMが問題解決する過程で自分の楽しみを奪ってしまうのではないかと危惧していましたが、Claude Codeを使ってこの問題をデバッグするのは本当に楽しかったです」と語っています。
・関連記事
独自のデータベースやWordPressなどのアプリを簡単にセルフホストできて管理できるオープンソースPaaS「Coolify」 - GIGAZINE
中国製KVMにひっそりマイクが搭載されていることが判明、中国拠点のサーバーと通信している痕跡も - GIGAZINE
SSHを使用したポート転送やトンネリングの設定をわかりやすく図示したビジュアルガイド - GIGAZINE
OpenSSHに重大な脅威となる脆弱性「regreSSHion」(CVE-2024-6387)が発覚、ほぼすべてのLinuxシステムに影響 - GIGAZINE
計算エラーの発生時にSSHの秘密鍵が盗み取られる危険があることを研究者が実証 - GIGAZINE
・関連コンテンツ
in AI, ソフトウェア, Posted by log1c_sh
You can read the machine translated English article How I solved the SSH 100-packet-per-keys….







