一覧NW-02-1 · Ethernetとリンク

Ethernetフレームの構造

最小フレームのバイト列から、長さと誤り検出の仕組みを確認します。

説明

Ethernetフレームは宛先MAC、送信元MAC、Type/Length、データ、必要なパディング、FCSから成ります。キャプチャに含まれる範囲と、線上の送信で使う時間は同じではありません。

FCS付きの64バイト試料から各欄を切り出し、CRCを再計算します。1ビットの破損を検出した後、IP MTUとの関係と、キャプチャ長に含まれない部分を確認します。

1FCSを含む64バイトのフレーム

frame-with-fcs.binはEthernet IIの試料です。実験用EtherType 0x88b5で4バイトのDATAを運び、パディングとFCSを含めています。VLANタグはありません。

xxd -g 1 -c 16位置と1バイト値を表示 frame-with-fcs.binFCSを含む試料

先頭14バイトがヘッダー、44 41 54 41がDATA、後続の0がパディングです。末尾09 4d 06 eeがFCSです。一般的なNICのキャプチャではFCSが除去される場合もありますが、この試料には含めています。

2宛先・送信元・EtherType

6・6・2バイトの各欄を切り出します。

python3 -cヘッダーを解釈 'from pathlib import Path; f=Path("frame-with-fcs.bin").read_bytes(); print(f[:6].hex(":"),f[6:12].hex(":"),hex(int.from_bytes(f[12:14],"big")))'MACとタイプの切出し

宛先は末尾0b、送信元は末尾0a、EtherTypeは0x88b5です。Type/Length欄は1536以上ならEtherType、1500以下なら長さとして使います。1501〜1535は、どちらにも分類しません。

3最小長に必要なパディング

基本フレームの最小64バイトからヘッダー14とFCS4を引き、データ部を求めます。

python3 -c最小長を計算 'payload=b"DATA"; padding=max(0,64-14-4-len(payload)); print(len(payload),padding,14+len(payload)+padding+4)'内容・パディング・全体

実データ4バイト、パディング42バイト、全体64バイトです。Ethernet IIのEtherTypeはデータ長を表しません。実データとパディングは上位プロトコルの長さ等で分けます。この試料ではDATAを4バイトと決めています。

4FCSの再計算

FCSを除く先頭60バイトからCRC-32を求め、保存されている値と比較します。

python3 -cCRCを計算 'from pathlib import Path; import zlib; f=Path("frame-with-fcs.bin").read_bytes(); stored=int.from_bytes(f[-4:],"little"); calculated=zlib.crc32(f[:-4]); print(hex(stored),hex(calculated),stored==calculated)'保存値と再計算値

両方0xee064d09で一致します。宛先MACからデータ・パディングまでが計算対象で、プリアンブルやSFDは含みません。ここではCRCの数値をリトルエンディアンの4バイトで末尾へ保存しています。

5一つのビットを反転した試料

damaged.binはDATAの先頭バイトの最下位ビットだけを変え、FCSを元のまま残しています。

python3 -c破損後を照合 'from pathlib import Path; import zlib; f=Path("damaged.bin").read_bytes(); print(f[14:18],hex(int.from_bytes(f[-4:],"little")),hex(zlib.crc32(f[:-4])),int.from_bytes(f[-4:],"little")==zlib.crc32(f[:-4]))'内容とCRCの不一致

DATAがEATAに変わり、CRCが一致しなくなります。FCS不正として検出できますが、元の文字を自動的に復元する機能ではありません。CRCは送信者の真正性を保証する署名でもありません。

6IPのMTUとEthernet全長

IP MTUを1500バイトとしたリンクで、1500と1501バイトのIPパケットを比較します。

python3 -cMTUと全長を比較 'mtu=1500; print([(n,n<=mtu,14+n+4) for n in (1500,1501)])'上限内外の二条件

IP1500バイトなら基本フレーム1518バイトです。1501バイトではこのMTUを超えます。EthernetがIPを自動的に分割するわけではなく、IPの種類、DF、経路MTU等の条件に応じて送信側やIP層で対応します。

7キャプチャした長さと回線上の送信時間

プリアンブル等8バイトとフレーム間隔12バイト相当を含め、100Mbit/秒で最小フレームを送る占有時間を求めます。

python3 -c記録範囲と時間を計算 'frame=64; occupied=8+frame+12; print(occupied,occupied*8/100000000*1000000,"us"); print(frame-4,"bytes without FCS")'プリアンブル・間隔・FCS

占有量は84バイト相当、時間は6.72µsです。FCSを除いたキャプチャは60バイトになるため、表示が64未満というだけでは不正と判断できません。フレーム間隔は実データではなく、時間をバイト数へ換算したものです。

まとめ

短いデータにはパディングを加え、基本フレームの最小長を満たします。

FCSは宛先MACからパディングまでの誤り検出に使います。

IP MTU、フレーム全長、キャプチャ長、線上の占有時間は対象範囲が異なります。

操作履歴と解説を振り返れます。

シェル / PCAPと通信の記録

1
xxd -g 1 -c 16 frame-with-fcs.bin
00000000: 02 00 00 00 00 0b 02 00 00 00 00 0a 88 b5 44 41  ..............DA
00000010: 54 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00  TA..............
00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030: 00 00 00 00 00 00 00 00 00 00 00 00 09 4d 06 ee  .............M..
2
python3 -c 'from pathlib import Path; f=Path("frame-with-fcs.bin").read_bytes(); print(f[:6].hex(":"),f[6:12].hex(":"),hex(int.from_bytes(f[12:14],"big")))'
02:00:00:00:00:0b 02:00:00:00:00:0a 0x88b5
3
python3 -c 'payload=b"DATA"; padding=max(0,64-14-4-len(payload)); print(len(payload),padding,14+len(payload)+padding+4)'
4 42 64
4
python3 -c 'from pathlib import Path; import zlib; f=Path("frame-with-fcs.bin").read_bytes(); stored=int.from_bytes(f[-4:],"little"); calculated=zlib.crc32(f[:-4]); print(hex(stored),hex(calculated),stored==calculated)'
0xee064d09 0xee064d09 True
5
python3 -c 'from pathlib import Path; import zlib; f=Path("damaged.bin").read_bytes(); print(f[14:18],hex(int.from_bytes(f[-4:],"little")),hex(zlib.crc32(f[:-4])),int.from_bytes(f[-4:],"little")==zlib.crc32(f[:-4]))'
b'EATA' 0xee064d09 0x91cfd69a False
6
python3 -c 'mtu=1500; print([(n,n<=mtu,14+n+4) for n in (1500,1501)])'
[(1500, True, 1518), (1501, False, 1519)]
7
python3 -c 'frame=64; occupied=8+frame+12; print(occupied,occupied*8/100000000*1000000,"us"); print(frame-4,"bytes without FCS")'
84 6.72 us
60 bytes without FCS

演習完了