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