Linuxでtc-mirredを使ったTraffic Mirroring設定 — サービスを止めずにパケットをIDS/IPSへコピーする

Network tutorial - IT technology blog
Network tutorial - IT technology blog

かつて、ピーク時間帯(夕方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はデフォルトのnoqueuepfifo_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以上)を処理しているインターフェイスに適用する場合は、本番適用前にベンチマークを取ることをお勧めする。

Share: