Home > ブログ > LinuxのUDPソケットとPath MTU Discovery

ブログ

LinuxのUDPソケットとPath MTU Discovery

LinuxのUDPソケットでPath MTUを取り扱う際の動作についてまとめます。

まずはPMTUとPMTU Discovery

PMTUとPMTU Discoveryは他のいろんなページでも解説されていますが、ここでも簡単におさらいしておきます。

ネットワーク上の各リンク(回線)には一度に送信できるデータの最大サイズを示すMTU(Maximum Transmission Unit)というものがあります。リンクMTUと呼んだりもします。 Ethernetの場合は1500オクテットで、ATM等回線種別によりMTU値は異なります。 また、EthernetでもVPNなどでトンネルを使う場合は1280とかの異なるMTUになります。

一方、PMTU(Path MTU)は通信相手との間でIPフラグメントなしで送信できる最大サイズで、経路中のリンクMTUの最小値がPMTUとなります。

異なるMTUがあるネットワーク
図1 異なるMTUがあるネットワーク

図1はよくPMTUの説明で使われる構成ですが、Node AからBへ通信する際のPMTUは1280オクテットになります。

ここでNode Aとしては、直接繋がっているリンクのMTU 1500は認識できますが、Router A/B間にMTU 1280オクテットのリンクがあることは認識できません。 このように、通信の初期状態では通信相手へのPMTUはわからないので、自分のリンクMTU(図1では1500オクテット)にしたがって送信IPパケットのサイズを決めることになります。 IPv4の場合、Node Aが1500オクテットのIPパケットを送信すると、IPv4ヘッダのDF(Don't Fragment)フラグが0ならRouter AでMTU 1280を越えないようにIPパケットが断片化(IPフラグメント)されます(図2)。

経路中でのIPフラグメント
図2 経路中でのIPフラグメント

IPフラグメントが発生しても最終的にはNode Bに到達できますが、IPフラグメントには以下の問題があるため、基本的に避けた方がよいとされています。

IPフラグメントを避けるためにはPMTUを知る必要があるのですが、そこで、PMTU Discoveryという手法が使われます。

PMTU DiscoveryではIPv4ヘッダのDF(Don't Fragment)フラグを1にして通信を行います。 DF(Don't Fragment)フラグが1の場合、中継データがMTUを越えている場合、ルーターはIPフラグメント処理を行わずにICMPエラー(Type: Destination Unreachable, Code: fragmentation needed and DF set)を返します(図3)。

Path MTU Discovery
図3 Path MTU Discovery

送信元ノードはこのDestination Unreachableメッセージを受信してPMTUの値を学習します(*2)。 PMTUを学習できたことで、それ以降の通信で、IPフラグメントが発生しないサイズでIPパケットを送信できるようになります。

PMTUを学習すると、TCPならIPフラグメントが発生しないように送信時のセグメントサイズが調整されます(*3)。アプリケーションはPMTU Discoveryの動作を特に気にする必要はありません。

UDPではアプリ(上位プロトコル)側で対応する必要があります。例としては、DTLSのハンドシェイクプロトコルのように上位レイヤー(DTLSレイヤー)でフラグメントを行えるようにするなど、パケット長がPMTUを越えないようにする仕組みが必要になります。 アプリ側がPMTUを考慮せず何もしなければ、Kernelに学習したPMTU値が残っている間は、PMTU越えのパケットはIPフラグメントされてから送信されます(図4)。

送信ノードでのIPフラグメント
図4 PMTU学習後の送信ノードでのIPフラグメント

LinuxのTCP/UDP通信ではデフォルトではDFフラグがセットされるので、自動的にPMTU Discoveryが行われることになります。

なお、IPv6ではそもそもルーターはIPフラグメントを行いません(送信元のノードでは行われます)。IPv4でいうDF = 1の動作しかありません。使用されるICMPメッセージがICMPv6のPacket Too Big メッセージになっていたりと、違いはありますが、基本的にPMTU Discoveryの動作自体はIPv4と同じです。

LinuxのUDPソケット

LinuxのUDP(AF_INET / SOCK_DGRAM)ソケットでは、送信サイズが65535バイトを越えている時にEMSGSIZEが返されますが、"パケット送信がPMTUを越えた場合にもEMSGSIZEを返す"とあります(man 7 udp)。

これにより、アプリケーションでPMTUの超過を検出して送信サイズを調整することができます。ただし、manの情報からだけでは、EMSGSIZEを返すケースが分かりづらいため少し解説します。

PMTUを越えたサイズのパケットを送信した場合、直ちにEMSGSIZEが返るわけではありません。厳密にはPMTUを超過したパケット送信をしたタイミングではなく、Fragmentation neededのICMPエラーを受け取った後の次の送信時にEMSGSIZEが返ります(図5)。以下に流れを説明します。

EMSGSIZE発生までの流れ
図5 EMSGSIZE発生までの流れ

ネットワーク構成は図1と同じとし、Node AからBへデータを送信するものとします。

Node Aは最初にPMTU未学習の状態で、1500オクテットのUDP/IPパケットを送信します。 このUDPデータグラムは正常にネットワークに送信できるので、send()は正常終了します。その後、MTU超えでICMPエラーが返ってくるわけですが、Destination Unreachable(Fragment needed)受信後、Node AのKernelのプロトコルスタックで以下の処理が行われます。

  • PMTUの学習(1280オクテット)
  • 該当UDPソケットでFragment neededがあったことをマーキング

そして次回送信時に、ソケットにマーキングされたエラー情報からFragment neededが発生していたことに気づき、EMSGSIZEが返る形になります。ちなみにこの時、PMTU内のサイズの送信であってもEMSGSIZEが返り、パケットは廃棄されます。この辺りも直感的ではない点です。

まとめると、send()でEMSGSIZEが返ったらPMTU DiscoveryによるDestination Unreachable(Fragment needed)を受け取って、Kernel内でPMTUが学習/更新されたということになります。

なお、上(図4)でも説明しましたが、学習済みのPMTUを越えるサイズのパケットを送信しようとした場合は、IPフラグメントされてから送信されます。EMSGSIZEにはなりません。EMSGSIZEが返るのはあくまでPMTU DiscoveryでPMTUが更新された場合です。

他の注意点としては、connect()で通信相手を指定したソケットでないとEMSGSIZEは返りません(*4)。connect()していないUDPソケットではsendto()で任意の宛先に送信できるためです。

UDPソケットでEMSGSIZEをハンドリングするのは、UDPを使ったプロトコルを実装するような場合くらいしか使わないような気もしますが、知っておくと役に立つこともあるかもしれません。

おまけ

一般的にPMTU値はホスト単位に記憶され、一定時間が経過するとクリアされます。これにより、定期的にPMTU Discoveryが行われるので、経路の変更などでPMTUが大きくなった場合にも対応できます。

Linuxでは学習したPMTUは以下のコマンドで確認できます。

$ ip route show cached
10.0.3.30 via 10.0.1.10 dev veth0 
    cache expires 243sec mtu 1280 

Targetを指定して表示する場合。

ip route get 10.0.3.3
10.0.3.30 via 10.0.1.10 dev veth0 src 10.0.1.1 uid 1000 
    cache expires 206sec mtu 1280

キャッシュのクリアは以下のコマンドで行えます。

$ sudo ip route flush cache

(*1) 実際、中継をハードウェアで高速転送するルーターでも、IPフラグメントが発生するとソフトウェア処理が発生したりします。

(*2) 通常、Kernel内に保持されプロトコルスタックの各所から参照されます。

(*3) TSO(TCP Segmentation Offload)/GSO(Generic Receive Offload)機能があると、TCP層でセグメントサイズの調整がされなかったりしますが、今回はこれらの機能については使っていないものとします。

(*4) 説明でsendto()ではなくsend()と記述しているのはこのため。

投稿日:2023/03/09 10:14

タグ: ネットワーク

ネットワーク関連のソフトウェア開発でしたらおまかせください。
詳細はこちら

Top

アーカイブ

タグ

Server (28) 作業実績 (21) PHP (19) ネットワーク (17) プログラミング (15) OpenSSL (10) C (8) C++ (8) PHP関連更新作業 (8) EC-CUBE (7) Webアプリ (7) laravel (6) 書籍 (5) Nginx (5) Linux (5) AWS (4) Vue.js (4) JavaScript (4) 与太話 (4) Rust (3) Symfony (2) お知らせ (2) Golang (2) OSS (1) MySQL (1) デモ (1) CreateJS (1) Apache (1)