#!/bin/bash # # pxe-dhcp-hook.sh - dnsmasq dhcp-script hook. # # Runs every time a PXE client gets/changes/releases a DHCP lease on # 172.16.9.0/24. Flushes that client's SMB (TCP 445 and 139) conntrack # entries and drops its lingering SMB sockets. Prevents stale server-side # state from causing "System error 53 - network path not found" when a # WinPE client re-images the same machine without a clean SMB session # teardown. # # SMB only, on purpose: the lease that fires this hook is iPXE's, and its # first HTTP fetch of the boot menu is racing this script. Killing every # socket or conntrack entry for the client IP broke that fetch. # # dnsmasq calls this with argv: # $1 = action (add | old | del) # $2 = mac address # $3 = client IP # $4 = hostname (may be empty) # # A re-image usually comes back to the SAME lease (same MAC, same IP), which # dnsmasq reports as "old", not "add" - and only at all because dnsmasq.conf # sets script-on-renewal. "old" also fires when a running Windows PC renews # its lease, so on "old" we only clear state when the request came from PXE # firmware or iPXE (vendor class PXEClient...): that machine has just # rebooted into a network boot, so any server-side session for its IP is # dead. A renewal from Windows (vendor class "MSFT 5.0") is left alone. # # Must exit quickly; dnsmasq waits for it. Logs to syslog via logger. set +e # dnsmasq's environment may lack sbin; keep any PATH it gave us first. PATH="${PATH:+$PATH:}/usr/sbin:/usr/bin:/sbin:/bin" action="$1" mac="$2" ip="$3" host="${4:-}" [ -z "$ip" ] && exit 0 clear_state() { local port for port in 445 139; do # client -> our share, and anything the other way round conntrack -D -p tcp -s "$ip" --dport "$port" >/dev/null 2>&1 conntrack -D -p tcp -d "$ip" --sport "$port" >/dev/null 2>&1 done # our SMB sockets (local port 445/139) whose peer is this client ss -K -t "( sport = :445 or sport = :139 ) and dst = $ip" >/dev/null 2>&1 logger -t pxe-dhcp-hook "cleared stale SMB state for $1 $ip ($mac${host:+ $host})" } case "$action" in add|del) clear_state "$action" ;; old) case "${DNSMASQ_VENDOR_CLASS:-}" in PXEClient*) clear_state "old (network boot, ${DNSMASQ_VENDOR_CLASS%%:UNDI*})" ;; esac ;; esac exit 0