From 9bac5a0cb8834882ec0085d14de1166bf8e2c3c0 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Wed, 11 Nov 2026 20:57:26 +0100 Subject: [PATCH] Say what the host sees when a container gets no address "No IP assigned after 60 attempts" was all the log held: 225 failures in a week, 7.5 % of all, with nothing to tell a missing DHCP server from a bridge without an uplink. The failure now logs the configured address and VLAN, the state of eth0, whether the bridge exists and has an uplink port, and for DHCP that no server answered. The MAC address is left out. --- .github/workflows/verify-split-files.yml | 5 ++ pve/backend.func | 33 ++++++++++++ tests/pve-no-ip-facts.sh | 68 ++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 tests/pve-no-ip-facts.sh diff --git a/.github/workflows/verify-split-files.yml b/.github/workflows/verify-split-files.yml index 85a3153..783ab57 100644 --- a/.github/workflows/verify-split-files.yml +++ b/.github/workflows/verify-split-files.yml @@ -30,6 +30,7 @@ on: - "tests/uv-required-version.sh" - "tests/docker-lxcfs.sh" - "tests/java-memory.sh" + - "tests/pve-no-ip-facts.sh" - "tests/sudo-env.sh" - "tests/deb822-local-keyring.sh" - "tests/gpu-pci-class.sh" @@ -91,6 +92,7 @@ on: - "tests/uv-required-version.sh" - "tests/docker-lxcfs.sh" - "tests/java-memory.sh" + - "tests/pve-no-ip-facts.sh" - "tests/sudo-env.sh" - "tests/deb822-local-keyring.sh" - "tests/gpu-pci-class.sh" @@ -176,6 +178,9 @@ jobs: - name: Java memory drop-in tests run: bash tests/java-memory.sh + - name: No-IP diagnosis tests + run: bash tests/pve-no-ip-facts.sh + - name: sudo environment tests run: bash tests/sudo-env.sh diff --git a/pve/backend.func b/pve/backend.func index da4bd73..45473aa 100644 --- a/pve/backend.func +++ b/pve/backend.func @@ -144,6 +144,38 @@ _pct_create() { return $rc } +# ------------------------------------------------------------------------------ +# _pve_no_ip_facts +# +# What the host can see about a container that got no address. It goes to the +# log, where "No IP assigned" used to be all a quarter of the network failures +# said. +# ------------------------------------------------------------------------------ +_pve_no_ip_facts() { + local ctid="$1" brg="$2" net0 ip4 tag link port uplinks=0 + net0="$(pct config "$ctid" 2>/dev/null | sed -n 's/^net0: //p')" + ip4="$(grep -oE '(^|,)ip=[^,]+' <<<"$net0" | cut -d= -f2 || true)" + tag="$(grep -oE '(^|,)tag=[^,]+' <<<"$net0" | cut -d= -f2 || true)" + link="$(pct exec "$ctid" -- ip -br link show dev eth0 2>/dev/null | awk '{print $2}' || true)" + msg_warn "Container network: ip=${ip4:-none}${tag:+, VLAN ${tag}}, eth0 ${link:-missing}" + + if [[ ! -d "/sys/class/net/${brg}" ]]; then + msg_warn "Bridge ${brg} does not exist on this host" + elif [[ -d "/sys/class/net/${brg}/brif" ]]; then + for port in "/sys/class/net/${brg}/brif"/*; do + [[ -e "$port" ]] || continue + case "${port##*/}" in + veth* | tap* | fwpr* | fwln*) ;; + *) uplinks=$((uplinks + 1)) ;; + esac + done + ((uplinks)) || msg_warn "Bridge ${brg} has no uplink port, so nothing outside this host can answer on it" + fi + if [[ "$ip4" == "dhcp" ]]; then + msg_warn "No DHCP server answered on ${brg}${tag:+ (VLAN ${tag})}" + fi +} + # ------------------------------------------------------------------------------ # _pct_start_failed [pct start output] # @@ -1128,6 +1160,7 @@ EOF if [ -z "$ip_in_lxc" ]; then msg_error "No IP assigned to CT $CTID after 60 attempts" + _pve_no_ip_facts "$CTID" "$BRG" msg_custom "🔧" "${YW}" "Troubleshooting:" echo " • Verify bridge ${BRG} exists and has connectivity" echo " • Check if DHCP server is reachable (if using DHCP)" diff --git a/tests/pve-no-ip-facts.sh b/tests/pve-no-ip-facts.sh new file mode 100644 index 0000000..0bdf5f8 --- /dev/null +++ b/tests/pve-no-ip-facts.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -euo pipefail + +CORE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +TEST_DIR="$(mktemp -d)" +trap 'rm -rf -- "$TEST_DIR"' EXIT +passed=0 failed=0 + +eval "$(awk '/^_pve_no_ip_facts[(][)] [{]/,/^}/' "$CORE_DIR/pve/backend.func" | + sed "s#/sys/class/net#$TEST_DIR/net#g")" + +check() { + if [[ "$3" == "$2" ]]; then + echo "PASS $1" + passed=$((passed + 1)) + else + echo "FAIL $1: expected <$2>, got <$3>" >&2 + failed=$((failed + 1)) + fi +} + +msg_warn() { echo "$1"; } +NET0="" LINK="UP" +pct() { + case "$1" in + config) echo "arch: amd64" && echo "net0: $NET0" ;; + exec) [[ -n "$LINK" ]] && echo "eth0@if12 $LINK aa:bb:cc:dd:ee:ff " ;; + esac +} +bridge() { + rm -rf "$TEST_DIR/net" + [[ "$1" == "none" ]] && return 0 + mkdir -p "$TEST_DIR/net/vmbr0" + [[ "$1" == "ovs" ]] && return 0 + mkdir -p "$TEST_DIR/net/vmbr0/brif" + local p + for p in "${@:2}"; do : >"$TEST_DIR/net/vmbr0/brif/$p"; done +} + +NET0="name=eth0,bridge=vmbr0,hwaddr=BC:24:11:00:00:01,ip=dhcp,tag=20,type=veth" +bridge linux veth101i0 fwpr101p0 +out="$(_pve_no_ip_facts 101 vmbr0)" +check "DHCP on a bridge without an uplink" \ + "Container network: ip=dhcp, VLAN 20, eth0 UP|Bridge vmbr0 has no uplink port, so nothing outside this host can answer on it|No DHCP server answered on vmbr0 (VLAN 20)" \ + "$(paste -sd'|' <<<"$out")" +check "the MAC address stays out of the log" 0 "$(grep -ci 'BC:24' <<<"$out" || true)" + +bridge linux veth101i0 enp3s0 +check "DHCP with an uplink blames the DHCP server only" \ + "Container network: ip=dhcp, VLAN 20, eth0 UP|No DHCP server answered on vmbr0 (VLAN 20)" \ + "$(_pve_no_ip_facts 101 vmbr0 | paste -sd'|')" + +NET0="name=eth0,bridge=vmbr0,ip=192.168.1.50/24,gw=192.168.1.1,type=veth" +LINK="" +check "a static address with eth0 missing" "Container network: ip=192.168.1.50/24, eth0 missing" \ + "$(_pve_no_ip_facts 101 vmbr0 | paste -sd'|')" + +LINK="DOWN" +bridge none +check "a bridge that is not there" "Container network: ip=192.168.1.50/24, eth0 DOWN|Bridge vmbr0 does not exist on this host" \ + "$(_pve_no_ip_facts 101 vmbr0 | paste -sd'|')" + +bridge ovs +check "an Open vSwitch bridge has no brif and is not judged" "Container network: ip=192.168.1.50/24, eth0 DOWN" \ + "$(_pve_no_ip_facts 101 vmbr0 | paste -sd'|')" + +printf '%s passed, %s failed\n' "$passed" "$failed" +((failed == 0))