このページの目次

ネットワークプロトコルスタックの概要

カバー範囲: sk_buff → NAPI → softirq (NET_RX/NET_TX) → プロトコルスタックの階層化 → socket API → net_device → マルチキュー カーネルバージョン: 2.6 ~ 6.x

概要

Linux のネットワークスタックは、世界中で最も広く使用されている TCP/IP 実装です。NIC の DMA によるパケット受信からユーザー空間での read() 呼び出しの返却まで、パケットは 10 層以上の処理を経由します。本稿では、この完全なパスを追跡し、中核となるデータ構造と設計上の判断に焦点を当てます。

sk_buff: パケット記述子

// include/linux/skbuff.h
struct sk_buff {
    struct sk_buff      *next, *prev;  // 双方向リスト (スケジューリングキュー)
    struct sock         *sk;           // 所属するソケット

    ktime_t             tstamp;        // タイムスタンプ (SO_TIMESTAMPING)
    struct net_device   *dev;          // 入力/出力デバイス

    // データポインタ (セグメント化された線形データ + 非線形の frags)
    unsigned char       *head, *data;
    unsigned int        len, data_len;
    sk_buff_data_t      tail, end;

    // プロトコルヘッダポインタ (階層ごとに解析されて設定される)
    __be16              protocol;      // L3 (ETH_P_IP, ETH_P_IPV6)
    unsigned char       *transport_header;  // L4 (TCP/UDP)
    unsigned char       *network_header;    // L3 (IP ヘッダ)
    unsigned char       *mac_header;        // L2 (Ethernet)

    // セグメンテーション/再構成
    unsigned int        gso_size, gso_segs;  // GSO: 巨大パケット → ハードウェアセグメンテーション
    unsigned short      nr_frags;            // 連続しないページ数
};

sk_buff の中核的な設計は、head/data/tail/end の4ポインタ配置です。データは head~end で確保されたバッファ内にあり、data~tail が有効なデータ領域、head~datatail~end がそれぞれ headroom/tailroom となります。プロトコルスタックが各層のヘッダを追加する際、data ポインタを前方にシフトさせることで既存の headroom を再利用し、コピーを不要にしています。

NAPI: 割込みとポーリングのハイブリッド

// net/core/dev.c
// 問題: 高速 NIC (10Gbps+) では毎秒数百万パケットを受信し、
//       パケットごとに1回の割込みが発生すると、割込み嵐が発生する
// NAPI: 最初のパケットで割込み通知 → 割込みを無効化 → softirq 内でパケットをポーリング

// NIC ドライバ:
//   1. パケット受信 → RX 割込みを無効化 → napi_schedule(&napi)
//   2. NET_RX_SOFTIRQ 発生 → napi_poll()
//      → バッチ処理でパケットを受信 (1バジェット、通常300パケット)
//      → 1バッチ処理完了 → まだパケットが残っている場合 → ポーリングを継続
//      → パケットが枯渇した場合 → RX 割込みを有効化 → ステップ1に戻る

パケット受信の完全なパス

flowchart TD
    DMA["NIC DMA<br/>→ リングバッファ (RX ring)"]
    DMA -->|"ハードウェア割込み"| IRQ["ドライバ割込みハンドラ<br/>napi_schedule()"]
    IRQ -->|"NET_RX_SOFTIRQ"| POLL["napi_poll()<br/>バッチ受信、最大300パケット"]

    POLL -->|"パケットごと"| NETIF["__netif_receive_skb()"]

    NETIF --> XDP["XDPフック (bpf)<br/>最も早期の処理ポイント"]

    NETIF --> RXH{"RXハンドラ<br/>リストのチェック"}
    RXH -->|"tcpdump"| AF_PACKET["AF_PACKET<br/>パケットソケットへコピー"]
    RXH -->|"bridge/bonding/VLAN"| REDIR["リダイレクトの可能性あり"]

    NETIF --> DELIVER["deliver_skb()"]

    DELIVER --> IP_RCV["ip_rcv()<br/>L3 (IPv4)"]

    IP_RCV --> IP_LOCAL["ip_local_deliver()"]

    IP_LOCAL --> NF{"netfilter<br/>PREROUTING"}
    NF -->|"FORWARD"| FWD["他のインターフェースへ転送"]
    NF -->|"INPUT"| IP_FINISH["ip_local_deliver_finish()"]

    IP_FINISH --> TCP_RCV["tcp_v4_rcv()<br/>L4 (TCP)"]

    TCP_RCV --> TCP_DO["tcp_v4_do_rcv()"]

    TCP_DO --> STATE{"接続状態は?"}
    STATE -->|"ESTABLISHED"| FAST["tcp_rcv_established()<br/>高速パス ⚡"]
    STATE -->|"LISTEN"| SLOW["tcp_rcv_state_process()"]
    SLOW --> QUEUE["データを受信キューに格納"]

    FAST --> QUEUE
    QUEUE --> WAKE["sk_data_ready()<br/>ブロック中の read() をウェイクアップ ✅"]

    classDef hw fill:#fff3e0,stroke:#ef6c00
    classDef irq fill:#ffebee,stroke:#c62828
    classDef l2 fill:#e3f2fd,stroke:#1565c0
    classDef l3 fill:#f3e5f5,stroke:#7b1fa2
    classDef l4 fill:#e8f5e9,stroke:#2e7d32
    classDef decision fill:#fff8e1,stroke:#f9a825
    class DMA,IRQ hw
    class POLL,NETIF,XDP,RXH,AF_PACKET,REDIR l2
    class IP_RCV,IP_LOCAL,IP_FINISH,DELIVER,NF,FWD l3
    class TCP_RCV,TCP_DO,FAST,SLOW,QUEUE,WAKE l4
    class STATE decision

マルチキューと RSS

RSS (Receive Side Scaling):
  NIC は hash(src_ip, dst_ip, src_port, dst_port) % N を使用
  → N 個の受信キュー → N 個の CPU

  NAPI の napi_struct はキュー単位 (per-queue) で存在する:
    → 各キューが独立した NAPI を持つ → パケット受信の並列化
    → 同一フローのパケットは常に同一キューへ → TCP の順序保持

XPS (Transmit Packet Steering):
  送信側のマルチキュー: 同様だが TX 向けに適用される

net_device: ネットワークデバイス抽象化

// include/linux/netdevice.h
struct net_device {
    char                name[IFNAMSIZ];    // eth0, wlan0
    const struct net_device_ops *netdev_ops;
    // → ndo_open, ndo_stop, ndo_start_xmit, ndo_set_rx_mode, ...

    struct net_device_stats stats;         // ドライバが埋める統計情報
    struct list_head    qdisc_list;        // 整列規則 (tc)

    unsigned int        num_rx_queues;
    unsigned int        num_tx_queues;
    struct netdev_rx_queue  *_rx;          // RX キュー配列 (キュー単位で NAPI)
};

デバッグ

# パケットの送受信統計
cat /proc/net/dev
ip -s link

# ソフト割込み統計 (NET_RX/NET_TX)
cat /proc/softirqs | grep NET

# NAPI 統計 (キュー単位でのポーリング)
ethtool -S eth0 | grep -E 'rx_packets|rx_bytes|tx_'

# パケットドロップの原因 (ドロップカウンター)
dropwatch -l kas

参考

  • ソースコード⁠: net/core/dev.c, include/linux/skbuff.h, include/linux/netdevice.h
  • カーネルドキュメント⁠: Documentation/networking/
  • LWN: "The Linux networking stack", "NAPI design"

キーワード: sk_buff, NAPI, NET_RX_SOFTIRQ, マルチキュー, RSS, net_device, XDP