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