#!/usr/bin/env python

# AutoIP/IPv4LL Address Hoarder 

# Copyright (C) 2010  Kelvin Lawson (kelvinl@users.sf.net)

# INSTRUCTIONS
#
# Assists in the compliance-testing of the address conflict detection
# in implementations of RFC3927, IPv4 Link-Local Addresses (also
# known as AutoIP).
#
# Deliberately exercises AutoIP address conflict resolution in devices
# on the local link by hoarding any address claimed by those other
# devices. While running it continually claims and hoards any AutoIP
# addresses claimed and announced via ARP messages on the link, making
# other devices continually reselect new random addresses without
# success.
#
# Address conflicts are caused on other devices through ARPs being
# generated from this script using the target device's AutoIP address
# as source (in other words it signals that someone else is using the
# address). These ARP packets are ARP Requests using the target's
# AutoIP address as source, and 169.254.1.1 as destination. That
# target IP address can be changed if necessary using the --target
# option.
#  ./autoip_hoard.py --target 169.254.1.2
#
# Default behaviour is to cause conflicts for all devices using AutoIP
# on the link. Can also be directed to only target a single device
# by passing its MAC address to the --mac option, for example:
#  ./autoip_hoard.py --mac 00:01:02:03:04:05
# 
# The script sends conflicting ARP messages whenever it sees an ARP
# packet on the link. Each single ARP packet received causes the
# generation of a single conflicting ARP request using the original
# sender's address as source. Once a target goes into address
# reselection, the ensuing claim announcements result in sufficient
# ARP packets being generated to keep the script fed and generating 
# many conflict packets. At this point the script perpetually 
# conflicts with all of the addresses which the target device selects.
# The target device may not initially be generating ARPs so if you wish
# to bring this process forward, you can use the --nudge option
# to target a specific IP address at startup. The script will then
# generate a pair of conflicting ARP requests using that IP address as
# source which should prompt the target device into address defense and
# eventually address reselection:
#  ./autoip_hoard.py --nudge 169.254.232.30
#
# Note that initial probes sent by a device when it is first testing
# whether an address is in use are not conflicted. Those ARP requests
# contain a source address of 0.0.0.0 while the device is not yet
# configured. We wait until the probe completes, and the probed
# source address is then used in claim announcements, before we start
# conflicting.


from scapy.all import *
from optparse import OptionParser


# We claim other devices' AutoIP addresses by sending ARPs pretending to be
# them. The ARP request target does not really matter, but we default to
# 169.254.1.1 and allow this to be changed via --target option.
default_arp_target = "169.254.1.1"

# Parser options
options = None

# ARP Callback function. Called whenever ARPs are received.
def arp_callback(pkt):
	global options

	# Look for ARP packets in particular
	if (ARP in pkt) and (pkt[ARP].op in (1,2)):

		# Print out received ARP details (except our own)
		if (pkt[ARP].pdst != options.arp_target):
			print "ARP received from %s: %s - %s" % \
				(pkt[Ether].src, pkt[ARP].psrc, pkt[ARP].pdst)

		# Send a new ARP request using the same source IP address as the
		# ARP sender seen on the wire. This is done for any ARP packet
		# which contains an AutoIP address as source, unless a target 
		# device was selected via --target, in which case only ARPs from
		# that target's MAC address will generate responses.
		# We also do not respond to our own generated packets if they are
		# received back, by checking the destination IP address in the ARP
		# is not our fake target (default 169.254.1.1).
		if (pkt[ARP].psrc[0:7] == "169.254" \
			and (pkt[ARP].pdst != options.arp_target) \
			and (options.target_mac == None) \
			     or (pkt[Ether].src == options.target_mac)):
			# Send two ARPs to hasten the defense and reselection
			for i in range(2):
				sendp(Ether(dst="ff:ff:ff:ff:ff:ff") \
				      /ARP(pdst=options.arp_target, psrc=pkt[ARP].psrc))
			return pkt.sprintf ("autoip_hoard: Claiming address %ARP.psrc%")


# Main function
def main():
	global options

	# Check for command-line options
	parser = OptionParser()
	parser.add_option ("--mac", dest="target_mac", \
						help="Target particular device (by MAC address). \
							Defaults to targeting all devices.")
	parser.add_option ("--nudge", dest="start_ip", \
						help="Kickstart by claiming a particular IP address. \
						Defaults to waiting until ARP packets are received.")
	parser.add_option ("--target", dest="arp_target", \
						help="All generated ARPs request this IP address. \
						Defaults to %s." % default_arp_target, \
						default=default_arp_target)
	(options, args) = parser.parse_args()

	# If initial nudge requested, start targeting a device by
	# claiming to be using the same IP address.
	if options.start_ip != None:
		print "autoip_hoard: Sending initial nudges using ARP request " \
			 + options.arp_target + " from " + options.start_ip
		for i in range(2):
			sendp (Ether(dst="ff:ff:ff:ff:ff:ff")\
				/ARP(pdst=options.arp_target, psrc=options.start_ip))

	# Now start the sniffer, all action happens in the sniffer callback
	print "autoip_hoard: Waiting for ARP packets"
	sniff(prn=arp_callback, filter="arp", store=0)


if __name__ == "__main__":
	sys.exit(main())
