Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/verify-split-files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down
33 changes: 33 additions & 0 deletions pve/backend.func
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,38 @@ _pct_create() {
return $rc
}

# ------------------------------------------------------------------------------
# _pve_no_ip_facts <ctid> <bridge>
#
# 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 <ctid> [pct start output]
#
Expand Down Expand Up @@ -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)"
Expand Down
68 changes: 68 additions & 0 deletions tests/pve-no-ip-facts.sh
Original file line number Diff line number Diff line change
@@ -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 <BROADCAST,UP>" ;;
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))