ARP
topic: Networking
tags: #networking
ARP = Address Resolution Protocol
We can send gratuitous ARP entry using scapy, but the other host won't accept it if the IP is not inside the ARP entry, unless we set this kernel option: sysctl -w net.ipv4.conf.all.arp_accept=1
There's a way to make sure the entry exists. We can make a ping to the target first using the IP that we want.
# -I <virtual IP> <target host>
ping -I 30.0.0.10 30.0.0.2
This will write the entry to the ARP table of 30.0.0.2, then we can send unsolicited ARP response to the 30.0.0.2 to update it to something else entirely.
#!/usr/bin/env python3
# gratuitous_arp.py
# Usage (run as root):
# sudo python3 gratuitous_arp.py --iface eth0 --src-ip 192.168.1.10 --src-mac 00:11:22:33:44:55 --count 3 --interval 0.5
#
# Sends gratuitous ARP replies (broadcast) announcing that <src-ip> is at <src-mac>.
import argparse
import time
import re
import sys
from scapy.all import Ether, ARP, sendp, conf
def is_mac(s):
return re.fullmatch(r"([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}", s) is not None
def main():
p = argparse.ArgumentParser(description="Send gratuitous ARP broadcasts with Scapy")
p.add_argument(
"--iface", "-I", required=True, help="Interface to send on (e.g. eth0)"
)
p.add_argument(
"--src-ip",
"-S",
required=True,
help="IP to claim (psrc and pdst for gratuitous ARP)",
)
p.add_argument(
"--src-mac", "-M", required=True, help="MAC to claim (hwsrc and Ethernet src)"
)
p.add_argument(
"--count", "-c", type=int, default=3, help="Number of gratuitous ARPs to send"
)
p.add_argument(
"--interval", "-i", type=float, default=0.5, help="Seconds between packets"
)
p.add_argument(
"--dry-run", action="store_true", help="Show packet and exit without sending"
)
args = p.parse_args()
if not is_mac(args.src_mac):
print("ERROR: invalid MAC format", file=sys.stderr)
sys.exit(1)
conf.iface = args.iface
eth = Ether(dst="ff:ff:ff:ff:ff:ff", src=args.src_mac)
# ARP op=2 (is-at / reply). For gratuitous ARP we set pdst == psrc and hwdst = ff:ff:ff...
arp = ARP(
op=2,
psrc=args.src_ip,
hwsrc=args.src_mac,
pdst=args.src_ip,
hwdst="ff:ff:ff:ff:ff:ff",
)
pkt = eth / arp
print(f"Interface: {args.iface}")
print(f"Announcing: {args.src_ip} is at {args.src_mac}")
print(f"Packets: {args.count}, interval: {args.interval}s")
print("Packet summary:")
pkt.show()
if args.dry_run:
print("Dry run: not sending.")
return
try:
for i in range(args.count):
sendp(pkt, iface=args.iface, verbose=False)
time.sleep(args.interval)
print("Sent.")
except PermissionError:
print("Permission denied: run as root.", file=sys.stderr)
sys.exit(2)
except Exception as e:
print("Error sending packets:", e, file=sys.stderr)
sys.exit(3)
if __name__ == "__main__":
main()