- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- DB保存
第21週でポート番号を見て、通信の相手が「どのアプリか」まで絞り込めるようになりました。今回はその2つの端点のあいだに「接続」という状態を作る手続きを扱います。ルータより上、アプリケーションより下の段の話です。
まず結論
IPは荷物を1つずつ投げるだけで、「つながっている」という状態をどこにも持ちません。TCPの接続とは、両端のメモリに置かれた合意のことです。その合意を作る手続きが3ウェイハンドシェイクです。
そして「3ウェイ」は3往復ではありません。3つのメッセージ、つまり1往復と片道です。クライアントがデータを送り始められるまでは1 RTT で、3つ目のACKと同時にデータを送れます。
「3ウェイハンドシェイク」でTCP接続が確立します。クライアントが最初のデータを送り始められるようになるまでに、往復(RTT)は何回必要でしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
「接続」はどこにも実在しない
第21週で、ポート番号によって通信の相手が「どのホストのどのアプリか」まで絞り込めることを見ました。しかし宛先が分かることと、そこと「つながっている」ことは別の話です。
ここで一度、下の段が何をしてくれているかを思い出してください。IPは荷物を1つずつ投げるだけです。 届いたかどうかを教えてくれませんし、投げた順に着くとも限りません。線の途中に「接続」という装置があるわけでもありません。
にもかかわらず、私たちはコードで connect() を呼び、「接続が確立した」「接続が切れた」と日常的に語ります。この接続とは何でしょうか。
両端のメモリに置かれた、合意の記録です。 サーバ側のメモリに「この相手とこの番号でやりとり中」という情報があり、クライアント側にも対応する情報がある。この2つが揃っている状態を、私たちは「接続」と呼んでいます。線の上には何もありません。
- ・ルータは通り過ぎるパケットを1つずつ転送するだけで、接続を知らない
- ・だから「接続の確立」とは、両端の状態を揃える作業にほかならない
すると問題は一気に具体的になります。離れた2つの場所にある状態を、どうやって揃えるのか。 3ウェイハンドシェイクは、その手続きです。
RFC 9293 の図をそのまま読む
規格が実際に何を書いているかを見ます。RFC 9293 §3.5 の Figure 6 が、最も単純な3ウェイハンドシェイクです。
TCP Peer A TCP Peer B1. CLOSED LISTEN2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED
読み方を補っておきます。左端の CLOSED SYN-SENT ESTABLISHED は状態名で、そのセグメントが出ていった/着いた後の状態を表します。中央の <CTL=SYN> が制御ビットです。原文では SYN / SYN,ACK(カンマ区切り)/ ACK と書かれています。
2行目、A が「私は 100 番から数え始めます」と宣言します(SEQ=100)。3行目、B が「101 番を待っています」と確認しつつ(ACK=101)、同時に「私は 300 番から数え始めます」と宣言します(SEQ=300)。4行目、A が「301 番を待っています」と返します。
この2行目から4行目までが、3つのメッセージです。
そして注目してほしいのが4行目と5行目のシーケンス番号です。どちらも SEQ=101 で、同じ番号になっています。RFCはその理由を括弧の中でこう説明しています。
the ACK does not occupy sequence number space (if it did, we would wind up ACKing ACKs!)
ACKだけのセグメントはシーケンス番号を消費しません。もし消費するなら、そのACKにもACKが必要になり、ACKのACKのACKが無限に続いてしまう——というわけです。
RFC 9293 の Figure 6 では、4行目の ACK(SEQ=101)と5行目のデータ付きセグメント(SEQ=101)のシーケンス番号が同じです。これはなぜですか。
「3ウェイ」は3往復ではない
ここが本レッスンで最も誤解される点です。そして誤解したまま性能を見積もると、数字が3倍ずれます。
「3ウェイ」が数えているのは、往復の回数ではなくメッセージの数です。 RFC 9293 自身がそう書いています。
this is called the three-way (or three message) handshake (3WHS)
数えてみます。A が SYN を送り、B から SYN,ACK が返る。ここまでで1往復です。3つ目の ACK は A から B へ向かう片道で、A はその返事を待ちません。合計は「1往復 + 片道」であって、3往復ではありません。
- ・Figure 6 の4行目と5行目のSEQが同じ = ACKは番号を消費しない = データを相乗りできる
- ・データが相手に届くまでは 1 RTT + 片道、その応答が返るまでは 2 RTT
なぜ相乗りできるかというと、さきほど確認した「ACKはシーケンス番号を消費しない」という性質のおかげです。4行目と5行目が同じ SEQ=101 である以上、この2つは1つのセグメントにまとめられます。
なぜ3回なのか — RFCは2つの説明を与えている
では、なぜ2回でも4回でもなく3回なのでしょうか。ここでRFCの書き方が面白く、2つの異なる説明が別々の節に置かれています。
1つ目は、同期の必要条件です(§3.4.1)。 接続を確立するには、各側が自分のISNを送り、それに対する確認応答を受け取る必要があります。原文は「The synchronization requires each side to send its own initial sequence number and to receive a confirmation of it in acknowledgment from the remote TCP peer」と書いています。素直に並べると4つの手順になります。
1) A --> B SYN my sequence number is X2) A <-- B ACK your sequence number is X3) A <-- B SYN my sequence number is Y4) A --> B ACK your sequence number is Y
そして続けてこう書かれています。「Because steps 2 and 3 can be combined in a single message this is called the three-way (or three message) handshake (3WHS)」。2番と3番はどちらも B から A への送信なので、1つのパケットにまとめられる。 これが SYN,ACK の正体です。4つの論理的な手順が、3つのメッセージに畳まれている——これが「3」の由来です。
2つ目の説明が、実は本題です。 RFC 9293 §3.5 には、こう書かれています。
The principal reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
つまりRFCが「主な理由」と呼んでいるのは、ISNの交換ではなく「古い重複SYNによる混乱の防止」のほうです。
古い重複SYNとは何でしょうか。ネットワークの中には、経路の迂回などで異様に遅れて届くパケットが存在しえます。ずっと前に終わった接続のSYNが、今ごろになって到着することがあるのです。
もし受け取った側がSYN1つで接続を開いてしまったら、誰も望んでいない接続が勝手に成立します。 §3.4.1 はこの問題をこう説明しています。最初のSYNを受け取った側には「それが古いものかどうかを知る術がない(has no way of knowing whether the segment was an old one or not)」ので、送信側に確認を求めなければならない、と。
3つ目のACKは、まさにこの確認です。B は自分が選んだ SEQ=300 を送り、「301 を確認してくる応答が返ってきたら本物だ」と判断できます。古い重複SYNには送り主の実体がないので、この確認は返ってきません。
ISNをランダムにする理由と、その限界
ここまでで「各側がISNを宣言する」と書いてきました。ではその番号はどう選ぶのでしょうか。0から始めれば簡単そうですが、規格はそれを許しません。
RFC 9293 §3.4.1 は生成式を定めています(SHLD-1)。
ISN = M + F(localip, localport, remoteip, remoteport, secretkey)M : 約4マイクロ秒ごとに進む32ビットのカウンタF() : 接続を識別する4つの値と秘密鍵を入力とする擬似乱数関数制約 : F() は外部から計算可能であってはならない (MUST-9)
なぜ予測困難にするのでしょうか。§3.4.1 が名指ししている脅威はこうです。「There are security issues that result if an off-path attacker is able to predict or guess ISN values」。
off-path attacker(経路外の攻撃者)とは、その通信を直接は見られない位置にいる相手のことです。パケットを覗けないので、シーケンス番号を当てずっぽうで言い当てるしかありません。ISNが予測可能なら、この当てずっぽうが成功してしまいます。だから F() は外部から計算できてはならない、と定められています。
ここで、保護の範囲を正しく捉えておくことが重要です。
RFC 9293 が3ウェイハンドシェイクの「principal reason(主な理由)」として挙げているのはどれですか。
TCPの3ウェイハンドシェイク — 通り道を作る
交わしているのはシーケンス番号の宣言と、その確認だけです。暗号も相手の身元確認も一切ありません。これが終わって得られるのは「順序どおりに、欠落なく届くバイトの流れ」であって、その中身は誰にでも読めます。3つのメッセージ = 1往復 + 片道です。
SYN : 私は 100 番から数え始めますSYN,ACK : 101 番を待っています / 私は 300 番からですACK : 301 番を待っています得られるもの: 順序どおり・欠落なく届く通り道守られないもの: 中身の秘密、相手が誰かの保証
TLSのハンドシェイク — 相手と鍵を確かめる
TLSが交わすのは、暗号パラメータの折衝・サーバ証明書・署名・鍵素材です。RFC 9846 は TLS のハンドシェイクを「authenticates the communicating parties, negotiates cryptographic algorithms and parameters, and establishes shared keying material」と説明しています(廃止された RFC 8446 では algorithms の箇所が modes でした)。順序と到達はTCPに任せきりです。
ClientHello : 使える暗号方式と鍵素材の候補ServerHello : 使う暗号方式の決定と鍵素材Certificate : サーバの証明書(あなたは誰か)CertificateVerify / Finished : 署名と完了確認得られるもの: 相手の認証と、共有された暗号鍵前提にしているもの: 順序どおり・欠落なく届く通り道
どちらも「ハンドシェイク」と呼ばれ、どちらも通信の冒頭で数往復のメッセージを交わします。この2つは何のために手を握っているのでしょうか。そして、なぜ両方とも必要なのでしょうか。
それぞれのハンドシェイクが終わった時点で「何が保証されたことになるか」を並べてみてください。片方が前提にしているものを、もう片方が作っています。
この章のまとめ
- 接続は線の上に実在しない。 両端のメモリに置かれた状態のペアを、私たちが「接続」と呼んでいる。3WHSはその状態を揃える手続き。
- 「3ウェイ」は3往復ではない。 RFC 9293 自身が「three-way (or three message) handshake」と書いている。1往復 + 片道であり、データを送り始められるまでは1 RTT。データが届くまでは1 RTT + 片道、応答が返るまでは2 RTT。
- 3つ目のACKにデータを相乗りできるのは、ACKがシーケンス番号を消費しないから(Figure 6 の4行目と5行目のSEQが同一)。
- なぜ3回かについて、RFCは2つの説明を持っている。 §3.4.1 の「本来4手順だが、中間の2つが SYN,ACK として1パケットに畳まれる」(同節は続けて、古いSYNかどうかを受け手が見分けられないことも挙げる)と、§3.5 の「古い重複SYNによる混乱の防止」。RFCが "principal reason" と呼ぶのは後者で、ISN交換だけを答えると規格が最も重視する点が落ちる。
- ISNのランダム化の現行規定は RFC 9293 §3.4.1。RFC 6528 は RFC 9293 に廃止されているので、現行規格として引くとそれ自体が誤りになる。
- ISNのランダム化が守るのは off-path attacker による当て推量まで。経路上でパケットを見られる相手には効かない。 TCP自身に暗号機能は無い。
- TCPの3WHSとTLSのハンドシェイクは目的が違う。 前者は信頼できる順序どおりの通り道を作り、後者はその上で認証と鍵共有を行う。RFC 9846 は下位トランスポートへの唯一の要求を「a reliable, in-order data stream」と書いている。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
「3ウェイハンドシェイクには3回の往復(3 RTT)が必要なので、接続確立は重い」という説明の評価として最も正確なものはどれですか。
用語集(6語)
- 3ウェイハンドシェイク (3WHS)
- TCPの接続を確立する手続き。SYN / SYN,ACK / ACK の3つのメッセージを交わす。RFC 9293 §3.4.1 と §3.5 が扱う。§3.4.1 の原文に「three-way (or three message) handshake」とあるとおり、3は往復ではなくメッセージの数を指す。
- ISN (初期シーケンス番号)
- 接続の開始時に各側が選ぶ32ビットの番号。以降のデータはこの番号からの相対位置で数えられる。RFC 9293 §3.4.1 が生成方法を規定しており、外部から計算できてはならない(MUST-9)。
- SYN / ACK
- TCPヘッダの制御ビット。SYN は synchronize の略で「シーケンス番号を同期したい」という意思表示、ACK は受け取った番号の確認応答。RFC 9293 の図では SYN,ACK のようにカンマで併記される。
- SYN-SENT / SYN-RECEIVED / ESTABLISHED
- 接続確立の途中および完了を表すTCPの状態名(RFC 9293 の表記はハイフン付き)。この「状態」こそが接続の実体で、両端のメモリの中だけに存在する。
- RTT (往復時間)
- パケットを送ってから応答が返るまでの時間。3WHS でデータを送り始められるまでは1 RTT、データが相手に届くまでは1 RTT + 片道、その応答が返るまでは2 RTT。
- TLSハンドシェイク
- TLS が通信相手の認証・暗号パラメータの折衝・鍵の確立を行う手続き(RFC 9846 §4)。TCPの3WHSとは目的が異なり、TCPが作った信頼できるストリームの上で、その後に行われる。