From 3e655f7981de905e019677f62460859468f25aaa Mon Sep 17 00:00:00 2001 From: Daniel Rossier Date: Mon, 24 Aug 2026 14:44:37 +0200 Subject: [PATCH] st.sh: pick free ports instead of counting QEMU instances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting a second emulator killed it on the spot: qemu-system-arm: -netdev user,id=n1,hostfwd=tcp::2222-:22: Could not set up host forwarding rule 'tcp::2222-:22' The gdb port was already offset per instance, derived from a count of running qemu-system processes, but the ssh forward stayed hardcoded at 2222. That counter cannot work here anyway: dbuild.sh runs every command in its own container, hence its own PID namespace, so a second container sees none of the first one's processes and hands out the very ports it already holds — while `docker run --network host` puts both on the same host stack. Probe instead. free_port() walks up from a base until a TCP connect to 127.0.0.1 fails, which notices every listener on the host whichever namespace opened it. The gdb stub and the ssh forward both go through it, and the chosen port is printed, so attaching a debugger or an scp knows where to aim. Validated with two emulators side by side, one on the host and one in a container: 1234/2222 and 1235/2223, both booting to the SO3 prompt. --- scripts/dbuild.sh | 5 +++-- scripts/st.sh | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/scripts/dbuild.sh b/scripts/dbuild.sh index f75ab1e63..9aef98610 100755 --- a/scripts/dbuild.sh +++ b/scripts/dbuild.sh @@ -225,8 +225,9 @@ fi # resource, so concurrent builds on one machine contend for them exactly # as they do outside a container. # -# --network host: keeps st.sh's slirp port forwards (guest ssh on 2222), -# the GDB stub and the Sense HAT bridge (4442) reachable from the host +# --network host: keeps st.sh's slirp port forwards (guest ssh on 2222, +# or the first free port above it), the GDB stub and the Sense HAT bridge +# (4442) reachable from the host # without publishing ports, and lets the recipes fetch through the host # resolver. diff --git a/scripts/st.sh b/scripts/st.sh index 0efd89f50..82ed06521 100755 --- a/scripts/st.sh +++ b/scripts/st.sh @@ -12,6 +12,28 @@ QEMU_AUDIO_DRV="none" GDB_PORT_BASE=1234 +SSH_PORT_BASE=2222 + +# First free TCP port at or above $1, probed on the host network. +# +# The instance counter below cannot serve here. It counts QEMU processes with +# `ps`, and every dbuild.sh container has its own PID namespace: a second +# container sees none of the first one's processes and would hand out the very +# same ports — while `docker run --network host` makes both share the host's. +# Two trees side by side (sye_sol and sye_student, say) then died on +# "Could not set up host forwarding rule 'tcp::2222-:22'". + +free_port() +{ + local port=$1 + + while (exec 3<>/dev/tcp/127.0.0.1/"$port") 2>/dev/null + do + port=$((port + 1)) + done + + printf '%s' "$port" +} # Parse our own options (currently just -d) out of the argument list before # what's left is forwarded to QEMU as USR_OPTION. @@ -42,10 +64,12 @@ launch_qemu() { # the virtio-net one above. QEMU_ETH_MAC_ADDR="$(printf 'DE:AD:BE:EF:%02X:%02X\n' $((0x10 + N_QEMU_INSTANCES)) $((N_QEMU_INSTANCES)))" - GDB_PORT=$((${GDB_PORT_BASE} + ${N_QEMU_INSTANCES})) + GDB_PORT=$(free_port ${GDB_PORT_BASE}) + SSH_PORT=$(free_port ${SSH_PORT_BASE}) echo -e "\033[01;36mMAC addr: " ${QEMU_MAC_ADDR} "\033[0;37m" echo -e "\033[01;36mGDB port: " ${GDB_PORT} "\033[0;37m" + echo -e "\033[01;36mSSH port: " ${SSH_PORT} "\033[0;37m" while IFS= read -r line; do # Check if the line starts with "IB_PLATFORM" @@ -90,7 +114,7 @@ launch_qemu() { # # * virtio-net for the Linux agency (AVZ boot chain) — it has no # smc911x node in its device tree, and hostfwd puts guest ssh on host - # port 2222; + # port ${SSH_PORT} (2222 unless taken); # * an SMSC LAN9118 for SO3, whose only Ethernet driver is smc911x # (devices/net/smc911x_lwip.c). QEMU's stock 'virt' machine has no # such device; the so3 QEMU patch adds one at 0x08804000 / SPI 15 — @@ -110,7 +134,8 @@ launch_qemu() { # the guest gets 10.0.2.15 immediately and NetworkManager-wait-online # succeeds in <1 s instead of timing out at 60 s as it did with tap+host # bridge that had no DHCP server. hostfwd exposes guest SSH on host - # port 2222 for convenience. Trade-off: guest is NAT'd, no LAN visibility. + # port ${SSH_PORT} for convenience. Trade-off: guest is NAT'd, no LAN + # visibility. # Bonus: no sudo needed (no tap device creation), so QEMU artefacts stay # owned by the regular user across runs. # @@ -155,7 +180,7 @@ launch_qemu() { -drive if=none,file=filesystem/sdcard.img.virt64,id=hd0,format=raw,file.locking=off \ -m 1024 \ ${DISPLAY_OPT} \ - -netdev user,id=n1,hostfwd=tcp::2222-:22 \ + -netdev user,id=n1,hostfwd=tcp::${SSH_PORT}-:22 \ -device virtio-net-device,netdev=n1,mac=${QEMU_MAC_ADDR} \ ${ETH_OPT} \ -gdb tcp::${GDB_PORT} @@ -179,7 +204,7 @@ launch_qemu() { -drive if=none,file=filesystem/sdcard.img.virt32,id=hd0,format=raw,file.locking=off \ -m 1024 \ ${DISPLAY_OPT} \ - -netdev user,id=n1,hostfwd=tcp::2222-:22 \ + -netdev user,id=n1,hostfwd=tcp::${SSH_PORT}-:22 \ -device virtio-net-device,netdev=n1,mac=${QEMU_MAC_ADDR} \ ${ETH_OPT} \ -gdb tcp::${GDB_PORT}