ARP You Kidding Me?
Once upon a time in a quiet corner of your network, the address resolution protocol (ARP) decided to throw a house party. Everyone showed up: the guy with five IPs and one shady MAC, a bunch of TTL-decrementing ICMP packets playing musical chairs, and of course, no security bouncer at the door. What followed was less of a rave and more of a denial-of-service rave crash. Enter: ARP cache poisoning and routing loops, a delightful way to turn your multi-hop ad hoc network into a battery-munching, channel-hogging mess.
If you’ve ever looked at your packet monitor and thought, “Why is this echo request circling like it's stuck in traffic on the M25?” then congrats: you've met the networking equivalent of Groundhog Day.
The Loop That Never Ends
An attacker named Eve (which is always the attacker’s name, because Dave's taken) sends just a handful of spoofed ARP packets. Suddenly your packets get lost in a love triangle where A thinks B lives at C’s house, C forwards to D, and D sends everything back to C, again and again, until TTL says, “I give up,” and a packet dies of exhaustion.
And your hosts? They’re busy burning CPU cycles and draining battery life faster than your fridge drains the milk you forgot to buy again.
Introducing: ARP Loop Guard
This is the part where a hero emerges, not with capes, but with a shell prompt. ARP Loop Guard is a one-liner-loving, sarcasm-tolerant script that lives to fight looping ICMP echoes and ARP cache confusion.
#!/bin/bash
# ---------------------------------------------------------------
# ARP Loop Guard – Detection & Defense for Ad Hoc Networks
# ---------------------------------------------------------------
# DESCRIPTION:
# Monitors for ARP spoofing and ICMP routing loops. Detects:
# - ICMP packets with erratic TTLs (routing loops)
# - ARP cache MAC addresses linked to multiple IPs
# RESPONDS WITH:
# - Auto-blocking via iptables
# - Timestamped logging
# - Auto-unblocking after timeout
#
# USAGE:
# 1. chmod +x arp_loop_guard.sh
# 2. sudo ./arp_loop_guard.sh
#
# LOG ROTATION:
# /etc/logrotate.d/arp_loop_guard :
# /var/log/arp_loop_guard.log {
# daily
# rotate 7
# compress
# missingok
# notifempty
# create 640 root adm
# }
#
# DEPENDENCIES:
# bash, awk, tcpdump, iptables, flock, tee, tail
# ---------------------------------------------------------------
# === CONFIGURATION ===
ICMP_LOOP_THRESHOLD=3
ARP_MAC_IP_THRESHOLD=3
ARP_SCAN_INTERVAL=60
UNBLOCK_CHECK_INTERVAL=300
UNBLOCK_AGE=3600
LOGFILE="/var/log/arp_loop_guard.log"
BLOCKLIST_FILE="/var/log/arp_loop_blocklist.txt"
declare -A blockList
mkdir -p "$(dirname "$LOGFILE")"
touch "$BLOCKLIST_FILE"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"
}
monitor_icmp_ttl() {
tcpdump -nn -l -i any icmp -v 2>/dev/null | awk -v threshold="$ICMP_LOOP_THRESHOLD" -v logfile="$LOGFILE" '
function log(msg) {
cmd = "echo [$(date +\"%Y-%m-%d %H:%M:%S\")] " msg " >> " logfile
system(cmd)
}
/echo request/ {
split($0, a, "id="); split(a[2], idPart, ",")
split($0, b, "ttl="); split(b[2], ttlPart, " ")
packetID = idPart[1]
ttl = ttlPart[1]
key = packetID
if (!(key ttl)) {
ttlCount[key]++
ttlSeen[key ttl]=1
}
if (ttlCount[key] > threshold) {
log("[ICMP] Possible loop detected for Packet ID " key " with TTL anomalies.")
}
}
'
}
monitor_arp_cache() {
while true; do
declare -A macToIPs count
while read -r ip hw_type flags mac mask device; do
[[ "$mac" == "00:00:00:00:00:00" ]] && continue
macToIPs[$mac]="${macToIPs[$mac]} $ip"
count[$mac]=$((count[$mac]+1))
done < <(tail -n +2 /proc/net/arp)
for mac in "${!count[@]}"; do
if [[ "${count[$mac]}" -ge "$ARP_MAC_IP_THRESHOLD" ]]; then
log "[ARP] MAC $mac linked to multiple IPs: ${macToIPs[$mac]}"
for ip in ${macToIPs[$mac]}; do
if ! iptables -C INPUT -s "$ip" -j DROP 2>/dev/null; then
iptables -A INPUT -s "$ip" -j DROP
log "[BLOCKED] IP $ip associated with MAC $mac"
(
flock -n 9 || exit 1
echo "$ip $(date +%s)" >> "$BLOCKLIST_FILE"
) 9>>"$BLOCKLIST_FILE"
blockList["$ip"]=1
else
log "[SKIPPED] IP $ip already blocked."
fi
done
fi
done
sleep "$ARP_SCAN_INTERVAL"
done
}
auto_unblock_ips() {
while true; do
tmpfile="$(mktemp)"
now=$(date +%s)
while read -r line; do
ip=$(echo "$line" | cut -d ' ' -f1)
ts=$(echo "$line" | cut -d ' ' -f2)
age=$((now - ts))
if (( age >= UNBLOCK_AGE )); then
if iptables -D INPUT -s "$ip" -j DROP 2>/dev/null; then
log "[UNBLOCKED] IP $ip after $age seconds"
else
log "[UNBLOCK FAILED] IP $ip not found or already removed"
fi
else
echo "$line" >> "$tmpfile"
fi
done < "$BLOCKLIST_FILE"
(
flock -n 9 || exit 1
cat "$tmpfile" > "$BLOCKLIST_FILE"
) 9>>"$BLOCKLIST_FILE"
rm "$tmpfile"
sleep "$UNBLOCK_CHECK_INTERVAL"
done
}
log "ARP Loop Guard started."
monitor_icmp_ttl &
monitor_arp_cache &
auto_unblock_ips
Here’s what it does:
- Watches ICMP packets like a tabloid paparazzi lurking outside a celebrity router, and flags any that can't decide what TTL they were born with. If your packets are doing laps, ARP Loop Guard is already judging them.
- Peeks into
/proc/net/arp
every 60 seconds, which is basically Bash’s version of meditation, except it side-eyes any MAC address with an identity crisis and more IPs than it has business claiming. - Drops suspicious IPs faster than you delete an email from “Crown Prince of Nigeria” offering high-yield investments. It uses
iptables
with ruthless efficiency, and now checks for duplicates before blocking. So no double drama. - Logs everything to
/var/log/arp_loop_guard.log
, in case you want to relive the security chaos later like it’s a network-themed true crime podcast. Bonus: it plays well withlogrotate
, so the log won’t grow bigger than your mistrust of open Wi-Fi. - Automatically unblocks IPs after an hour of time-out, like a well-behaved toddler who’s finally learned to play nice with TTL fields. And if an unblock attempt fails? It logs that too, like a disappointed parent.
- Uses
flock
for thread-safe blocklist writing, so your ARP and unblock routines don’t accidentally bump into each other and spill their IP lists all over the filesystem like clumsy waiters at a protocol dinner party. - Requires no cloud, no Python, no virtual environments, and definitely no tradeoffs with your soul. Just a dash of
bash
, a pinch ofawk
, a sniffer oftcpdump
, a sprinkle ofiptables
, and one battle-hardened log file.
The Real Villain
Let’s be honest, the villain here isn't ARP or Eve. It’s neglect. It’s deploying ad hoc networks without defenses, like building a treehouse with a welcome mat for termites. ARP was never meant to live in multi-hop chaos, and yet here we are, bolting on protocols like a toddler assembling IKEA.
Thankfully, ARP Loop Guard is a cheeky patch on this madness. It watches, waits, and when the weird starts happening, it raises its eyebrow (metaphorically) and shuts things down.
Disclaimer
ARP Loop Guard is a bash-powered superhero in many scenarios, but even capes have limitations. So, before you deploy it across your entire IT empire like it's an enchanted talisman, consider the following:
If you’re running a high-speed enterprise network with VLANs, DHCP snooping, and the kind of centralized control that would make a medieval guildmaster weep with joy, then this script will do precisely nothing... except feel quietly judged by your network switches. In IPv6-only realms? Forget it. ARP isn’t invited to that party, and your script will be standing awkwardly in the corner clutching /proc/net/arp
like a broken map.
If you're on a cloud VM? Congratulations, you've abstracted away the entire network layer. This script will stare blankly at an empty ARP cache, whispering “where did everybody go?” NAT environments are another rollercoaster: block one IP and potentially ghost a whole condo building’s worth of devices. And in dynamic IP setups, you might end up blocking your own printer because it's just trying to exist with a fresh lease and an innocent heart.
Long story short: ARP Loop Guard thrives in scrappy, decentralized environments, where devices act like free-range chickens with IPs and MACs flapping freely. If your network is an over-engineered palace of control, this little script might just light a log, sip its own output, and wait for its turn on a Raspberry Pi mesh.
Final Words Before We Loop Again
If you care about your network, and you do, otherwise you wouldn’t be reading this, don’t let it get spoofed into infinite loops and power-sucking echo mazes. Install the script. Watch it work. Sip your coffee while rogue MACs get blocked and TTL abusers are banished.
Because while ARP may be flawed, Bash never forgets. And neither does ARP Loop Guard.