121 lines
4.0 KiB
Python
121 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""scapy-dhcp-listener.py
|
|
Listen for DHCP packets using scapy to learn when LAN
|
|
hosts request IP addresses from DHCP Servers.
|
|
Copyright (C) 2018 Jonathan Cutrer
|
|
License Dual MIT, 0BSD
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
from scapy.all import *
|
|
import time
|
|
|
|
__version__ = "0.0.3"
|
|
|
|
# Fixup function to extract dhcp_options by key
|
|
def get_option(dhcp_options, key):
|
|
|
|
must_decode = ['hostname', 'domain', 'vendor_class_id']
|
|
try:
|
|
for i in dhcp_options:
|
|
if i[0] == key:
|
|
# If DHCP Server Returned multiple name servers
|
|
# return all as comma seperated string.
|
|
if key == 'name_server' and len(i) > 2:
|
|
return ",".join(i[1:])
|
|
# domain and hostname are binary strings,
|
|
# decode to unicode string before returning
|
|
elif key in must_decode:
|
|
return i[1].decode()
|
|
else:
|
|
return i[1]
|
|
except:
|
|
pass
|
|
|
|
|
|
def handle_dhcp_packet(packet):
|
|
|
|
# Match DHCP discover
|
|
if DHCP in packet and packet[DHCP].options[0][1] == 1:
|
|
print('---')
|
|
print('New DHCP Discover')
|
|
#print(packet.summary())
|
|
#print(ls(packet))
|
|
hostname = get_option(packet[DHCP].options, 'hostname')
|
|
print(f"Host {hostname} ({packet[Ether].src}) asked for an IP")
|
|
|
|
|
|
# Match DHCP offer
|
|
elif DHCP in packet and packet[DHCP].options[0][1] == 2:
|
|
print('---')
|
|
print('New DHCP Offer')
|
|
#print(packet.summary())
|
|
#print(ls(packet))
|
|
|
|
subnet_mask = get_option(packet[DHCP].options, 'subnet_mask')
|
|
lease_time = get_option(packet[DHCP].options, 'lease_time')
|
|
router = get_option(packet[DHCP].options, 'router')
|
|
name_server = get_option(packet[DHCP].options, 'name_server')
|
|
domain = get_option(packet[DHCP].options, 'domain')
|
|
|
|
print(f"DHCP Server {packet[IP].src} ({packet[Ether].src}) "
|
|
f"offered {packet[BOOTP].yiaddr}")
|
|
|
|
print(f"DHCP Options: subnet_mask: {subnet_mask}, lease_time: "
|
|
f"{lease_time}, router: {router}, name_server: {name_server}, "
|
|
f"domain: {domain}")
|
|
|
|
|
|
# Match DHCP request
|
|
elif DHCP in packet and packet[DHCP].options[0][1] == 3:
|
|
print('---')
|
|
print('New DHCP Request')
|
|
#print(packet.summary())
|
|
#print(ls(packet))
|
|
|
|
requested_addr = get_option(packet[DHCP].options, 'requested_addr')
|
|
hostname = get_option(packet[DHCP].options, 'hostname')
|
|
print(f"Host {hostname} ({packet[Ether].src}) requested {requested_addr}")
|
|
|
|
|
|
# Match DHCP ack
|
|
elif DHCP in packet and packet[DHCP].options[0][1] == 5:
|
|
print('---')
|
|
print('New DHCP Ack')
|
|
#print(packet.summary())
|
|
#print(ls(packet))
|
|
|
|
subnet_mask = get_option(packet[DHCP].options, 'subnet_mask')
|
|
lease_time = get_option(packet[DHCP].options, 'lease_time')
|
|
router = get_option(packet[DHCP].options, 'router')
|
|
name_server = get_option(packet[DHCP].options, 'name_server')
|
|
|
|
print(f"DHCP Server {packet[IP].src} ({packet[Ether].src}) "
|
|
f"acked {packet[BOOTP].yiaddr}")
|
|
|
|
print(f"DHCP Options: subnet_mask: {subnet_mask}, lease_time: "
|
|
f"{lease_time}, router: {router}, name_server: {name_server}")
|
|
|
|
# Match DHCP inform
|
|
elif DHCP in packet and packet[DHCP].options[0][1] == 8:
|
|
print('---')
|
|
print('New DHCP Inform')
|
|
#print(packet.summary())
|
|
#print(ls(packet))
|
|
|
|
hostname = get_option(packet[DHCP].options, 'hostname')
|
|
vendor_class_id = get_option(packet[DHCP].options, 'vendor_class_id')
|
|
|
|
print(f"DHCP Inform from {packet[IP].src} ({packet[Ether].src}) "
|
|
f"hostname: {hostname}, vendor_class_id: {vendor_class_id}")
|
|
|
|
else:
|
|
print('---')
|
|
print('Some Other DHCP Packet')
|
|
print(packet.summary())
|
|
print(ls(packet))
|
|
|
|
return
|
|
|
|
if __name__ == "__main__":
|
|
sniff(filter="udp and (port 67 or 68)", prn=handle_dhcp_packet) |