SSH は実際には「1 回のキー入力につき 100 パケットを送る」わけではありません。送信されるパケット数は、SSH プロトコル設計とネットワーク状況に応じて決まります。

---

## 「1 キーストローク=100 パケット」に見える理由

| # | 原因 | 説明 |
|---|------|------|
| 1 | **パケットの分割(フラグメンテーション)** | SSH のトランスポート層はデータを固定長ブロック(既定では 64 KiB)に分割します。<br>キー入力で生成される数バイトがそのブロックサイズまでパディングされ、1 パケットとして送信されます。 |
| 2 | **暗号化オーバーヘッド** | 各パケットには暗号ヘッダー・MAC(メッセージ認証コード)が付加され、場合によっては圧縮ヘッダーも含まれます。<br>これらの余分なデータが「実際に送信された」パケット数を増やすように見える原因となります。 |
| 3 | **ネットワークフラグメンテーション(MTU)** | 大きい SSH パケットは、ネットワークスタックによって MTU サイズ(約 1500 バイト)ごとに分割されます。<br>1 本の SSH パケットが複数の IP フラグメントへ変換され、各フラグメントが「別個のパケット」として計測されることがあります。 |
| 4 | **TCP ウィンドウと再送** | 小さなパケットは高いレイテンシ環境では ACK(確認応答)や再送を頻繁に発生させ、観測上のパケット数が増加します。 |
| 5 | **サーバ側バッファリング** | サーバは複数のキー入力をまとめて送信する前にバッファリングし、その後いくつかのパケットに分割して転送することがあります。 |

---

## 結論

「1 キーストローク=100 パケット」という誤解は、SSH のブロックベース設計、暗号化オーバーヘッド、MTU によるフラグメンテーション、およびネットワーク挙動の組み合わせによって生じます。プロトコル自体が意図的にリンクを詰め込むわけではありません。

2026/01/23 4:27

SSH は実際には「1 回のキー入力につき 100 パケットを送る」わけではありません。送信されるパケット数は、SSH プロトコル設計とネットワーク状況に応じて決まります。 --- ## 「1 キーストローク=100 パケット」に見える理由 | # | 原因 | 説明 | |---|------|------| | 1 | **パケットの分割(フラグメンテーション)** | SSH のトランスポート層はデータを固定長ブロック(既定では 64 KiB)に分割します。<br>キー入力で生成される数バイトがそのブロックサイズまでパディングされ、1 パケットとして送信されます。 | | 2 | **暗号化オーバーヘッド** | 各パケットには暗号ヘッダー・MAC(メッセージ認証コード)が付加され、場合によっては圧縮ヘッダーも含まれます。<br>これらの余分なデータが「実際に送信された」パケット数を増やすように見える原因となります。 | | 3 | **ネットワークフラグメンテーション(MTU)** | 大きい SSH パケットは、ネットワークスタックによって MTU サイズ(約 1500 バイト)ごとに分割されます。<br>1 本の SSH パケットが複数の IP フラグメントへ変換され、各フラグメントが「別個のパケット」として計測されることがあります。 | | 4 | **TCP ウィンドウと再送** | 小さなパケットは高いレイテンシ環境では ACK(確認応答)や再送を頻繁に発生させ、観測上のパケット数が増加します。 | | 5 | **サーバ側バッファリング** | サーバは複数のキー入力をまとめて送信する前にバッファリングし、その後いくつかのパケットに分割して転送することがあります。 | --- ## 結論 「1 キーストローク=100 パケット」という誤解は、SSH のブロックベース設計、暗号化オーバーヘッド、MTU によるフラグメンテーション、およびネットワーク挙動の組み合わせによって生じます。プロトコル自体が意図的にリンクを詰め込むわけではありません。

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

著者のSSHベースのTUIゲーム(bubbletea + wish)は約2 000人の同時プレイヤーを想定して設計されましたが、予期せぬ高CPUと帯域幅使用量に悩まされました。「your screen is too small」というテストハーネスはリソース使用量を約50%削減しました。ポート 22でのパケットキャプチャでは約413 kパケットが確認され、274 k(66 %)が36バイトのチューブ、139 k(34 %)がゼロバイトACK、数個の72バイトパケットでした。この36バイトメッセージは単一キー入力SSHセッションでも約20 msごとに現れました。

ssh -vvv
で確認したところ、OpenSSH の「obfuscate_keystroke_timing」拡張(2023年追加)が SSH2_MSG_PING を介してタイピングタイミングをマスクするためにこれらのチューブパケットを送信しています。クライアント側でこの機能を無効化 (
ObscureKeystrokeTiming=no
) すると、CPU と帯域幅はほぼ半分に削減されますが、ユーザー設定が必要です。

サーバー側のオーバーヘッドを除去するため、著者は

golang.org/x/crypto/ssh
をフォークし、
[email protected]
拡張(ピン/ポンコード)を宣言していたコミットをロールバックし、依存関係を置き換えました。これにより総CPU使用率が約30 %から11.6 %、システムコール時間が3.1 秒から0.66 秒へ、暗号処理時間が1.6 秒から0.11 秒へ、帯域幅が6.5 Mbit/sから約3 Mbit/sに減少し、高同時接続SSHゲームの性能向上に大きく貢献しました。

著者はまた、LLMs(Claude Code)がデバッグを支援したものの、人間による監督がパッチを特定し適用するために不可欠であると指摘しています。将来的にはよりターゲットを絞った最適化が検討されるかもしれませんが、現在の修正はリソース集約型TUIアプリケーションユーザーのスケーラビリティとレイテンシを既に改善しています。

本文

なぜ私が気にするのか?

2026 年 1 月 22日
TUI(BubbleTea と Wish で構築した)上でゲームデータを送る SSH セッション中、1 回だけキー入力しただけで生成されるパケット数の膨大さに驚きました。


1. tcpdump のサンプル出力

$ ./first_lines_of_pcap.sh single-key.pcap
   1   0.000s  CLIENT->SERVER   36 bytes
   2   0.007s  SERVER->CLIENT  564 bytes
   3   0.015s  CLIENT->SERVER    0 bytes
   4   0.015s  CLIENT->SERVER   36 bytes
   5   0.015s  SERVER->CLIENT   36 bytes
   6   0.026s  CLIENT->SERVER    0 bytes
   7   0.036s  CLIENT->SERVER   36 bytes
   8   0.036s  SERVER->CLIENT   36 bytes
   9   0.046s  CLIENT->SERVER    0 bytes
 10   0.059s  CLIENT->SERVER   36 bytes

summary_pcap.sh
スクリプトは統計を簡潔に示します。

$ ./summarize_pcap.sh single-key.pcap
Total packets: 270

  36-byte msgs:   179 packets (66.3%)   6444 bytes
  Other data:       1 packet  (0.4%)    564 bytes
  TCP ACKs:        90 packets (33.3%)

  Data sent:      6444 bytes in 36‑byte messages,  564 bytes in other data
  Ratio:          11.4x more data in 36‑byte messages than other data

  Data packet rate: ~90 packets/second (avg 11.1 ms between data packets)

質問: なぜ 1 回のキー入力でこんなに多くのトラフィックが発生するのでしょうか?


2. スクリプト

# first_lines_of_pcap.sh
tshark -r "$1" \
  -T fields -e frame.number -e frame.time_relative -e ip.src -e ip.dst -e tcp.len | \
  awk 'NR<=10 {
      dir = ($3 ~ /71\.190/ ? "CLIENT->SERVER" : "SERVER->CLIENT");
      printf "%3d  %6.3fs  %-4s  %3s bytes\n", $1, $2, dir, $5
  }'
# summarize_pcap.sh
tshark -r "$1" -Y "frame.time_relative <= 2.0" \
  -T fields -e frame.time_relative -e tcp.len | awk '
{
    count++
    payload = $2

    if (payload == 0) {
        acks++
    } else if (payload == 36) {
        mystery++
        if (NR > 1 && prev_data_time > 0) {
            delta = $1 - prev_data_time
            sum_data_deltas += delta
            data_intervals++
        }
        prev_data_time = $1
    } else {
        game_data++
        game_bytes = payload
        if (NR > 1 && prev_data_time > 0) {
            delta = $1 - prev_data_time
            sum_data_deltas += delta
            data_intervals++
        }
        prev_data_time = $1
    }
}
END {
    print "Total packets:", count
    printf "  36-byte msgs:   %3d packets (%5.1f%%)  %5d bytes\n", mystery, 100*mystery/count, mystery*36
    printf "  Other data:     %3d packet  (%5.1f%%)  %5d bytes\n", game_data, 100*game_data/count, game_bytes
    printf "  TCP ACKs:       %3d packets (%5.1f%%)\n", acks, 100*acks/count
    printf "  Data sent:      %d bytes in 36-byte messages,  %d bytes in other data\n", mystery*36, game_bytes
    printf "  Ratio:          %.1fx more data in 36-byte messages than other data\n", (mystery*36)/game_bytes
    avg_ms = (sum_data_deltas / data_intervals) * 1000
    printf "  Data packet rate: ~%d packets/second (avg %.1f ms between data packets)\n",
           int(1000/avg_ms + 0.5), avg_ms
}'

3. 発見

  • 私は SSH 上で動く高速ゲームを構築しており、80×60 の TUI を 秒間10回 更新しています。
  • ターゲットは同時に 2,000 人以上のプレイヤー → 秒間約 1 億セル更新です。
  • テストハーネスが壊れ、サーバ側が通常ゲームデータではなく「画面が小さい」メッセージだけを送っていたため、CPU と帯域幅は 約 50 % 割り込まれました(100 % ではありません)。
  • tcpdump
    壊れている場合正常な場合 のトラフィックを比較しました。
timeout 30s tcpdump -i eth0 'port 22' -w with-breaking-change.pcap
timeout 30s tcpdump -i eth0 'port 22' -w without-breaking-change.pcap
  • 「壊れた」キャプチャは 36 バイトのパケットが全体の 66 %、ゼロバイト ACK が 34 %、数個の 72 バイトパケットを含んでいました。
    これらの 36 バイトパケットは約 20 ms 間隔で届いています

4. 根本原因 – 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)
  • 2023 年以降、OpenSSH は キー入力タイミングの偽装 を追加し、タイプパターンを隠します。
  • キーが押されるたびに ~20 ms ごとに「チャフ」パケット(
    SSH2_MSG_PING
    )を送信します。
  • これらが今回観測した 36 バイトのメッセージです。

ゲームでは遅延が重要なため、このオーバーヘッドは望ましくありません。


5. 対策

クライアント側で無効化(テスト時に機能)

ssh -o ObscureKeystrokeTiming=no user@host
  • CPU 使用率が劇的に低下しました。
  • ボットは有効なデータを受け取り続けました。

しかし、ユーザーがこのオプションを知って設定するのは難しいかもしれません。

サーバ側で Go SSH ライブラリを修正

  1. この偽装は Go の
    crypto/ssh
    にある
    [email protected]
    拡張に紐づいています。
  2. ちょっとしたコミット(ping/pong 機構の追加)を戻すと、拡張広告が消えます。
  3. golang.org/x/crypto/ssh
    をフォークし、その変更を revert して
    replace
    ディレクティブで利用:
// go.mod
replace golang.org/x/crypto/ssh => ../my-ssh-fork
  1. パッチ適用後の結果は次の通りです。
指標BeforeAfter
CPU29.90 %11.64 %
システムコール3.10 s0.66 s
暗号処理1.6 s0.11 s
帯域幅~6.5 Mbit/s~3 Mbit/s

CPU 使用率が 50 % 超減 – スケーラビリティ向上に大きく貢献。


6. LLM 辅助デバッグへの思索

  • Claude Code を使って pcap を解析・要約したのは 楽しく効率的でした。
  • エージェントが実行したコマンドを確認できるので、メンタルモデルが保てました。
  • それでも ChatGPT の曖昧な回答に対して指摘し、Go ライブラリのフォークを考慮させる必要がありました。LLM は「助け」、最終的には人間の洞察が不可欠です。

結論

予期せぬ 36 バイトパケットは SSH のキー入力タイミング偽装チャフ でした。
クライアント側で無効化するか、Go SSH ライブラリを修正してこの機能を排除すれば、CPU と帯域幅のオーバーヘッドが大幅に削減され、数千人同時プレイヤーでも遅延を抑えつつスケールできます。

同じ日のほかのニュース

一覧に戻る →

2026/01/23 0:20

**GPTZero、NeurIPS 2025受理論文で新たに100件の幻覚現象を発見**

2026/01/18 8:29

**Scheme を WebAssembly にコンパイル**

## Japanese Translation: --- ## Summary Pythonで実装されたオープンソースScheme実装「Bob」は、15周年を記念してネイティブWASMバイナリを生成するWebAssembly(WASM)コンパイラを追加しました。新しい `WasmCompiler` は解析済みのScheme式を直接WASMテキストに変換し、その後 wasm‑tools スイートでコンパイルされ、Node.js経由で実行されます。 コンパイラの核心は、Schemeプリミティブを実装する約1,000行のWASMコードから成ります: - **オブジェクト表現** – SchemeオブジェクトはWASM GC型にマッピングされます: - `$PAIR` 構造体は `car` と `cdr` を `(ref null eq)` 参照として保持します。 - `$BOOL` 構造体は単一の `i32`(0 = false、非ゼロ = true)を保持します。 - `$SYMBOL` 構造体は線形メモリ内でオフセットと長さを表す2つの `i32` を保存します。 - **数値** – 整数値は `i31` 型を使用してボックス化されていない整数を直接参照します。 - **シンボル** – シンボルは線形メモリに固定オフセット(例: `(data (i32.const 2048) "foo")`)で発行され、アドレス/長さペアで参照されます。 - **組み込み関数** – `write` 関数はWASMテキスト内で直接実装され、ホスト関数として `write_char` と `write_i32` の2つだけをインポートします。 Bobはすでにインタープリタ、コンパイラ、VM、およびカスタムマーク・アンド・スウィープGCを備えたC++ VMを提供しています。追加されたコンパイラは今後さらに進化する予定ですが、現在のwasmtime用Pythonバインディングは2023年10月に仕様に組み込まれたWASM GC提案をまだサポートしていないため、SchemeをWebAssembly上で完全にガベージコレクション実行することが制限されています。 それでもユーザーは今やSchemeを直接WebAssemblyとして実行できるようになり、クロスプラットフォームのデプロイメントとJavaScript/Node.js環境とのより緊密な統合の可能性が開かれます。

2026/01/23 2:41

**CSS の光学的錯覚**

## Japanese Translation: (すべての主要なポイントを統合したもの) > 記事は、マウスホバーに応じて反応するインタラクティブな CSS ベースの錯視デモの CodePen ギャラリーを提示しています。 > 各デモは、Poggendorff の歪んだ線(傾いた 2 つのグラデーションと `::before`/`::after` を使用)、誘導グラデーション効果、Cornsweet & White の色コントラストトリック(黒白格子に `mix-blend-mode` を適用)、リングおよびチェッカーボードパターン、重なり合う線の色球体、曲率盲点、Café Wall イルлю(3 本のグラデーションで平行線を斜めにする)、ペノース三角形やエビングハウス円、カニッツァ四角形など、古典的な視覚現象を示しています。 > ギャラリーには、エビングハウス錯視のアニメーション版、回転する「タワー」、色のファン、逆スピーク、モーションバインディング、メンツラインズ、ウォッリングカラーなども含まれ、ドット線の動きやコントラスト非同期、息を吸う四角形、トロックラー消失といった静的に動きを示唆するパターンも掲載されています。 > すべての効果は CSS グラデーション、疑似要素、`mix-blend-mode`、およびキーフレームアニメーションで実現され、微妙な背景やホバー変更がどれほど印象的な視覚トリックを生み出せるかを示しています。 > コレクションは Patrick Pester の「35 optical…」リストと Michael Bach の「154 Visual Phenomena & Optical Illusions」に触発されています。 > Medium と DEV に公開されており、著者は将来の追加や改良の可能性について読者にコメントを残すよう呼びかけています。 *この改訂された要約は、すべての主要なポイントを完全に反映し、推測を加えず、明確で簡潔に保っています。*

SSH は実際には「1 回のキー入力につき 100 パケットを送る」わけではありません。送信されるパケット数は、SSH プロトコル設計とネットワーク状況に応じて決まります。 --- ## 「1 キーストローク=100 パケット」に見える理由 | # | 原因 | 説明 | |---|------|------| | 1 | **パケットの分割(フラグメンテーション)** | SSH のトランスポート層はデータを固定長ブロック(既定では 64 KiB)に分割します。<br>キー入力で生成される数バイトがそのブロックサイズまでパディングされ、1 パケットとして送信されます。 | | 2 | **暗号化オーバーヘッド** | 各パケットには暗号ヘッダー・MAC(メッセージ認証コード)が付加され、場合によっては圧縮ヘッダーも含まれます。<br>これらの余分なデータが「実際に送信された」パケット数を増やすように見える原因となります。 | | 3 | **ネットワークフラグメンテーション(MTU)** | 大きい SSH パケットは、ネットワークスタックによって MTU サイズ(約 1500 バイト)ごとに分割されます。<br>1 本の SSH パケットが複数の IP フラグメントへ変換され、各フラグメントが「別個のパケット」として計測されることがあります。 | | 4 | **TCP ウィンドウと再送** | 小さなパケットは高いレイテンシ環境では ACK(確認応答)や再送を頻繁に発生させ、観測上のパケット数が増加します。 | | 5 | **サーバ側バッファリング** | サーバは複数のキー入力をまとめて送信する前にバッファリングし、その後いくつかのパケットに分割して転送することがあります。 | --- ## 結論 「1 キーストローク=100 パケット」という誤解は、SSH のブロックベース設計、暗号化オーバーヘッド、MTU によるフラグメンテーション、およびネットワーク挙動の組み合わせによって生じます。プロトコル自体が意図的にリンクを詰め込むわけではありません。 | そっか~ニュース