- pxe-nic-check: count 802.1X only from an authenticator (EAPOL type 0 with EAP Request/Success/Failure). iPXE sends EAPOL-Start on every NIC open and Windows sends Start/Response, so a PXE-booting bay made the verdict 'ge' and the watch stopped dnsmasq for the floor. - pxe-nic-check: join the STP/PAE/LLDP/CDP/PVST+ multicast groups on every capture socket, so CDP-only ports are heard on a plain NIC. - pxe-nic-check --watch: if dnsmasq was (re)started inside the window, re-check (~6 s) before stopping it; skip when already stopped. New --state-cmd and --print-imaging. - pxe-nic-carrier (+ pxe-nic-carrier.service): networkd-dispatcher hook for the imaging NIC only. Carrier loss stops dnsmasq; carrier gain restarts it through its guard if it was running or the hook stopped it. - pxe-dhcp-hook.sh: clear only SMB (tcp 445/139) conntrack entries and sockets, not all traffic for the client, which raced iPXE's first HTTP fetch. - GetPxeScript.aspx: every boot line has a failure branch (iPXE 2.0 accepts an unsigned EFI kernel and fails at boot under Secure Boot, which aborted the script). - Tests: client EAPOL, packet_mreq packing, watch race, shell helpers on stub PATH, lab scenarios eapol-client, br-eapol-client, race, carrier.
146 lines
5.4 KiB
Bash
Executable File
146 lines
5.4 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# pxe-nic-carrier - re-check the imaging NIC whenever its cable moves.
|
|
#
|
|
# The dnsmasq guard (pxe-nic-check --guard) runs only when dnsmasq starts,
|
|
# and the watch timer only every 5 min. If the imaging cable is moved into
|
|
# a GE port while dnsmasq runs, dnsmasq would answer DHCP on the GE LAN
|
|
# until the next watch. This hook closes that gap:
|
|
#
|
|
# carrier lost dnsmasq is stopped at once (if it was running) and a
|
|
# marker remembers that this hook stopped it.
|
|
# carrier back pxe-nic-carrier.service runs "pxe-nic-carrier --gain":
|
|
# after a 2 s settle, if dnsmasq is running or the marker
|
|
# is set, "systemctl restart dnsmasq" - its ExecStartPre
|
|
# guard checks the NIC while dnsmasq is down and refuses
|
|
# to start it on the GE network. Otherwise it only runs
|
|
# the guard to refresh /run/pxe-nic-status.json. dnsmasq
|
|
# that an operator or the watch stopped stays stopped.
|
|
#
|
|
# Only the imaging NIC (and, for a bridge, its ports) counts, as resolved
|
|
# by "pxe-nic-check --print-imaging" from /etc/pxe-server/nic.conf or the
|
|
# dnsmasq interface= pin. Events on any other NIC (the GE NIC) exit at
|
|
# once and touch nothing. Link state is read from sysfs, not from the
|
|
# event name, and remembered per interface, so the same transition seen
|
|
# twice (carrier, then routable) acts once.
|
|
#
|
|
# Called by networkd-dispatcher with IFACE and STATE in the environment
|
|
# (installed as /etc/networkd-dispatcher/<state>.d/50-pxe-nic-carrier for
|
|
# carrier, degraded, degraded-carrier, enslaved, routable, no-carrier and
|
|
# off), or by hand: pxe-nic-carrier IFACE. Logs: journalctl -t pxe-nic-carrier
|
|
#
|
|
# The dispatcher waits for this script, so the event path never runs a
|
|
# check itself: loss is two systemctl calls, gain hands off to the service.
|
|
|
|
set -u
|
|
|
|
CHECK="${PXE_NIC_CHECK:-/usr/local/sbin/pxe-nic-check}"
|
|
SYS_NET="${PXE_SYS_NET:-/sys/class/net}"
|
|
RUN="${PXE_NIC_CARRIER_RUN:-/run/pxe-nic-carrier}"
|
|
SYSTEMCTL="${PXE_SYSTEMCTL:-systemctl}"
|
|
SETTLE="${PXE_NIC_CARRIER_SETTLE:-2}"
|
|
GAIN_UNIT="pxe-nic-carrier.service"
|
|
TAG="pxe-nic-carrier"
|
|
|
|
PATH="${PATH:+$PATH:}/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
|
|
log() { # log PRIORITY MESSAGE
|
|
logger -t "$TAG" -p "daemon.$1" -- "$2" 2>/dev/null || \
|
|
echo "$TAG: $2" >&2
|
|
}
|
|
|
|
imaging_ifaces() {
|
|
"$CHECK" --print-imaging 2>/dev/null
|
|
}
|
|
|
|
carrier_of() { # 1 if the interface has carrier, else 0 (down reads EINVAL)
|
|
local c
|
|
c=$(cat "$SYS_NET/$1/carrier" 2>/dev/null) || c=0
|
|
[ "$c" = 1 ] && echo 1 || echo 0
|
|
}
|
|
|
|
any_carrier() { # any imaging interface or port with carrier
|
|
local i
|
|
for i in "$@"; do
|
|
[ "$(carrier_of "$i")" = 1 ] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
dnsmasq_running() { # active, starting or reloading
|
|
case "$("$SYSTEMCTL" is-active dnsmasq 2>/dev/null)" in
|
|
active|activating|reloading) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
on_event() {
|
|
local iface="$1" list=() match="" i now prev
|
|
mapfile -t list < <(imaging_ifaces)
|
|
for i in "${list[@]}"; do
|
|
[ -n "$i" ] && [ "$i" = "$iface" ] && match=1
|
|
done
|
|
[ -n "$match" ] || exit 0 # not the imaging NIC: hands off
|
|
|
|
mkdir -p "$RUN" || exit 0
|
|
exec 9>"$RUN/event.lock"
|
|
flock -w 10 9 || log warning "lock busy, handling $iface anyway"
|
|
|
|
now=$(carrier_of "$iface")
|
|
prev=$(cat "$RUN/link.$iface" 2>/dev/null) || prev=unknown
|
|
echo "$now" > "$RUN/link.$iface"
|
|
[ "$now" = "$prev" ] && exit 0 # same state again: already handled
|
|
|
|
if [ "$now" = 0 ]; then
|
|
# an in-flight gain check must not restart dnsmasq after this
|
|
"$SYSTEMCTL" stop "$GAIN_UNIT" >/dev/null 2>&1
|
|
if dnsmasq_running; then
|
|
: > "$RUN/stopped-dnsmasq"
|
|
"$SYSTEMCTL" stop --no-block dnsmasq
|
|
log warning "carrier lost on imaging NIC $iface (${STATE:-event}): stopped dnsmasq; it restarts after the guard passes when the link returns"
|
|
else
|
|
log info "carrier lost on imaging NIC $iface (${STATE:-event}); dnsmasq not running"
|
|
fi
|
|
else
|
|
log notice "carrier up on imaging NIC $iface (${STATE:-event}, was $prev): re-checking before dnsmasq serves"
|
|
"$SYSTEMCTL" restart --no-block "$GAIN_UNIT"
|
|
fi
|
|
exit 0
|
|
}
|
|
|
|
on_gain() {
|
|
local list=() rc
|
|
sleep "$SETTLE"
|
|
mapfile -t list < <(imaging_ifaces)
|
|
if [ "${#list[@]}" -eq 0 ] || [ -z "${list[0]}" ]; then
|
|
log err "imaging NIC unknown, nothing to re-check"
|
|
exit 0
|
|
fi
|
|
if ! any_carrier "${list[@]}"; then
|
|
log info "imaging NIC ${list[0]} lost carrier again, nothing to do"
|
|
exit 0
|
|
fi
|
|
if dnsmasq_running || [ -e "$RUN/stopped-dnsmasq" ]; then
|
|
# restart = stop, then the ExecStartPre guard with dnsmasq down
|
|
"$SYSTEMCTL" restart dnsmasq
|
|
rc=$?
|
|
rm -f "$RUN/stopped-dnsmasq"
|
|
if [ "$rc" -eq 0 ]; then
|
|
log notice "imaging NIC ${list[0]}: guard passed, dnsmasq (re)started"
|
|
else
|
|
log crit "imaging NIC ${list[0]}: dnsmasq NOT started (guard refused or start failed, rc $rc); see journalctl -u dnsmasq. Fix the cabling, then systemctl start dnsmasq"
|
|
fi
|
|
else
|
|
"$CHECK" --guard >/dev/null 2>&1
|
|
rc=$?
|
|
log info "imaging NIC ${list[0]}: dnsmasq was not running, left stopped (guard rc $rc, status refreshed)"
|
|
fi
|
|
exit 0
|
|
}
|
|
|
|
case "${1:-}" in
|
|
--gain) on_gain ;;
|
|
-h|--help) sed -n '2,36p' "$0"; exit 0 ;;
|
|
*) on_event "${1:-${IFACE:-}}" ;;
|
|
esac
|