2. パッシブ監視 Pythonコード(ARPフラッピング検出)
既存のサーバや PC 上で、「ARP を眺めて IP/MAC の変化を検出したい」なら、
scapy で数十行のスクリプトを書けば実現できます。
前提
OS:Linux or macOS(Windows でも WSL や権限周りを調整すれば可)
pip install scapy
root 権限で実行(パケットキャプチャのため)
コード例:ARP の IP→MAC 変化を監視
from scapy.all import sniff, ARP
from collections import defaultdict
import time
# 現在認識している IP -> MAC
ip_to_mac = {}
# IPごとの変更履歴(タイムスタンプとMAC)
change_history = defaultdict(list)
# フラッピング判定のしきい値
WINDOW_SEC = 60 # 何秒間を見るか
FLAP_COUNT = 3 # この回数以上変わったら「バタバタ」とみなす
def handle_arp(pkt):
if ARP not in pkt:
return
arp = pkt[ARP]
# ARP Reply (is-at) のみ見る
if arp.op != 2:
return
ip = arp.psrc
mac = arp.hwsrc.lower()
now = time.time()
if ip not in ip_to_mac:
ip_to_mac[ip] = mac
print(f"[{time.strftime('%H:%M:%S')}] 初回観測: {ip} -> {mac}")
return
if ip_to_mac[ip] != mac:
old_mac = ip_to_mac[ip]
ip_to_mac[ip] = mac
change_history[ip].append((now, mac))
print(f"[{time.strftime('%H:%M:%S')}] IP {ip} のMACが変化: {old_mac} -> {mac}")
# 直近 WINDOW_SEC 秒以内の変更回数を確認
recent_changes = [
t for (t, _) in change_history[ip]
if now - t <= WINDOW_SEC
]
if len(recent_changes) >= FLAP_COUNT:
print(f" !!! IP {ip} でMACフラッピング検知: "
f"{WINDOW_SEC}秒以内に {len(recent_changes)} 回変更")
if __name__ == "__main__":
# sniff(filter="arp", iface="eth0", prn=handle_arp, store=False)
# iface を指定しない場合、全インターフェースが対象(環境依存)
sniff(filter="arp", prn=handle_arp, store=False)
使い方イメージ
監視したい VLAN/セグメントにぶら下がっている Linux マシンで実行
しばらく放置しておく
IP 1つに対して、MAC が何度も入れ替わると MACフラッピング検知 のメッセージが出る
「arpテーブルのMACアドレスがバタバタしている」が本当に発生しているなら、
このスクリプトを流しっぱなしにすると、どの IP でどの MAC が入れ替わっているかが見えます。
. アクティブスキャン Pythonコード(同一IPに複数MACがいないか)
重複している IP を一括チェックしたい場合は、サブネットに対して ARP を投げて
同じ IP に対して複数の MAC アドレスから応答があるか を確認します。
コード例:サブネット全体を ARP スキャンして重複チェック
from scapy.all import ARP, Ether, srp
# スキャン対象のサブネット(自分の環境に合わせて変更)
TARGET_NET = "192.168.1.0/24"
def scan_duplicates(network):
# ブロードキャストで ARP リクエスト送信
arp = ARP(pdst=network)
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
packet = ether / arp
print(f"[*] Scanning {network} ...")
answered, _ = srp(packet, timeout=3, verbose=False)
ip_to_macs = {}
for snd, rcv in answered:
ip = rcv.psrc
mac = rcv.hwsrc.lower()
ip_to_macs.setdefault(ip, set()).add(mac)
duplicates = {ip: macs for ip, macs in ip_to_macs.items() if len(macs) > 1}
if not duplicates:
print("重複IPは検出されませんでした。")
else:
print("重複している可能性のあるIP:")
for ip, macs in duplicates.items():
print(f" {ip}: {', '.join(macs)}")
if __name__ == "__main__":
scan_duplicates(TARGET_NET)
ポイント
1回のスキャンでは「たまたま片方だけ応答する」ケースもあるので、
数回繰り返して結果を比較すると精度が上がります。
セグメントをまたいだ IP 重複にはもちろん無力なので、
L3 境界ごとに実行するイメージです。