ARPキャッシュの深層:OS内部挙動とGratuitous ARPが織りなすパケットのドラマ
ネットワークエンジニアやインフラアーキテクトであれば、トラブルシューティングの最中に arp -n や ip neighbor show を叩き、カーネルが保持するARPテーブルを睨みつけた経験が一度や二度ではないはずだ。
「なぜ、IPアドレスとMACアドレスの紐付けが突然消失したのか」
「なぜ、機器をリプレイスした直後に通信がブラックホール化するのか」
私たちは普段、OSがよしなに処理してくれるレイヤー2とレイヤー3の橋渡しを過信しがちだが、その裏側では、Linuxカーネルの緻密なステートマシンと、ネットワーク上を飛び交う微小なパケットたちが、緊密なダンスを踊っている。
今回は、このARP(Address Resolution Protocol)キャッシュテーブルの管理メカニズム、エントリのライフサイクル、そしてセキュリティの観点から見逃せない Gratuitous ARP(GARP)の挙動について、パケットレベルの解像度で徹底的に紐解いていこう。
—
1. ARPキャッシュの生命維持:Linuxカーネルにおけるステートマシン
OSは、MACアドレスを解決するたびに無限にそれを保持するわけではない。ARPテーブルは有限の資源であり、ネットワークのトポロジ変更(VLANの移動、NICの交換、DHCPによるIPの再割り当てなど)に追従するため、厳密な生存期間(エイジング)とステート管理が行われている。
Linuxカーネル(Neighbour subsystem)において、ARPエントリは以下の主要なステート(状態)のいずれかに属している。
- INCOMPLETE: ARPリクエストを送信したが、まだARPリプライを受信していない状態。解決待ちの過渡期。
- REACHABLE: 正常に通信が可能で、直近で双方向のトラフィックが確認された状態。
- STALE: エントリは有効だが、最後の通信から一定時間が経過し、期限切れの可能性を示している状態。
- DELAY:
STALE状態のエントリに対してパケット送信要求が発生した際、即座にARPを撃つのではなく、上位プロトコルの確認を待つタイマー稼働状態。 - PROBE:
DELAY期間内に応答がない、あるいはタイマーが切れたため、ユニキャストのARPリクエスト(プローブ)を再送して生存確認を行っている状態。 - FAILED: 最終的に応答が得られず、エントリの解決に失敗した状態。
カーネルパラメータによる生存期間の制御
Linuxのネットワーキングスタックでは、これらのステート遷移を決定づけるタイマー値が /proc/sys/net/ipv4/neigh/ 配下のパラメータとして露出している。実務の現場でパフォーマンスや収束速度(Convergence Time)をチューニングする際、この深淵を覗くことになる。
# デフォルトインターフェースにおけるネイバー(ARP)設定の確認
$ ip -s neigh show
# または sysctl でカーネルパラメータを直接確認
$ sysctl -a | grep net.ipv4.neigh.default
主要なパラメータの意味とチューニングの勘所を以下に整理する。
# /etc/sysctl.conf のチューニング例(高密度なデータセンター環境向け)
# ガベージコレクタが古いエントリの掃除を始める閾値(エントリ数)
net.ipv4.neigh.default.gc_thresh1 = 128
# ソフトリミット:この数を超えると、過去5秒間参照されていないエントリが削除対象になる
net.ipv4.neigh.default.gc_thresh2 = 512
# ハードリミット:この数を超えると、カーネルは新規のARP解決を拒否し始める
net.ipv4.neigh.default.gc_thresh3 = 1024
# REACHABLE状態とみなされる時間(ミリ秒)。デフォルトは通常30秒(30000)
net.ipv4.neigh.default.base_reachable_time_ms = 30000
# ガベージコレクタが実行されるインターバル(秒)
net.ipv4.neigh.default.gc_interval = 30
高可用性(HA)クラスターやフェイルオーバーが頻発する環境では、base_reachable_time_ms をあえて短く設定し、障害発生時のMACアドレスキャッシュの残留時間を最小化することが、パケットのブラックホール化を防ぐ定石となる。
—
2. Gratuitous ARP(GARP)の衝撃:エントリ強制更新のメカニズム
通常、ARPリクエストは「誰それのIPアドレスを持つマシンのMACアドレスを教えてくれ」という問い合わせだが、Gratuitous ARP(無償ARP) はその常識を覆す。
GARPとは、「自分自身のIPアドレス」を送信元IPに指定し、宛先IPにも自分自身のIPを記述したARPリクエスト(あるいはARPリプライ)を、ブロードキャスト(ff:ff:ff:ff:ff:ff)でネットワーク上に放流する挙動を指す。
このパケットがワイヤー上を流れた瞬間、スイッチングハブやルーター、そして同じセグメントに存在するすべてのホストのARPキャッシュに強烈なインパクトを与える。
GARPが引き起こすカーネルの挙動
LinuxカーネルがGARPを受信したとき、内部では何が起きているのか。
1. 既存エントリの検索: 受信したGARPパケットの「送信元IPアドレス(SPA)」と「送信元MACアドレス(SHA)」を抽出する。
2. キャッシュの更新(あるいは新規作成):
- すでにARPテーブルにそのIPアドレスのエントリが存在する場合、カーネルはタイムスタンプをリフレッシュし、MACアドレスを新しいものに即座に書き換える(強制上書き)。
- エントリが存在しない場合、基本的には無視されることが多い(※カーネルの
arp_acceptパラメータ設定に依存する)。
セキュリティ上の脅威:ARPスプーフィング(ARPポイズニング)の温床
この「GARPや一方的なARPリプライを無条件に信じてキャッシュを更新してしまう」という設計は、レイヤー2ネットワークにおける最大の弱点、すなわち ARPスプーフィング(ARP Poisoning) の根幹を成している。
悪意ある攻撃者が、実在するデフォルトゲートウェイ(ルーター)のIPアドレスと「攻撃者のNICのMACアドレス」を紐付けた偽のGARPパケットを狂ったようにブロードキャストし続ければ、同一セグメント上の全ホストのARPキャッシュは瞬く間に毒され、すべてのトラフィックが攻撃者のマシンを経由する「中間者攻撃(MitM)」が完成する。
この脆弱性を緩和するため、モダンなLinuxディストリビューションでは厳格な検証モードが用意されている。
# カーネルパラメータでARPの学習挙動を厳格化する
# arp_announce: 送信パケットにおける送信元IPアドレスの選択ポリシー(0: 自由, 1: 同一サブネット優先, 2: 最適なローカルアドレス)
# arp_ignore: ARPリクエストに対する応答ポリシー
# 外部からの怪しいARPを簡単に受け入れないための設定例
$ sudo sysctl -w net.ipv4.conf.all.arp_accept=0
$ sudo sysctl -w net.ipv4.conf.all.arp_announce=2
—
3. 通信発生時のパケット挙動:L2からL4、そしてTLSハンドシェイクへの接続
では、ARPキャッシュが一度 STALE になり、そこから実際にWebサーバーへHTTPSリクエストを送る際のパケットの連鎖を、極限まで解像度を上げて追ってみよう。
ステップ1:ルーティングとネイバーのルックアップ
アプリケーションが https://example.com(例: 192.168.10.50)へのTCP接続(ポート443)を要求する。
Linuxのネットワークスタックは、まずルーティングテーブルを引く。
$ ip route get 192.168.10.50
192.168.10.50 dev eth0 src 192.168.10.10 uid 1000
cache
宛先が同一セグメント、あるいはデフォルトゲートウェイ経由であることが判明すると、カーネルはネクストホップのIPアドレスに対応するMACアドレスをARPキャッシュから探す。
もし該当エントリが STALE であれば、カーネルはパケットをいったん保留(または送信しつつ DELAY ステートへ移行)し、速やかに ユニキャスト(またはブロードキャスト)のARPリクエスト を送出する。
ステップ2:ARP解決の完了とレイヤー2フレームの構築
宛先からARPリプライが返ってくると、カーネルはエントリを REACHABLE に格上げし、ようやく目的のIPパケット(IPv4ヘッダー + TCPセグメント)をイーサネットフレームにカプセル化する。
ここで、イーサネットヘッダーの宛先MACアドレスに、先ほど取得した実機のMACアドレスが書き込まれる。
[ Ethernet Header ] -> Destination: AA:BB:CC:DD:EE:FF / Source: 11:22:33:44:55:66
[ IP Header ] -> Source: 192.168.10.10 / Destination: 192.168.10.50
[ TCP Header ] -> Source Port: 54321 / Destination Port: 443 / SYN
ステップ3:トランスポート層(TCP)とTLSの最適化
レイヤー2のハードルを越えたパケットが相手に届くと、いよいよTCPの3ウェイ・ハンドシェイク(SYN, SYN-ACK, ACK)が始まる。
ここでインフラアーキテクトが意識すべきは、RTT(Round Trip Time)の削減 と TCPウィンドウサイズ・バッファのチューニング だ。
レイヤー2のARP解決にもたつくと、TCPの初期ハンドシェイクに遅延が生じ、クライアント側の体感速度(TTFB: Time To First Byte)に悪影響を及ぼす。特にコンテナ環境や仮想化基盤(KubernetesのCNIなど)では、仮想ブリッジやvethペアを経由する過程でARP/NDP(IPv6の場合)のキャッシュ枯渇やフラッディングが発生しやすいため、ネイバーテーブルのサイズやGC(ガベージコレクション)の閾値を適切にサイジングすることが極めて重要となる。
さらに、TCPハンドシェイクが完了した直後の TLS 1.3ハンドシェイク においては、1-RTT(またはZero-RTT)での暗号化確立が求められる。ネットワークの基礎であるARP層が正常かつ高速にキャッシュを維持していることこそが、上位レイヤーにおける秒速のTLSセキュア通信の土台となっているのである。
—
4. 現場で使える!ARPトラブルシューティングと自動化スクリプト
最後に、実務の現場で遭遇する「ARPに起因するパケットロスや接続不良」を迅速に切り分けるためのPythonスクリプトと、確認用コマンドを紹介しよう。
トラブルシューティングの鉄板コマンド
# 特定のインターフェースにおけるARPキャッシュの状態をリアルタイム監視
$ watch -n 1 "ip neigh show"
# 特定のIPに対するカーネルのネイバー状態を強制的に期限切れ(STALE)にする
# (ルーターのMAC変更直後や、障害切り分け時に極めて有効)
$ sudo ip neigh flush dev eth0 to 192.168.10.1
Pythonによる簡易ARPスキャナ&キャッシュチェッカー
セキュリティ診断や、ネットワークセグメント内のゾンビIP・MACの不整合を検知するために役立つ、Scapyを使用したスクリプトの断片を提示する。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from scapy.all import ARP, Ether, srp
import sys
def scan_arp_network(target_ip_range):
"""
指定されたサブネットに対してARPリクエストをブロードキャストし、
アクティブなホストのIPとMACアドレスのペアを収集して表示する。
"""
print(f"[*] Scanning network range: {target_ip_range} ...")
# イーサネットブロードキャストフレームとARPリクエストを結合
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
arp = ARP(pdst=target_ip_range)
packet = ether / arp
# パケットを送受信し、応答を得る
result = srp(packet, timeout=3, verbose=0)[0]
devices = []
for sent, received in result:
devices.append({'ip': received.psrc, 'mac': received.hwsrc})
print(f"[*] Found {len(devices)} active devices.\n")
print("IP Address\t\tMAC Address")
print("-" * 40)
for device in devices:
print(f"{device['ip']}\t\t{device['mac']}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 arp_scanner.py <IP_Range>")
print("Example: python3 arp_scanner.py 192.168.10.0/24")
sys.exit(1)
# ルート権限(CAP_NET_RAW)が必要な点に注意
scan_arp_network(sys.argv[1])
—
まとめ
ARPキャッシュという、一見すると地味なOSの内部コンポーネント。しかしその裏側には、Linuxカーネルの緻密なステートマシン、GARPによるネットワーク間の動的な同期、そしてレイヤー2からレイヤー7のTLSハンドシェイクに至るまでの壮大なパケットのバトンリレーが存在している。
インフラストラクチャの規模が拡大し、クラウドネイティブやマイクロサービスアーキテクチャが主流となった現代においても、このネットワークの「足回り」の挙動を深く理解しているかどうかが、障害発生時の切り分けスピードとシステム全体の信頼性を大きく左右する。
パケットが流れる足元を疑い、カーネルの鼓動に耳を傾けること――それこそが、真のネットワーク・セキュリティスペシャリストの条件なのだ。
コメント