はじめに
ネットワークの勉強をしていると、
「イーサネットヘッダは14bytes」
「IPv4ヘッダは20bytes」
などの数字がポンポン出てきますよね。
参考書やサイトを読んでも、この数字は 「そういうものだ」 、と書かれているだけで、なぜその数字になるのかを説明してくれるものが意外と少ない。
その 「なぜ」 を理解するために、この記事を書いてみました。
この記事は、ネスペ受験者やネットワーク初学者の方が、OSI参照モデルとイーサネットについて、納得しながらざっくりと理解できるように目指しています。
この記事は
「細かいプロトコルは説明しない」で
イーサネットの流れを理解することを目的としています。
OSI参照モデルとは
ネットワークの通信は、たくさんの仕事を分担して成り立っています。
この「仕事の分担表」を7つの層(レイヤ)に分けて整理したものが OSI参照モデル です。
この記事では、下の層から順番に L1 → L2 → L3 → L4 と積み上げていきます。
L5〜L7は実際の通信ではまとめて扱われることが多いので、最後にまとめて説明します。
L1 物理層
電気(や光、電波)の信号の話です。
簡単に言うと、流れているか流れていないかの二択で0, 1の二つの状態です。
この0と1を bit(ビット) と呼びます。
0 : 0がひとつなので1bit
1 : 1がひとつなので1bit
10 : 1と0がひとつずつあるので2bits
実際は「電圧レベル」や「符号化方式」で0/1を作っています。
通信を始めるとき、いきなりデータを送っても受信側は読み取れません。
そこでまず、10101010 という規則的なパターンを送って、受信側に「このリズムで読んでね」と伝えます。
10101010は、1bitが8個あつまっているので、8bitsです。
bitを8個集めた単位を byte(バイト) と呼びます。
ネットワークの計算では、byte が基本の単位になります。
この10101010を7回繰り返すことを プリアンブル といいます。
プリアンブルは、8bits × 7 = 56bits、つまり 7bytes です。
このプリアンブルの直後に、最後だけ少し変えた 10101011 を送ります。
これが「ここからデータ本体が始まるよ!」の合図で、これを SFD と呼びます。
SFDは1bitが8個あつまっているので、
8bits = 1byte です。
L1 物理層は、プリアンブル 7bytesとSFD 1byteで構成されているので、
合計 7 + 1 = 8bytes
L2 データリンク層(イーサネットヘッダ)
データリンク層の役割は 「隣の人に渡すこと」 です。
この層の主役は、MACアドレスと呼ばれるアドレスの話です。
00-00-5E-00-53-01のように英数字が入り混じったアドレスをMACアドレスと呼び、16進数で表しています。
MACアドレスは、隣りの機器を識別するためにつかいます。
会社でも、同じ会社の人に挨拶するときは、「○○課の××です。」と名乗りますよね?
それと同じで、MACアドレスも「ベンダー識別子(OUI)」と「ベンダー内管理番号」の
2セットになっています。
MACアドレスは12桁で表します。先ほどの通りセットになっているので、6桁+6桁です。
16進数は、一桁に0〜15までの数をつかいます。16は、$2^4$、つまり 4bits です。
ここまでで、16進数は、一桁に4bits必要なことがわかりました。
ここで、MACアドレスに戻ります。MACアドレスは、6桁+6桁で12桁必要になります。
16進数は1桁で4bits必要なことを先ほど学んだので、MACアドレスは、4bits × 12 = 48bits
つまり6bytes を使うことがわかります。
データリンク層の役割は、隣りの人に情報を渡すことです。
つまり、誰に、誰が渡すのか、という情報が必要です。
「誰に」を 宛先MACアドレス 、「誰が」を 送信元MACアドレス といいます。
また、イーサネットヘッダは、「Ethertype」と呼ばれるものがくっついています。
このEthertypeの役割は、「上位プロトコルが何か」を示すことです。
Ethertypeは4桁の16進数です。4桁の16進数なので、4bits × 4 = 16bits、つまり2bytes です。
本記事は一般的なEthernet II(EtherType) 前提で説明します。
(IEEE 802.3 では Length として使われる場合があります)
イーサネットヘッダは、MACアドレスが二つとEthertypeがひとつで構成されています。
MACアドレスは6bytes、Ethertypeが2bytesなので、
合計 : 6bytes × 2 + 2bytes = 14bytes
VLAN タグ(802.1Q)について(クリックで開きます)
ところで、先ほどの会社の例えを思い出してください。
実は、ネットワークにも「課」のような仕組みがあります。それが VLAN です。
VLANは、1つの物理的なネットワークを、論理的に複数のネットワークに分ける仕組みです。
例えば、同じフロアにいる営業部と開発部のPCが同じスイッチに繋がっていても、
VLANを設定すれば、お互いの通信を分離できます。
同じ建物にいるけど、部署ごとに別の内線番号体系を使っているようなイメージです。
この「どのVLANに属するか」を示す情報が、イーサネットヘッダに挿入されます。
これを VLANタグ(802.1Q) と呼びます。
VLANタグは、本来Ethertypeがあった場所に割り込みます。
通常: 宛先MAC(6) | 送信元MAC(6) | Ethertype(2)
VLANあり: 宛先MAC(6) | 送信元MAC(6) | VLANタグ(4) | Ethertype(2)
Ethertypeが後ろに押し出されていますね。
受信側は、Ethertypeの位置を読んだとき「あれ、0x8100 だぞ?」と気づきます。
これが「VLANタグがついているよ」というフラグになるんです。
この 0x8100 を TPID(Tag Protocol Identifier) と呼びます。
4桁の16進数なので、4×4 = 16bits = 2bytes です。
残りの2bytesは TCI(Tag Control Information) で、
ここにVLAN IDと優先度の情報が入っています。
| フィールド | サイズ | 説明 |
|---|---|---|
| PCP(Priority Code Point) | 3bits | フレームの優先度(0〜7)。L3のToSと似た役割。 |
| DEI(Drop Eligible Indicator) | 1bit | 混雑時に破棄してよいかのフラグ。 |
| VLAN ID | 12bits | どのVLANに属するかを示す番号。 |
VLAN IDは12bitsなので、$2^{12}$ = 4,096通り 。
つまり、1つのネットワーク上に理論上最大4,096個のVLANを作れるということですね。
理論上は4096個使えますが、
VID 0とVID 4095は予約なので、通常使えるのは1〜4094(4094個)になります。
$$
3\text{ bits (PCP)} + 1\text{ bit (DEI)} + 12\text{ bits (VLAN ID)} = 16\text{ bits} = 2\text{ bytes}
$$
TPIDの2bytesと合わせて、VLANタグ全体で 2 + 2 = 4bytes です。
VLANタグが入ると、イーサネットヘッダは 14 + 4 = 18bytes になります。
VLANタグがついたフレームを タグ付きフレーム(Tagged Frame) 、
ついていないものを アンタグフレーム(Untagged Frame) と呼びます。
L3 ネットワーク層(IPヘッダ)
この層の役割は、「届け先まで道を見つけて、届けること」 です。
L2は「隣の人に渡す」でしたが、L3は「最終目的地まで届ける」層です。
会社で例えるなら、L2が「隣の席の人に書類を手渡しする」だとすると、L3は「本社から大阪支社の○○さん宛に書類を送る」イメージです。
IPヘッダはこのように構成されています。
では、順を追って説明します。
IPアドレス
IPアドレスには、実はバージョンがあります。
現在主流なのは「IPv4」です。名前の通り、バージョン4ですね。
詳しくは後程説明しますが、この記事ではIPv4アドレスとIPv4ヘッダを扱います。
IPv4アドレスは、192.168.20.10のように数字で表されています。
これらの数字は、10進数で表現された8bits、つまり0〜255の数から構成されています。この8bitsの数を.で区切って4つ組み合わせたものをIPv4アドレスといいます。
例えば192.168.20.10なら192/168/20/10という4つの塊ですね。
8bits×4で32bits、つまり 4bytes です。
この一つの塊を、「オクテット」と言ったりします。168は第二オクテット、となります。
$2^8$ の8だからオクテットなんですね。
ネットワークの分野でデータを効率に扱うための区切りをワード単位と言います。
IPv4ヘッダは32bitsをワード単位としています。
IPv4アドレスは32bitsなのでこのワード単位に綺麗に収まっていますね。
IPアドレスもMACアドレスと同じように、「宛先IPアドレス」と「送信元IPアドレス」があります。L2では「誰に」「誰が」でしたね。L3でも同じ考え方です。
ただし、L2のMACアドレスが「隣の人」を指すのに対して、IPアドレスは「最終的な届け先」と「最初の送り主」を指します。
合計 : 4bytes × 2 = 8bytes
プロトコル
L2に「Ethertype」があったように、L3にも「プロトコル番号」と呼ばれるフィールドがあります。
役割も同じで、「次の層(L4)のデータが何のプロトコルなのか」を示す番号です。
このプロトコルは10進数で表現され、8bitsつまり、1byte です。
合計 : 1byte × 1 = 1byte
バージョン(Version)
IPアドレスの章で少し触れたフィールドです。
このフィールドは、「4」か「6」(IPv6)かを区別するためのものです。
たったそれだけの情報なので、サイズも小さく、4bits です。
4か6のバージョンしかないなら、1bitで足りるのでは?と思いますが、実は、他のbitsは将来的な拡張のために用意されているのです。
他のbitsに過去の他のバージョンが割り当てられたりしています。
ところで、バージョンだけだと1byteに満たないですね。実は、次のフィールドとセットで1byteになります。
ヘッダ長(IHL: Internet Header Length)
IPv4ヘッダの長さを表すフィールドです。
実は、IPヘッダには「オプション」という追加情報がくっつくことがあり、
その場合はIPヘッダの基本サイズの20bytesより大きくなります。
だから、「このヘッダは何bytesですよ」と教えてあげる必要があるんですね。
このフィールドも 4bits です。
先ほどのバージョンと合わせて、4+4で8bits = 1byte になりました!
ヘッダ長は4bits、つまり0〜15までの数字が入りますが、単位は 「4bytes(32bits)」 です。なので、IPヘッダが20bytesの場合、
20÷4 = 5 で5がこのフィールドに入ります。
ToS(Type of Service)/ DSCP
通信の「優先度」を指定するフィールドです。
例えば、ビデオ通話のデータは遅延すると困りますよね?
そういうときに「このパケットは優先的に扱ってね」と指示するためのものです。
サイズは 8bits = 1byte です。
パケット長(Total Length)
IPヘッダ+そのあとに続くデータを合わせた、IPパケット全体の長さを表します。
サイズは 16bits = 2bytes です。
パケット長の単位は 「1byte(8bits)」 です。つまり、IPパケットの最大サイズは、$2^{16}-1$ で 65,535bytes になります。
TTL(Time To Live)
個人的に一番おもしろいフィールドです。
これは、パケットがネットワーク上をたらい回しにされて、永遠にさまよい続けるのを防ぐための仕組みです。
ルータ(L3の機器)を1台通過するたびに、TTLの値が1ずつ減ります。0になったら、そのパケットは破棄されます。
サイズは 8bits = 1byte です。
8bitsなので、最大値は 255 。
つまり、最大255台のルータを経由できるということになります。
識別子(Identification)
大きなデータを送るとき、途中で分割(フラグメント)されることがあります。
届いた先で、バラバラの断片を見て「これは元々同じデータだよ」と判断するための番号が識別子です。
識別子は、同じ送信元・宛先・プロトコルの間で「同時に飛んでいるパケット」を区別できれば十分です。
ここで、同時に飛んでいるパケット数を見積るための基準として使われていたのがTTLです。 元々TTLは秒数として設計されていました。TTLは8bitsなので最大255秒、つまり、同じ通信ペア間のパケットは最大255秒の間識別されている必要がありました。
この仕組みが設計された当時のネットワークでは、255秒間に送ることのできるパケット数が今よりもずっと少なかったです。そのため、当時の通信速度で衝突しないサイズであること、そしてIPヘッダのワード単位32bitsに収まるように、識別子のサイズは16bitsに設計されました。
識別子は16bitsなので、同時に65,536通りのパケット数がやり取りできます。
IPv4の問題点(クリックで開きます)
識別子が設計された当時から、現代のネットワークは飛躍的に向上しています。 現代の回線速度では255秒間に65,536個を軽く超えうるため、この設計が問題になっています。 これがIPv6でフラグメンテーションの仕組み自体が大きく変更された理由の一つです。フラグ(Flags)と フラグメントオフセット(Fragment Offset)
この2つはどちらもフラグメント(分割)に関するフィールドです。
フラグは 「分割していいか」「まだ続きがあるか」 を示す情報で、フラグメントオフセットは 「分割されたデータの元の位置」 を示す情報です。
フラグ
フラグは 3bits のフィールドで、1bitsずつ役割が違います。
このbitは「3種類の値を表す2進数」ではなく、「1bitのフラグが3個横に並んでいる」と考えてください。
| bits | 名前 | 説明 |
|---|---|---|
| 0 | 予約 | 将来のために確保されているだけで、今は使われていません。 |
| 1 | DF(Don't Fragment) | 「このパケットを分割するな」という指示です。1なら分割禁止。 |
| 2 | MF(More Fragments) | 「まだ続きの断片があるよ」という合図です。1なら「後続あり」、0なら「これが最後(または分割されていない)」。 |
フラグメントオフセット
パケット長の最大値は、65,535bytesでしたね。なので、0〜65,535の 65,536通り を表現するために、必要なbitは$2^{16}$ で16bitsになります。
ただ、フラグが3bits使っているので、素直に使うと3+16で19bitsになります。
2bytes = 16bitsなので、このままでは2bytesからはみ出してしまいます。
そこで、フラグメントオフセットの単位を 「8bytes = 64bits」 とします。
そうすると、65,536 ÷ 8 = 8,192、つまり $2^{13}$ になります。
こうすることで、フラグ 3bits + フラグメントオフセット 13bits で 合わせて16bits 。
合計 : 3bits + 13bits = 2bytes
識別子が16bits、フラグ + フラグメントオフセットが16bitsなので、合わせて32bitsになり、ワード単位32bitsに収まります。
ヘッダチェックサム(Header Checksum)
データが壊れていないかを確認するためのフィールドです。サイズは 16bits = 2bytes です。
仕組みを簡単に説明すると、送信側は、IPヘッダ全体を16bit(2bytes)ずつに区切って、全部足し算します。
IPヘッダは、20bytesが基本なので、20÷2 = 10個 の塊になります。
ただし、このとき、チェックサムフィールド自体は0にしておきます。
つまり、実質的には残り9個の塊の値を使って計算するイメージです。
足した結果のbitsを全部反転(0→1、1→0)させた値が、チェックサムになります。
受信側は、チェックサムフィールドを含めて同じ計算をします。
データが壊れていなければ、計算結果が全bitsが1(0xFFFF)になります。
ならなければ、「どこか壊れている」と判断できるわけですね。
データの破損チェックが、こんな単純な足し算になっているのには理由があります。
TTLを思い出してください。ルーターを1台通過するたびに値が1減りましたよね。
TTLが変わるということは、ヘッダの内容が変わるということ。
つまり、チェックサムもルーターを通るたびに再計算が必要なんです。
だからこそ、足し算とbits反転だけで済むシンプルな方式が選ばれています。
チェックサムの詳しい計算例(クリックで開きます)
では、実際に計算してみましょう。
例として、以下のIPヘッダを使います。
| フィールド | 値 |
|---|---|
| バージョン(4) + IHL(5) + ToS(0) | 0x4500 |
| パケット長(115) | 0x0073 |
| 識別子 | 0x0000 |
| フラグ+オフセット | 0x4000 |
| TTL(64) + プロトコル(6) | 0x4006 |
| チェックサム(計算前) |
0x0000 ← ここを0にしておく |
| 送信元IP(192.168.1.1) |
0xC0A8 , 0x0101
|
| 宛先IP(192.168.1.2) |
0xC0A8 , 0x0102
|
ここで、送信元IPアドレスを例に、IPアドレスがなぜこの16進数になるか確認しておきましょう。
192.168.1.1 を1オクテット(8bits)ずつ16進数に変換すると、
192 = 0xC0
168 = 0xA8
1 = 0x01
1 = 0x01
これを2bytesずつまとめると、0xC0A8 と 0x0101 になります。
では、16bit(2bytes)ずつの塊を全部足します。
0x4500
+ 0x0073
+ 0x0000
+ 0x4000
+ 0x4006
+ 0x0000 ← チェックサム(0にしている)
+ 0xC0A8
+ 0x0101
+ 0xC0A8
+ 0x0102
---------
= 0x248CC
結果が16bitを超えました(0x248CC 16bitを超えています)。
ここで「桁あふれ」の処理をします。
はみ出した上位の桁(0x2)を、下位16bit(0x48CC)に足し戻します。
0x48CC + 0x2 = 0x48CE
最後に、全bitsを反転させます。
0x48CE = 0100 1000 1100 1110(2進数)
反転 → 1011 0111 0011 0001(2進数)
= 0xB731
これがチェックサム値です。
送信側は、チェックサムフィールドに 0xB731 を入れて送信します。
受信側の検証
今度はチェックサムフィールドに 0xB731 を入れた状態で、同じ足し算をします。
0x4500
+ 0x0073
+ 0x0000
+ 0x4000
+ 0x4006
+ 0xB731 ← 今度はチェックサム値が入っている
+ 0xC0A8
+ 0x0101
+ 0xC0A8
+ 0x0102
---------
= 0x2FFFD
桁あふれ処理:
0xFFFD + 0x2 = 0xFFFF
結果が 0xFFFF(全bits1)になりました!
これで「データは壊れていない」と確認できます。
もし通信途中でヘッダの値が1bitでも変わっていたら、この結果は 0xFFFF にならず、「壊れている」と検知できるわけです。
IPヘッダの合計
さあ、全部そろいました!総まとめの計算です!
| フィールド | サイズ |
|---|---|
| バージョン(4bits) + ヘッダ長(4bits) | 1byte |
| ToS | 1byte |
| パケット長 | 2bytes |
| 識別子 | 2bytes |
| フラグ(3bits) + フラグメントオフセット(13bits) | 2bytes |
| TTL | 1byte |
| プロトコル | 1byte |
| ヘッダチェックサム | 2bytes |
| 送信元IPアドレス | 4bytes |
| 宛先IPアドレス | 4bytes |
合計: 1+1+2+2+2+1+1+2+4+4 = 20bytes
L4 トランスポート層(TCP/UDPヘッダ)
今のネットワークを支えている重要な層です。
これまでの層は、「届けること」を目的としていましたが、この層は、「正しく届けること」 を目的としています。
トランスポート層はこのように構成されています。
ポート番号
この層の主役は「ポート番号」です。
L3のIPアドレスで「どの機器か」は特定できました。
でも、スマホやPCでは、ブラウザ、LINE、メールなどたくさんのアプリが同時に通信していますよね?
IPアドレスだけでは、届いたデータを「どのアプリに渡すか」がわかりません。
そこで登場するのがポート番号です。
アパートで例えるなら、IPアドレスが「建物の住所」で、ポート番号が「部屋番号」のようなものです。
ポート番号は 0〜65535 の範囲です。
65536通り = $2^{16}$、つまり 16bits = 2bytes ですね。
ポート番号にも「送信元ポート番号」と「宛先ポート番号」があります。
L2ではMACアドレス、L3ではIPアドレス、そしてL4ではポート番号。
毎回「送信元」と「宛先」のペアが出てきますね。
ネットワークの基本は、「誰が」「誰に」なんです。
トランスポート層には、代表的なプロトコルが2つあります。
「TCP」と「UDP」 です。
UDP(User Datagram Protocol)
まずは、シンプルなUDPから。
UDPは 「とにかく速く送る。届いたかどうかは知らない。」 というプロトコルです。
無責任に聞こえますが、ビデオ通話やオンラインゲームなど、「多少データが欠けても、遅れるよりマシ」な場面で大活躍します。
UDPヘッダの構成はとてもシンプルです。
| フィールド | サイズ |
|---|---|
| 送信元ポート番号 | 2bytes |
| 宛先ポート番号 | 2bytes |
| データ長 | 2bytes |
| チェックサム | 2bytes |
合計: 2+2+2+2 = 8bytes
TCP(Transmission Control Protocol)
「とにかく速く送る」 UDPに対して、
TCPは 「確実に届ける。届いたか必ず確認する。」 というプロトコルです。
Webサイトの表示やファイルのダウンロードなど、
「1bitも欠けちゃダメ」な場面で使われます。
確実に届けるためには、いろいろな情報が必要です。そのぶん、ヘッダもUDPより大きくなります。
送信元ポート番号 / 宛先ポート番号
それぞれ 2bytes です。
ここまではUDPと同じですね。ここからがTCPならではのフィールドです。
シーケンス番号
データを正しい順番に並べ直すための番号です。
ネットワーク上では、パケットがバラバラの順番で届くことがあります。
そこで「これは1番目のデータ、これは2番目」と番号をつけておけば、届いた先で正しく組み立て直すことができます。
4bytes = 32bits なので、最大で $2^{32}$ = 約43億通りの番号が使えます。
確認応答番号(ACK番号)
「○番まで受け取ったよ!」と相手に伝えるための番号です。
TCPが「確実に届ける」を実現している、最も重要な仕組みのひとつです。
これも 4bytes = 32bits です。
データオフセット
IPヘッダのヘッダ長(IHL)と全く同じ仕組みです。
TCPヘッダの長さを表します。
4bitsのフィールドで、単位は 「32bits(4bytes)」。
データオフセットのサイズ自体は 4bits です。
予約(Reserved)
将来のために確保されているフィールドです。今は使われていません。
データオフセットと合わせて、4+4 = 8bits = 1byte 。
L3のバージョン + IHLと同じパターンですね!
フラグ(Control Flags)
TCPの通信状態を管理するためのフィールドです。
IPヘッダのフラグと同じく、「1bitのフラグが横に並んでいる」構造です。
8bits = 8個のフラグがあります。
| bits | 名前 | 役割 |
|---|---|---|
| 0 | CWR | 「輻輳ウィンドウを縮小したよ」。ネットワークの混雑を検知して、送信量を減らしたことを相手に伝える。ECEとセットで使う。 |
| 1 | ECE | 「ネットワークが混雑しているよ」。ルーターから混雑の通知を受け取ったことを送信側に伝える。CWRとセットで使う。 |
| 2 | URG | 「緊急データがある」。緊急ポインタフィールドと組み合わせて、優先的に処理してほしいデータの位置を示す。実際にはほとんど使われていない。 |
| 3 | ACK | 「受け取ったよ」。確認応答番号フィールドが有効であることを示す。最初のSYN以降、ほぼ全てのパケットでこのフラグが立っている。 |
| 4 | PSH | 「すぐにアプリに渡して」。受信側のバッファに溜め込まずに、即座にアプリケーションへ渡してほしいとき。 |
| 5 | RST | 「接続を強制リセット」。異常が発生したとき、または存在しない接続にパケットが届いたときに使う。 |
| 6 | SYN | 「通信を始めたい」。TCP接続の確立時に使う。シーケンス番号の同期を行う。 |
| 7 | FIN | 「通信を終了する」。もう送るデータがないことを相手に伝える。 |
TCP通信の開始は 3ウェイハンドシェイク と呼ばれていて、このフラグを使って行われます。
この通信方法のおかげで、「確実に届ける。届いたか必ず確認する。」 が成立しています。
クライアント → サーバー : SYN (接続したいです)
サーバー → クライアント : SYN + ACK (いいですよ、こちらからも接続します)
クライアント → サーバー : ACK (了解です)
輻輳制御について(クリックで開きます)
TCPのフラグにCWRとECEがありましたね。
これらは 輻輳制御(ふくそうせいぎょ) に関わるフラグです。
輻輳とは、ネットワークが混雑してパケットが渋滞している状態のことです。
なぜ輻輳制御が必要なのか?
先ほどウィンドウサイズで「フロー制御」を学びました。
フロー制御は 受信側 の処理能力に合わせて送信量を調整する仕組みです。
一方、輻輳制御は ネットワーク(経路) の混雑状況に合わせて送信量を調整する仕組みです。
受信側には余裕があっても、途中の経路が混雑していたらパケットは失われます。
だから、両方の制御が必要なんですね。
輻輳制御の基本的な仕組み
TCPは「輻輳ウィンドウ(cwnd)」という値を内部で管理しています。
ウィンドウサイズ(受信側の余裕)と輻輳ウィンドウ(経路の余裕)、小さい方 に合わせてデータを送ります。
輻輳制御には段階があります。
1. スロースタート
最初は輻輳ウィンドウを小さく(1セグメント)から始めます。
ACKが返ってくるたびに、ウィンドウを 倍々 に増やしていきます。
1 → 2 → 4 → 8 → 16 → ...
名前は「スロー」ですが、指数関数的に増えるので実際はかなり速いです。
「いきなり全力で送らず、様子を見ながら加速する」イメージです。
2. 輻輳回避
スロースタートで一定の閾値(ssthresh)に達すると、
増加ペースを 1ずつ に落とします。
16 → 17 → 18 → 19 → ...
ここからは慎重に、少しずつ送信量を増やしていきます。
3. 輻輳検知と対応
パケットが失われた(=ACKが返ってこない)とき、
TCPは「ネットワークが混雑している」と判断します。
このとき、輻輳ウィンドウを大幅に縮小して、再びスロースタートまたは輻輳回避に戻ります。
ECEとCWRの役割
従来の輻輳制御は「パケットが失われてから気づく」方式でした。
つまり、実際に被害が出てから対処するわけです。
ECN(Explicit Congestion Notification)は、これを改善する仕組みです。
ルーターが「そろそろ混雑しそうだよ」とパケットに印をつけ、
パケットが失われる前に 送信側に知らせることができます。
送信側 → ルーター → 受信側
↓
「混雑しそう」と印をつける
↓
受信側 → 送信側 : ECE(混雑通知を受け取ったよ)
送信側 → 受信側 : CWR(送信量を減らしたよ)
ECEは「受信側→送信側」への混雑報告、
CWRは「送信側→受信側」への対処完了報告です。
この2つがセットで使われる理由が、これでわかりますね。
ウィンドウサイズ
「一度にどれくらいのデータを受け取れるか」を相手に伝えるフィールドです。
ウィンドウサイズを伝えることで、送信側は「相手が受け取れる分だけ送る」という制御ができます。
これを フロー制御 と呼びます。
16bits = 2bytesで、最大値は65,535。単位はbytesです。
ウインドウサイズについて(クリックで開きます)
TCPのウィンドウサイズは最大65,535bytes(約64KB)。
ちなみに、WASM(WebAssembly)のメモリページも1ページ = 64KBです。
Linuxでは昔からページサイズが4KBですが、
近年はメモリをより効率的に扱うために2MBや1GBの「Huge Page」を使うケースも増えてます。
64KBというサイズは、「小さすぎず、大きすぎず、ハードウェアとソフトウェアの間でちょうどいい」みたいな、絶妙な落としどころなんですね。
IPパケットの「パケット長」との違いは、
パケット長 →「この1個のパケットのサイズ」
ウィンドウサイズ →「相手がまとめて受け取れるデータの総量」
ウィンドウサイズはL4の話で、「確認応答(ACK)を待たずに、連続で何bytesまで送っていいか」を示しています。
つまり 複数パケットにまたがる 値です。
チェックサム
L3のIPヘッダにもありましたね。データが壊れていないか確認するためのものです。
これも 16bits = 2bytesです。
緊急ポインタ
「このデータは急ぎで処理してね!」という場合に使う特殊なフィールドです。
実際にはあまり使われませんが、フィールドとしては存在しています。
これも 16bits = 2bytesです。
これで、L4トランスポート層が全部そろいました!
| フィールド | サイズ |
|---|---|
| 送信元ポート番号 | 2bytes |
| 宛先ポート番号 | 2bytes |
| シーケンス番号 | 4bytes |
| 確認応答番号 | 4bytes |
| データオフセット(4bits) + 予約(4bits) | 1byte |
| フラグ | 1byte |
| ウィンドウサイズ | 2bytes |
| チェックサム | 2bytes |
| 緊急ポインタ | 2bytes |
合計: 2+2+4+4+1+1+2+2+2 = 20bytes
ここまでのまとめ
これまでの計算結果を並べてみましょう。
| 層 | ヘッダ | サイズ |
|---|---|---|
| L2 | イーサネットヘッダ | 14bytes |
| L3 | IPヘッダ | 20bytes |
| L4 | TCPヘッダ(UDPなら8bytes) | 20bytes |
つまり、TCPの場合、データが送られるとき、
本来のデータに加えて、最低でも 14+20+20 = 54bytes のヘッダがくっついています。
たった数bytesのデータを送るだけでも、54bytesの「荷札」が必要なんですね。
L5〜L7 アプリケーション層
実は、ここまででイーサネットのフレーム構造はほぼ網羅しています。
L4までを「ネットワーク層」、後のL5, L6, L7は「アプリケーション層」と呼ばれ、
ソフトウェア独自のデータになっています。
なので、後はダイジェストでお送りします。
L5 セッション層
通信の「開始・維持・終了」を管理する層です。
例えば、Webサイトにログインしてからログアウトするまでの一連のやり取り、あれが「セッション」です。
L6 プレゼンテーション層
データの「表現形式」を整える層です。
文字コード(UTF-8など)の変換や、暗号化・復号、画像データの圧縮・展開など、人間とコンピュータの「通訳」のような役割です。
L7 アプリケーション層
私たちが直接触れる層です。
HTTP(Webサイト)、SMTP(メール)、DNS(名前解決)など、「何をしたいか」に応じたプロトコルがここにいます。
ブラウザでWebサイトを見ているとき、この層と会話しています。
ペイロード
このL5〜L7のデータをまとめて 「ペイロード」 と呼びます。
ペイロードは直訳すると「積み荷」。ここまで計算してきたヘッダたちが「荷札」なら、ペイロードは「届けたい荷物そのもの」ですね。
では、このペイロードは何bytesまで使えるのでしょうか?
実は、イーサネットでは、1回で運べるデータの上限が決まっていて、最大 1500bytes というルールがあります。これを 「MTU(最大転送単位)」 と呼びます。
このMTUは、L3以降のデータ(IPヘッダ+TCPヘッダ+ペイロード)を合わせたものです。
つまり、TCPの場合、1500 - 20(IP)- 20(TCP)= 1460bytes 。
UDPなら 1500 - 20 - 8 = 1472bytes ですね。
これがペイロードに使える最大サイズです。
総まとめ:イーサネットフレームの全貌
今までのL1〜L7までのフレーム構造をまとめましょう。
L1 物理層
プリアンブル(7) + SFD(1) = 8bytes
L2 イーサネットヘッダ
宛先MAC(6) + 送信元MAC(6) + Ethertype(2) = 14bytes
L3 IPヘッダ
宛先IP(4) + 送信元IP(4) + プロトコル(1) + その他(11) = 20bytes
L4 TCPヘッダ
宛先ポート番号(2) + 送信元ポート番号(2) + その他(16) = 20bytes(UDPなら8bytes)
L5〜L7 ペイロード
最大 1460bytes(TCPの場合)
FCS(Frame Check Sequence)
ただ、実は最後にL2におまけが付きます。
FCS(Frame Check Sequence) と呼ばれる、イーサネットフレーム全体の情報が壊れていないかを検知するためのフィールドです。
L3のヘッダチェックサムやL4のチェックサムと似た役割ですが、FCSはフレーム全体を対象にしている点が違います。
これが 4bytes です。
一般的な「Ethernetフレーム長」は宛先MAC〜FCSを指すことが多く、プリアンブル/SFDは別扱いです。
本記事の1526bytesは“線上で送られるイメージ”としてL1要素も加えています。
FCSの計算方式(CRC-32)について(クリックで開きます)
FCSの計算には CRC-32(Cyclic Redundancy Check - 32bit) という方式が使われています。
IPヘッダのチェックサムは「足し算+bits反転」でしたが、CRCは 「割り算の余り」 を使います。
ただし、普通の割り算ではなく、XOR(排他的論理和)を使った割り算 です。
まず、XORのルールをおさらいしましょう。
0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0
「同じなら0、違うなら1」。これだけです。
CRCの考え方
CRCでは、送信したいデータ全体を一つの巨大な2進数とみなして、
あらかじめ決められた「生成多項式」と呼ばれる数で割ります。
その 余り がCRC値(= FCSに入る値)になります。
CRC-32の生成多項式は 0x04C11DB7(33bit)です。
実際のイーサネットフレームでこの計算をすると桁数が膨大になるので、
ここでは仕組みを理解するために、小さなCRCの例 で計算してみましょう。
簡易CRCの計算例
送信データ: 1101011(7bit)
生成多項式: 1011(4bit → CRCは3bit)
手順1: 送信データの末尾に、CRCのbit数(3bit)分の0を追加します。
1101011 → 1101011000
なぜ0を追加するかというと、この「余りを入れる場所」を確保するためです。
手順2: XORを使った割り算をします。
普通の筆算の割り算と同じ要領ですが、引き算の代わりにXORを使います。
1101011000 ÷ 1011
1101011000
XOR 1011
--------
0110011000
1011
--------
0111 11000
1011
--------
010011000
1011
--------
01001000
1011
--------
0010000
1011
--------
001100
1011
--------
00110
000
-----
余り = 110
※筆算のポイント: 先頭が1のときだけ生成多項式でXORします。先頭が0のときはスキップ(0でXOR)します。
手順3: 元のデータの末尾に、余り(110)をくっつけます。
1101011 + 110 → 1101011110
これが送信されるデータです。
受信側の検証
受信側は、受け取ったデータ 1101011110 を同じ生成多項式 1011 で割ります。
データが壊れていなければ、余りが0 になります。
1101011110 ÷ 1011 → 余り = 000 ✓
IPヘッダのチェックサムでは結果が 0xFFFF になれば正常でしたが、
CRCでは 余りが0 になれば正常です。考え方は似ていますね。
なぜCRCが使われるのか?
IPヘッダのチェックサムは「足し算」なので計算が軽い反面、
例えば2つの値が入れ替わっても検出できない場合があります。
CRCは「割り算」を使うため、以下の検出能力に優れています。
- 1bitの誤り → 100%検出
- 2bitの誤り → 100%検出
- 奇数個のbitの誤り → 100%検出
- 連続したbitの誤り(バースト誤り、32bit以下)→ 100%検出
イーサネットフレーム全体は、ヘッダだけでなくペイロードも含む大量のデータです。
そのため、より強力なエラー検出が必要で、CRC-32が採用されています。
ちなみに、CRC-32の生成多項式 0x04C11DB7 を2進数にすると33bitあります。
33bitの生成多項式で割った余りが32bit。
だから CRC-32 = 32bits = 4bytes なんですね。
ここでも、計算から4bytesが導けました。
最終合計
これまでの合計をまとめましょう。
| レイヤ | 役割 | 主要ヘッダ/要素 | サイズ |
|---|---|---|---|
| L1 | 信号の同期 | プリアンブル + SFD | 8 bytes |
| L2 | 隣接機器への配送 | イーサネットヘッダ | 14 bytes |
| L3 | エンド間配送 | IPヘッダ | 20 bytes |
| L4 | 通信の信頼性/識別 | TCPヘッダ(UDPなら8) | 20 bytes |
| L5-7 | アプリケーション | ペイロード | 最大 1460 bytes |
| L2 末尾 | エラーチェック | FCS | 4 bytes |
合計: 8 + 14 + 20 + 20 + 最大1460 + 4 = 最大1526bytes
ただし、イーサネットフレーム長はL1レイヤー除いて計算するので、
イーサネットフレーム長 = 最大1518bytes
長くなりましたが、最後までお付き合いいただきありがとうございました!
「なんとなく暗記」ではなく「自分で計算して納得」できていただけたなら、この記事の目的は達成です。
参考資料
今回の解説にあたり、以下の仕様(RFC/標準規格)を参考にしました。
- OSI参照モデル・全体概念
-
L2: イーサネット / VLAN
- RFC 894: A Standard for the Transmission of IP Datagrams over Ethernet Networks
- IEEE 802.1Q (VLAN Tagging)
- L3: IPv4
- L4: TCP / UDP
この記事の図は、以下の記事を参考に全てAIエージェントに作ってもらいました。











