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 608