portFindNextAvailablePort consulted LibrePortal's own network_resources table plus a hardcoded list (8080, 6060) and CFG_RESERVED_PORTS_EXTRA — while the comment above it claimed a picked port "can never collide with a host service at compose-up time". It can: the list only covers what someone thought to write down. Ask the kernel instead, via ss, read once per allocation rather than per candidate. No ss => empty set => exactly the old behaviour. Found while restoring 13 apps onto a desktop, though not the cause there: stoat's livekit publishes a FIXED udp range (50000-50100, which it advertises to clients and so cannot be re-rolled), and kdeconnectd held 50016. That collision needs its own answer; this fixes the randomly-allocated ports, which had the same exposure with no reason to. Also make the bulk restore stop reporting a half-running app as a clean success. continue-on-error lets a failed compose-up log and carry on, so restoreAppStart returns 0 either way — which is how that run printed "13 apps restored" while four of stoat's containers had exited 101. checkSuccess already appends every failure to error_report.log, so watch it grow across each app and name the ones that were noisy. scripts/dev/lp-port-host-test binds a real socket and asserts the allocator refuses that port; verified it fails when the check is removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
2.8 KiB
Bash
Executable File
72 lines
2.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# Does port allocation avoid a port the HOST is already listening on?
|
|
#
|
|
# scripts/dev/lp-port-host-test
|
|
#
|
|
# It used to avoid only a hardcoded list (8080, 6060) plus whatever an admin had
|
|
# written into CFG_RESERVED_PORTS_EXTRA, while the comment above it claimed a
|
|
# picked port "can never collide with a host service at compose-up time". On a
|
|
# desktop, a first-run restore handed stoat's livekit a UDP port kdeconnectd
|
|
# already held; compose failed with "address already in use", four dependent
|
|
# containers exited 101, and the app came back half-running under a restore that
|
|
# reported success.
|
|
#
|
|
# The test binds a real socket and asserts the allocator refuses that port.
|
|
|
|
REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
fail=0
|
|
chk(){ if [[ "$2" == "$3" ]]; then echo " ok $1"; else echo " FAIL $1: got '$2' want '$3'"; fail=1; fi; }
|
|
|
|
command -v ss >/dev/null 2>&1 || { echo " SKIP ss not available"; exit 0; }
|
|
|
|
# Hold a real port for the duration, so this tests the kernel's answer and not a
|
|
# fixture's idea of one.
|
|
python3 -c '
|
|
import socket, sys, time
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
s.bind(("127.0.0.1", 0)); s.listen(1)
|
|
print(s.getsockname()[1], flush=True)
|
|
time.sleep(30)
|
|
' > /tmp/lp-port-host-test.$$ &
|
|
holder=$!
|
|
trap 'kill $holder 2>/dev/null; rm -f /tmp/lp-port-host-test.$$' EXIT
|
|
for _ in $(seq 1 50); do [[ -s /tmp/lp-port-host-test.$$ ]] && break; sleep 0.1; done
|
|
BOUND=$(cat /tmp/lp-port-host-test.$$)
|
|
[[ -n "$BOUND" ]] || { echo " FAIL could not bind a test port"; exit 1; }
|
|
echo "--- holding tcp/$BOUND ---"
|
|
|
|
source "$REPO/scripts/network/ports/core/port_find_next_available.sh"
|
|
|
|
echo "--- the kernel's view includes it ---"
|
|
chk "in host set" "$(portHostBoundPorts | grep -cx "$BOUND")" "1"
|
|
|
|
echo "--- allocation refuses it ---"
|
|
# A one-port range containing only the bound port: with no other candidate, a
|
|
# correct allocator returns nothing rather than handing back a port in use.
|
|
isNotice(){ :; }; isError(){ :; }
|
|
runInstallOp(){ :; } # empty => the port table claims nothing
|
|
docker_dir="/tmp"; db_file="lp-port-host-test.db.$$"; : > "$docker_dir/$db_file"
|
|
CFG_PORT_RANGE="$BOUND-$BOUND"
|
|
CFG_RESERVED_PORTS_EXTRA=""
|
|
allocated_port="sentinel"
|
|
portFindNextAvailablePort
|
|
rm -f "$docker_dir/$db_file"
|
|
chk "refused the bound port" "$allocated_port" ""
|
|
|
|
echo "--- a free range still allocates ---"
|
|
CFG_PORT_RANGE="49000-49010"
|
|
: > "$docker_dir/$db_file"
|
|
allocated_port=""
|
|
portFindNextAvailablePort
|
|
rm -f "$docker_dir/$db_file"
|
|
if [[ "$allocated_port" =~ ^[0-9]+$ ]] && (( allocated_port >= 49000 && allocated_port <= 49010 )); then
|
|
echo " ok allocated $allocated_port"
|
|
else
|
|
echo " FAIL free range gave '$allocated_port'"; fail=1
|
|
fi
|
|
|
|
echo ""
|
|
if (( fail )); then echo "FAILED"; exit 1; fi
|
|
echo "All port checks passed."
|