Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3#
4# Copyright (c) 2025 Meta Platforms, Inc. and affiliates
5#
6# Dependencies:
7# * virtme-ng
8# * busybox-static (used by virtme-ng)
9# * qemu (used by virtme-ng)
10#
11# shellcheck disable=SC2317,SC2119
12
13readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
14readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
15
16source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
17
18readonly VSOCK_TEST="${SCRIPT_DIR}"/vsock_test
19readonly TEST_GUEST_PORT=51000
20readonly TEST_HOST_PORT=50000
21readonly TEST_HOST_PORT_LISTENER=50001
22readonly SSH_GUEST_PORT=22
23readonly SSH_HOST_PORT=2222
24readonly VSOCK_CID=1234
25readonly WAIT_PERIOD=3
26readonly WAIT_PERIOD_MAX=60
27readonly WAIT_QEMU=5
28readonly PIDFILE_TEMPLATE=/tmp/vsock_vmtest_XXXX.pid
29declare -A PIDFILES
30
31# virtme-ng offers a netdev for ssh when using "--ssh", but we also need a
32# control port forwarded for vsock_test. Because virtme-ng doesn't support
33# adding an additional port to forward to the device created from "--ssh" and
34# virtme-init mistakenly sets identical IPs to the ssh device and additional
35# devices, we instead opt out of using --ssh, add the device manually, and also
36# add the kernel cmdline options that virtme-init uses to setup the interface.
37readonly QEMU_TEST_PORT_FWD="hostfwd=tcp::${TEST_HOST_PORT}-:${TEST_GUEST_PORT}"
38readonly QEMU_SSH_PORT_FWD="hostfwd=tcp::${SSH_HOST_PORT}-:${SSH_GUEST_PORT}"
39readonly KERNEL_CMDLINE="\
40 virtme.dhcp net.ifnames=0 biosdevname=0 \
41 virtme.ssh virtme_ssh_channel=tcp virtme_ssh_user=$USER \
42"
43readonly LOG=$(mktemp /tmp/vsock_vmtest_XXXX.log)
44readonly TEST_NAMES=(vm_server_host_client vm_client_host_server vm_loopback)
45readonly TEST_DESCS=(
46 "Run vsock_test in server mode on the VM and in client mode on the host."
47 "Run vsock_test in client mode on the VM and in server mode on the host."
48 "Run vsock_test using the loopback transport in the VM."
49)
50
51readonly USE_SHARED_VM=(vm_server_host_client vm_client_host_server vm_loopback)
52
53VERBOSE=0
54
55usage() {
56 local name
57 local desc
58 local i
59
60 echo
61 echo "$0 [OPTIONS] [TEST]..."
62 echo "If no TEST argument is given, all tests will be run."
63 echo
64 echo "Options"
65 echo " -b: build the kernel from the current source tree and use it for guest VMs"
66 echo " -q: set the path to or name of qemu binary"
67 echo " -v: verbose output"
68 echo
69 echo "Available tests"
70
71 for ((i = 0; i < ${#TEST_NAMES[@]}; i++)); do
72 name=${TEST_NAMES[${i}]}
73 desc=${TEST_DESCS[${i}]}
74 printf "\t%-35s%-35s\n" "${name}" "${desc}"
75 done
76 echo
77
78 exit 1
79}
80
81die() {
82 echo "$*" >&2
83 exit "${KSFT_FAIL}"
84}
85
86check_result() {
87 local rc arg
88
89 rc=$1
90 arg=$2
91
92 cnt_total=$(( cnt_total + 1 ))
93
94 if [[ ${rc} -eq ${KSFT_PASS} ]]; then
95 cnt_pass=$(( cnt_pass + 1 ))
96 echo "ok ${cnt_total} ${arg}"
97 elif [[ ${rc} -eq ${KSFT_SKIP} ]]; then
98 cnt_skip=$(( cnt_skip + 1 ))
99 echo "ok ${cnt_total} ${arg} # SKIP"
100 elif [[ ${rc} -eq ${KSFT_FAIL} ]]; then
101 cnt_fail=$(( cnt_fail + 1 ))
102 echo "not ok ${cnt_total} ${arg} # exit=${rc}"
103 fi
104}
105
106vm_ssh() {
107 ssh -q -o UserKnownHostsFile=/dev/null -p ${SSH_HOST_PORT} localhost "$@"
108 return $?
109}
110
111cleanup() {
112 terminate_pidfiles "${!PIDFILES[@]}"
113}
114
115check_args() {
116 local found
117
118 for arg in "$@"; do
119 found=0
120 for name in "${TEST_NAMES[@]}"; do
121 if [[ "${name}" = "${arg}" ]]; then
122 found=1
123 break
124 fi
125 done
126
127 if [[ "${found}" -eq 0 ]]; then
128 echo "${arg} is not an available test" >&2
129 usage
130 fi
131 done
132
133 for arg in "$@"; do
134 if ! command -v > /dev/null "test_${arg}"; then
135 echo "Test ${arg} not found" >&2
136 usage
137 fi
138 done
139}
140
141check_deps() {
142 for dep in vng ${QEMU} busybox pkill ssh; do
143 if [[ ! -x $(command -v "${dep}") ]]; then
144 echo -e "skip: dependency ${dep} not found!\n"
145 exit "${KSFT_SKIP}"
146 fi
147 done
148
149 if [[ ! -x $(command -v "${VSOCK_TEST}") ]]; then
150 printf "skip: %s not found!" "${VSOCK_TEST}"
151 printf " Please build the kselftest vsock target.\n"
152 exit "${KSFT_SKIP}"
153 fi
154}
155
156check_vng() {
157 local tested_versions
158 local version
159 local ok
160
161 tested_versions=("1.33" "1.36" "1.37")
162 version="$(vng --version)"
163
164 ok=0
165 for tv in "${tested_versions[@]}"; do
166 if [[ "${version}" == *"${tv}"* ]]; then
167 ok=1
168 break
169 fi
170 done
171
172 if [[ ! "${ok}" -eq 1 ]]; then
173 printf "warning: vng version '%s' has not been tested and may " "${version}" >&2
174 printf "not function properly.\n\tThe following versions have been tested: " >&2
175 echo "${tested_versions[@]}" >&2
176 fi
177}
178
179handle_build() {
180 if [[ ! "${BUILD}" -eq 1 ]]; then
181 return
182 fi
183
184 if [[ ! -d "${KERNEL_CHECKOUT}" ]]; then
185 echo "-b requires vmtest.sh called from the kernel source tree" >&2
186 exit 1
187 fi
188
189 pushd "${KERNEL_CHECKOUT}" &>/dev/null
190
191 if ! vng --kconfig --config "${SCRIPT_DIR}"/config; then
192 die "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})"
193 fi
194
195 if ! make -j$(nproc); then
196 die "failed to build kernel from source tree (${KERNEL_CHECKOUT})"
197 fi
198
199 popd &>/dev/null
200}
201
202create_pidfile() {
203 local pidfile
204
205 pidfile=$(mktemp "${PIDFILE_TEMPLATE}")
206 PIDFILES["${pidfile}"]=1
207
208 echo "${pidfile}"
209}
210
211terminate_pidfiles() {
212 local pidfile
213
214 for pidfile in "$@"; do
215 if [[ -s "${pidfile}" ]]; then
216 pkill -SIGTERM -F "${pidfile}" > /dev/null 2>&1
217 fi
218
219 if [[ -e "${pidfile}" ]]; then
220 rm -f "${pidfile}"
221 fi
222
223 unset "PIDFILES[${pidfile}]"
224 done
225}
226
227vm_start() {
228 local pidfile=$1
229 local logfile=/dev/null
230 local verbose_opt=""
231 local kernel_opt=""
232 local qemu_opts=""
233 local qemu
234
235 qemu=$(command -v "${QEMU}")
236
237 if [[ "${VERBOSE}" -eq 1 ]]; then
238 verbose_opt="--verbose"
239 logfile=/dev/stdout
240 fi
241
242 qemu_opts="\
243 -netdev user,id=n0,${QEMU_TEST_PORT_FWD},${QEMU_SSH_PORT_FWD} \
244 -device virtio-net-pci,netdev=n0 \
245 -device vhost-vsock-pci,guest-cid=${VSOCK_CID} \
246 --pidfile ${pidfile}
247 "
248
249 if [[ "${BUILD}" -eq 1 ]]; then
250 kernel_opt="${KERNEL_CHECKOUT}"
251 fi
252
253 vng \
254 --run \
255 ${kernel_opt} \
256 ${verbose_opt} \
257 --qemu-opts="${qemu_opts}" \
258 --qemu="${qemu}" \
259 --user root \
260 --append "${KERNEL_CMDLINE}" \
261 --rw &> ${logfile} &
262
263 timeout "${WAIT_QEMU}" \
264 bash -c 'while [[ ! -s '"${pidfile}"' ]]; do sleep 1; done; exit 0'
265}
266
267vm_wait_for_ssh() {
268 local i
269
270 i=0
271 while true; do
272 if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then
273 die "Timed out waiting for guest ssh"
274 fi
275 if vm_ssh -- true; then
276 break
277 fi
278 i=$(( i + 1 ))
279 sleep ${WAIT_PERIOD}
280 done
281}
282
283# derived from selftests/net/net_helper.sh
284wait_for_listener()
285{
286 local port=$1
287 local interval=$2
288 local max_intervals=$3
289 local protocol=tcp
290 local pattern
291 local i
292
293 pattern=":$(printf "%04X" "${port}") "
294
295 # for tcp protocol additionally check the socket state
296 [ "${protocol}" = "tcp" ] && pattern="${pattern}0A"
297
298 for i in $(seq "${max_intervals}"); do
299 if awk -v pattern="${pattern}" \
300 'BEGIN {rc=1} $2" "$4 ~ pattern {rc=0} END {exit rc}' \
301 /proc/net/"${protocol}"*; then
302 break
303 fi
304 sleep "${interval}"
305 done
306}
307
308vm_wait_for_listener() {
309 local port=$1
310
311 vm_ssh <<EOF
312$(declare -f wait_for_listener)
313wait_for_listener ${port} ${WAIT_PERIOD} ${WAIT_PERIOD_MAX}
314EOF
315}
316
317host_wait_for_listener() {
318 local port=$1
319
320 wait_for_listener "${port}" "${WAIT_PERIOD}" "${WAIT_PERIOD_MAX}"
321}
322
323vm_vsock_test() {
324 local host=$1
325 local cid=$2
326 local port=$3
327 local rc
328
329 # log output and use pipefail to respect vsock_test errors
330 set -o pipefail
331 if [[ "${host}" != server ]]; then
332 vm_ssh -- "${VSOCK_TEST}" \
333 --mode=client \
334 --control-host="${host}" \
335 --peer-cid="${cid}" \
336 --control-port="${port}" \
337 2>&1 | log_guest
338 rc=$?
339 else
340 vm_ssh -- "${VSOCK_TEST}" \
341 --mode=server \
342 --peer-cid="${cid}" \
343 --control-port="${port}" \
344 2>&1 | log_guest &
345 rc=$?
346
347 if [[ $rc -ne 0 ]]; then
348 set +o pipefail
349 return $rc
350 fi
351
352 vm_wait_for_listener "${port}"
353 rc=$?
354 fi
355 set +o pipefail
356
357 return $rc
358}
359
360host_vsock_test() {
361 local host=$1
362 local cid=$2
363 local port=$3
364 local rc
365
366 # log output and use pipefail to respect vsock_test errors
367 set -o pipefail
368 if [[ "${host}" != server ]]; then
369 ${VSOCK_TEST} \
370 --mode=client \
371 --peer-cid="${cid}" \
372 --control-host="${host}" \
373 --control-port="${port}" 2>&1 | log_host
374 rc=$?
375 else
376 ${VSOCK_TEST} \
377 --mode=server \
378 --peer-cid="${cid}" \
379 --control-port="${port}" 2>&1 | log_host &
380 rc=$?
381
382 if [[ $rc -ne 0 ]]; then
383 set +o pipefail
384 return $rc
385 fi
386
387 host_wait_for_listener "${port}"
388 rc=$?
389 fi
390 set +o pipefail
391
392 return $rc
393}
394
395log() {
396 local redirect
397 local prefix
398
399 if [[ ${VERBOSE} -eq 0 ]]; then
400 redirect=/dev/null
401 else
402 redirect=/dev/stdout
403 fi
404
405 prefix="${LOG_PREFIX:-}"
406
407 if [[ "$#" -eq 0 ]]; then
408 if [[ -n "${prefix}" ]]; then
409 awk -v prefix="${prefix}" '{printf "%s: %s\n", prefix, $0}'
410 else
411 cat
412 fi
413 else
414 if [[ -n "${prefix}" ]]; then
415 echo "${prefix}: " "$@"
416 else
417 echo "$@"
418 fi
419 fi | tee -a "${LOG}" > "${redirect}"
420}
421
422log_host() {
423 LOG_PREFIX=host log "$@"
424}
425
426log_guest() {
427 LOG_PREFIX=guest log "$@"
428}
429
430test_vm_server_host_client() {
431 if ! vm_vsock_test "server" 2 "${TEST_GUEST_PORT}"; then
432 return "${KSFT_FAIL}"
433 fi
434
435 if ! host_vsock_test "127.0.0.1" "${VSOCK_CID}" "${TEST_HOST_PORT}"; then
436 return "${KSFT_FAIL}"
437 fi
438
439 return "${KSFT_PASS}"
440}
441
442test_vm_client_host_server() {
443 if ! host_vsock_test "server" "${VSOCK_CID}" "${TEST_HOST_PORT_LISTENER}"; then
444 return "${KSFT_FAIL}"
445 fi
446
447 if ! vm_vsock_test "10.0.2.2" 2 "${TEST_HOST_PORT_LISTENER}"; then
448 return "${KSFT_FAIL}"
449 fi
450
451 return "${KSFT_PASS}"
452}
453
454test_vm_loopback() {
455 local port=60000 # non-forwarded local port
456
457 vm_ssh -- modprobe vsock_loopback &> /dev/null || :
458
459 if ! vm_vsock_test "server" 1 "${port}"; then
460 return "${KSFT_FAIL}"
461 fi
462
463 if ! vm_vsock_test "127.0.0.1" 1 "${port}"; then
464 return "${KSFT_FAIL}"
465 fi
466
467 return "${KSFT_PASS}"
468}
469
470shared_vm_test() {
471 local tname
472
473 tname="${1}"
474
475 for testname in "${USE_SHARED_VM[@]}"; do
476 if [[ "${tname}" == "${testname}" ]]; then
477 return 0
478 fi
479 done
480
481 return 1
482}
483
484shared_vm_tests_requested() {
485 for arg in "$@"; do
486 if shared_vm_test "${arg}"; then
487 return 0
488 fi
489 done
490
491 return 1
492}
493
494run_shared_vm_tests() {
495 local arg
496
497 for arg in "$@"; do
498 if ! shared_vm_test "${arg}"; then
499 continue
500 fi
501
502 run_shared_vm_test "${arg}"
503 check_result "$?" "${arg}"
504 done
505}
506
507run_shared_vm_test() {
508 local host_oops_cnt_before
509 local host_warn_cnt_before
510 local vm_oops_cnt_before
511 local vm_warn_cnt_before
512 local host_oops_cnt_after
513 local host_warn_cnt_after
514 local vm_oops_cnt_after
515 local vm_warn_cnt_after
516 local name
517 local rc
518
519 host_oops_cnt_before=$(dmesg | grep -c -i 'Oops')
520 host_warn_cnt_before=$(dmesg --level=warn | grep -c -i 'vsock')
521 vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops')
522 vm_warn_cnt_before=$(vm_ssh -- dmesg --level=warn | grep -c -i 'vsock')
523
524 name=$(echo "${1}" | awk '{ print $1 }')
525 eval test_"${name}"
526 rc=$?
527
528 host_oops_cnt_after=$(dmesg | grep -i 'Oops' | wc -l)
529 if [[ ${host_oops_cnt_after} -gt ${host_oops_cnt_before} ]]; then
530 echo "FAIL: kernel oops detected on host" | log_host
531 rc=$KSFT_FAIL
532 fi
533
534 host_warn_cnt_after=$(dmesg --level=warn | grep -c -i 'vsock')
535 if [[ ${host_warn_cnt_after} -gt ${host_warn_cnt_before} ]]; then
536 echo "FAIL: kernel warning detected on host" | log_host
537 rc=$KSFT_FAIL
538 fi
539
540 vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l)
541 if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then
542 echo "FAIL: kernel oops detected on vm" | log_host
543 rc=$KSFT_FAIL
544 fi
545
546 vm_warn_cnt_after=$(vm_ssh -- dmesg --level=warn | grep -c -i 'vsock')
547 if [[ ${vm_warn_cnt_after} -gt ${vm_warn_cnt_before} ]]; then
548 echo "FAIL: kernel warning detected on vm" | log_host
549 rc=$KSFT_FAIL
550 fi
551
552 return "${rc}"
553}
554
555BUILD=0
556QEMU="qemu-system-$(uname -m)"
557
558while getopts :hvsq:b o
559do
560 case $o in
561 v) VERBOSE=1;;
562 b) BUILD=1;;
563 q) QEMU=$OPTARG;;
564 h|*) usage;;
565 esac
566done
567shift $((OPTIND-1))
568
569trap cleanup EXIT
570
571if [[ ${#} -eq 0 ]]; then
572 ARGS=("${TEST_NAMES[@]}")
573else
574 ARGS=("$@")
575fi
576
577check_args "${ARGS[@]}"
578check_deps
579check_vng
580handle_build
581
582echo "1..${#ARGS[@]}"
583
584cnt_pass=0
585cnt_fail=0
586cnt_skip=0
587cnt_total=0
588
589if shared_vm_tests_requested "${ARGS[@]}"; then
590 log_host "Booting up VM"
591 pidfile="$(create_pidfile)"
592 vm_start "${pidfile}"
593 vm_wait_for_ssh
594 log_host "VM booted up"
595
596 run_shared_vm_tests "${ARGS[@]}"
597 terminate_pidfiles "${pidfile}"
598fi
599
600echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}"
601echo "Log: ${LOG}"
602
603if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then
604 exit "$KSFT_PASS"
605else
606 exit "$KSFT_FAIL"
607fi