xref: /freebsd/crypto/openssh/regress/multiplex.sh (revision 2574974648c68c738aec3ff96644d888d7913a37)
1#	$OpenBSD: multiplex.sh,v 1.41 2025/12/07 02:59:53 dtucker Exp $
2#	Placed in the Public Domain.
3
4tid="connection multiplexing"
5
6trace "will use ProxyCommand $proxycmd"
7make_tmpdir
8CTL=${SSH_REGRESS_TMP}/ctl-sock
9
10if config_defined DISABLE_FD_PASSING ; then
11	skip "not supported on this platform (FD passing disabled)"
12fi
13
14P=3301  # test port
15
16wait_for_mux_master_ready()
17{
18	for i in 1 2 3 4 5 6 7 8 9; do
19		${SSH} -F $OBJ/ssh_config -S $CTL -Ocheck otherhost \
20		    >/dev/null 2>&1 && return 0
21		sleep $i
22	done
23	fatal "mux master never becomes ready"
24}
25
26maybe_add_scp_path_to_sshd
27enable_all_kexes_in_sshd
28start_sshd
29
30start_mux_master()
31{
32	trace "start master, fork to background"
33	${SSH} -Nn2 -MS$CTL -F $OBJ/ssh_config -oSendEnv="_XXX_TEST" somehost \
34	    -E $TEST_REGRESS_LOGFILE 2>&1 &
35	# NB. $SSH_PID will be killed by test-exec.sh:cleanup on fatal errors.
36	SSH_PID=$!
37	wait_for_mux_master_ready
38}
39
40start_mux_master
41
42verbose "test $tid: setenv"
43trace "setenv over multiplexed connection"
44_XXX_TEST=blah ${SSH} -F $OBJ/ssh_config -oSendEnv="_XXX_TEST" -S$CTL otherhost sh << 'EOF'
45	test X"$_XXX_TEST" = X"blah"
46EOF
47if [ $? -ne 0 ]; then
48	fail "environment not found"
49fi
50
51verbose "test $tid: envpass"
52trace "env passing over multiplexed connection"
53${SSH} -F $OBJ/ssh_config -oSetEnv="_XXX_TEST=foo" -S$CTL otherhost sh << 'EOF'
54	test X"$_XXX_TEST" = X"foo"
55EOF
56if [ $? -ne 0 ]; then
57	fail "environment not found"
58fi
59
60for mode in "" "-Oproxy"; do
61	verbose "test $tid: transfer $mode"
62	rm -f ${COPY}
63	trace "ssh transfer over $mode multiplexed connection and check result"
64	${SSH} $mode -F $OBJ/ssh_config -S$CTL otherhost cat ${DATA} > ${COPY}
65	test -f ${COPY}		|| fail "ssh -Sctl: failed copy ${DATA}"
66	cmp ${DATA} ${COPY}	|| fail "ssh -Sctl: corrupted copy of ${DATA}"
67
68	rm -f ${COPY}
69	trace "ssh transfer over $mode multiplexed connection and check result"
70	${SSH} $mode -F $OBJ/ssh_config -S $CTL otherhost cat ${DATA} > ${COPY}
71	test -f ${COPY}		|| fail "ssh -S ctl: failed copy ${DATA}"
72	cmp ${DATA} ${COPY}	|| fail "ssh -S ctl: corrupted copy of ${DATA}"
73done
74
75rm -f ${COPY}
76trace "sftp transfer over multiplexed connection and check result"
77echo "get ${DATA} ${COPY}" | \
78	${SFTP} -S ${SSH} -F $OBJ/ssh_config -oControlPath=$CTL otherhost >>$TEST_REGRESS_LOGFILE 2>&1
79test -f ${COPY}			|| fail "sftp: failed copy ${DATA}"
80cmp ${DATA} ${COPY}		|| fail "sftp: corrupted copy of ${DATA}"
81
82rm -f ${COPY}
83trace "scp transfer over multiplexed connection and check result"
84${SCP} -S ${SSH} -F $OBJ/ssh_config -oControlPath=$CTL otherhost:${DATA} ${COPY} >>$TEST_REGRESS_LOGFILE 2>&1
85test -f ${COPY}			|| fail "scp: failed copy ${DATA}"
86cmp ${DATA} ${COPY}		|| fail "scp: corrupted copy of ${DATA}"
87
88rm -f ${COPY}
89verbose "test $tid: forward"
90trace "forward over TCP/IP and check result"
91$NC -N -l 127.0.0.1 $((${PORT} + 1)) < ${DATA} >`ssh_logfile nc` &
92netcat_pid=$!
93${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -L127.0.0.1:$((${PORT} + 2)):127.0.0.1:$((${PORT} + 1)) otherhost >>$TEST_SSH_LOGFILE 2>&1
94sleep 1  # XXX remove once race fixed
95$NC 127.0.0.1 $((${PORT} + 2)) < /dev/null > ${COPY}
96cmp ${DATA} ${COPY}		|| fail "ssh: corrupted copy of ${DATA}"
97kill $netcat_pid 2>/dev/null
98rm -f ${COPY} $OBJ/unix-[123].fwd
99
100trace "forward over UNIX and check result"
101$NC -N -Ul $OBJ/unix-1.fwd < ${DATA} > /dev/null &
102netcat_pid=$!
103${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -L$OBJ/unix-2.fwd:$OBJ/unix-1.fwd otherhost >>$TEST_SSH_LOGFILE 2>&1
104${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -R$OBJ/unix-3.fwd:$OBJ/unix-2.fwd otherhost >>$TEST_SSH_LOGFILE 2>&1
105sleep 1  # XXX remove once race fixed
106$NC -U $OBJ/unix-3.fwd < /dev/null > ${COPY}
107cmp ${DATA} ${COPY}		|| fail "ssh: corrupted copy of ${DATA}"
108kill $netcat_pid 2>/dev/null
109rm -f ${COPY} $OBJ/unix-[123].fwd
110
111for s in 0 1 4 5 44; do
112   for mode in "" "-Oproxy"; do
113	trace "exit status $s over multiplexed connection ($mode)"
114	verbose "test $tid: status $s ($mode)"
115	${SSH} -F $OBJ/ssh_config -S $CTL $mode otherhost exit $s
116	r=$?
117	if [ $r -ne $s ]; then
118		fail "exit code mismatch: $r != $s"
119	fi
120
121	# same with early close of stdout/err
122	trace "exit status $s with early close over multiplexed connection ($mode)"
123	${SSH} -F $OBJ/ssh_config -S $CTL -n $mode otherhost \
124                exec sh -c \'"sleep 2; exec > /dev/null 2>&1; sleep 3; exit $s"\'
125	r=$?
126	if [ $r -ne $s ]; then
127		fail "exit code (with sleep) mismatch: $r != $s"
128	fi
129   done
130done
131
132verbose "test $tid: cmd check"
133${SSH} -F $OBJ/ssh_config -S $CTL -Ocheck otherhost >>$TEST_REGRESS_LOGFILE 2>&1 \
134    || fail "check command failed"
135
136verbose "test $tid: cmd forward local (TCP)"
137${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -L $P:localhost:$PORT otherhost \
138     || fail "request local forward failed"
139sleep 1  # XXX remove once race fixed
140${SSH} -F $OBJ/ssh_config -p$P otherhost true \
141     || fail "connect to local forward port failed"
142${SSH} -F $OBJ/ssh_config -S $CTL -Ocancel -L $P:localhost:$PORT otherhost \
143     || fail "cancel local forward failed"
144${SSH} -F $OBJ/ssh_config -p$P otherhost true \
145     && fail "local forward port still listening"
146
147verbose "test $tid: cmd forward remote (TCP)"
148${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -R $P:localhost:$PORT otherhost \
149     || fail "request remote forward failed"
150sleep 1  # XXX remove once race fixed
151${SSH} -F $OBJ/ssh_config -p$P otherhost true \
152     || fail "connect to remote forwarded port failed"
153${SSH} -F $OBJ/ssh_config -S $CTL -Ocancel -R $P:localhost:$PORT otherhost \
154     || fail "cancel remote forward failed"
155${SSH} -F $OBJ/ssh_config -p$P otherhost true \
156     && fail "remote forward port still listening"
157
158verbose "test $tid: cmd forward local (UNIX)"
159${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -L $OBJ/unix-1.fwd:localhost:$PORT otherhost \
160     || fail "request local forward failed"
161sleep 1  # XXX remove once race fixed
162echo "" | $NC -U $OBJ/unix-1.fwd | \
163    grep "Invalid SSH identification string" >/dev/null 2>&1 \
164     || fail "connect to local forward path failed"
165${SSH} -F $OBJ/ssh_config -S $CTL -Ocancel -L $OBJ/unix-1.fwd:localhost:$PORT otherhost \
166     || fail "cancel local forward failed"
167N=$(echo "xyzzy" | $NC -U $OBJ/unix-1.fwd 2>&1 | grep "xyzzy" | wc -l)
168test ${N} -eq 0 || fail "local forward path still listening"
169rm -f $OBJ/unix-1.fwd
170
171verbose "test $tid: cmd forward remote (UNIX)"
172${SSH} -F $OBJ/ssh_config -S $CTL -Oforward -R $OBJ/unix-1.fwd:localhost:$PORT otherhost \
173     || fail "request remote forward failed"
174sleep 1  # XXX remove once race fixed
175echo "" | $NC -U $OBJ/unix-1.fwd | \
176    grep "Invalid SSH identification string" >/dev/null 2>&1 \
177     || fail "connect to remote forwarded path failed"
178${SSH} -F $OBJ/ssh_config -S $CTL -Ocancel -R $OBJ/unix-1.fwd:localhost:$PORT otherhost \
179     || fail "cancel remote forward failed"
180N=$(echo "xyzzy" | $NC -U $OBJ/unix-1.fwd 2>&1 | grep "xyzzy" | wc -l)
181test ${N} -eq 0 || fail "remote forward path still listening"
182rm -f $OBJ/unix-1.fwd
183
184verbose "test $tid: cmd conninfo"
185conninfo=`${SSH} -F $OBJ/ssh_config -S $CTL -Oconninfo otherhost` \
186     || fail "request remote forward failed"
187if ! echo "$conninfo" | egrep -- "-> 127.0.0.1:$port" >/dev/null; then
188       fail "conninfo"
189fi
190
191verbose "test $tid: cmd exit"
192${SSH} -F $OBJ/ssh_config -S $CTL -Oexit otherhost >>$TEST_REGRESS_LOGFILE 2>&1 \
193    || fail "send exit command failed"
194
195# Wait for master to exit
196wait $SSH_PID
197kill -0 $SSH_PID >/dev/null 2>&1 && fail "exit command failed"
198
199# Enable compression and alternative kex for next conninfo test.
200if $SSH -Q compression | grep zlib@openssh.com >/dev/null; then
201	compression=yes
202else
203	compression=no
204fi
205echo compression $compression >>$OBJ/ssh_config
206echo kexalgorithms curve25519-sha256 >>$OBJ/ssh_config
207echo ciphers aes128-ctr >>$OBJ/ssh_config
208
209# Restart master and test -O stop command with master using -N
210verbose "test $tid: cmd stop"
211trace "restart master, fork to background"
212start_mux_master
213
214verbose "test $tid: cmd conninfo algos"
215conninfo=`${SSH} -F $OBJ/ssh_config -S $CTL -Oconninfo otherhost` \
216     || fail "request remote forward failed"
217if echo "$conninfo" | grep "kexalgorithm curve25519-sha256" >/dev/null &&
218    echo "$conninfo" | grep "cipher aes128-ctr" >/dev/null; then
219	trace "ok conninfo algos"
220else
221	fail "conninfo algos"
222fi
223if [ "$compression" = "yes" ]; then
224	verbose "test $tid: cmd conninfo compression"
225	if echo "$conninfo" | grep "compression zlib" >/dev/null &&
226	    echo "$conninfo" | grep "compressed" >/dev/null; then
227		trace "ok conninfo compression"
228	else
229		fail "conninfo compression"
230	fi
231fi
232
233# start a long-running command then immediately request a stop
234${SSH} -F $OBJ/ssh_config -S $CTL otherhost "sleep 10; exit 0" \
235     >>$TEST_REGRESS_LOGFILE 2>&1 &
236SLEEP_PID=$!
237${SSH} -F$OBJ/ssh_config -S$CTL -Ostop otherhost >>$TEST_REGRESS_LOGFILE 2>&1 \
238    || fail "send stop command failed"
239
240# wait until both long-running command and master have exited.
241wait $SLEEP_PID
242[ $! != 0 ] || fail "waiting for concurrent command"
243wait $SSH_PID
244[ $! != 0 ] || fail "waiting for master stop"
245kill -0 $SSH_PID >/dev/null 2>&1 && fatal "stop command failed"
246SSH_PID="" # Already gone, so don't kill in cleanup
247
248