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 timeout "${WAIT_TOTAL}" \ 240 bash -c 'while [[ ! -s '"${pidfile}"' ]]; do sleep 1; done; exit 0' 241} 242 243vm_wait_for_ssh() { 244 local i 245 246 i=0 247 while true; do 248 if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then 249 die "Timed out waiting for guest ssh" 250 fi 251 if vm_ssh -- true; then 252 break 253 fi 254 i=$(( i + 1 )) 255 sleep ${WAIT_PERIOD} 256 done 257} 258 259# derived from selftests/net/net_helper.sh 260wait_for_listener() 261{ 262 local port=$1 263 local interval=$2 264 local max_intervals=$3 265 local protocol=tcp 266 local pattern 267 local i 268 269 pattern=":$(printf "%04X" "${port}") " 270 271 # for tcp protocol additionally check the socket state 272 [ "${protocol}" = "tcp" ] && pattern="${pattern}0A" 273 274 for i in $(seq "${max_intervals}"); do 275 if awk -v pattern="${pattern}" \ 276 'BEGIN {rc=1} $2" "$4 ~ pattern {rc=0} END {exit rc}' \ 277 /proc/net/"${protocol}"*; then 278 break 279 fi 280 sleep "${interval}" 281 done 282} 283 284vm_wait_for_listener() { 285 local port=$1 286 287 vm_ssh <<EOF 288$(declare -f wait_for_listener) 289wait_for_listener ${port} ${WAIT_PERIOD} ${WAIT_PERIOD_MAX} 290EOF 291} 292 293host_wait_for_listener() { 294 local port=$1 295 296 wait_for_listener "${port}" "${WAIT_PERIOD}" "${WAIT_PERIOD_MAX}" 297} 298 299vm_vsock_test() { 300 local host=$1 301 local cid=$2 302 local port=$3 303 local rc 304 305 # log output and use pipefail to respect vsock_test errors 306 set -o pipefail 307 if [[ "${host}" != server ]]; then 308 vm_ssh -- "${VSOCK_TEST}" \ 309 --mode=client \ 310 --control-host="${host}" \ 311 --peer-cid="${cid}" \ 312 --control-port="${port}" \ 313 2>&1 | log_guest 314 rc=$? 315 else 316 vm_ssh -- "${VSOCK_TEST}" \ 317 --mode=server \ 318 --peer-cid="${cid}" \ 319 --control-port="${port}" \ 320 2>&1 | log_guest & 321 rc=$? 322 323 if [[ $rc -ne 0 ]]; then 324 set +o pipefail 325 return $rc 326 fi 327 328 vm_wait_for_listener "${port}" 329 rc=$? 330 fi 331 set +o pipefail 332 333 return $rc 334} 335 336host_vsock_test() { 337 local host=$1 338 local cid=$2 339 local port=$3 340 local rc 341 342 # log output and use pipefail to respect vsock_test errors 343 set -o pipefail 344 if [[ "${host}" != server ]]; then 345 ${VSOCK_TEST} \ 346 --mode=client \ 347 --peer-cid="${cid}" \ 348 --control-host="${host}" \ 349 --control-port="${port}" 2>&1 | log_host 350 rc=$? 351 else 352 ${VSOCK_TEST} \ 353 --mode=server \ 354 --peer-cid="${cid}" \ 355 --control-port="${port}" 2>&1 | log_host & 356 rc=$? 357 358 if [[ $rc -ne 0 ]]; then 359 set +o pipefail 360 return $rc 361 fi 362 363 host_wait_for_listener "${port}" 364 rc=$? 365 fi 366 set +o pipefail 367 368 return $rc 369} 370 371log() { 372 local redirect 373 local prefix 374 375 if [[ ${VERBOSE} -eq 0 ]]; then 376 redirect=/dev/null 377 else 378 redirect=/dev/stdout 379 fi 380 381 prefix="${LOG_PREFIX:-}" 382 383 if [[ "$#" -eq 0 ]]; then 384 if [[ -n "${prefix}" ]]; then 385 awk -v prefix="${prefix}" '{printf "%s: %s\n", prefix, $0}' 386 else 387 cat 388 fi 389 else 390 if [[ -n "${prefix}" ]]; then 391 echo "${prefix}: " "$@" 392 else 393 echo "$@" 394 fi 395 fi | tee -a "${LOG}" > "${redirect}" 396} 397 398log_host() { 399 LOG_PREFIX=host log "$@" 400} 401 402log_guest() { 403 LOG_PREFIX=guest log "$@" 404} 405 406test_vm_server_host_client() { 407 if ! vm_vsock_test "server" 2 "${TEST_GUEST_PORT}"; then 408 return "${KSFT_FAIL}" 409 fi 410 411 if ! host_vsock_test "127.0.0.1" "${VSOCK_CID}" "${TEST_HOST_PORT}"; then 412 return "${KSFT_FAIL}" 413 fi 414 415 return "${KSFT_PASS}" 416} 417 418test_vm_client_host_server() { 419 if ! host_vsock_test "server" "${VSOCK_CID}" "${TEST_HOST_PORT_LISTENER}"; then 420 return "${KSFT_FAIL}" 421 fi 422 423 if ! vm_vsock_test "10.0.2.2" 2 "${TEST_HOST_PORT_LISTENER}"; then 424 return "${KSFT_FAIL}" 425 fi 426 427 return "${KSFT_PASS}" 428} 429 430test_vm_loopback() { 431 local port=60000 # non-forwarded local port 432 433 if ! vm_vsock_test "server" 1 "${port}"; then 434 return "${KSFT_FAIL}" 435 fi 436 437 if ! vm_vsock_test "127.0.0.1" 1 "${port}"; then 438 return "${KSFT_FAIL}" 439 fi 440 441 return "${KSFT_PASS}" 442} 443 444run_test() { 445 local host_oops_cnt_before 446 local host_warn_cnt_before 447 local vm_oops_cnt_before 448 local vm_warn_cnt_before 449 local host_oops_cnt_after 450 local host_warn_cnt_after 451 local vm_oops_cnt_after 452 local vm_warn_cnt_after 453 local name 454 local rc 455 456 host_oops_cnt_before=$(dmesg | grep -c -i 'Oops') 457 host_warn_cnt_before=$(dmesg --level=warn | grep -c -i 'vsock') 458 vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops') 459 vm_warn_cnt_before=$(vm_ssh -- dmesg --level=warn | grep -c -i 'vsock') 460 461 name=$(echo "${1}" | awk '{ print $1 }') 462 eval test_"${name}" 463 rc=$? 464 465 host_oops_cnt_after=$(dmesg | grep -i 'Oops' | wc -l) 466 if [[ ${host_oops_cnt_after} -gt ${host_oops_cnt_before} ]]; then 467 echo "FAIL: kernel oops detected on host" | log_host 468 rc=$KSFT_FAIL 469 fi 470 471 host_warn_cnt_after=$(dmesg --level=warn | grep -c -i 'vsock') 472 if [[ ${host_warn_cnt_after} -gt ${host_warn_cnt_before} ]]; then 473 echo "FAIL: kernel warning detected on host" | log_host 474 rc=$KSFT_FAIL 475 fi 476 477 vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l) 478 if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then 479 echo "FAIL: kernel oops detected on vm" | log_host 480 rc=$KSFT_FAIL 481 fi 482 483 vm_warn_cnt_after=$(vm_ssh -- dmesg --level=warn | grep -c -i 'vsock') 484 if [[ ${vm_warn_cnt_after} -gt ${vm_warn_cnt_before} ]]; then 485 echo "FAIL: kernel warning detected on vm" | log_host 486 rc=$KSFT_FAIL 487 fi 488 489 return "${rc}" 490} 491 492QEMU="qemu-system-$(uname -m)" 493 494while getopts :hvsq:b o 495do 496 case $o in 497 v) VERBOSE=1;; 498 b) BUILD=1;; 499 q) QEMU=$OPTARG;; 500 h|*) usage;; 501 esac 502done 503shift $((OPTIND-1)) 504 505trap cleanup EXIT 506 507if [[ ${#} -eq 0 ]]; then 508 ARGS=("${TEST_NAMES[@]}") 509else 510 ARGS=("$@") 511fi 512 513check_args "${ARGS[@]}" 514check_deps 515check_vng 516handle_build 517 518echo "1..${#ARGS[@]}" 519 520log_host "Booting up VM" 521pidfile="$(create_pidfile)" 522vm_start "${pidfile}" 523vm_wait_for_ssh 524log_host "VM booted up" 525 526cnt_pass=0 527cnt_fail=0 528cnt_skip=0 529cnt_total=0 530for arg in "${ARGS[@]}"; do 531 run_test "${arg}" 532 rc=$? 533 if [[ ${rc} -eq $KSFT_PASS ]]; then 534 cnt_pass=$(( cnt_pass + 1 )) 535 echo "ok ${cnt_total} ${arg}" 536 elif [[ ${rc} -eq $KSFT_SKIP ]]; then 537 cnt_skip=$(( cnt_skip + 1 )) 538 echo "ok ${cnt_total} ${arg} # SKIP" 539 elif [[ ${rc} -eq $KSFT_FAIL ]]; then 540 cnt_fail=$(( cnt_fail + 1 )) 541 echo "not ok ${cnt_total} ${arg} # exit=$rc" 542 fi 543 cnt_total=$(( cnt_total + 1 )) 544done 545 546terminate_pidfiles "${pidfile}" 547 548echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}" 549echo "Log: ${LOG}" 550 551if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then 552 exit "$KSFT_PASS" 553else 554 exit "$KSFT_FAIL" 555fi 556