ネットワークスタック 基礎

クライアントが curl https://api.example.com/user/42 を実行する。 そのバイトは curl プロセスを離れ、カーネルを横切り、ワイヤに乗り、 遠端のサーバプロセス内に浮上する必要がある。両者の間に 5 層: HTTP(L7)→ TCP(L4)→ IP(L3)→ Ethernet(L2)→ 物理(L1)。 4 セクションでスタックを下る:5 層(各ヘッダのコストと保護);ソケットとポート(カーネルの struct sock とあらゆる接続を束縛する 4-tuple);ルーティング、ARP、MTU + 対話的なパケット降下ウォークスルー; そしてNIC ドライバ —— リングバッファ、NAPI、RSS、XDP、 カーネルバイパス。最後にクイックリファレンス

01

OSI のおとぎ話 vs TCP/IP の現実 —— 実際に使う 5 層

curl https://api.example.com/user/42 が走るとき、 そのバイトは公衆インターネット越しにサーバに到達する必要がある。 2 つのプロセスの間に 5 層が立つ;各層は出口でヘッダを加え、入口でそれを剥がす。

大学では OSI 7 層モデルを教える:物理、データリンク、ネットワーク、 トランスポート、セッション、プレゼンテーション、アプリケーション。 歴史としては有用。本番では 5 層(TCP/IP モデル)で推論する —— 「セッション」と「プレゼンテーション」層には実装がない; それらが本来やるはずだったことはアプリケーションか TLS に吸収された。

層     名前              現実の例                          ヘッダサイズ
L7     アプリケーション  HTTP、gRPC、DNS、SSH、SMTP        可変
L4     トランスポート    TCP、UDP、QUIC                    20 B(TCP)/ 8 B(UDP)
L3     ネットワーク      IPv4、IPv6、ICMP                  20 B(IPv4)/ 40 B(IPv6)
L2     データリンク      Ethernet、Wi-Fi(802.11)         14 B + 4 B FCS
L1     物理              銅、光ファイバ、無線              該当なし(ライン符号化)

カプセル化 —— 各層が何を加えるか

/user/42 への HTTP GET リクエストを 1 つ送る:

HTTP リクエストボディ                                    ~500 B
+ TCP ヘッダ(src/dst ポート、seq、ack、flag、window)   +20 B   → segment 520 B
+ IPv4 ヘッダ(src/dst IP、TTL、proto=6、チェックサム)  +20 B   → packet  540 B
+ Ethernet ヘッダ(dst MAC、src MAC、EtherType=0x0800)  +14 B   → frame   554 B
+ フレームチェックシーケンス(CRC32、トレーラ)         + 4 B   → wire    558 B

ワイヤ上の MTU 上限:標準 Ethernet で 1500 B。それを超えるものは L3 で
フラグメント化、または TCP では MSS によって MTU 未満にセグメント化される。

受信側のサーバでは、NIC がワイヤからビットを引き上げ、ドライバがフレームを上に渡し、 Ethernet が自身の 14 B を剥ぎ EtherType で派遣し、IP が 20 B を剥ぎプロトコル番号 (6 = TCP)で派遣し、TCP が 20 B を剥ぎ宛先ポートでリスニング中のstruct sock にルーティングし、アプリケーションが recv()で読む。各層の唯一の仕事は「私のヘッダを剥がして、残りを上階の正しい隣人に渡す」。

それを実装するカーネルサブシステム

Linux カーネルでは、降下は各層を担うサブシステムのチェーンで実装される:

  • ソケット層(net/socket.c)—— BSD ソケット API をプロトコル固有の呼び出しに翻訳する。
  • TCP / UDP(net/ipv4/tcp.cnet/ipv4/udp.c)—— セグメント化、シーケンス番号、再送、フロー制御(TCP); 長さ + チェックサムだけ(UDP)。
  • IP(net/ipv4/ip_output.c)—— ルーティングテーブルルックアップ、TTL、フラグメント化、次ホップの ARP 解決。
  • qdisc(キューイング規則、net/sched/)—— インターフェイスごとの出口キュー、パケット順序を決定しトラフィックシェーピングを適用 (fq、htb、codel)。
  • ドライバ / NIC —— バイトをカードの TX リングバッファに DMA; カードがそれらをワイヤにシリアライズする。

RX では全てが逆方向に走る:NIC IRQ → ドライバ NAPI poll → IP input → TCP input → ソケット受信キュー → ユーザ recv() ウェイクアップ。 section 03 のデモでこの降下を端から端まで追跡する。

要点。「本番コードでは OSI 7 層を忘れる —— 5 層: L7 アプリケーション、L4 トランスポート(TCP / UDP)、L3 ネットワーク(IP)、 L2 リンク(Ethernet)、L1 物理。各層は出口で自身のヘッダを加え、入口で剥がし、 残りを上階の正しい隣人に渡す。典型的な ~500 バイトの HTTP リクエストは ワイヤ上で ~558 バイトに育つ —— ヘッダ 54 バイト —— フレームあたり 1500 バイトの MTU 上限がかかる。」

02

ソケット、ポート、そしてカーネルの struct sock

ソケットは、送受信バッファ、シーケンス番号、輻輳状態、そしてフローを一意に 識別する 4-tuple を所有するカーネルオブジェクトへのユーザ空間ハンドル。 ワイヤより上のすべてが最終的にこの struct を通じて読み書きする。

curl が socket(AF_INET, SOCK_STREAM, 0) を呼ぶと、カーネルはstruct sock(TCP では struct tcp_sock、これはstruct sock を最初のメンバとして埋め込む —— 古典的な Linux struct 継承) を包む struct socket を割り当てる。syscall はそのオブジェクトを指す ファイルディスクリプタを返す、open() がファイルに対して返すのと同じ方式で。 各 send() / recv() / read() / write()は VFS 層を通り、ソケットの file_operations にディスパッチされ、 TCP 固有のルーチンに着地する。

送受信バッファ

各ソケットはカーネルメモリ内にフローごとの 2 つのバッファを所有する:

  • SO_SNDBUF —— TCP が ACK を待つ間 send() が保持できるバイト数。 Linux デフォルト ~200 KB;net.core.wmem_max でキャップ(~4 MB)。
  • SO_RCVBUF —— ワイヤから到着しアプリケーションがまだ読んでいないバイト数。 Linux デフォルト ~200 KB;これは TCP の通告受信ウィンドウの基礎でもある。

これらの上限は TCP の帯域幅遅延積(BDP)と相互作用する。長肥ネットワークリンク —— 1 Gbps × 100 ms RTT = 12.5 MB が飛行中 —— にはそのサイズのウィンドウが必要、 さもなければスループットはリンクレートの一部に崩壊する。Linux の自動チューニング (tcp_rmemtcp_wmem sysctl)が通常これを扱う;SO_RCVBUF を手動で設定すると自動チューニングを無効化し、ほぼ常に間違い。

bind、listen、accept —— サーバ側

サーバは bind() で(ローカル IP、ローカルポート)タプルを予約し、listen(fd, backlog) で受動とマークし、その後 accept()が完全に確立された接続を accept キューから引き出す。2 つのキューが関与する:

SYN キュー   —— ハーフオープン:SYN 受信、SYN-ACK 送信、ACK 待ち
                net.ipv4.tcp_max_syn_backlog でキャップ
                SYN フラッド下、カーネルは SYN クッキーへフォールバック

Accept キュー —— 完全確立:3WHS 完了、accept() 待ち
                min(backlog, somaxconn) でキャップ
                オーバーフロー → SYN-ACK 再送、最終的に RST または破棄

Accept キューのオーバーフローは最も見落とされる本番バグの 1 つ:カーネルが 静かに新しい接続を破棄し、ロードバランサがタイムアウトを見、アプリケーションは 何もログしない。リスニングソケット上の ss -lntRecv-Q(現在の accept キュー深さ)と Send-Q(設定された上限)を見る。

4-tuple 一意性ルール

TCP は 4-tuple によって受信セグメントをデマルチプレックスする:

(src IP, src port, dst IP, dst port)

任意の 2 つのアクティブな接続は、これら 4 つの少なくとも 1 つで異ならなければならない。
(*, 443) でリッスンするサーバは何百万もの接続を持てる、(src IP、src port)が
クライアントごとに変わるから。
1 つの(dst IP、dst port)に接続するクライアントは自身のエフェメラルポート範囲に縛られる。

Linux のデフォルトエフェメラルポート範囲は 32768–60999(net.ipv4.ip_local_port_range)—— 約 28K ポート。 これは 1 つのソース IP から単一の(dst IP、dst port)への同時アウトバウンド接続の最大数でもある。 1 つのバックエンドプールに ~28K 並行接続を超えて当てるフロントエンドは、connect() 時に EADDRNOTAVAIL に当たる。修正: ポート範囲を広げる、複数のソース IP を使う、バックエンドポートを増やす、 または HTTP keep-alive で TCP 接続を再利用する接続プールを使う。

UDP —— 同じアイデア、よりシンプル

UDP ソケットも 4-tuple、送信バッファ、受信バッファを持つ —— しかしシーケンス番号なし、 ack なし、輻輳制御なし、順序保証なし。各 sendto() はワイヤ上の 1 パケット (MTU まで原子的);各 recvfrom() はちょうど 1 パケットを返す。 受信バッファが満杯になると、カーネルは静かに破棄する。UDP はパケットパイプを与え; TCP はバイトストリームを与える。

epoll、レディネスシグナル

数千のソケットを持つ現代のサーバは、各々を順に読まない。epoll_create +epoll_ctl ですべての fd を登録し、その後 epoll_wait で ブロックする、これがデータ準備完了または送信バッファ空きスペースのあるサブセットを返す。 ソケット層は受信キューがパケットを得るか、送信バッファが排出されるたびに、 epoll インスタンスにシグナルする。レディネスモデルについては I/O モデル primer で深く扱う —— この primer ではカーネルがソケットごとのレディネスをファーストクラス通知として 公開すること、そして代替手段(ソケットごとに 1 スレッド)が数万接続を超えてスケールしないこと を知っていれば十分。

要点。「ソケット fd は送受信バッファ、TCP 状態、4-tuple (src IP、src port、dst IP、dst port)を所有するカーネル struct sock を指す。 サーバ側:bind → listen → accept、SYN キューと accept キューが静かなキャパシティの ボトルネック。クライアント側:エフェメラルポート範囲が 1 つの(dst IP、dst port)への 同時アウトバウンド接続を ~28K でキャップ。UDP は信頼性を取り除いた同じアイデア; epoll は数千を一度に待つ方法。」

03

IP ルーティング、ARP、MTU —— パケットが実際にどう道を見つけるか

パケットがソケット層を離れたら、ワイヤに到達するために 3 つが必要:出ていくインターフェイス、 次ホップ IP、宛先 MAC。ルーティングが最初の 2 つに答え;ARP が 3 番目に答え; MTU が結果がどれだけ大きくなれるかを束縛する。

カーネルのルーティングテーブルは宛先 IP をキーとした最長プレフィクスマッチ。ip route show がそれを表示する;各出ていくパケットについて、 カーネルは最も特定的なマッチプレフィクスを見つけ、その(インターフェイス、次ホップ)ペアを使う。

$ ip route show
default via 10.0.0.1 dev eth0 proto dhcp metric 100
10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.42
169.254.0.0/16 dev eth0 scope link metric 1000

宛先 93.184.216.34 → 「default」にマッチ → 次ホップ 10.0.0.1 経由 eth0
宛先 10.0.0.55     → 10.0.0.0/24 にマッチ → 次ホップはホスト自身

ARP —— カーネルは MAC を必要とする

Ethernet ヘッダは IP ではなく宛先 MAC を要求する。ARP(アドレス解決プロトコル)が橋渡しをする: 「10.0.0.1 を持っているのは誰?10.0.0.42 に伝えよ。」返信(「10.0.0.1 は 00:1b:21:3a:7c:9f にいる」) は近隣テーブルに ~60 秒キャッシュされる;ip neigh show がそれを表示する。

$ ip neigh show
10.0.0.1 dev eth0 lladdr 00:1b:21:3a:7c:9f REACHABLE
10.0.0.55 dev eth0 lladdr 52:54:00:12:34:56 STALE
10.0.0.99 dev eth0 INCOMPLETE              ← まだ ARP 応答なし

ARP は設計上認証されていない —— 任意のホストが任意の IP を所有していると主張できる。ARP スプーフィング / ポイズニングはこれを悪用する: 同じ L2 セグメント上の攻撃者が「私が 10.0.0.1 だ」と発表し、被害者のキャッシュが更新され、 攻撃者が静かな中間者となる。緩和策は L2 の上にある:スイッチ上の動的 ARP インスペクション (疑わしい返信を破棄)、802.1X ポート認証、または単に信頼できないホストをサーバと同じ L2 セグメントに置かない。 クラウド VPC は各テナントを独自の仮想 L2 に分離してこれを解決する。

MTU と 1500 バイトの天井

Ethernet の標準ペイロードは 1500 バイト(MTU)。 20 B IP + 20 B TCP で、TCP がセグメントあたり運べる最大ペイロードは1460 バイト(MSS)。データセンターはパケットあたりのオーバーヘッドを 償却するために ジャンボフレーム(MTU 9000)を有効にすることが多い —— ストレージレプリケーション、内部 RPC、NVMe-over-TCP に有用。公衆インターネットでは 1500 のまま。

パス MTU より大きいパケットがルータに現れると、2 つの結果:

  • IPv4、DF=0 —— ルータがパケットを MTU サイズに断片化、受信者が再構築。 実践では避けられる、断片化はパフォーマンスを殺すから:単一の断片の損失が パケット全体の再送を強制し、再構築バッファは DoS 標的。
  • IPv4 DF=1、または IPv6 —— ルータがパケットを破棄し、 次ホップ MTU を含む ICMP「断片化必要」を送り返す。送信者は今後のパケットを縮小する。 これが パス MTU 発見(PMTUD)

TCP は DF=1 を設定しハンドシェイク時にパス MTU を発見、その後 MSS で各セグメントを パス MTU 以下に保つことで、完全に断片化を回避する。

PMTUD ブラックホール

PMTUD は ICMP Type 3 Code 4(「断片化必要、DF 設定」)が送信者に届くことに依存する。 ALL ICMP をブロックする誤設定のファイアウォール —— 「セキュリティ強化」テンプレートで うんざりするほど一般的 —— がこれらのメッセージを静かに飲み込む。症状:小さな応答は瞬時に到着、 大きいものはハング。送信者は同じ大きすぎるパケットを永遠に再送し続ける。修正は少なくとも ICMP Type 3 を許可するか、代わりにプローブ損失から MTU を推論するPLPMTUD(パケット化層 PMTUD、RFC 4821)に頼ること。

端から端まで歩く

以下のデモは、同じパケットがクライアントで 5 層を降り、サーバで再び上がる様子を示し、 下りで各ステップでヘッダが加わり、上りで各ステップで剥がれる。size 列を見る —— 500 B の HTTP がワイヤで 558 B になり、その後サーバプロセス内で 500 B にアンラップされる。

パケット降下 —— curl HTTP GET、クライアントスタック下、サーバスタック上ステップ 0 —— curl が HTTP リクエストを構築クライアント(curl プロセス)サイズ: 500 BL7 アプリケーション (HTTP)L4 トランスポート (TCP)L3 ネットワーク (IPv4)L2 データリンク (Ethernet)L1 物理 (ワイヤビット)
+ HTTP リクエスト:「GET /user/42 HTTP/1.1\r\nHost: api.example.com\r\n...」。約 500 バイトのテキスト。アプリケーション層は名前、メソッド、ボディしか知らない。
1 / 8
各層の唯一の仕事は出口で自身のヘッダを加え、入口でそれを剥がすこと。 クライアントで下りに 5 層、サーバで逆向きに同じ 5 層。ワイヤは 1 袋のバイトしか見ない; 構造は反対側で復元される。

要点。「IP ルーティングは宛先 IP の最長プレフィクスマッチで 出ていくインターフェイスと次ホップゲートウェイを選ぶ。ARP がその次ホップ IP を Ethernet ヘッダのための MAC に変える。MTU(標準 1500 B、ジャンボ 9000 B)が フレームサイズを束縛する。TCP はパス MTU 発見で断片化を回避する —— 最も一般的なバグは依存する ICMP をブロックするファイアウォールで、 小さなリクエストは動き大きなものはハングする PMTUD ブラックホールを生む。」

04

ドライバ、リングバッファ、NAPI、RSS、XDP、そしてカーネルバイパス

スタックの底はほとんどのエンジニアが見ない部分 —— そして、サーバが秒間 10 万か 1000 万パケットで頭打ちになるかを決める部分。リングバッファ、割り込み合体、 キューごとの CPU アフィニティ、そして eBPF / カーネルバイパスの出口。

NIC とカーネルはメインメモリ内の一対の リングバッファ で出会う:

  • RX リング —— ディスクリプタの配列、各々がカーネル割り当てバッファ (典型的に 2 KB)を指す。NIC が「準備完了」ディスクリプタを所有する; パケットが到着すると、バイトをバッファに DMA し、テールポインタを進める。
  • TX リング —— 同じアイデアの逆方向:カーネルが送信パケットバッファを 指すディスクリプタを書き、NIC が DMA してシリアライズする。

典型的なリングサイズは 256 から 4096 エントリ;ethtool -G eth0 rx 4096で調整する。カーネルが十分速く排出していないために RX リングが満杯になると、 NIC はパケットを破棄し、ethtool -S eth0rx_dropped またはrx_missed_errors として可視のカウンタをインクリメントする。 負荷下でそのカウンタが上昇するのは「ボックスが追いつけない」の動かぬ証拠。

割り込みから NAPI へ

2003 年以前、受信した各パケットはハードウェア IRQ を発生させた。10 Gbps では それは ~80 万 IRQ / 秒を意味した —— 割り込みオーバーヘッドだけで CPU を飽和させた。 修正は NAPI(「新しい」API、今や 20 歳):

  1. 最初のパケットが到着 → NIC が IRQ を発生。
  2. ドライバの IRQ ハンドラがそのキューのさらなる IRQ を無効化し、softirq をスケジュール。
  3. softirq がプロセスコンテキストで napi_poll を実行、呼び出しごとにbudget(デフォルト 64)パケットまで排出する。
  4. リングが空になると、NAPI は IRQ を再有効化する。パケットがまだ速く到着しているなら、 ループはポーリングモードに留まり IRQ はオフのまま —— パケットあたりの IRQ コストはゼロ。

これは上の層の epoll と同じアイデア:負荷下でイベントあたり割り込みから 償却ポーリングに切り替える。top 内の softirq 数が 2 桁に 上昇するのは NAPI がまさにその仕事をしている;単一の CPU が 100% %si に 張り付いているときだけ心配する。

RSS —— コア間にファンアウト

Receive Side Scaling は、単一の高速 NIC を多コアマシンで使えるようにする NIC 機能。NIC は複数の RX リング(キューごとに 1 つ、典型的には CPU ごとに 1 つ)を持つ; 受信時にパケット 4-tuple をハッシュし、結果をマスクしてキューを選び、そのキューのリングに DMA し、そのキューを所有する CPU に IRQ する。カーネルは CPU N でキュー N のためにnapi_poll を実行するだけ。同じフロー → 同じキュー → 同じ CPU → 温かい L1/L2 キャッシュ、コア間同期なし。

cat /proc/interrupts | grep eth0 で診断する —— すべてのパケットが 1 つの CPU に山積するなら、RSS が誤設定されている(または 4-tuple が分割できない 1 つの象フローを見ている)。十分なキューを持たない NIC の ソフトウェアフォールバックは RPS(Receive Packet Steering)、 カーネルがソフトウェアでハッシュを行い、プロセッサ間割り込みでディスパッチする。

XDP —— ドライバレベルの eBPF

XDP(eXpress Data Path)は、任意の sk_buff 割り当てやプロトコル処理の前に、ドライバ内 に eBPF プログラムをアタッチさせる。プログラムは生のパケットバッファ上で 実行され、判定を返す:

XDP_DROP    —— 即座に破棄(DDoS スクラブ、ACL ドロップ)
XDP_PASS    —— 通常のスタックを上へ続行
XDP_TX      —— 同じ NIC からバウンスして出る(ロードバランサ)
XDP_REDIRECT —— 別の NIC または AF_XDP ソケットに送る

ドライバでの XDP_DROP はパケットあたり ~50 ナノ秒 —— iptables でドロップするより 桁違いに安い。Cloudflare の L3 DDoS スクラバー、Facebook の Katran L4 ロードバランサ、 Cilium のコンテナネットワーキングはすべて XDP を使う。制限:飛行中の TCP 状態なし、 断片化パケットなし、BPF プログラムに収まるものだけ(現代の verifier で 100 万命令)。

カーネルバイパス —— DPDK、AF_XDP

XDP でも十分速くない場合、カーネルを完全にスキップする。DPDK(Data Plane Development Kit)は UIO または VFIO 経由で NIC をユーザ空間ドライバにバインドする; アプリケーションはピン留めした CPU 上でビジーループでリングバッファをポーリングする。 パケットあたりのレイテンシは ~3 µs(カーネルスタック)から ~300 ns(DPDK)に落ち、 1 コアで 64 バイトパケット ~1400 万 pps を維持できる —— 10 Gbps の線速。 トレーディングシステム、NFV アプライアンス、ハイエンドファイアウォールがここに住む。

AF_XDP はカーネルの答え:特別なソケットタイプで、アプリケーションが RX/TX リングを自身のアドレス空間にマップし、ドライバがアプリケーションメモリに直接 DMA し、 sk_buff を完全にバイパスする。DPDK スループットに近く、運用上の苦痛がはるかに少ない (NIC の排他所有権なし、カスタムドライバなし)。

注意:カーネルバイパスはあなたが TCP、再送、輻輳制御、ARP を実装することを意味する。 ストレージレプリケーションとトレーディングには価値がある。典型的な web サーバには、 カーネルスタックはすでにコアあたり ~100 万 pps できる —— ほとんどのアプリケーションのボトルネックよりはるか下。

要点。「NIC とカーネルは RAM 内の RX/TX リングバッファで出会う; 満杯のリングは ethtool -Srx_dropped として表示される。 NAPI は負荷下でポーリングに切り替えることで割り込みを償却する。RSS は 4-tuple を キューにハッシュし、そのキューを CPU にピン留めし、単一の高速 NIC を N 本の独立した 受信パイプラインに変える。XDP はドライバで eBPF を実行して <100 ns のドロップを実現; DPDK と AF_XDP はそれでも遅すぎるときにカーネルを完全にバイパスする。」

05

クイックリファレンス

冷たく説明できる価値のある 6 つの核心問題と、コードレビューで一目で見抜くべき 5 つの赤旗。

パケットが横断する 5 層、各層は何を加えるか?

L7 アプリケーション(HTTP、可変サイズ、名前とメソッド);L4 トランスポート (TCP 20 B / UDP 8 B、ポート + 信頼性);L3 ネットワーク(IPv4 20 B、ソース / 宛先 IP + TTL); L2 データリンク(Ethernet 14 B + 4 B FCS、宛先 / ソース MAC); L1 物理(ライン符号化、1500 B MTU)。~500 B の HTTP リクエストはワイヤで ~558 B。 OSI のセッション層とプレゼンテーション層には真の実装がない —— それらが意図していたことはアプリケーションまたは TLS に吸収された。

4-tuple とは何、なぜ接続数を束縛するか?

(src IP、src port、dst IP、dst port)—— TCP はこれによって受信セグメントを デマルチプレックスする。任意の 2 つのアクティブな接続は少なくとも 1 つで異ならなければならない。 (*, 443) でリッスンするサーバは何百万もの受信接続を持てる(クライアントが src IP/port を変える)。 単一クライアントから単一の(dst IP、dst port)は自身のエフェメラルポート範囲に束縛される (Linux デフォルト 32768-60999、~28K)。それに当たると connect()EADDRNOTAVAIL を返す。修正:より広いポート範囲、より多くのソース IP、 より多くのバックエンドポート、または HTTP keep-alive。

パス MTU 発見とは何で、壊れると何が起こるか?

TCP は DF(Don't Fragment)ビットを設定し、ICMP「断片化必要」メッセージを リッスンしてパス上の最小 MTU を発見する。その後 MSS を使って各セグメントをそのサイズ以下に保つ。 ファイアウォールがすべての ICMP をブロックすると(一般的な「セキュリティ強化」のミス)、 PMTUD は静かに失敗する:小さな応答は瞬時に到着し、大きいものはハングする、 大きすぎるパケットがボトルネックルータで破棄され、通知が送信者に届かないから。 修正:ICMP type 3 を許可するか、PLPMTUD(RFC 4821)を有効化する。

RSS とは何で、なぜ高スループットサーバに重要か?

Receive Side Scaling —— NIC は複数の RX キューを持ち、各受信パケットの 4-tuple を ハッシュして 1 つを選び、そのキューにピン留めされた CPU に IRQ する。同じフロー → 同じキュー → 同じ CPU → 温かいキャッシュ、コア間同期なし。RSS なしでは、 すべての RX 処理が 1 つのコアに山積し、単一の 10 Gbps NIC を排出できない。cat /proc/interrupts | grep eth0 を確認 —— 平坦な分布は RSS が動作している;1 列が支配しているなら動作していない。

いつ XDP または DPDK に手を伸ばすか?

XDP は通常のスタックの前に安価なドロップや些細な L2/L3 処理が必要なとき: DDoS スクラビング(XDP_DROP がパケットあたり ~50 ns)、L4 ロードバランシング (Katran、Cilium)、またはパケットあたりの ACL。DPDK / AF_XDP は完全なカーネルバイパスと 線速転送が必要なとき:トレーディングシステム、NFV アプライアンス、ハイエンドファイアウォール。 コストは TCP、再送、輻輳制御を自分で実装すること —— 典型的な web サーバには、 コアあたり ~100 万 pps のカーネルスタックはすでにアプリケーションの実際のボトルネックを越えている。

ドライバレベルのパケットドロップを示すシグナルは?

ethtool -S eth0 | grep -E 'drop|miss|error'は NIC 自身のカウンタを表示する;負荷下で上昇する rx_dropped またはrx_missed_errors はカーネルが排出できる前に RX リングが満杯になったことを意味する。/proc/net/softnet_stat(第 3 列は CPU ごとのドロップパケット、 CPU ごとの入力キューがオーバーフローしたから)と nstat -az TcpExtListenDrops TcpExtListenOverflowsでソケット層 accept キューオーバーフローをクロスチェック。ethtool -G(リングサイズ)、ethtool -C(割り込み合体)、または RSS キューを拡張して調整。

コードレビューの 5 つの赤旗

  1. BDP を測定せずに SO_SNDBUF / SO_RCVBUF をチューニング。これらを手動で設定すると Linux 自動チューニングが無効化され、通常スループットが損なわれる。 正しい値はリンクの帯域幅 × RTT;それ以外の人は sysctl デフォルトをそのままに。
  2. アプリが 1 つの(dst IP、dst port)に 10 万のアウトバウンド接続を開く。エフェメラルポート範囲が ~28K で上限。本番で EADDRNOTAVAIL を得る。 HTTP keep-alive 付きの接続プーリングを使うか、複数の宛先 / ソース IP にファンアウトする。
  3. 明示的なメッセージ境界処理のない UDP コード。recvfrom() はちょうど 1 データグラムを返すか、バッファテールバイトを破棄する (MSG_TRUNC で通知)。コードが「N バイトになるまで読み続ける」と仮定するなら、 UDP 形状コードに TCP 形状のバグがある。
  4. ホットパスで BPF フィルタなしの本番 tcpdump。フィルタなしの tcpdump は各パケットをユーザ空間にコピーし、 パケットあたりのコストが容易に 10×。最低でも BPF 式 (tcp port 443)を渡し、カーネルにコピーをフィルタさせる。
  5. エッジで ICMP を完全にブロック。パス MTU 発見が静かに壊れ、近隣到達可能性検出が壊れ、traceroute が壊れる。 少なくとも ICMP type 3(宛先到達不能)、type 4(送信元抑制)、type 11(時間超過)を許可する。 「すべての ICMP をブロック」は 1990 年代のセキュリティ慣用句で、害が利を上回る。