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と通信の記録
xxd -g 1 -c 16 frame-with-fcs.bin00000000: 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..
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
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
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
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
python3 -c 'mtu=1500; print([(n,n<=mtu,14+n+4) for n in (1500,1501)])'[(1500, True, 1518), (1501, False, 1519)]
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
演習完了