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