Files
windows95/tools/probe-tcp.sh
Felix Rieseberg b74e6c7b0a Fix guest TCP recv() stalling when v86 NE2000 TX ring wraps (#363)
* Fix guest TCP recv() stalling under concurrent traffic

v86's fake_network stores TCPConnection routing fields (hsrc/hdest/
psrc/pdest) as zero-copy subarrays of the SYN frame, which is itself a
view into the NE2000 TX ring. Win95's driver uses a 12-slot ring; once
it wraps (any concurrent SMB/NBNS/ping while waiting for an upstream
reply), pump() emits segments with whatever IP now occupies that slot,
the guest RSTs them, and recv() blocks forever.

- libv86.js: copy the four address arrays at TCPConnection construction
  (matches felixrieseberg/v86@dd13099c on fake-network-copy-tcp-addrs,
  now merged into windows95-base)
- tools/probe-tcp.sh + net/tcp-trace.ts + tcp-relay.ts test stub +
  debug-harness WIN95_PROBE_RUN2: end-to-end regression harness
  (boot → ping -t → telnet → async write after ring wrap → assert ACK).
  All env-gated, no production-path change.
- docs/v86-patches.md: tracker for all fork patches + upstream PR state
- update-v86 SKILL.md: cross-link and new fork-branch entry

* Drop checked-in upstream PR description

Belongs on the GitHub PR, not in the repo.
2026-04-12 08:03:03 -07:00

66 lines
2.1 KiB
Bash
Executable File

#!/bin/bash
# Probe the tcp-relay recv() path: boot Win95, telnet to a fake upstream
# on port 7777, and dump the per-frame trace. PASS = guest ACKs the async
# banner (i.e., recv() returned).
set -e
cd "$(dirname "$0")/.."
PORT=7777
TRACE=/tmp/win95-tcp-trace.log
RELAY="${TMPDIR:-/tmp}/win95-tcp-relay.log"
STATUS=/tmp/win95-probe.json
TIMEOUT=${TIMEOUT:-90}
pkill -9 -f "windows95.*electron" 2>/dev/null || true
sleep 1
rm -f "$HOME/Library/Application Support/windows95/state-v4.bin"
rm -f "$STATUS" /tmp/win95-probe.done /tmp/win95-screen.png "$TRACE" "$RELAY"
rm -rf dist
node tools/vite-build.js > /tmp/win95-build.log 2>&1 || {
echo "BUILD FAILED"; tail -30 /tmp/win95-build.log; exit 1
}
WIN95_PROBE=1 \
WIN95_PROBE_RUN="${RUN:-ping -t 8.8.8.8}" \
WIN95_PROBE_RUN2="${RUN2:-telnet 1.1.1.1 $PORT}" \
WIN95_PROBE_RUN2_WAIT="${RUN2_WAIT:-3000}" \
WIN95_PROBE_RUN_AFTER="${RUN_AFTER:-}" \
WIN95_PROBE_RUN_WAIT="${RUN_WAIT:-6000}" \
WIN95_TCP_TEST_PORT=$PORT \
WIN95_TCP_TEST_MODE="${MODE:-banner}" \
WIN95_TCP_TEST_DELAY="${DELAY:-50}" \
WIN95_TCP_TEST_BYTES="${BYTES:-3000}" \
WIN95_TCP_TRACE=$PORT \
WIN95_SMB_SHARE="$HOME/Downloads" \
./node_modules/.bin/electron . > /tmp/win95-electron.log 2>&1 &
PID=$!
echo "electron pid=$PID, waiting up to ${TIMEOUT}s…"
VERDICT=TIMEOUT
for i in $(seq 1 "$TIMEOUT"); do
kill -0 $PID 2>/dev/null || { VERDICT=CRASHED; break; }
if [ -f /tmp/win95-probe.done ] && grep -q FAIL /tmp/win95-probe.done; then
VERDICT="BOOT_$(cat /tmp/win95-probe.done)"; break
fi
if [ -f "$TRACE" ] && grep -q '→ guest .* banner' "$RELAY" 2>/dev/null; then
# Banner was written; give guest 8 s to ACK, then decide.
sleep 8
if grep -Eq 'guest→.* ack=(279[8-9]|2[89][0-9]{2}|[3-9][0-9]{3}|[1-9][0-9]{4,}) ' "$TRACE"; then
VERDICT=PASS
else
VERDICT=FAIL
fi
break
fi
sleep 1
done
kill $PID 2>/dev/null || true
wait $PID 2>/dev/null || true
echo "─── relay ───"; [ -f "$RELAY" ] && cat "$RELAY"
echo "─── trace ───"; [ -f "$TRACE" ] && cat "$TRACE"
echo "═══ $VERDICT ═══"
[ "$VERDICT" = PASS ]