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