# ネットワーク（TCP/IP）：URL を入力してからページが表示されるまで

> OSI 参照モデルと TCP/IP の 4 階層を、カプセル化・IP アドレスと最長プレフィックス一致・TCP のウィンドウ制御という三つの軸で整理し、URL 入力から HTML が届くまでの往復時間を実際に見積もる。
> https://rikai.mugen-giken.com/computer-science/software-engineering/networking-tcp-ip

## 0. この記事の要点

- インターネットは「層」に分けて設計されています。層の本質は**カプセル化**、すなわち上位層のデータをそのまま下位層のペイロードとして包み、下位層は中身を解釈しないという規律です。この規律のおかげで、アプリケーションの数と物理媒体の数を掛け算ではなく足し算で扱えます。
- IP アドレスは「番号」ではなく**アドレスの集合（プレフィックス）**として扱われます。プレフィックスの集合は入れ子か素かのどちらかにしかならず、そこから**最長プレフィックス一致が一意に定まる**ことが証明できます（<Ref to="thm-lpm" />）。経路表という巨大な分散データ構造が矛盾なく動くのは、この単純な事実のおかげです。
- TCP のスループットには $W/\mathrm{RTT}$ という上限があります（<Ref to="prop-window" />）。帯域を増やしても RTT が縮まらない限り、ウィンドウ $W$ を広げなければ速くなりません。長距離通信で「回線は速いのに遅い」現象の大半はこれで説明できます。
- 3 ウェイハンドシェイクの 3 回目は冗長ではありません。2 回で済ませると、遅延して届いた古い接続要求で誤った接続が成立します（<Ref to="prop-handshake" />）。
- ブラウザに URL を入れてから HTML が届くまでには、DNS・TCP・TLS・HTTP でそれぞれ往復が発生します。国際回線の RTT を 80 ms とすると初回アクセスに約 320 ms かかり、この内訳を数えられることが性能改善の出発点になります（<Ref to="ex-rtt-budget" />）。

## 1. 動機 — 二台をつなぐのは簡単、全世界をつなぐのは難しい

二台の計算機をケーブルでつなぎ、電圧の高低でビットを送る。これだけなら数十行のプログラムで書けます。難しいのは、**互いに設計を知らない何十億台**の機器を、**誰も全体を管理していない**状態でつなぐことです。

歴史的には 1969 年の ARPANET が出発点でした。当初のプロトコル NCP は、すべてのホストが同種のネットワークにつながっていることを前提にしていました。しかし 1970 年代に入ると、無線パケット網や衛星回線など、性質のまったく異なるネットワークが現れます。「ネットワークどうしをつなぐ」という問題（inter-networking、これが internet の語源です）に Vinton Cerf と Robert Kahn が 1974 年の論文で与えた答えが、現在の TCP/IP の骨格です。1983 年 1 月 1 日、ARPANET は NCP を捨てて TCP/IP に一斉移行します。

彼らの設計の中心にあるのは、次の二つの割り切りです。

**第一に、パケット交換にする。** 通話のように回線を占有する方式（回線交換）では、通信していない間も帯域が遊びます。データを小さな塊（パケット）に切って宛先を書き、共有された回線に流し込めば、複数の通信で帯域を分け合えます。代わりに、パケットは順序が入れ替わったり、混雑時に捨てられたりします。

**第二に、中継ノードを賢くしない。** 途中のルータは「次にどこへ渡すか」だけを決め、再送も順序の復元も末端のホストが行います。これを end-to-end 原則と呼びます。中継が単純だからこそルータは高速化でき、新しい種類の通信を追加してもネットワークの中身を書き換えずに済みます。

そのうえで機能を**層**に分けます。動機は抽象化の美しさというより、単純な組み合わせの数え上げです。アプリケーションが $m$ 種類、物理媒体が $n$ 種類あるとき、すべての組み合わせを個別に実装すれば $mn$ 通りの作業が要ります。両者の間に共通の中間層（IP）を挟めば、上向きに $m$ 個、下向きに $n$ 個、合計 $m+n$ 個で済みます。IP が一種類しかなく上下が多様なこの構造を、**砂時計モデル**と呼びます。

<Aside type="note">
砂時計のくびれには代償もあります。IP の置き換えは極端に難しく、IPv6 への移行は 1998 年の仕様策定から四半世紀を経てなお完了していません。層に分けるとは、変更の容易な場所と困難な場所を選ぶことでもあります。
</Aside>

<div data-gated data-pagefind-ignore>

## 2. 準備：パケット交換と遅延のモデル

<Definition id="def-protocol" title="プロトコルと PDU">
**プロトコル**とは、通信する二つ以上の主体が交換するメッセージの形式と、送受信に際して取る動作の順序を定めた規約です。ある層のプロトコルがやり取りする単位をその層の **PDU**（Protocol Data Unit）と呼び、PDU は**ヘッダ**（その層の制御情報）と**ペイロード**（上位層から預かったデータ）からなります。
</Definition>

性能の議論に入る前に、遅延がどこから来るのかを分解しておきます。「どこを改善すれば速くなるか」を判断する土台になります。

<Proposition id="prop-delay" title="端点間遅延の分解">
送信元から宛先までの経路が $N$ 本のリンクからなり、リンク $i$ の帯域を $R_i$ [bit/s]、物理長を $\ell_i$ [m]、その媒体中の信号伝搬速度を $v_i$ [m/s] とします。サイズ $L$ [bit] のパケットが各中継ノードで**蓄積交換**される（パケット全体を受信し終えてから次のリンクへ送り出す）とき、パケットの端点間遅延 $T$ は
$$
T = \sum_{i=1}^{N}\left(\frac{L}{R_i} + \frac{\ell_i}{v_i} + d^{\mathrm{proc}}_i + d^{\mathrm{queue}}_i\right)
$$
で与えられます。ここで $d^{\mathrm{proc}}_i$ はノード $i$ での処理遅延、$d^{\mathrm{queue}}_i$ は送信待ち行列での待ち時間です。
</Proposition>

<Proof of="prop-delay">
リンク $i$ に注目します。ノードがパケットの先頭ビットを送り出してから最後のビットを送り出すまでにかかる時間が**送信遅延**で、$L$ ビットを毎秒 $R_i$ ビットの速度で押し出すのですから $L/R_i$ です。最後のビットが回線に乗ってから相手側に届くまでの時間が**伝搬遅延**で、距離 $\ell_i$ を速度 $v_i$ で進むのですから $\ell_i/v_i$ です。したがってリンク $i$ の通過に $L/R_i + \ell_i/v_i$ かかります。

蓄積交換の仮定により、ノードはパケット全体を受け取るまで次のリンクへの送出を開始しません。よって各リンクの通過時間は重ならず、単純に足し合わされます。これに、ヘッダを読んで出力ポートを決める処理遅延と、その出力ポートが塞がっている間の待ち時間を各ノードで加えれば、主張の式を得ます。
</Proof>

四つの成分のうち、経路の混雑度に左右されるのは $d^{\mathrm{queue}}_i$ だけです。その定常的な大きさは待ち行列のモデルから見積もれます（<Ref to="computer-science/software-engineering/cloud-computing#prop-mm1" text="M/M/1 待ち行列" />）。以下の見積もりでは、混雑していない経路を想定して送信遅延と伝搬遅延に注目します。

<Example id="ex-delay" title="太平洋を越える遅延の内訳">
東京からサンフランシスコまで、光ファイバの実経路長を $\ell = 12{,}000$ km、ファイバ中の光速を $v = 2.0 \times 10^8$ m/s とします。伝搬遅延の合計は
$$
\frac{1.2 \times 10^7\ \mathrm{m}}{2.0 \times 10^8\ \mathrm{m/s}} = 6.0 \times 10^{-2}\ \mathrm{s} = 60\ \mathrm{ms}
$$
です。一方、1500 バイト（$L = 12{,}000$ bit）のパケットが 1 Gbit/s のリンク 10 本を蓄積交換で通るときの送信遅延の合計は
$$
10 \times \frac{1.2 \times 10^4\ \mathrm{bit}}{1.0 \times 10^9\ \mathrm{bit/s}} = 1.2 \times 10^{-4}\ \mathrm{s} = 0.12\ \mathrm{ms}
$$
にすぎません。片道 60 ms のうち送信遅延は 0.2% です。往復時間（RTT）はおよそ 120 ms になります。

すべてのリンクを 10 Gbit/s に増強しても、片道遅延は 60.12 ms から 60.01 ms にしか縮みません。**帯域はお金で買えますが、伝搬遅延は光速で決まっており買えません。** 遅いページを速くしたいとき、まず数えるべきは往復回数です。
</Example>

## 3. 階層モデル：OSI と TCP/IP

### 3.1. 二つのモデル

**OSI 参照モデル**は ISO と ITU-T が 1980 年代に標準化した 7 層のモデルで、通信機能を物理層から応用層まで整理した「教科書上の座標系」です。これに対し **TCP/IP モデル**は、実際に動くプロトコル群を後から 4 層に整理したものです。実務では両者が混ぜて使われ、「L3 スイッチ」「L7 ロードバランサ」のように OSI の層番号が日常語になっています。

| TCP/IP（4 層） | OSI（7 層） | PDU の呼び名 | 代表的なプロトコル | アドレスの単位 |
|---|---|---|---|---|
| アプリケーション層 | 応用・プレゼンテーション・セッション（5〜7） | メッセージ | HTTP, DNS, SMTP, TLS | URL・ドメイン名 |
| トランスポート層 | トランスポート（4） | セグメント / データグラム | TCP, UDP, QUIC | ポート番号 |
| インターネット層 | ネットワーク（3） | パケット | IP, ICMP | IP アドレス |
| リンク層 | データリンク・物理（1〜2） | フレーム | Ethernet, Wi-Fi (802.11) | MAC アドレス |

<Remark id="rem-osi-reality">
OSI の第 5 層（セッション）と第 6 層（プレゼンテーション）に対応する独立したプロトコルは、TCP/IP にはほぼ存在しません。TLS は「第 6 層」と呼ばれますが、実装上は TCP の上に載る一つのプロトコルです。OSI モデルは共通語彙として使い、実装の構造そのものだと思わないでください。
</Remark>

### 3.2. カプセル化

<Definition id="def-encapsulation" title="カプセル化">
層 $k$ のプロトコルが上位層 $k+1$ から受け取った PDU を、内容を解釈せずそのまま自分のペイロードとし、自層のヘッダ（必要ならトレーラも）を付けて層 $k$ の PDU を作ることを**カプセル化**といいます。受信側では逆に、層 $k$ が自層のヘッダを取り除き、ペイロードを層 $k+1$ に渡します。
</Definition>

「内容を解釈せず」が層の独立性の本体です。IP はペイロードが TCP か UDP か QUIC かを気にせず（番号を運ぶだけで）、Ethernet は中身が IPv4 か IPv6 かを気にしません。

<Figure caption="HTTP メッセージが Ethernet フレームになるまで。外側の層は内側の中身を解釈しない">
<svg viewBox="0 0 740 240" width="100%" role="img" aria-label="カプセル化の入れ子構造を示す図">
  <g fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="310" y="20" width="380" height="36" rx="4" stroke="var(--sl-color-accent)" />
    <rect x="250" y="76" width="60" height="36" rx="4" />
    <rect x="310" y="76" width="380" height="36" rx="4" stroke="var(--sl-color-accent)" />
    <rect x="190" y="132" width="60" height="36" rx="4" />
    <rect x="250" y="132" width="440" height="36" rx="4" />
    <rect x="130" y="188" width="60" height="36" rx="4" />
    <rect x="190" y="188" width="500" height="36" rx="4" />
    <rect x="690" y="188" width="40" height="36" rx="4" />
  </g>
  <g fill="currentColor" font-size="12" text-anchor="middle">
    <text x="500" y="43">HTTP メッセージ（1460 B）</text>
    <text x="280" y="99">TCP 20B</text>
    <text x="500" y="99">ペイロード 1460 B</text>
    <text x="220" y="155">IP 20B</text>
    <text x="470" y="155">TCP セグメント 1480 B</text>
    <text x="160" y="211">Eth 14B</text>
    <text x="440" y="211">IP パケット 1500 B</text>
    <text x="710" y="211">FCS</text>
  </g>
  <g fill="currentColor" font-size="12" text-anchor="start">
    <text x="8" y="43">アプリケーション層</text>
    <text x="8" y="99">トランスポート層</text>
    <text x="8" y="155">インターネット層</text>
    <text x="8" y="211">リンク層</text>
  </g>
</svg>
</Figure>

<Definition id="def-mtu" title="MTU と MSS">
あるリンクが一度に運べる**ペイロードの最大長**（IP パケットの最大長）を **MTU**（Maximum Transmission Unit）、TCP が一つのセグメントに載せられるアプリケーションデータの最大長を **MSS**（Maximum Segment Size）と呼びます。オプションのない IPv4 と TCP では
$$
\mathrm{MSS} = \mathrm{MTU} - (\text{IP ヘッダ長}) - (\text{TCP ヘッダ長})
$$
です。
</Definition>

<Example id="ex-mss" title="イーサネット上での有効伝送率">
標準的なイーサネットの MTU は 1500 バイトです。IPv4 ヘッダ 20 バイト、TCP ヘッダ 20 バイトを引くと
$$
\mathrm{MSS} = 1500 - 20 - 20 = 1460\ \text{バイト}
$$
となります。IP パケットの長さに対する有効データの割合は $1460/1500 = 97.3\%$ です。

リンク層まで数えます。イーサネットフレームは宛先・送信元 MAC アドレスと型フィールドで 14 バイト、末尾の誤り検出符号（FCS）で 4 バイトを使い、回線上ではさらにプリアンブル 8 バイトとフレーム間ギャップ 12 バイト分の時間を消費します。1 回の送信が占有するのは
$$
1500 + 14 + 4 + 8 + 12 = 1538\ \text{バイト}
$$
分で、有効伝送率は $1460/1538 = 94.9\%$ です。1 Gbit/s の回線で得られる TCP の理論最大スループットは $0.949 \times 1$ Gbit/s $= 949$ Mbit/s ということになります。「1 Gbps 回線なのに 940 Mbps しか出ない」のは異常ではなく、ヘッダの分です。
</Example>

<Aside type="caution">
経路の途中に MTU の小さいリンクがあると、IP パケットは分割（フラグメンテーション）されます。断片が一つでも失われると元のパケットが復元できないため、損失率が実質的に増幅されます。IPv6 では中継ルータによる分割が廃止され、送信元が Path MTU Discovery で経路上の最小 MTU を調べる方式になりました。
</Aside>

## 4. インターネット層：アドレスと経路制御

### 4.1. IP アドレスとプレフィックス

<Definition id="def-ip-address" title="IPv4 アドレスと CIDR プレフィックス">
IPv4 アドレスは 32 ビットの列で、8 ビットずつ 10 進で区切って `192.0.2.1` のように書きます。**プレフィックス** $P = p/n$（$0 \le n \le 32$）とは、上位 $n$ ビットが $p$ の上位 $n$ ビットと一致する 32 ビット列**全体の集合**を指します。$n$ を**プレフィックス長**と呼びます。この表記法を **CIDR**（Classless Inter-Domain Routing）といいます。
</Definition>

強調したいのは、**個々のアドレスより集合が主役**だという点です。経路表はアドレスを一つずつ覚えるのではなく、「この集合に属する宛先は隣のルータへ」という規則をプレフィックス単位で持ちます。$2^{32}$ 個のアドレスを $10^5$ 程度の規則に圧縮できるからこそ、全世界の経路が 1 台のメモリに載ります。

<Example id="ex-cidr" title="プレフィックスの計算">
プレフィックス `203.0.113.128/25` を考えます。プレフィックス長が 25 なので、固定されるのは上位 25 ビット、すなわち最初の 3 オクテットと第 4 オクテットの最上位 1 ビットです。第 4 オクテット $128 = (1000\,0000)_2$ の最上位ビットは 1 ですから、この集合は
$$
\{\,\texttt{203.0.113.}x \mid x = 128, 129, \ldots, 255 \,\}
$$
すなわち 128 個のアドレスからなります。一般に長さ $n$ のプレフィックスは $2^{32-n}$ 個のアドレスを含みます。

- `203.0.113.130` は含まれるか。$130 = (1000\,0010)_2$ で最上位ビットが 1 なので、含まれます。
- `203.0.113.60` は含まれるか。$60 = (0011\,1100)_2$ で最上位ビットが 0 なので、含まれません。

このうち先頭の `203.0.113.128` は**ネットワークアドレス**、末尾の `203.0.113.255` は**ブロードキャストアドレス**として予約されるため、ホストに割り当てられるのは $128 - 2 = 126$ 個です。一般に $n \le 30$ のとき割当可能数は $2^{32-n} - 2$ です。
</Example>

### 4.2. 最長プレフィックス一致

経路表には、同じ宛先アドレスにマッチする規則が複数入りえます。`203.0.113.0/24` が ISP A へ、`203.0.113.128/25` が ISP B へ、という二つが同時に存在する状況は普通です。このとき「より細かいほうを選ぶ」のが**最長プレフィックス一致**です。これが常に一意に決まることを示します。

<Lemma id="lem-laminar" title="プレフィックスの入れ子性">
二つのプレフィックス $P_1 = p_1/n_1$、$P_2 = p_2/n_2$ について、集合として $P_1 \cap P_2 \ne \varnothing$ ならば、$n_1 \le n_2$ のとき $P_2 \subseteq P_1$ が成り立ちます。すなわち任意の二つのプレフィックスは、互いに素であるか、一方が他方を含むかのいずれかです。
</Lemma>

<Proof of="lem-laminar">
$a \in P_1 \cap P_2$ を一つ取り、$n_1 \le n_2$ とします。アドレス $a$ の上位 $k$ ビットからなる列を $a[1..k]$ と書きます。

$a \in P_1$ より $a[1..n_1] = p_1[1..n_1]$、$a \in P_2$ より $a[1..n_2] = p_2[1..n_2]$ です。$n_1 \le n_2$ なので、後者の等式の最初の $n_1$ ビットだけを見れば $a[1..n_1] = p_2[1..n_1]$ を得ます。二つを合わせると
$$
p_2[1..n_1] = a[1..n_1] = p_1[1..n_1]
$$
です。さて任意の $b \in P_2$ を取ると $b[1..n_2] = p_2[1..n_2]$、とくに $b[1..n_1] = p_2[1..n_1] = p_1[1..n_1]$ ですから、$b \in P_1$ です。よって $P_2 \subseteq P_1$ が示されました。

$P_1 \cap P_2 = \varnothing$ でない限りこの議論が使えるので、二つのプレフィックスは素か入れ子かのどちらかです。
</Proof>

<Theorem id="thm-lpm" title="最長プレフィックス一致の一意性">
$\mathcal{P}$ を有限個のプレフィックスの集合、$a$ を 32 ビットのアドレスとし、$\mathcal{M} = \{P \in \mathcal{P} \mid a \in P\}$ とおきます。$\mathcal{M} \ne \varnothing$ ならば、$\mathcal{M}$ の中でプレフィックス長が最大のものはただ一つ存在し、それは包含関係に関する $\mathcal{M}$ の最小元です。
</Theorem>

<Proof of="thm-lpm">
まず、$\mathcal{M}$ の元でプレフィックス長が等しいものは一致します。実際 $P = p/n$ と $Q = q/n$ がともに $a$ を含むなら、$p[1..n] = a[1..n] = q[1..n]$ であり、プレフィックスは上位 $n$ ビットのみで定まるので $P = Q$ です。したがって $\mathcal{M}$ の元のプレフィックス長はすべて相異なり、$\mathcal{M}$ は有限で空でないので、長さ最大の元 $P^\ast$ がただ一つ定まります。

次に $P^\ast$ が包含に関する最小元であることを見ます。任意の $P \in \mathcal{M}$ を取ると、$a \in P \cap P^\ast$ より $P \cap P^\ast \ne \varnothing$ であり、$P$ の長さは $P^\ast$ の長さ以下ですから、<Ref to="lem-laminar" /> を $(P_1, P_2) = (P, P^\ast)$ に適用して $P^\ast \subseteq P$ を得ます。これがすべての $P \in \mathcal{M}$ について成り立つので、$P^\ast$ は $\mathcal{M}$ の最小元です。
</Proof>

この定理は「最長のものを選ぶ」規則が「候補のうち最も限定的な指示に従う」規則と同じであることを示しています。設計者は大きな範囲に大まかな規則を書き、例外だけを長いプレフィックスで上書きできます。経路表の最後に必ず置かれる**デフォルト経路** `0.0.0.0/0` は長さ 0 のプレフィックス、すなわち全アドレスの集合であり、これが $\mathcal{M} \ne \varnothing$ を保証します。

<Remark id="rem-ipv6-nat">
IPv4 のアドレスは $2^{32} \approx 4.3 \times 10^9$ 個しかなく、2011 年に IANA の在庫が枯渇しました。現在は二つの対処が併存しています。一つは 128 ビットに拡張した IPv6、もう一つは NAT（Network Address Translation）で、内部で `192.168.0.0/16` などの**プライベートアドレス**を使い、外向きには 1 個のグローバルアドレスを共有する方式です。NAT は送信元のアドレスとポート番号を書き換えるため、<Ref to="def-socket" /> の 4 つ組が経路の途中で変化します。層の独立性を破る仕組みですが、実務では前提として扱う必要があります。クラウド上の VPC も、この private/public の区別の上に構成されています（[クラウドコンピューティング](/computer-science/software-engineering/cloud-computing) の <Ref to="computer-science/software-engineering/cloud-computing#ex-three-tier" /> を参照してください）。
</Remark>

## 5. トランスポート層：TCP と UDP

### 5.1. ポート番号による多重化

IP アドレスはホストを指しますが、1 台のホストでは多数のプロセスが同時に通信します。これを区別するのがポート番号（16 ビット）です。

<Definition id="def-socket" title="接続を識別する 4 つ組">
TCP の一つの**接続**は
$$
(\text{送信元 IP},\ \text{送信元ポート},\ \text{宛先 IP},\ \text{宛先ポート})
$$
という 4 つ組で一意に識別されます。到着したセグメントは、この 4 つ組が一致する接続に配送されます。UDP には接続の概念がなく、宛先の 2 つ組（IP とポート）だけで受け取り口が決まります。
</Definition>

サーバは 80 番（HTTP）、443 番（HTTPS）、53 番（DNS）のような**よく知られたポート**で待ち受け、クライアントは OS が割り当てる**エフェメラルポート**を使います。

<Example id="ex-ephemeral" title="1 台のクライアントが張れる同時接続数">
Linux の既定のエフェメラルポート範囲は 32768〜60999 で、$60999 - 32768 + 1 = 28{,}232$ 個です。<Ref to="def-socket" /> より、同一のクライアント IP から同一のサーバ（同じ IP・同じポート）へ張れる同時接続は、送信元ポートが相異なる必要があるため**最大 28,232 本**です。

さらに TCP は、接続終了後もしばらく `TIME_WAIT` 状態で 4 つ組を保持します（既定でおよそ 60 秒）。毎秒 $r$ 本の短命な接続を張り続けると、到着率 $r$・滞在時間 60 秒に <Ref to="computer-science/software-engineering/cloud-computing#thm-little" text="リトルの法則" /> を当てはめて、定常状態で約 $60r$ 本分が占有されます。よって $60r \le 28{,}232$ すなわち $r \le 470$ 本/秒が上限です。負荷試験で「500 リクエスト/秒あたりから接続エラーが出る」現象の典型的な原因がこれです。対策は接続の使い回し（HTTP の keep-alive、データベースのコネクションプール）で、[データベース設計の基礎](/computer-science/software-engineering/database-design) で設計する永続データ層へアクセスするときも、この制約は同じように効きます。
</Example>

TCP と UDP の役割分担を整理します。

| | TCP | UDP |
|---|---|---|
| 接続 | あり（ハンドシェイクが必要） | なし |
| 到達保証・順序保証 | あり（確認応答と再送） | なし |
| 流量制御・輻輳制御 | あり | なし（アプリが実装する） |
| ヘッダ長 | 20 バイト以上 | 8 バイト |
| 向く用途 | HTTP, メール, ファイル転送 | DNS, 音声・映像, QUIC の土台 |

UDP は「機能が足りない」のではなく、**必要な機能だけを上に載せるための素材**です。HTTP/3 が UDP 上に QUIC を実装するのは、カーネルにある TCP は改良しにくく、再送や輻輳制御をユーザ空間で設計したいからです。

### 5.2. 3 ウェイハンドシェイク

TCP の接続確立は SYN、SYN+ACK、ACK の 3 通で行われます。クライアントは初期シーケンス番号 $x$ を選んで `SYN(seq=x)` を送り、サーバは自分の初期シーケンス番号 $y$ を選んで `SYN(seq=y), ACK(ack=x+1)` を返し、クライアントが `ACK(ack=y+1)` を返して確立します。3 通目は冗長に見えますが、そうではありません。

<Proposition id="prop-handshake" title="2 ウェイでは接続確立が安全でない">
接続確立を「クライアントの `SYN`」と「サーバの `SYN+ACK`」の 2 通のみで完了とし、サーバは `SYN+ACK` を送った時点で接続を確立済みと見なす方式を考えます。ネットワークがパケットを任意に遅延させて重複配送しうるとき、この方式では、クライアントが接続を望んでいないのにサーバ側だけが接続を確立してしまう実行系列が存在します。
</Proposition>

<Proof of="prop-handshake">
次の実行を構成します。

1. 時刻 $t_0$ に、クライアント C が `SYN(seq=x)` をサーバ S に送ります。この `SYN` は経路の途中で複製され、片方は正常に届いて接続が確立し、通信を終えて閉じられます。もう片方は、あるルータのキューに滞留したまま残ります。
2. C は再起動するなどして、この接続に関する状態をすべて失います。
3. 時刻 $t_1 > t_0$ に、滞留していた複製 `SYN(seq=x)` が S に到達します。S から見ると、これは正当な新しい接続要求と区別がつきません（`SYN` の内容は $t_0$ のときと完全に同一だからです）。
4. S は接続用の資源を確保し、`SYN+ACK` を返し、2 ウェイの規約により確立済みと見なします。以後 S は、この接続でデータを受け取れる状態で待ち、場合によっては自分からデータを送ります。
5. C にはこの接続の記録がないので、届いた `SYN+ACK` を無視するか破棄します。

結果として、C は何も要求していないのに S 側だけが接続を保持します。これが主張した実行系列です。

3 ウェイならこの実行は成立しません。ステップ 4 で S は確立せず、`ack = y+1` を含む ACK を待ちます。$y$ は S がこの試行のために新しく選んだ値なので、$t_0$ に作られた古いパケットが $y+1$ を含むことはできません。C はこの `SYN+ACK` に対応する接続を持たないので `RST` を返し、S は資源を解放します。すなわち 3 回目の ACK は「相手が**この試行**を認識していること」の証拠であり、鮮度の検査になっています。この理由から初期シーケンス番号は予測困難でなければならず、現在の実装は乱数に基づいて生成します。
</Proof>

<Aside type="caution">
サーバがステップ 4 で確保する資源は有限です。ACK を返さない `SYN` を大量に送って資源を枯渇させる攻撃を SYN フラッドと呼び、対策には、状態を持たず $y$ に情報を埋め込む SYN cookie が使われます。
</Aside>

### 5.3. ウィンドウとスループットの上限

TCP は、確認応答（ACK）を待たずに送出できるデータ量に上限を設けます。これを**ウィンドウ**と呼び、受信側のバッファ空きを表す広告ウィンドウと、混雑度の推定である輻輳ウィンドウの小さいほうが実効値です。

<Proposition id="prop-window" title="ウィンドウによるスループットの上限">
送信側の実効ウィンドウが常に $W$ バイト以下、往復時間が常に $R$ 秒以上であるとします。すなわち、任意の時刻において確認応答が返っていない（未確認の）データ量は $W$ バイト以下であり、あるバイトを送出してからその確認応答が届くまでに少なくとも $R$ 秒かかるものとします。このとき、時間 $[0, T]$ に送出できるデータ量 $B(T)$ について
$$
B(T) \le \left(\frac{T}{R} + 1\right) W
$$
が成り立ち、したがって長時間平均のスループットは $W/R$ を超えません。
</Proposition>

<Proof of="prop-window">
任意の時刻 $t$ を取り、半開区間 $(t - R,\ t]$ に送出されたバイトを考えます。仮定より、これらのバイトの確認応答が届くのは送出時刻から $R$ 秒以上あとですから、時刻 $t$ の時点ではいずれもまだ確認されていません。よってこれらはすべて未確認データに含まれ、その総量は $W$ 以下でなければなりません。式で書けば、任意の $t$ について
$$
B(t) - B(t - R) \le W
$$
です（$B$ は送出済みバイト数の累積、$t < 0$ では $B(t) = 0$ とします）。

$m = \lceil T/R \rceil$ とおき、この不等式を $t = R,\ 2R,\ \ldots,\ mR$ について足し合わせると、左辺は望遠鏡和になって
$$
B(mR) - B(0) \le mW
$$
を得ます。$B(0) = 0$ かつ $B$ は非減少、$mR \ge T$ なので $B(T) \le B(mR) \le mW$ です。$m = \lceil T/R \rceil \le T/R + 1$ を代入すれば主張の不等式が従います。両辺を $T$ で割って $T \to \infty$ とすれば、平均スループット $B(T)/T$ の上極限は $W/R$ 以下です。
</Proof>

<Example id="ex-bdp" title="帯域遅延積とウィンドウスケール">
東京とサンフランシスコの間を $R = 100$ ms、回線を 100 Mbit/s とします。TCP ヘッダのウィンドウフィールドは 16 ビットしかないので、拡張しなければ $W \le 65{,}535$ バイトです。<Ref to="prop-window" /> より、スループットの上限は
$$
\frac{65{,}535 \times 8\ \mathrm{bit}}{0.1\ \mathrm{s}} = 5.24 \times 10^{6}\ \mathrm{bit/s} = 5.24\ \mathrm{Mbit/s}
$$
です。100 Mbit/s の回線の 5% しか使えません。

回線を埋め切るのに必要なウィンドウは**帯域遅延積**（BDP）
$$
\mathrm{BDP} = 100 \times 10^{6}\ \mathrm{bit/s} \times 0.1\ \mathrm{s} = 10^{7}\ \mathrm{bit} = 1.25\ \mathrm{MB}
$$
で、16 ビットの枠に収まりません。この問題を解くのが RFC 7323 のウィンドウスケールオプションで、接続確立時にシフト量 $s$（$0 \le s \le 14$）を交換し、以後ウィンドウ値を $2^{s}$ 倍して解釈します。$1.25 \times 10^{6}/65{,}535 \approx 19.1$ なので $2^{5} = 32$ 倍あれば足り、$s = 5$ 以上を選べば回線を使い切れます。

なお、シフト量は `SYN` でしか交換できず、途中の機器がこれを削除すると、接続はできるのに遅いという診断の難しい状態になります。
</Example>

<Example id="ex-rto" title="再送タイムアウトの計算">
再送タイマ RTO の値は、RFC 6298 で次のように定められています。新しい RTT の測定値 $R'$ が得られたとき、$\alpha = 1/8$、$\beta = 1/4$ として
$$
\begin{aligned}
\mathrm{RTTVAR} &\leftarrow (1-\beta)\,\mathrm{RTTVAR} + \beta\,\lvert \mathrm{SRTT} - R' \rvert \\
\mathrm{SRTT} &\leftarrow (1-\alpha)\,\mathrm{SRTT} + \alpha R' \\
\mathrm{RTO} &\leftarrow \mathrm{SRTT} + 4\,\mathrm{RTTVAR}
\end{aligned}
$$
と更新します（RTTVAR の更新に**古い** SRTT を使う点に注意してください）。

いま $\mathrm{SRTT} = 100$ ms、$\mathrm{RTTVAR} = 50$ ms の状態で $R' = 120$ ms が観測されたとします。
$$
\begin{aligned}
\mathrm{RTTVAR} &= 0.75 \times 50 + 0.25 \times \lvert 100 - 120 \rvert = 37.5 + 5 = 42.5\ \mathrm{ms} \\
\mathrm{SRTT} &= 0.875 \times 100 + 0.125 \times 120 = 87.5 + 15 = 102.5\ \mathrm{ms} \\
\mathrm{RTO} &= 102.5 + 4 \times 42.5 = 272.5\ \mathrm{ms}
\end{aligned}
$$
平均に 4 標準偏差相当の余裕を足すので、RTT が安定した経路では RTO は平均の 1.1 倍程度、揺らぐ経路では数倍になります。早すぎる再送は輻輳を悪化させるため、TCP は保守的な側に倒しています。
</Example>

## 6. アプリケーション層：DNS と HTTP

### 6.1. DNS — 名前を住所に変える分散データベース

人間は `www.example.com` を覚え、IP は 32 ビットの数を要求します。この対応表が **DNS** です。表は一箇所になく、ドメイン名の階層に沿って**権威**が分割されています。

<Definition id="def-dns" title="ドメイン名の階層と権威サーバ">
ドメイン名 `www.example.com.` は、右端の根（ルート、空ラベル）から左へ `com`、`example`、`www` とたどる木のパスです。各ノードに対して、その配下の情報について正しい答えを返す責任を持つサーバを**権威 DNS サーバ**といい、親のゾーンは子のゾーンの権威サーバを指す `NS` レコードだけを保持します。名前とアドレスの対応は `A` レコード（IPv4）、`AAAA` レコード（IPv6）として保持されます。
</Definition>

クライアント（スタブリゾルバ）は通常、`/etc/resolv.conf` などで指定された**フルサービスリゾルバ**に問い合わせるだけです。木をたどるのはリゾルバの仕事で、次の**反復問い合わせ**を行います。

<Example id="ex-dns-trace" title="www.example.com の反復解決">
リゾルバのキャッシュが空だとして、手順を追います。

1. ルートサーバ（13 系統のアドレスがあらかじめ設定されています）に `www.example.com の A レコードは?` と尋ねます。ルートは答えを知りませんが、「`com` の権威は次の NS だ」と返します。
2. `com` の権威サーバに同じ質問をします。「`example.com` の権威は次の NS だ」と返ってきます。
3. `example.com` の権威サーバに同じ質問をします。ここで初めて `A` レコード（本記事では説明用に `203.0.113.10` とします）が返ります。
4. リゾルバは答えをクライアントに返し、同時に各段の応答を **TTL**（レコードごとに指定された秒数）の間キャッシュします。

段数は 3 段ですが、実運用ではリゾルバがルートと TLD の情報をほぼ常時キャッシュしているため、キャッシュミス時でも往復は 1〜2 回で済みます。逆に TTL を極端に短くすると（たとえば 30 秒）、この往復が毎回体感遅延に上乗せされます。TTL は「切り替えの速さ」と「解決の速さ」のトレードオフです。

手元で同じ手順を再現するには、次のコマンドが使えます。

```bash
dig +trace www.example.com A
```
</Example>

<Remark id="rem-dns-transport">
DNS は主に UDP の 53 番ポートを使います。1 往復で終わるため軽く、失われたら再送すればよいからです。ただし応答が 512 バイトを超えると、EDNS0 で大きなサイズを許可するか TCP に切り替える必要があります。DNSSEC の署名を含む応答は大きくなりやすく、障害の起きやすい部分です。
</Remark>

### 6.2. HTTP — テキストの要求と応答

HTTP は単純なプロトコルです。クライアントは「メソッド、対象、バージョン」からなるリクエスト行と `名前: 値` の形のヘッダ行を送り、空行のあとに本体を置きます。サーバはステータス行とヘッダ、空行、本体を返します。次のコードは Python の標準ライブラリだけで HTTP/1.1 のリクエストを組み立て、応答ヘッダを表示します。

```python
import socket

request = (
    "GET / HTTP/1.1\r\n"
    "Host: example.com\r\n"
    "User-Agent: rikai-demo/1.0\r\n"
    "Connection: close\r\n"
    "\r\n"
)

with socket.create_connection(("example.com", 80), timeout=5) as sock:
    sock.sendall(request.encode("ascii"))
    chunks = []
    while True:
        data = sock.recv(4096)
        if not data:
            break
        chunks.append(data)

response = b"".join(chunks)
header, _, body = response.partition(b"\r\n\r\n")
print(header.decode("ascii", errors="replace"))
print(f"body: {len(body)} bytes")
```

`Host` ヘッダは HTTP/1.1 で必須です。1 つの IP アドレスで複数のドメインを提供する（バーチャルホスト）ために、どのサイト宛かをアプリケーション層で伝える必要があるからです。`Connection: close` を外すと、サーバは接続を維持したまま次のリクエストを待ちます（keep-alive）。ステータスコードは 3 桁で、2xx は成功、3xx はリダイレクト、4xx はクライアント側の誤り、5xx はサーバ側の誤りを表します。

| バージョン | 下位プロトコル | 主な変更点 |
|---|---|---|
| HTTP/1.1 (1997, RFC 9112) | TCP | 持続接続、`Host` ヘッダ必須、チャンク転送 |
| HTTP/2 (2015, RFC 9113) | TCP + TLS | ヘッダ圧縮（HPACK）、1 接続上のストリーム多重化 |
| HTTP/3 (2022, RFC 9114) | QUIC (UDP + TLS 1.3) | ストリームごとの独立再送、接続確立の 1-RTT 化 |

HTTP/2 の動機は、HTTP/1.1 の**ヘッドオブラインブロッキング**です。1.1 では 1 本の接続で 1 リクエストずつしか処理できず、ブラウザは同一ホストに 6 本程度の TCP 接続を張って並列化していました。HTTP/2 はこれを 1 本の接続上のストリーム多重化で解消します。ただし土台が TCP である限り、1 つのパケットが失われるとその後ろのすべてのストリームが待たされます。HTTP/3 が UDP に移ったのは、この待ち合わせを消すためです。

## 7. URL を入力してからページが表示されるまで

ここまでの各層を 1 本の時系列に並べ、`https://www.example.com/` を入力してから HTML が届くまでを追います。

<Figure caption="初回アクセスで発生する往復。DNS・TCP・TLS・HTTP でそれぞれ 1 往復ずつかかる">
<Mermaid code={`sequenceDiagram
    participant B as ブラウザ
    participant R as フルサービスリゾルバ
    participant S as Web サーバ
    B->>R: www.example.com の A レコードは?
    R-->>B: 203.0.113.10 (TTL 300)
    B->>S: SYN seq=x
    S-->>B: SYN seq=y, ACK x+1
    B->>S: ACK y+1
    B->>S: TLS ClientHello + 鍵共有
    S-->>B: ServerHello + 証明書 + Finished
    B->>S: Finished（以後は暗号化）
    B->>S: GET / HTTP/1.1, Host: www.example.com
    S-->>B: 200 OK + HTML 本体
    B->>B: HTML 解析、CSS/JS/画像の追加取得へ`} />
</Figure>

順に見ます。

1. **URL の解析。** スキーム（`https`）、ホスト（`www.example.com`）、ポート（省略時は 443）、パス（`/`）に分解します。国際化ドメイン名は Punycode に変換されます。
2. **名前解決。** ブラウザ内キャッシュ、OS のキャッシュ、`hosts` ファイル、フルサービスリゾルバの順に問い合わせます。<Ref to="ex-dns-trace" /> の手順で `A`／`AAAA` レコードが得られます。キャッシュに当たれば往復は 0 回、外れれば 1〜2 回です。
3. **TCP 接続の確立。** 得られた IP アドレスの 443 番ポートに 3 ウェイハンドシェイクを行います（<Ref to="prop-handshake" />）。データを送れるまで 1 往復です。送信元は自ホストのエフェメラルポートで、接続は <Ref to="def-socket" /> の 4 つ組で識別されます。
4. **TLS ハンドシェイク。** TLS 1.3 では ClientHello に鍵共有の材料を載せるため、1 往復で鍵が確立します（TLS 1.2 では 2 往復必要でした）。サーバ証明書の検証もここで行われます。
5. **HTTP リクエストとレスポンス。** `GET / HTTP/1.1` を送り、`200 OK` と HTML を受け取ります。これで 1 往復です。
6. **描画と追加取得。** HTML を解析しながら CSS・JavaScript・画像を追加取得します。この段階で 2〜5 の一部が別ホストに対して繰り返されます。

<Example id="ex-rtt-budget" title="往復回数から所要時間を見積もる">
RTT を $R = 80$ ms、DNS はキャッシュミス（リゾルバは TLD をキャッシュ済みで 1 往復）、TLS 1.3、HTML は 1 パケットに収まるとします。<Ref to="ex-delay" /> で見たとおり送信遅延は無視できるので、往復回数だけを数えます。

| 段階 | 往復数 | 所要時間 |
|---|---|---|
| DNS 解決 | 1 | 80 ms |
| TCP 3 ウェイハンドシェイク | 1 | 80 ms |
| TLS 1.3 ハンドシェイク | 1 | 80 ms |
| HTTP リクエスト／レスポンス | 1 | 80 ms |
| 合計 | 4 | **320 ms** |

2 回目以降のアクセスでは、DNS が TTL の間キャッシュされ、TCP 接続も keep-alive で再利用されるため、HTTP の 1 往復（80 ms）で済みます。**初回 320 ms、2 回目 80 ms** という 4 倍の差が、往復回数の数え上げだけから出てきます。

改善の方向も読めます。CDN で物理距離を縮めれば $R$ 自体が下がり（80 ms から 10 ms へ）、全段が同じ比率で速くなります。HTTP/3 なら TCP と TLS のハンドシェイクが統合されて 1 往復減ります。逆に、サーバの処理を 10 ms 速くしても全体の 3% です。**どこを直すべきかは、この表を作ってから決めてください。**
</Example>

<Aside type="tip">
コンテナの中からこの流れを追うときは、名前解決の経路が 1 段増えます。Kubernetes では Pod（<Ref to="computer-science/software-engineering/containers-and-kubernetes#def-pod" />）の `/etc/resolv.conf` がクラスタ DNS を指し、`サービス名.名前空間.svc.cluster.local` が解決されます。「外からは引けるのに Pod からは引けない」障害は、ほぼこの段の設定です（[仮想化技術（Docker・Kubernetes）](/computer-science/software-engineering/containers-and-kubernetes)）。
</Aside>

## 8. 演習

<Exercise id="exr-cidr" difficulty="易">
IPv4 アドレス `198.51.100.200` が、プレフィックス長 26 のブロックに分割された空間の中でどのブロックに属するかを求め、そのブロックのネットワークアドレス、ブロードキャストアドレス、ホストに割り当て可能なアドレス数を答えてください。

<Solution>
プレフィックス長 26 のブロックが含むアドレス数は $2^{32-26} = 2^{6} = 64$ 個です。したがって第 4 オクテットは 0〜63、64〜127、128〜191、192〜255 の 4 ブロックに分かれます。$200$ は最後のブロックに入ります。実際、$200 = (1100\,1000)_2$ であり、上位 2 ビット（第 26 ビットまでに含まれる部分）は $(11)_2$、これは $192 = (1100\,0000)_2$ の上位 2 ビットと一致します。

- 所属ブロック: `198.51.100.192/26`
- ネットワークアドレス: `198.51.100.192`
- ブロードキャストアドレス: `198.51.100.255`
- 割り当て可能なホスト数: $2^{6} - 2 = 62$ 個

</Solution>
</Exercise>

<Exercise id="exr-window-scale" difficulty="標準">
帯域 1 Gbit/s、RTT 60 ms の経路があります。

(1) この経路を埋め切るのに必要なウィンドウサイズ（帯域遅延積）をバイト単位で求めてください。
(2) ウィンドウスケールを使わない場合（$W \le 65{,}535$ バイト）の最大スループットを求めてください。
(3) 経路を埋め切るために必要な最小のウィンドウスケール係数 $s$（ウィンドウ値を $2^{s}$ 倍する）を求めてください。

<Solution>
**(1)** 帯域遅延積は
$$
1.0 \times 10^{9}\ \mathrm{bit/s} \times 6.0 \times 10^{-2}\ \mathrm{s} = 6.0 \times 10^{7}\ \mathrm{bit} = 7.5 \times 10^{6}\ \text{バイト}
$$
すなわち 7.5 MB です。

**(2)** <Ref to="prop-window" /> より上限は
$$
\frac{65{,}535 \times 8}{0.06} = \frac{524{,}280}{0.06} = 8.738 \times 10^{6}\ \mathrm{bit/s} \approx 8.74\ \mathrm{Mbit/s}
$$
回線の 0.87% しか使えません。

**(3)** 必要な倍率は $7.5 \times 10^{6} / 65{,}535 = 114.4\ldots$ です。$2^{6} = 64 < 114.4$、$2^{7} = 128 \ge 114.4$ なので $s = 7$ が最小です。このとき表現できる最大ウィンドウは $65{,}535 \times 128 = 8{,}388{,}480$ バイトで、7.5 MB を上回ります。

長距離・広帯域の経路（long fat network）では、送受信バッファの設定が決定的に効きます。OS のバッファ上限が 4 MB なら、ウィンドウスケールを有効にしても実効ウィンドウは 4 MB で頭打ちになり、$4 \times 10^{6} \times 8 / 0.06 = 533$ Mbit/s しか出ません。
</Solution>
</Exercise>

<Exercise id="exr-rto" difficulty="標準">
ある TCP 接続で $\mathrm{SRTT} = 200$ ms、$\mathrm{RTTVAR} = 50$ ms の状態にあります。次の RTT 測定値が $R' = 140$ ms でした。RFC 6298 の更新式（<Ref to="ex-rto" /> 参照、$\alpha = 1/8$、$\beta = 1/4$）に従って、更新後の SRTT、RTTVAR、RTO を求めてください。

<Solution>
更新の順序が重要です。RTTVAR を先に、**古い** SRTT を使って計算します。
$$
\begin{aligned}
\mathrm{RTTVAR} &= \left(1 - \tfrac{1}{4}\right)\times 50 + \tfrac{1}{4}\times \lvert 200 - 140\rvert \\
&= 0.75 \times 50 + 0.25 \times 60 = 37.5 + 15 = 52.5\ \mathrm{ms}
\end{aligned}
$$
次に SRTT を更新します。
$$
\mathrm{SRTT} = \left(1 - \tfrac{1}{8}\right)\times 200 + \tfrac{1}{8}\times 140 = 175 + 17.5 = 192.5\ \mathrm{ms}
$$
最後に RTO を求めます。
$$
\mathrm{RTO} = 192.5 + 4 \times 52.5 = 192.5 + 210 = 402.5\ \mathrm{ms}
$$

測定値が平均より 60 ms 小さかったのに、RTO は更新前の $200 + 4\times 50 = 400$ ms からわずかに**増えて** 402.5 ms になりました。RTTVAR は平均からのずれの大きさを追うので、上下どちらにぶれても増えるためです。なお RFC 6298 は RTO の下限を 1 秒と定めており、実装によってはこの値は 1 秒に丸められます。
</Solution>
</Exercise>

<Exercise id="exr-h2-budget" difficulty="難">
1 つのホストから 100 個の小さなリソース（各レスポンスは 1 パケットに収まるとします）を取得します。RTT は 80 ms、TLS 1.3 を使い、サーバの処理時間と送信遅延は無視します。

(1) HTTP/1.1 で同一ホストへ 6 本の TCP 接続を並列に張り（パイプラインは使わない）、各接続でリクエストを 1 つずつ順に処理する場合、全リソースの取得完了までの時間を見積もってください。接続確立にかかる時間も含めてください。
(2) HTTP/2 で 1 本の接続に 100 個のリクエストを多重化する場合の時間を見積もってください。
(3) HTTP/3 を使うと (2) はどう変わりますか。

<Solution>
**(1)** 6 本の接続はすべて並列に張れるので、確立にかかるのは 1 本分の時間です。TCP の 3 ウェイハンドシェイクで 1 往復、TLS 1.3 で 1 往復、合わせて $2 \times 80 = 160$ ms かかります。

その後、100 個のリクエストを 6 本に振り分けます。1 本あたりのリクエスト数は最大 $\lceil 100/6 \rceil = 17$ 個で、各リクエストは 1 往復ずつ順番に処理されるので $17 \times 80 = 1360$ ms です。合計は
$$
160 + 1360 = 1520\ \mathrm{ms}
$$
となります。

**(2)** 接続確立は 1 本分で同じく 160 ms です。多重化により 100 個のリクエストを一度に送れるので、応答が返るのは 1 往復後、80 ms です。合計は
$$
160 + 80 = 240\ \mathrm{ms}
$$
です。(1) の約 $1520/240 \approx 6.3$ 倍の高速化になります。

**(3)** HTTP/3 の土台である QUIC は、トランスポートの接続確立と TLS 1.3 の鍵交換を 1 往復に統合します。確立が 160 ms から 80 ms に減り、合計は $80 + 80 = 160$ ms です。さらに同じサーバに以前接続していれば 0-RTT 再開が使え、理論上は 80 ms まで縮みます。

**遅延支配的な環境では往復回数がほぼすべてを決めます。** (1) の 1520 ms のうち 1360 ms、89% は「順番待ち」であり、帯域とは無関係です。
</Solution>
</Exercise>

## 参考文献

- Kevin R. Fall, W. Richard Stevens, *TCP/IP Illustrated, Volume 1: The Protocols*, 2nd ed., Addison-Wesley, 2011 — リンク層から HTTP まで、実際のパケットを見ながら追う定番。第 3 章（リンク層）、第 5 章（IP）、第 12〜17 章（TCP）。
- James F. Kurose, Keith W. Ross, *Computer Networking: A Top-Down Approach*, 8th ed., Pearson, 2020 — 本記事と同じくアプリケーション層から降りていく構成。第 1 章（遅延の分解）、第 3 章（トランスポート層）。
- V. Cerf, R. Kahn, "A Protocol for Packet Network Intercommunication", *IEEE Transactions on Communications* **22**(5) (1974), 637–648 — TCP/IP の原論文。
- RFC 9293, *Transmission Control Protocol (TCP)*, 2022 — TCP の現行仕様（RFC 793 を置き換えたもの）。[https://www.rfc-editor.org/rfc/rfc9293](https://www.rfc-editor.org/rfc/rfc9293)
- RFC 6298, *Computing TCP's Retransmission Timer*, 2011 — <Ref to="ex-rto" /> の更新式の出典。[https://www.rfc-editor.org/rfc/rfc6298](https://www.rfc-editor.org/rfc/rfc6298)
- RFC 9110, *HTTP Semantics*, 2022 — メソッド・ステータスコード・ヘッダの現行定義。[https://www.rfc-editor.org/rfc/rfc9110](https://www.rfc-editor.org/rfc/rfc9110)
- RFC 1035, *Domain Names — Implementation and Specification*, 1987 — DNS のメッセージ形式とレコード型。[https://www.rfc-editor.org/rfc/rfc1035](https://www.rfc-editor.org/rfc/rfc1035)

## Appendix: インターネットチェックサムが見逃す誤り

**チェックサムの定義。** IP・TCP・UDP のヘッダには 16 ビットのチェックサムがあります。その計算法（RFC 1071）は、対象データを 16 ビット語 $w_1, \ldots, w_n$ に区切り、**1 の補数和**
$$
S = w_1 \boxplus w_2 \boxplus \cdots \boxplus w_n
$$
を求め、その 1 の補数 $\overline{S}$ を送るというものです。ここで $a \boxplus b$ は、$a + b$ を計算して 16 ビットからあふれたキャリーを最下位に巡回加算する演算です。受信側は $\overline{S}$ を含めた全語の和を取り、結果が全ビット 1 になることを確認します。

この演算は見かけより単純な代数的意味を持ちます。

**補題（1 の補数和の正体）。** $0$ と $2^{16}-1$ を同一視すると、$\boxplus$ は $\mathbb{Z}/(2^{16}-1)\mathbb{Z}$ の加法と一致します。実際、$a + b \ge 2^{16}$ のとき $a \boxplus b = a + b - 2^{16} + 1 = a + b - (2^{16}-1)$ であり、これは $a+b$ から $2^{16}-1$ を引くこと、すなわち法 $2^{16}-1$ での還元です。$a + b < 2^{16}$ のときは $a \boxplus b = a + b$ でそのままです。よって $\boxplus$ は法 $2^{16}-1$ の加法であり、とくに**可換かつ結合的**です。

<Proposition id="prop-checksum" title="インターネットチェックサムが検出できない誤り">
チェックサムの対象となる 16 ビット語の列 $w_1, \ldots, w_n$ に対して、次の二種類の改変はチェックサムを変化させず、したがって検出できません。

1. 語の順序の入れ替え。すなわち任意の置換 $\sigma$ に対する $w_{\sigma(1)}, \ldots, w_{\sigma(n)}$。
2. 相殺する誤り。すなわち、ある $i \ne j$ と $d$ について $w_i$ を $w_i \boxplus d$ に、$w_j$ を $w_j \boxminus d$ に変える改変（$\boxminus$ は $\boxplus$ の逆演算）。
</Proposition>

<Proof of="prop-checksum">
補題より、$S$ は法 $m = 2^{16}-1$ での和 $\sum_i w_i \bmod m$ に等しく、$\boxplus$ は可換かつ結合的です。

1 について。有限個の元の和は加える順序によらないので、$\sum_i w_{\sigma(i)} \equiv \sum_i w_i \pmod m$ です。よって $S$ は不変で、チェックサムも変わりません。

2 について。改変後の和は
$$
\sum_{k \ne i, j} w_k + (w_i + d) + (w_j - d) \equiv \sum_k w_k \pmod m
$$
であり、$d$ の項が打ち消し合います。よってこれも検出されません。
</Proof>

**実務上の意味。** 1 の帰結として、機器のバグでパケット内の 2 バイト境界のブロックが入れ替わってもチェックサムは通ってしまいます。実測に基づく報告として、Stone と Partridge の研究（"When the CRC and TCP Checksum Disagree", *ACM SIGCOMM* 2000）は、TCP チェックサムを通過した誤りが数千〜数万パケットに 1 回の割合で存在しうると論じています。

データの完全性が重要な場面では、アプリケーション層で SHA-256 などのハッシュ検証を行ってください（偶然の衝突が事実上起きないことの見積もりは <Ref to="computer-science/software-engineering/version-control-git#prop-birthday" text="誕生日境界による上界" /> にあります）。TCP が保証するのは「多くの偶発的な誤りを高い確率で捨てること」であって「届いたデータが正しいこと」ではありません。層が保証する内容を過大評価しないことも、階層モデルを正しく使うことの一部です。


</div>
