From d3a0f90ca1a16c3cbc5c19e1adb4e19a23228230 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Wed, 14 Oct 2026 17:19:33 -0400 Subject: [PATCH] Re-imaging the same PC: clear its stale SMB/conntrack state every time A PC whose imaging failed and is re-imaged gets the same IP, and the server still held state from the dead run, so net use failed (System error 53). The April fix (18537ac) did not cover it: - The dnsmasq hook only acted on 'add'/'del'. A re-image comes back to the existing lease, which dnsmasq reports as 'old', and only when the hostname/MAC changed. Now dnsmasq sets script-on-renewal, and the hook clears state on 'old' when the request comes from PXE firmware or iPXE (vendor class PXEClient), i.e. the machine just network-booted. A Windows lease renewal (MSFT 5.0) is left alone. - The 1 h conntrack timeout never applied after a reboot: systemd-sysctl runs before ufw loads nf_conntrack, so the keys did not exist and the 5-day default stayed. /etc/modules-load.d/pxe-conntrack.conf loads it first. - smb-soft-reset.sh flushed neighbours on br-pxe, which no longer exists. Live 2026-10-14. Hook tested on the server with an unused IP: network-boot renewal clears, Windows renewal and no vendor class do not, add clears. --- playbook/pxe-server-helpers/pxe-dhcp-hook.sh | 27 +++++++++++++++---- playbook/pxe-server-helpers/smb-soft-reset.sh | 6 ++--- playbook/pxe_server_setup.yml | 14 ++++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/playbook/pxe-server-helpers/pxe-dhcp-hook.sh b/playbook/pxe-server-helpers/pxe-dhcp-hook.sh index df454db..6eb72da 100755 --- a/playbook/pxe-server-helpers/pxe-dhcp-hook.sh +++ b/playbook/pxe-server-helpers/pxe-dhcp-hook.sh @@ -14,6 +14,14 @@ # $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 @@ -25,13 +33,22 @@ host="${4:-}" [ -z "$ip" ] && exit 0 +clear_state() { + /usr/sbin/conntrack -D -s "$ip" >/dev/null 2>&1 + /usr/sbin/conntrack -D -d "$ip" >/dev/null 2>&1 + /usr/bin/ss -K "dst = $ip" >/dev/null 2>&1 + /usr/bin/ss -K "src = $ip" >/dev/null 2>&1 + logger -t pxe-dhcp-hook "cleared stale state for $1 $ip ($mac${host:+ $host})" +} + case "$action" in add|del) - /usr/sbin/conntrack -D -s "$ip" >/dev/null 2>&1 - /usr/sbin/conntrack -D -d "$ip" >/dev/null 2>&1 - /usr/bin/ss -K "dst = $ip" >/dev/null 2>&1 - /usr/bin/ss -K "src = $ip" >/dev/null 2>&1 - logger -t pxe-dhcp-hook "cleared stale state for $action $ip ($mac${host:+ $host})" + clear_state "$action" + ;; + old) + case "${DNSMASQ_VENDOR_CLASS:-}" in + PXEClient*) clear_state "old (network boot, ${DNSMASQ_VENDOR_CLASS%%:UNDI*})" ;; + esac ;; esac diff --git a/playbook/pxe-server-helpers/smb-soft-reset.sh b/playbook/pxe-server-helpers/smb-soft-reset.sh index 388956e..e2f409d 100755 --- a/playbook/pxe-server-helpers/smb-soft-reset.sh +++ b/playbook/pxe-server-helpers/smb-soft-reset.sh @@ -15,7 +15,7 @@ # Step 3: restart smbd (full smbd restart, kills all child sessions) # Step 4: kill any leftover smbd child processes that survived restart # Step 5: flush conntrack for 172.16.9.0/24 (kernel connection tracking) -# Step 6: flush ARP / neighbour cache on br-pxe +# Step 6: flush ARP / neighbour cache # Step 7: drop TCP sockets on port 445 via ss -K # Step 8: restart dnsmasq (DHCP/TFTP state as a last resort before reboot) @@ -65,8 +65,8 @@ else fi pause "Step 5 done" -echo "=== Step 6/8: flush ARP / neighbour cache on br-pxe ===" -ip neigh flush dev br-pxe 2>&1 || ip neigh flush all 2>&1 || true +echo "=== Step 6/8: flush ARP / neighbour cache ===" +ip neigh flush all 2>&1 || true pause "Step 6 done" echo "=== Step 7/8: drop tcp sockets on port 445 ===" diff --git a/playbook/pxe_server_setup.yml b/playbook/pxe_server_setup.yml index c36307e..b8a879e 100644 --- a/playbook/pxe_server_setup.yml +++ b/playbook/pxe_server_setup.yml @@ -205,6 +205,11 @@ # client re-images the same machine and hits a stale SMB session. # Script runs as root by default (dnsmasq --dhcp-scriptuser default). dhcp-script=/usr/local/sbin/pxe-dhcp-hook.sh + # Also call it when a client comes back to its existing lease (a + # re-image of the same PC), which dnsmasq otherwise reports only if + # the hostname or MAC changed. The hook acts on those only for PXE + # firmware / iPXE requests. + script-on-renewal - name: "Create TFTP directory" file: @@ -628,6 +633,15 @@ mode: '0644' notify: reload sysctl + - name: "Load nf_conntrack at boot so the conntrack sysctl applies" + # systemd-sysctl runs before ufw loads nf_conntrack, so without this the + # net.netfilter.* keys do not exist yet at boot and the 1 h timeout is + # silently skipped (the kernel default of 5 days stays in force). + copy: + dest: /etc/modules-load.d/pxe-conntrack.conf + content: "nf_conntrack\n" + mode: '0644' + - name: "Deploy SMB diagnostic + soft-reset helper scripts" copy: src: "{{ usb_mount }}/pxe-server-helpers/{{ item }}"