こんにちは、シニアネットワークエンジニアの私です。
Web APIの設計やモダンなクラウドインフラの構築に日々奔走している皆さん、ふと「L2(データリンク層)の足元」を覗いたことはあるでしょうか?
「いや、うちはAWSやGCPの上でREST APIを叩いているから、物理レイヤーやイーサネットのことはクラウドベンダーが勝手にやってくれているよ」――そう思ったあなた。ちょっと待ってください。
コンテナ間の通信トラブル、VPCピアリングの謎のパケットロス、あるいはオンプレミスとクラウドを接続するハイブリッド環境での不可解なMTU問題。そうした現場の修羅場で最後にものを言うのは、結局のところ「パケットが実際にどう流れているか」の解像度です。
今回は、イーサネットフレームの頭頂部にひっそりたたずみながら、上位プロトコルの運命を一身に背負っている隠れた立役者、EtherType(タイプフィールド)について、実務の現場で役立つ知見を交えて徹底的に解説します。教科書をなぞるだけではない、プロトコルの深淵を覗いてみましょう。
—
1. イーサネットフレームの構造と EtherType の正体
私たちが普段何気なくやり取りしているHTTPリクエストも、データベースのクエリも、最終的には電気信号や光パルスに変換され、イーサネットフレームという「封筒」に包まれてネットワークを駆け巡ります。
従来のDIXイーサネット(Ethernet II)フレームの構造を思い出してください。
+-------------------+-------------------+-------------------+-------------------+
| Destination MAC | Source MAC | EtherType | Payload |
| (6オクテット) | (6オクテット) | (2オクテット) | (46〜1500オクテット)|
+-------------------+-------------------+-------------------+-------------------+
このフレームの先頭から13〜14オクテット目、宛先MACアドレスと送信元MACアドレスの直後に陣取っているのが、今回主役となるEtherType(2オクテット)です。
なぜ EtherType が必要なのか?
スイッチやNIC(ネットワークインターフェースカード)が受信したフレームのペイロード(中身)を取り出したとき、それを「IPv4パケット」として処理すべきか、「ARPリクエスト」として解釈すべきか、あるいは「PPPoE」として処理すべきかを判断しなければなりません。
この「仕分け人」の役割を果たしているのが EtherType です。郵便の封筒に「親展」「請求書在中」と書くようなもので、L2のスイッチやOSのネットワークスタックは、この2バイトの数字を見て、どのL3プロトコルハンドラに処理を渡すべきかを瞬時に判断しています。
—
2. 実務でよく遭遇する主要な EtherType 値
RFC 894 や IEEE 802.3 の世界では、いくつかの値が標準化されています。実務の現場(Wiresharkでのパケットキャプチャやネットワーク機器のACL設定など)で頻繁に目にする主要な値を整理しておきましょう。
| EtherType (16進数) | プロトコル名称 | 概要・実務での文脈 |
| :— | :— | :— |
| 0x0800 | IPv4 (Internet Protocol version 4) | 最も一般的。Web API通信やデータベース接続など、ほとんどのTCP/IPトラフィックがこれに該当します。 |
| 0x0806 | ARP (Address Resolution Protocol) | 同一セグメント内でIPアドレスからMACアドレスを引くためのプロトコル。ネットワーク障害時にARPテーブルの枯渇やフラッピングを調査する際によく見ます。 |
| 0x8100 | VLAN Tag (IEEE 802.1Q) | トランクポート等で流れるIEEE 802.1Qタグ付きフレーム。この値の後ろにVLAN ID(TCI)が続き、さらにその内側に「本当の」EtherType(例: 0x0800)が隠れる二重構造になります。 |
| 0x86DD | IPv6 (Internet Protocol version 6) | 次世代IPプロトコル。クラウドネイティブなインフラやコンテナネットワーク(IPv6デュアルスタック環境)で必須の存在です。 |
【現場のTips】EtherType と Length フィールドの歴史的背景
IEEE 802.3の初期仕様では、この2オクテットのフィールドは EtherType ではなく「フレーム長(Length)」を表すために使われていました(ペイロードのオクテット数を示す)。しかし、これでは上位プロトコルを識別できないため、のちにRFC 894で「0x0600(1536)以上の値であれば EtherType として解釈し、それ未満ならフレーム長として扱う」という巧妙な互換性ルール(Type/Lengthフィールド)が策定されました。現在、1536未満の値が EtherType として使われることは基本的にありません。
—
3. シーケンス:フレームがスイッチを通過する際の解釈フロー
ネットワーク機器やOSのカーネルが、パケットを受信してから EtherType を処理するまでの大まかな流れをシーケンスとして確認しておきます。
[ 送信元ホスト (API Client) ] [ L2 スイッチ ] [ 受信側ホスト (API Server) ]
| | |
|--- [Ethernet Frame] ----------------->| |
| (EtherType: 0x0800 = IPv4) |--- [Ethernet Frame] ------------->|
| | (転送処理: MACアドラーズ学習) |
| | |
| | OSカーネルでの処理: |
| | 1. L2ヘッダー剥離 |
| | 2. EtherType確認(0x0800)|
| | 3. IPv4スタックへ渡す |
v v v
L2スイッチは基本的に EtherType の中身を深く気にする必要がありません(QoSのプライオリティ制御やVLANマッピングで参照することはありますが)。スイッチの主業務はあくまで宛先MACアドレスに基づくL2転送です。
しかし、パケットを受け取った「エンドホスト(OSのカーネル)」は別です。NICからドライバ、そしてネットワークスタックへ上がってきた段階で EtherType を厳密にチェックし、適切なプロトコルレイヤーへディスパッチしています。
—
4. トラブルシューティング:パケットキャプチャとデバッグの実践
APIの応答が返らない、あるいはパケットロスが起きているという現場で、シニアエンジニアが真っ先にやる作業の一つが tcpdump や Wireshark を使ったL2キャプチャです。
ここでは、Pythonなどのアプリケーション層から少し離れて、インフラレイヤーで EtherType を意識したデバッグを行う際の実践的なコマンドを紹介します。
tcpdump によるパケットのフィルタリング例
特定の EtherType(例えばARPやIPv6)だけをフィルタリングしてキャプチャしたい場合、tcpdump のフィルタ式にイーサネットプロトコルを指定できます。
# インターフェイス eth0 において、ARPパケット (EtherType: 0x0806) のみをキャプチャする
sudo tcpdump -i eth0 ether proto 0x0806 -nnvv
# IPv6トラフィック (EtherType: 0x86DD) をリアルタイムで監視する
sudo tcpdump -i eth0 ether proto 0x86DD -nnvv
Pythonでソケットを直接叩く(L2生パケットの観測)
アプリケーション開発者であっても、Linuxの AF_PACKET ソケット(RAWソケット)を使用すれば、OSのネットワークスタックをバイパスして、EtherType を含むイーサネットフレームを直接構築・送受信するプログラムを書くことができます。
以下は、Linux環境で特定の EtherType(例: 自社独自プロトコルなどのカスタムEtherType 0x88B5 など)を持つフレームをキャプチャする概念的なPythonスクリプトです(実行には root 権限が必要です)。
import socket
import struct
def capture_raw_ethernet():
# AF_PACKETを使用して、L2レイヤー(イーサネットフレーム)のソケットを作成
# 0x0003 (ETH_P_ALL) を指定することで、すべてのEtherTypeのパケットをキャプチャする
try:
raw_socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003))
except PermissionError:
print("エラー: このスクリプトを実行するにはroot権限が必要です(sudoを実行してください)。")
return
print("L2パケットのキャプチャを開始します... (Ctrl+Cで終了)")
while True:
# パケットを生データとして受信 (最大サイズ 65535バイト)
packet = raw_socket.recvfrom(65535)[0]
# イーサネットヘッダーの解析 (宛先MAC: 6バイト, 送信元MAC: 6バイト, EtherType: 2バイト)
dest_mac, src_mac, eth_type = struct.unpack('!6s6sH', packet[:14])
# MACアドレスを人間が読みやすい16進数表記に変換
dest_str = ':'.join(f'{b:02x}' for b in dest_mac)
src_str = ':'.join(f'{b:02x}' for b in src_mac)
# EtherTypeを16進数で表示
eth_type_hex = f'0x{eth_type:04x}'
print(f"Dst: {dest_str} | Src: {src_str} | EtherType: {eth_type_hex}")
if __name__ == "__main__":
capture_raw_ethernet()
このコードを実行しながら、別のターミナルから ping を飛ばしたりWeb APIを叩いたりすると、0x0800(IPv4)や 0x86DD(IPv6)のフレームが次々と流れてくるのが目で見えます。普段抽象化されているレイヤーが目の前で動く感動を味わえるはずです。
—
5. まとめ:上位レイヤーを支えるL2の知見
Web APIの設計やクラウドのインフラ構築において、直接 EtherType の値を手動で設定する機会はほとんどありません。VPCやKubernetesのCNI(Container Network Interface)が、そのあたりをすべて美しく隠蔽してくれているからです。
しかしだからこそ、
- 「なぜコンテナ間の通信でVLANタグやトンネリング(VXLANなど)がオーバーヘッドを生むのか」
- 「なぜパケットキャプチャの生データに予期せぬEtherTypeが現れるのか」
といった疑問に直面したとき、今回解説した EtherType のようなL2の基礎知識が、あなたの頭の中で鮮やかなパズルを組み立てる鍵となります。
「動いているからよし」ではなく、「なぜ動いているのか」を突き詰めること。それこそが、トラブルに強い真のインフラエンジニア、そして信頼されるバックエンド・アーキテクトへの第一歩です。
それでは、次の深淵でお会いしましょう。
コメント