MADwaS stands for Monitor ARP and DNS to Wake a Server. It is a small Python service for home labs and small LAN environments where a server should stay powered off until someone actually needs it.
The script watches for ARP who-has requests and DNS queries for configured hosts. If
a sleeping machine is requested, MADwaS checks whether that machine is already
reachable. If it is still offline, MADwaS sends a Wake-on-LAN magic packet and can
optionally send a notification email.
MADwaS is aimed at setups such as:
- a NAS that should wake only when a client tries to access it
- a media server or VDR host (Linux Video Disk Recorder https://tvdr.de) that is not needed 24/7
- a home lab machine that should become available on demand
- a Raspberry Pi or other always-on Linux box acting as the wake-up controller
Configured hosts may use IPv4, IPv6, or both in a single monitoring entry.
MADwaS is designed for networks where one lightweight, always-on system can observe local traffic and wake a heavier system only when needed.
Typical deployment:
- A target host such as a NAS, media server, or lab server is asleep most of the time.
- A Raspberry Pi or another low-power Linux host runs MADwaS continuously.
- A client on the LAN tries to reach the sleeping host by name or address.
- MADwaS detects that request and decides whether the host needs to be woken.
This approach is useful when you want:
- on-demand availability without teaching every client how to send WoL packets
- lower power usage for machines that are only needed occasionally
- one central place to manage wake-up behavior and logging
- dual-stack support for hosts that use both IPv4 and IPv6
- if you run a local DNS Server (e.g. Pi-Hole) in your local network where you can
run MADwas (to make use of DNS name query monitoring)
- MADwaS loads
config.yamlor, if it does not exist yet, falls back toconfig_template.yaml. - It normalizes the configured host data and builds lookup tables for monitored IP addresses and DNS names.
- It starts packet sniffing with a narrow BPF filter:
arp[6:2] == 1 or udp dst port 53 - For each matching packet, MADwaS checks whether the request is relevant. It ignores:
- traffic generated by the host running MADwaS
- source IPs listed in
blocked_ip - requests coming from the monitored host itself
- For IPv4 targets, ARP requests and DNS queries can trigger a wake-up path. For IPv6 targets, DNS queries are the trigger and ICMPv6 is used for reachability checks.
- If one host entry contains both IPv4 and IPv6 addresses, MADwaS treats them as one logical host. If either configured address answers, the host is considered online.
- Valid requests are placed into a worker queue.
- The worker thread checks whether the host is already reachable:
- ICMP echo for IPv4
- ICMPv6 echo for IPv6
- If the host is offline, MADwaS sends the Wake-on-LAN magic packet, optionally sends a notification email, waits for the configured cooldown, and then accepts new wake requests for that host.
- Duplicate requests for the same host are suppressed while one wake-up attempt is still being processed.
The implementation in dns_wol.py is intentionally compact. The main components are:
-
WakeupRequestRepresents one queued wake-up job. It carries the triggering source, the target MAC address, the requested DNS name if present, and the normalized set of IP addresses that belong to the host. -
ConfigurationReads the YAML configuration, normalizes IP data, and builds fast lookup maps for packet matching. One configuration entry can describe a single host with multiple IP addresses, including mixed IPv4 and IPv6. -
__discover_local_ip_addresses()Collects the addresses assigned to the host running MADwaS. This prevents the script from reacting to its own follow-up traffic. -
__arp_check()and__dns_query_check()Parse and validate sniffed packets. These functions decide whether a packet maps to a monitored host and whether it should create a wake-up request. -
__add_object_to_thread_queue()Adds new wake-up work only if no other wake-up for the same host is already pending. -
__check_thread_queue()Runs as a dedicated worker thread. It processes queued requests one by one so that wake-up handling stays simple and predictable. -
wakeup_monitored_host()Performs the actual decision and action flow: reachability check, WoL send, notification mail, cooldown wait, and pending-state cleanup.
MADwaS is targeted at Linux-based always-on nodes in a local network, typically:
- a Raspberry Pi
- a small x86 mini PC
- a home server that stays online all the time
- a VM or container host with direct visibility into the relevant LAN traffic
You need:
- Linux with permission to sniff packets and send ICMP and WoL packets
- Python 3.10 or newer
- visibility of the ARP or DNS traffic that should trigger wake-ups
- network reachability to send Wake-on-LAN packets to the target segment
- optional local SMTP service on
localhostif notification emails are wanted
Network visibility notes:
- On a typical switched LAN, ARP requests are often the most reliable trigger for IPv4 hosts.
- DNS-based wake-up works best when MADwaS runs on the DNS server, near the DNS path, or on a segment where those queries are visible.
- IPv6 wake-up handling is DNS-driven. ARP is IPv4-only, so IPv6 hosts are not woken by neighbor discovery traffic.
- Wi-Fi client isolation, VLAN separation, or routed topologies can prevent the required broadcast or query traffic from being visible.
- MADwaS is not aimed at arbitrary internet-facing wake-up scenarios. It is intended for trusted local networks where packet visibility is expected.
Copy config_template.yaml to config.yaml and adjust it:
version: 1
blocked_ip:
- "192.168.101.2"
wait_time: 45
monitoring:
- dns_name: "server.example.local"
ip: "192.168.101.4"
ipv6: "2001:db8::50"
mac: "d4:3d:7e:4a:16:87"
- dns_name: "server-v6-only.example.local"
ip: "2001:db8::60"
mac: "d4:3d:7e:4a:16:87"
from_mail: "madwas@example.local"
to_mail: "admin@example.local"
enable_mail: trueImportant fields:
blocked_ip: source IPs that must never trigger a wake-upwait_time: cooldown after a successful wake-up attemptmonitoring: list of targets to watchdns_name: DNS query name that should trigger wake-upip: primary target IP address; can be IPv4 or IPv6ipv4: optional explicit IPv4 address for the same host if you prefer not to useipipv6: optional IPv6 address for the same hostips: optional list of additional IP addresses for the same hostmac: target MAC address used for the magic packetenable_mail: enables or disables notification mail
Example dual-stack host entry:
monitoring:
- dns_name: "nas.example.local"
ip: "192.168.101.4"
ipv6: "2001:db8::50"
mac: "d4:3d:7e:4a:16:87"Notes:
- Use one monitoring entry per host when that host has both IPv4 and IPv6 addresses.
- ARP matching only applies to the IPv4 address.
- DNS triggers work for both IPv4 and IPv6 hosts.
- Reachability checks try all configured addresses for the host and skip Wake-on-LAN if any one of them answers.
git clone https://github.com/od2017/MADwaS.git
cd MADwaS
python3 -m pip install -r requirements.txt
cp config_template.yaml config.yamlThen edit config.yaml and start the script with sufficient privileges:
sudo python3 dns_wol.pyThe script reads log_config.yaml for logging configuration and writes logs according
to that file. By default the rotating file handler first tries the system log path
/var/log/dns_wol.log. If that path is not writable for the current user, it falls
back to the project-local dns_wol.log next to dns_wol.py, regardless of the
current working directory. The script then listens indefinitely until interrupted.
To force debug logging without editing log_config.yaml, start the script with
--debug:
python3 dns_wol.py --debugThis overrides the configured logger and handler levels in memory for that run only.
The repository includes dns_wol.service as an example unit file. Adjust it for your
environment, then install and enable it:
sudo cp dns_wol.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable dns_wol.service
sudo systemctl start dns_wol.serviceRun the unit tests locally with:
python3 -m pytest -q tests/test_dns_wol.pyRun the tests with coverage using:
python3 -m pytest --cov=./ --cov-branch --cov-report=term-missing -q tests/test_dns_wol.pyThe test suite currently covers:
- ignoring locally generated ARP requests
- ignoring locally generated DNS requests
- enqueuing valid remote ARP requests
- enqueuing valid monitored DNS requests
- deduplicating pending wake-up jobs across IPv4 and IPv6 addresses of the same host
- collecting local IPv4 and IPv6 addresses correctly