Building a Python Detector for Rogue DHCP Servers
A rogue DHCP server can quietly redirect devices to the wrong gateway, DNS resolver, or network segment. It may be caused by a misconfigured access point, an unauthorised home router, malware, or someone connecting inexpensive equipment to an office switch. The result can range from intermittent connectivity to traffic interception and loss of access to internal services.
Python is well suited to a lightweight DHCP monitoring utility. With Scapy, a script can send a DHCP discovery broadcast, collect offers, compare them with an approved server list, and report suspicious responses. This approach is useful for Linux administrators, developers maintaining small networks, and teams that want a practical diagnostic tool rather than a large monitoring platform.
Why Rogue DHCP Servers Matter
The Dynamic Host Configuration Protocol normally assigns an IP address, subnet mask, default gateway, DNS servers, and lease duration. A client generally accepts the first suitable offer, so an unauthorised server can influence network traffic before an administrator notices anything unusual.
A false gateway can create a man-in-the-middle position, while an altered DNS server can redirect websites or internal hostnames. In a Brisbane office, a Melbourne co-working space, or a home network using an NBN connection, a spare wireless router connected in bridge or access point mode can accidentally provide DHCP services to every device on the same broadcast domain.
Detection is especially important on flat networks where staff, printers, cameras, and development systems share a VLAN. A small Python utility can expose unexpected DHCP offers during troubleshooting or run periodically as part of a Linux monitoring job.
How DHCP Offer Detection Works
A client begins with a DHCPDISCOVER broadcast. Available servers respond with DHCPOFFER packets containing an offered address and options such as the server identifier and router address. The detector listens for those offers and records the Ethernet source address, offered IP, DHCP server identifier, and gateway.
The approved server list should be defined per VLAN or subnet. A single trusted address may be enough for a home lab, but a business network could have separate DHCP services for Sydney, Perth, and remote sites. DHCP relay agents also need consideration: the packet’s server identifier may represent the central server, while the Ethernet source belongs to a legitimate relay.
The script should therefore identify anomalies rather than immediately label every unfamiliar MAC address as hostile. A new offer is evidence for investigation, not proof of an attack. Wireless isolation, VLAN boundaries, and managed-switch configuration can change what the detector is able to observe.
Preparing The Python Environment
Install Python 3 and Scapy on a Linux host connected to the network segment under test:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install scapy
Packet capture and raw Ethernet transmission normally require root privileges or suitable Linux capabilities. Run the utility with sudo during testing, and select the correct interface with ip link. On an Australian home setup, this might be eth0 on a small server, while a laptop could use a name such as enp3s0 or a wireless interface.
Testing should be performed on a network you own or administer. Capturing DHCP traffic generally exposes configuration metadata rather than application content, but network monitoring can still have privacy implications under the Privacy Act 1988. Document the purpose, restrict log access, and avoid collecting more traffic than the diagnostic task requires.
Writing The Detector
The following example sends a DHCP discovery, waits for offers, and compares the results with approved server identifiers. Replace the sample values with the addresses used on the local VLAN.
#!/usr/bin/env python3
import time
from scapy.all import (
AsyncSniffer, BOOTP, DHCP, Ether, IP, UDP,
get_if_hwaddr, sendp
)
INTERFACE = "eth0"
TRUSTED_SERVERS = {"192.168.1.1"}
TRUSTED_MACS = {"aa:bb:cc:dd:ee:ff"}
def option_map(packet):
return {
key: value
for key, value in packet[DHCP].options
if isinstance(key, str)
}
def inspect_offer(packet):
if not (packet.haslayer(DHCP) and packet.haslayer(BOOTP)):
return
options = option_map(packet)
message_type = options.get("message-type")
if message_type not in (2, "offer", b"offer"):
return
server_id = options.get("server_id")
offered_ip = packet[BOOTP].yiaddr
source_mac = packet[Ether].src.lower()
server_text = str(server_id)
trusted = (
server_text in TRUSTED_SERVERS
or source_mac in TRUSTED_MACS
)
status = "OK" if trusted else "SUSPICIOUS"
print(f"[{status}] server={server_text} "
f"offered_ip={offered_ip} mac={source_mac}")
sniffer = AsyncSniffer(
iface=INTERFACE,
filter="udp and (port 67 or port 68)"
)
sniffer.start()
time.sleep(1)
client_mac = get_if_hwaddr(INTERFACE)
discover = (
Ether(dst="ff:ff:ff:ff:ff:ff") /
IP(src="0.0.0.0", dst="255.255.255.255") /
UDP(sport=68, dport=67) /
BOOTP(op=1, chaddr=bytes.fromhex(client_mac.replace(":", ""))) /
DHCP(options=[("message-type", "discover"), "end"])
)
sendp(discover, iface=INTERFACE, verbose=False)
time.sleep(5)
sniffer.stop()
for packet in sniffer.results:
inspect_offer(packet)
The TRUSTED_SERVERS and TRUSTED_MACS sets provide two comparison points. Some environments do not include a reliable server identifier, and some network designs use relays, so recording both values makes troubleshooting easier. The packet filter limits capture to DHCP-related UDP traffic rather than storing unrelated packets.
Reading Results And Investigating
A normal run may show one approved offer, although redundant DHCP servers can produce two legitimate responses. A suspicious result deserves a check of the gateway, DNS options, lease duration, and vendor information. The detector can be extended to print the router, name_server, and domain options for that purpose.
If an unknown offer appears, inspect managed-switch MAC address tables to locate the physical port. Disconnect recently installed routers, travel access points, smart appliances, or Internet Connection Sharing hosts one at a time. On a home network, check whether a second router has its DHCP service enabled; on a workplace network, confirm that the device is not an approved wireless controller or relay.
Run the script several times because a rogue server may be intermittent. DHCP renewals, sleep-and-wake cycles, and new client connections can produce different observations. Save timestamps and interface names in logs, but avoid recording full packet captures unless they are necessary for incident analysis.
Making The Script More Reliable
A production version should accept command-line options for the interface, timeout, trusted addresses, and output format. JSON output makes it easier to feed findings into cron, systemd timers, or an existing Linux monitoring service. Exit status can also indicate whether an unapproved server was found, allowing a Nagios-style check to raise an alert.
The detector only sees broadcasts that reach its interface. It will not automatically inspect every VLAN, and Wi-Fi client isolation may prevent one wireless station from seeing another. Deploy a sensor in each relevant broadcast domain, or use switch telemetry and central DHCP logs for broader coverage.
IPv6 uses DHCPv6 and Router Advertisements, which this IPv4-focused example does not inspect. A mature network audit should include those protocols, particularly where dual-stack devices are common. Python’s standard logging module, rotating files, and a small allow-list configuration can turn the prototype into a dependable administration tool.
Practical Deployment Recommendations
Use the script as one layer in a wider network control process. Preventive configuration reduces the number of unexpected offers, while periodic discovery tests provide evidence when settings drift. Australian organisations should align monitoring with internal security policies and, where relevant, the Australian Cyber Security Centre’s Essential Eight guidance.
- Run discovery checks from a trusted Linux host on each important VLAN.
- Record approved DHCP server IP addresses, relay addresses, and hardware MAC addresses.
- Disable DHCP on spare routers and access points before connecting them to production networks.
- Use managed-switch DHCP snooping and trusted uplink settings where the hardware supports them.
- Keep timestamps, interface names, and alert results for incident review without retaining unnecessary packet data.
A detector is most valuable when its output is connected to a clear response path. Once an unauthorised server is located, remove or isolate it, verify client gateway and DNS settings, renew affected leases, and review whether any sensitive traffic could have crossed the device.
