はじめに
Applicationがsend()やwrite()を実行しても、DataがそのままNICへ渡されるわけではありません。DataはSocket Send Bufferで管理され、TCP、IP / Routing、qdisc、Device Driver、TX Ringを通り、最後にNICがDMAでMemory上のDataを読み取ってNetworkへ送信します。
本記事では、Packet受信と対になる送信経路を扱い、ApplicationからNICまでDataがどのように流れるのかを処理順に解説します。あわせて、Linux上で送信処理を確認するための代表的なCommandも紹介します。
Packet送信の全体像
LinuxにおけるPacket送信の基本的な流れは以下の通りです。

実際のLinux Network Stackでは、Netfilter、Network Namespace、Bridge、VLAN、Tunnel、Traffic Control、各種Offloadなどが加わる場合があります。本記事では、Local Applicationが物理Network InterfaceからTCP Dataを送信する基本経路に絞って説明します。
① ApplicationがDataを送る
ApplicationはSocketのFile Descriptorを指定し、send()またはwrite()を実行してDataをKernelへ渡します。

System Callが成功して返したByte数は、Local KernelがそのDataを受け入れた量を表します。相手Hostへの到達や、Remote Applicationによる処理完了を意味するわけではありません。
Blocking Socketで送信Bufferに十分な空きがなければ、Processは空きができるまで待つことがあります。Non-blocking SocketではEAGAINなどが返ることがあり、Application側で再試行します。また、write()やsend()では要求した全Byteが必ず一度に受理されるとは限らないため、戻り値を確認することが重要です。
② TCPで送信Dataを処理する
Applicationから受け取ったDataはSocket Send Bufferで管理されます。TCPでは、まだ送信していないDataだけでなく、送信済みでもACKを受信していないDataなども送信状態として管理します。

TCPはSocket Send BufferのDataをByte StreamとしてSequence Numberで管理し、Source / Destination Port、ACK、Flags、WindowなどのTCP Header情報を設定しながら送信処理を進めます。Receiver WindowとCongestion Windowなどの制約を考慮し、必要に応じて未確認Dataを再送します。
送信単位にはMSSが関係します。ただしGSOやTSOが有効な環境では、KernelやDriverが比較的大きなBufferのまま後段へ渡し、DriverやNIC側でWire上のSegment Sizeへ分割することがあります。このため、Host上のtcpdumpではWire上より大きなTCP Packetに見える場合があります。
なお、Socket Send Bufferを大きくすれば常に高速になるわけではありません。送信性能はApplicationの書き込み方、Receiver Window、Congestion Window、RTT、Packet Lossなどにも左右されます。
③ IP / Routingで送信経路を決める
TCPの処理後、IP LayerではSource / Destination IP Address、Protocol、TTLまたはHop Limitなどの情報を持つIP Headerが関連付けられます。
続いてLinuxはDestination IP Addressに対してRoute Lookupを行い、Output Interface、Source Address、Next Hopなどを決定します。同一Subnet上の宛先ならDestination Host自身がNext Hopとなり、別Subnetなら通常はGatewayがNext Hopになります。
Ethernetで送信するにはNext HopのMAC Addressも必要です。Neighbor Tableに有効なEntryがなければ、IPv4ではARP、IPv6ではNeighbor DiscoveryによってMAC Addressを解決します。

FirewallやNATが構成されている場合は、Netfilter HookなどでPacketが検査・変更されることがあります。PolicyによってDropされたPacketはNICまで到達しません。
④ qdiscで送信PacketをQueueingする
Routing後のPacketは、Network Interfaceの送信処理へ進みます。この経路で重要になるのがqdisc(Queueing Discipline)です。

qdiscは単純にPacketを並べるだけではなく、どのPacketから送るかを決めるScheduling、帯域を調整するShaping、公平性やLatencyの制御、条件に応じたDrop / Markingなどを行えます。
qdiscへPacketが滞留するとQueueing Delayが増え、Queueが上限に達するとDropが発生する場合があります。一方、一部のVirtual Deviceや構成ではnoqueueが表示され、通常のqdisc Queueを経由しない場合もあります。
⑤ Device DriverがTX Descriptorを準備する
qdiscから取り出されたPacketはNetwork Deviceの送信処理を経てDevice Driverへ渡されます。DriverはNICが送信Dataを扱えるように、Memory上のBuffer情報をもとにTX Descriptorを準備します。

Checksum OffloadやTSOなどが使われる場合、DriverはNICへ必要な処理内容もDescriptorなどを通じて伝えます。
⑥ TX RingへDescriptorを登録する
準備したTX DescriptorはTX Ringへ登録されます。TX RingはPacket Dataそのものを並べる場所というより、NICが処理する送信Dataの場所やLengthなどを示すDescriptor Ringです。

TX Ringに空きがなくなると、Driverは送信Queueを一時停止することがあります。NICによる送信処理が進み、CompletionによってDescriptorを再利用できるようになると送信を再開します。
⑦ NICがDMAでDataを読み取って送信する
Driverは送信BufferをDMAで扱える形にし、その情報をDescriptorとしてNICへ示します。NICはTX DescriptorをもとにMain Memory上のDataをDMA Readで取得します。

受信時はNICがPacket DataをMain MemoryへDMA Writeするのに対し、送信時はNICがMain Memory上の送信DataをDMA Readする、という対称関係で考えると分かりやすくなります。
NICはDMA Readで取得した送信Dataを、Ethernet FrameとしてNetworkへ送出します。Checksum Offload、TSO、VLAN Offloadなどが有効な場合は、NICがChecksum計算やSegment分割、VLAN Tag処理などの一部を担当します。
NICからFrameが送信されたことは、Remote ApplicationがDataを処理したことを意味しません。TCPではRemote TCPからACKを受けることでByteの到達を確認し、Application Levelの処理完了はHTTP ResponseなどApplication Protocol側の応答で確認します。
⑧ 送信完了後にDescriptorを回収する
NICがTX Descriptorに対応する送信処理を完了すると、DriverはTX Completionを確認し、送信に使用したDescriptorやKernel内のPacket Bufferを再利用できる状態にします。必要に応じてDMA Mappingも解除します。

ここで重要なのは、TX CompletionとTCP ACKは別の処理だという点です。TX CompletionはNIC側の送信処理が完了したことを示しますが、Remote HostへのTCP Data到達を示すものではありません。

LinuxでPacket送信を確認する
Packet送信で問題が発生した場合も、Socket → Routing → Neighbor → qdisc → Device Driver → TX Ring → NICという送信経路に沿って確認すると、どこで処理が滞っているのかを切り分けやすくなります。
| 確認箇所 | コマンド | 主に確認できること |
|---|---|---|
| Socket / TCP | ss -ntmi | Send-Q、Socket Memory、RTT、cwndなどTCP内部情報 |
| Routing | ip route get 198.51.100.20 | Output Interface、Source Address、Next Hopなど |
| Neighbor | ip neigh show | Next HopのIP AddressとMAC Address、Neighbor State |
| qdisc | tc -s qdisc show dev enp1s0 | qdiscの種類、送信量、Drop、Overlimit、Backlogなど |
| NIC / Interface | ip -s link show dev enp1s0 | TX Packet数、Bytes、Error、Dropなど |
| TX Ring | ethtool -g enp1s0 | RX / TX Ringの現在値・最大値 |
| Offload | ethtool -k enp1s0 | TSO、GSO、Checksum Offloadなどの設定 |
| NIC / Driver統計 | ethtool -S enp1s0 | NIC / Driver固有のTX Counter、Drop、Errorなど |
| NIC設定 | ethtool enp1s0 | Link Up / Down、Speed、Duplex、Auto-negotiationなど |
| Network Traffic | sar -n DEV 1 | InterfaceごとのPacket数、Bytes、Traffic量 |
| Network Error | sar -n EDEV 1 | InterfaceごとのError、Drop、FIFO Errorなど |
| Packet | tcpdump -i enp1s0 -nn | KernelのCapture Pointを通過する送受信Packet |
例えばSend-Qが継続的に増えている場合は、Applicationが書き込む速度に対してTCPの送信が追いついていない可能性があります。RTT、cwnd、Receiver Window、Packet Lossなどとあわせて確認します。
qdiscのBacklogやDropが増えている場合は、Network Interfaceへ送り出す前のQueueでPacketが滞留している可能性があります。
さらにNIC / Driver固有のTX DropやError Counterが増えている場合は、Device Driver、TX Ring、NIC付近まで確認範囲を広げます。Counter名はNICやDriverによって異なるため、ethtool -Sの複数のCounterをTraffic量とあわせて確認することが重要です。
また、tcpdumpでPacketが確認できても、実際にNICからWireへ正常に送信されたことを直接保証するものではありません。tcpdumpのCapture PointとNIC Hardware Counterは観測している位置が異なるため、必要に応じてRemote Host側のCaptureやNetwork機器のCounterなどと比較します。
まとめ
LinuxのPacket送信では、Applicationがsend()やwrite()を実行すると、DataはSocket Send Bufferへ渡され、TCP / IP、Routing、qdiscなどの処理を経てDevice Driverへ渡されます。
Device Driverは送信Dataを参照するTX Descriptorを準備してTX Ringへ登録し、NICはTX DescriptorをもとにMain Memory上のDataをDMA Readで取得してNetworkへ送信します。
送信後はDriverがTX Completionを確認し、使用済みのDescriptorやPacket Bufferを回収して再利用できる状態にします。
ここで重要なのは、send()の成功、TX Completion、TCP ACKの受信、Remote Applicationの処理完了は、それぞれ別の段階だという点です。send()が成功しただけでは、相手HostへのData到達を意味しません。
Packet送信で問題が発生した場合は、Socket、Routing、Neighbor、qdisc、Device Driver、TX Ring、NICと送信経路に沿って確認すると、Packetがどこで滞留・Dropしているのかを切り分けやすくなります。
ss、ip route、ip neigh、tc、ethtool、sar、tcpdumpなどを組み合わせ、各Layerの状態を確認することが重要です。
Linuxにおけるネットワークの仕組みについては、以下の書籍にまとめています。興味がありましたら、ぜひ読んでみてください。
Linuxのネットワークの仕組みを
図解で理解したい方へ
Socket・TCP・ルーティング・NIC 現場で役立つ通信処理の基礎知識など Linuxにおけるネットワークの仕組みを図解を中心にわかりやすく解説しています。
Kindleで書籍を見る →


コメント