#!/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/.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