かつて、ピーク時間帯(夕方6時から8時頃)にしか再現しないintermittent packet lossの原因追跡に、まる3日間費やしたことがある。日中は何も問題ないのに、その時間帯になると途端にユーザーからのクレームが絶えなくなる。問題は、CPUとディスクへの影響を考えると、本番サーバーでtcpdumpを常時実行するわけにはいかないことだった。もっとpassiveな方法 — サーバーに気づかれずにトラフィックをキャプチャする手段が必要だった。
そこで辿り着いたのが、tc-mirredを使ったTraffic Mirroringだ。トラフィックフローに直接干渉するのではなく、すべてのパケットを別のインターフェイスにコピーしてIDSマシンへ転送する。本番サーバーへの影響はゼロ — スイッチ上のSPANポートと同じ仕組みを、純粋にソフトウェアで実現するイメージだ。
tc-mirredとは何か、どのように動作するのか
tc(Traffic Control)はLinuxカーネルのキュー管理、シェーピング、パケットフィルタリングのためのコアツールだ。mirred(Mirror and Redirect)モジュールはtcのアクションの一つで、2つの動作モードを提供する:
- mirror:パケットを別のインターフェイスにコピーし、元のパケットはそのまま通過させる — IDS/IPS向けに使うのはこのモードだ
- redirect:パケットを別のインターフェイスに完全に転送し、元のパケットはドロップされる
ソリューションの全体アーキテクチャ:
[Client] ──→ [eth0: Production server] ──→ [Internet/Backend]
│
│ (mirror copy、元のトラフィックに影響なし)
↓
[eth1: Monitoring interface]
│
↓
[IDS/IPS: Suricata / Zeek / Wireshark]
tcpdumpやWiresharkをサーバー上で直接実行する場合との重要な違い:ミラーリングはカーネルレベルで動作するためオーバーヘッドが非常に低く、IDSはrawパケットを受信できる — iptablesによってドロップされたパケットも含めて。
環境の準備
サーバーには最低2つのネットワークインターフェイスが必要だ:
eth0:実際のトラフィックを処理しているメインインターフェイスeth1:IDS/IPSマシンに接続するインターフェイス — IPアドレスは不要、UP状態であればよい
monitoring interfaceを起動する:
ip link set eth1 up
ip link show eth1 # 状態を確認: UP
カーネルモジュールact_mirredが有効か確認する:
lsmod | grep mirred
# 出力がない場合は手動でロード:
modprobe act_mirred
Traffic Mirroringの設定手順
ingressトラフィックのミラーリング(受信方向)
最もよく誤解される部分:ingressトラフィックでは、Linuxは特別なingress qdisc(handle ffff:)を別途追加する必要がある — これはルーティング決定の前にパケットを処理するためのカーネルのpseudo-qdiscだ。
# ステップ1: eth0にingress qdiscを追加
tc qdisc add dev eth0 ingress
# ステップ2: 受信する全IPトラフィックにmirrorフィルタを追加
tc filter add dev eth0 parent ffff: \
protocol ip \
u32 match u32 0 0 \
action mirred egress mirror dev eth1
パラメータの説明:
parent ffff::ingress qdiscにフィルタをアタッチするu32 match u32 0 0:すべてのパケットにマッチ(オフセット0にビットマスク0 = すべてにマッチ)action mirred egress mirror dev eth1:eth1にコピーを送出する
egressトラフィックのミラーリング(送信方向)
egressにはひと手間必要だ — Linuxはデフォルトのnoqueueやpfifo_fastにフィルタをアタッチできないため、まず親qdiscを作成しなければならない:
# ステップ1: デフォルトqdiscをprioに置き換える
tc qdisc add dev eth0 root handle 1: prio
# ステップ2: egressにmirrorフィルタを追加
tc filter add dev eth0 parent 1: \
protocol ip \
u32 match u32 0 0 \
action mirred egress mirror dev eth1
双方向ミラーリングスクリプト
IDSがフルセッションを把握するには、リクエストとレスポンスの両方が必要だ。ingressとegressを組み合わせたスクリプトを以下に示す:
#!/bin/bash
# /usr/local/bin/setup-mirror.sh
PROD_IF="eth0"
MONITOR_IF="eth1"
# monitoring interfaceをUP状態にする
ip link set $MONITOR_IF up
# === Ingressミラー(受信トラフィック) ===
tc qdisc add dev $PROD_IF ingress
tc filter add dev $PROD_IF parent ffff: \
protocol ip u32 match u32 0 0 \
action mirred egress mirror dev $MONITOR_IF
# === Egressミラー(送信トラフィック) ===
tc qdisc add dev $PROD_IF root handle 1: prio
tc filter add dev $PROD_IF parent 1: \
protocol ip u32 match u32 0 0 \
action mirred egress mirror dev $MONITOR_IF
echo "[OK] Traffic mirroring active: $PROD_IF → $MONITOR_IF"
ミラーリングの動作確認
# eth0のすべてのqdiscを表示
tc qdisc show dev eth0
# ingressフィルタを表示
tc filter show dev eth0 ingress
# egressフィルタを表示
tc filter show dev eth0
# パケットカウンターを監視(数回実行してSent数が増加していることを確認)
tc -s filter show dev eth0 ingress
IDSマシン上でtcpdumpを使ってパケットを受信していることを確認する:
tcpdump -i eth1 -n -c 50
# eth0で直接キャプチャした場合と同様のトラフィックが見えるはず
tc-mirredを使う際の実践的なTips
ポートまたはIPによる選択的ミラーリング
サーバーのスループットが高い場合、全トラフィックのミラーリングはmonitoring interfaceを過負荷にする可能性がある。ポートや送信元IPでフィルタリングすることで、負荷を大幅に軽減できる:
# TCPポート443のみをミラー(ingress)
tc filter add dev eth0 parent ffff: \
protocol ip \
u32 match ip dport 443 0xffff \
action mirred egress mirror dev eth1
# 特定のIPアドレスからのトラフィックのみをミラー
tc filter add dev eth0 parent ffff: \
protocol ip \
u32 match ip src 10.0.0.100/32 \
action mirred egress mirror dev eth1
ミラーリング停止時のクリーンアップ
#!/bin/bash
# /usr/local/bin/cleanup-mirror.sh
PROD_IF="eth0"
# ingress qdiscを削除(内部のすべてのフィルタも一緒に削除される)
tc qdisc del dev $PROD_IF ingress 2>/dev/null
# root qdiscを削除、カーネルが自動的にデフォルトに復元する
tc qdisc del dev $PROD_IF root 2>/dev/null
echo "[OK] Mirroring stopped, defaults restored"
systemdを使ったリブート後の自動起動
tcの設定はリブート後に消える — これは初めて使ったときに引っかかった点だ。自動化するためのsystemdサービスを作成しよう:
# /etc/systemd/system/traffic-mirror.service
[Unit]
Description=Traffic Mirroring via tc-mirred
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/setup-mirror.sh
ExecStop=/usr/local/bin/cleanup-mirror.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
chmod +x /usr/local/bin/setup-mirror.sh /usr/local/bin/cleanup-mirror.sh
systemctl daemon-reload
systemctl enable --now traffic-mirror
systemctl status traffic-mirror
まとめ
tc-mirredはあまり語られないツールの一つだが、他の方法では解決できない実際の問題を解決してくれる:本番トラフィックに干渉しないpassiveな監視を、SPANポート対応のスイッチハードウェアなしに実現できる。
あのpacket lossのデバッグの後、eth1でpassive IDSモードのSuricataを常時稼働させるようにした — ミラートラフィックを受け取り、分析してアラートを出すが、本番環境には一切触れない。結果として、ピーク時間帯にアクセスレイヤーの古いスイッチが過負荷になっているせいで、一部のクライアントが過剰なretransmitをしているという明確なパターンを発見できた。ミラーリングなしではそのパターンは見えなかっただろう。
実用上の注意点:ミラーリングには小さなCPUオーバーヘッドがある — 現代的なサーバーなら通常2%未満だ。ただし、非常に高いスループット(10Gbps以上)を処理しているインターフェイスに適用する場合は、本番適用前にベンチマークを取ることをお勧めする。

