a73x

05ab5eeb

test: a cold attach is three ssh runs — endpoint, start, endpoint

a73x   2026-08-28 19:53

Commit message
test: a cold attach is three ssh runs — endpoint, start, endpoint

test/e2e_04_handoff.sh
Old New
@@ -7,8 +7,9 @@
7 # per daemon: the handoff dials the DEFAULT socket on the far side, and the 7 # per daemon: the handoff dials the DEFAULT socket on the far side, and the
8 # far side is this box, so each scenario's default socket has to be one of 8 # far side is this box, so each scenario's default socket has to be one of
9 # ours rather than the operator's. Two daemons, no more — the one a cold 9 # ours rather than the operator's. Two daemons, no more — the one a cold
10 # attach auto-starts, and the key-mismatch one that cannot be shared 10 # attach starts by running `muxd start` over the shim, and the key-mismatch
11 # because it holds a key nothing else on this box has. 11 # one that cannot be shared because it holds a key nothing else on this box
12 # has.
12 SSHIM_DIR="${TMPDIR:-/tmp}/muxd-e2e-sshim-$$" 13 SSHIM_DIR="${TMPDIR:-/tmp}/muxd-e2e-sshim-$$"
13 defer_rm "$SSHIM_DIR" 14 defer_rm "$SSHIM_DIR"
14 SSHIM_PIDLOG="$SSHIM_DIR/pids" 15 SSHIM_PIDLOG="$SSHIM_DIR/pids"
@@ -90,9 +91,10 @@ HPATH="$SSHIM_DIR:$(dirname "$MUXD"):$PATH"
90 # attempt below would fail at DNS without ever dialling. 91 # attempt below would fail at DNS without ever dialling.
91 HHOST="mux-e2e@127.0.0.1" 92 HHOST="mux-e2e@127.0.0.1"
92 HCACHE="$XDG_CACHE_HOME/mux/hosts/$HHOST" 93 HCACHE="$XDG_CACHE_HOME/mux/hosts/$HHOST"
93 # (a) COLD: no daemon, no cache. The attach has to produce the daemon (via 94 # (a) COLD: no daemon, no cache. The attach has to produce the daemon —
94 # `muxd endpoint`'s own auto-start), fetch coordinates over the shim, and 95 # `muxd endpoint` refuses a box that has none, and the CLIENT answers that
95 # END UP ON QUIC — and that last part is proven by observation rather than 96 # by running `muxd start` over the shim — fetch coordinates over the shim,
97 # and END UP ON QUIC — and that last part is proven by observation rather than
96 # inference: the ssh the announce arrived on is killed by the client on 98 # inference: the ssh the announce arrived on is killed by the client on
97 # QUIC success, so once its pid is OBSERVED gone, a second marker still 99 # QUIC success, so once its pid is OBSERVED gone, a second marker still
98 # making the round trip can only be riding QUIC. The pipe's owner is dead. 100 # making the round trip can only be riding QUIC. The pipe's owner is dead.
@@ -108,12 +110,20 @@ wait_for "$OUT.h1" "cold-one" 25 || {
108 echo " A client that blocks here read no announce line off the ssh pipe." 110 echo " A client that blocks here read no announce line off the ssh pipe."
109 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; } 111 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; }
110 112
111 # Exactly one ssh, and its pid, from the log rather than from ps. 113 # Exactly three ssh runs, and the LAST one's pid, from the log rather than
114 # from ps. Three is the cold shape and it is bounded: `muxd endpoint` on a
115 # box with no daemon, then `muxd start`, then `muxd endpoint` again. Never
116 # a fourth — the ask buys one start and one retry — and the count is what
117 # says so: a client that looped would show four here, not a hang.
112 HSHIMS=$(wc -l < "$SSHIM_PIDLOG") 118 HSHIMS=$(wc -l < "$SSHIM_PIDLOG")
113 [ "$HSHIMS" -eq 1 ] || { 119 [ "$HSHIMS" -eq 3 ] || {
114 echo "e2e FAIL: cold handoff ran $HSHIMS ssh invocations, want exactly 1" 120 echo "e2e FAIL: cold handoff ran $HSHIMS ssh invocations, want exactly 3"
121 echo " (muxd endpoint on an empty box, muxd start, muxd endpoint)"
115 cat "$SSHIM_PIDLOG"; exit 1; } 122 cat "$SSHIM_PIDLOG"; exit 1; }
116 HSHIMPID=$(head -1 "$SSHIM_PIDLOG") 123 # The LAST, not the first: the first two are the refusal and the start, and
124 # both are already dead by construction. The ssh whose death proves QUIC
125 # took over is the one that carried the announce.
126 HSHIMPID=$(tail -1 "$SSHIM_PIDLOG")
117 wait_pid_gone "$HSHIMPID" "cold handoff: QUIC took over, so ssh must be gone" 127 wait_pid_gone "$HSHIMPID" "cold handoff: QUIC took over, so ssh must be gone"
118 # The second marker is typed only now, after the death was OBSERVED, so its 128 # The second marker is typed only now, after the death was OBSERVED, so its
119 # round trip is traffic that moved after the pipe's owner died — by 129 # round trip is traffic that moved after the pipe's owner died — by
@@ -129,12 +139,15 @@ wait_for "$OUT.h1" "cold-two" 20 || {
129 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; } 139 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; }
130 pipe_detach "cold handoff client" 140 pipe_detach "cold handoff client"
131 141
132 # The daemon `muxd endpoint` started, by the pid its own up-line reported — 142 # The daemon the CLIENT started, by the pid `muxd start`'s own up-line
133 # the only handle this suite has on a process that is nobody's child. Same 143 # reported — the only handle this suite has on a process that is nobody's
144 # child. The line is `muxd start`'s now, not `muxd endpoint`'s, and it
145 # reaches this file the same way ssh's own stderr does: inherited. Same
134 # accepted gap as the M13 blocks: a failure above leaves it untracked, and 146 # accepted gap as the M13 blocks: a failure above leaves it untracked, and
135 # the trap's stop-by-socket is what covers that. 147 # the trap's stop-by-socket is what covers that.
136 grep -q '^muxd endpoint: starting' "$OUT.h1.err" || { 148 grep -q '^muxd: starting' "$OUT.h1.err" || {
137 echo "e2e FAIL: cold handoff printed no endpoint starting line" 149 echo "e2e FAIL: cold handoff printed no muxd start line — the client did not"
150 echo " start the daemon that muxd endpoint refused to"
138 cat "$OUT.h1.err"; exit 1; } 151 cat "$OUT.h1.err"; exit 1; }
139 HAPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.h1.err" | head -1) 152 HAPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.h1.err" | head -1)
140 defer_kill "$HAPID" 153 defer_kill "$HAPID"