【実務・中級編】 5GユーザープレーンプロトコルGTP-U(GTP User Plane)のヘッダーフォーマットとTeIDの役割 – 家庭用ネットワーク・IoT・モバイル通信実践ガイド

5Gの「見えない背骨」を解剖する:GTP-UとTeIDが語るパケットの旅路

こんにちは。現場でパケットキャプチャを叩き続け、数々の「なぜか繋がらない」という迷宮を駆け抜けてきたネットワークエンジニアとして、今日は少し硬派な話をしようと思う。

Web API開発やクラウドインフラ運用に携わる皆さんは、普段 HTTP や gRPC、あるいは QUIC といったレイヤーで仕事をしているはずだ。だが、5Gという巨大なモバイルネットワークの正体は、それら上位層を「カプセル化」してどこまでも運び続ける、巨大なトンネルの集合体である。

その心臓部でパケットの交通整理を行っているのが GTP-U (GPRS Tunnelling Protocol User Plane) だ。今日は、仕様書の無機質な羅列ではなく、実務でトラブルシューティングする際に必ず立ち返るべき「GTP-Uの作法」を紐解いていく。

—

GTP-Uとは何か:パケットの「着る服」

私たちがスマホで動画を見るとき、そのデータ(IPパケット)はそのまま無線空間を飛ぶわけではない。gNB(基地局)からUPF(User Plane Function:5Gのデータ中継点)へ向かう間、元のパケットはGTP-Uというカプセルの中に格納される。

なぜわざわざカプセル化するのか? それは、モバイル網の中で「どのユーザーの、どのセッションの通信か」を識別し、ルーティングするためだ。このカプセルの先頭に付くのがGTP-Uヘッダーであり、その中核を担うのが TeID (Tunnel Endpoint Identifier) である。

GTP-U ヘッダーの構造(要点)

GTP-Uヘッダー(8バイト)は、UDP(ポート2152)の上に載る。特に重要なのは以下のフィールドだ。

1. Flags: バージョン情報や拡張ヘッダーの有無を示す。
2. Message Type: 通常のユーザーデータなら 0xFF (G-PDU)。
3. TeID (4 bytes): ここが最重要だ。 トンネルの識別子であり、UPFはこのIDを見て「あ、これはAさんのYouTube通信だな」と判断し、次のネットワーク先へパケットを転送する。

—

なぜ TeID がエンジニアを悩ませるのか

実務で私が最も恐れるのは、セッション確立時の TeID の不一致だ。

UPF側でセッション情報(PFCP:Packet Forwarding Control Protocol)を作成する際、gNBとUPF間でこの TeID の値を交換する。もし、片方が 0x12345678 と思っているのに、もう片方が 0x87654321 を送ってきたらどうなるか? 答えは簡単、そのパケットはUPFで「行き先不明」とみなされ、無慈悲にドロップされる。

実務的なデバッグTips:Wiresharkでの確認

トラブルが発生した際、私はまず udp.port == 2152 でフィルタリングし、gtp.teid に注目する。

# tcpdumpでまずはキャプチャする(権限に注意)
# -i any: 全インターフェース
# port 2152: GTP-Uのデフォルトポート
sudo tcpdump -i any port 2152 -w gtp_debug.pcap

Wiresharkで開いた際、GTPv1-U のパケットを展開し、TeID の値がPFCPのログと一致しているか確認してほしい。ここがズレていれば、それはネットワークの問題ではなく、制御プレーン(SMF/UPF間のシグナリング)の設計ミスだ。

—

コードで考える:パケットをどうハンドリングするか

皆さんがWebサービスを構築する際、直接GTPヘッダーを叩くことは稀かもしれない。しかし、NFV(ネットワーク機能仮想化)や独自のパケット処理エンジンを開発する場合、このヘッダーをパースする必要が出てくる。

PythonでGTP-Uヘッダーを読み解くイメージを書いてみた。

import struct

def parse_gtp_header(data):
    """
    GTP-Uの先頭8バイトをパースする簡易ロジック
    """
    # 最初の8バイトを取得
    header = data[:8]
    # struct.unpackを使用してバイナリを展開
    # B: unsigned char (1 byte), I: unsigned int (4 bytes)
    # 詳細はRFC 2909/3GPP TS 29.281を参照
    flags, msg_type, length, teid = struct.unpack('!BBHI', header)
    
    print(f"Flags: {bin(flags)}")
    print(f"Message Type: {hex(msg_type)}")
    print(f"TeID: {hex(teid)}") # ここがルーティングの命綱
    
    return teid

# ダミーのGTP-Uパケットデータ(実際にはsocketから受信したもの)
dummy_packet = b'\x30\xff\x00\x00\x12\x34\x56\x78' + b'\x45\x00...'
parse_gtp_header(dummy_packet)

この TeID をプログラムで扱う際、最も注意すべきは エンディアン だ。ネットワーク通信は基本的にネットワークバイトオーダー(ビッグエンディアン)であるため、バイナリ処理を行う際は !I(ビッグエンディアンのunsigned int)といった指定を忘れないでほしい。

—

現場からの教訓:最後に

5Gの普及に伴い、UPFをエッジクラウドに配置する構成が増えている。その際、ファイアウォールの設定ミスで UDP 2152 が遮断されているケースに何度か遭遇した。

「パケットは届いているのに、なぜかスループットが出ない」「通信が途切れる」といった症状が出たとき、まずは以下の3点をチェックしてほしい。

1. UDP 2152 が全経路で許可されているか?(ステートフルなFWはGTPを正しく処理できないことがある)
2. MTUサイズは適切か?(GTPヘッダー分だけパケットサイズが増えるため、L2スイッチのMTU設定が1500のままだとフラグメンテーションが多発し、激しい性能劣化を招く)
3. TeIDのマッピング情報は最新か?(PFCPのシーケンスが正常終了しているかログを追う)

ネットワークは、目に見えない論理的な「繋がり」の集大成だ。TeID という小さな数値一つが、数千キロ離れたスマホとサーバを繋いでいる。その重みを理解できれば、君たちも一流のネットワークエンジニアの仲間入りだ。

次回の記事では、このGTP-Uのトンネルをさらに効率化する GTP-U Extension Header の深淵に触れていこうと思う。それでは、また現場で会おう。

コメント

タイトルとURLをコピーしました