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 for i in $(seq "${max_intervals}"); do 255 if awk '{print $2" "$4}' /proc/net/"${protocol}"* | \ 256 grep -q "${pattern}"; then 257 break 258 fi 259 sleep "${interval}" 260 done 261} 262 263vm_wait_for_listener() { 264 local port=$1 265 266 vm_ssh <<EOF 267$(declare -f wait_for_listener) 268wait_for_listener ${port} ${WAIT_PERIOD} ${WAIT_PERIOD_MAX} 269EOF 270} 271 272host_wait_for_listener() { 273 wait_for_listener "${TEST_HOST_PORT_LISTENER}" "${WAIT_PERIOD}" "${WAIT_PERIOD_MAX}" 274} 275 276__log_stdin() { 277 cat | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }' 278} 279 280__log_args() { 281 echo "$*" | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }' 282} 283 284log() { 285 local prefix="$1" 286 287 shift 288 local redirect= 289 if [[ ${VERBOSE} -eq 0 ]]; then 290 redirect=/dev/null 291 else 292 redirect=/dev/stdout 293 fi 294 295 if [[ "$#" -eq 0 ]]; then 296 __log_stdin | tee -a "${LOG}" > ${redirect} 297 else 298 __log_args "$@" | tee -a "${LOG}" > ${redirect} 299 fi 300} 301 302log_setup() { 303 log "setup" "$@" 304} 305 306log_host() { 307 local testname=$1 308 309 shift 310 log "test:${testname}:host" "$@" 311} 312 313log_guest() { 314 local testname=$1 315 316 shift 317 log "test:${testname}:guest" "$@" 318} 319 320test_vm_server_host_client() { 321 local testname="${FUNCNAME[0]#test_}" 322 323 vm_ssh -- "${VSOCK_TEST}" \ 324 --mode=server \ 325 --control-port="${TEST_GUEST_PORT}" \ 326 --peer-cid=2 \ 327 2>&1 | log_guest "${testname}" & 328 329 vm_wait_for_listener "${TEST_GUEST_PORT}" 330 331 ${VSOCK_TEST} \ 332 --mode=client \ 333 --control-host=127.0.0.1 \ 334 --peer-cid="${VSOCK_CID}" \ 335 --control-port="${TEST_HOST_PORT}" 2>&1 | log_host "${testname}" 336 337 return $? 338} 339 340test_vm_client_host_server() { 341 local testname="${FUNCNAME[0]#test_}" 342 343 ${VSOCK_TEST} \ 344 --mode "server" \ 345 --control-port "${TEST_HOST_PORT_LISTENER}" \ 346 --peer-cid "${VSOCK_CID}" 2>&1 | log_host "${testname}" & 347 348 host_wait_for_listener 349 350 vm_ssh -- "${VSOCK_TEST}" \ 351 --mode=client \ 352 --control-host=10.0.2.2 \ 353 --peer-cid=2 \ 354 --control-port="${TEST_HOST_PORT_LISTENER}" 2>&1 | log_guest "${testname}" 355 356 return $? 357} 358 359test_vm_loopback() { 360 local testname="${FUNCNAME[0]#test_}" 361 local port=60000 # non-forwarded local port 362 363 vm_ssh -- "${VSOCK_TEST}" \ 364 --mode=server \ 365 --control-port="${port}" \ 366 --peer-cid=1 2>&1 | log_guest "${testname}" & 367 368 vm_wait_for_listener "${port}" 369 370 vm_ssh -- "${VSOCK_TEST}" \ 371 --mode=client \ 372 --control-host="127.0.0.1" \ 373 --control-port="${port}" \ 374 --peer-cid=1 2>&1 | log_guest "${testname}" 375 376 return $? 377} 378 379run_test() { 380 local host_oops_cnt_before 381 local host_warn_cnt_before 382 local vm_oops_cnt_before 383 local vm_warn_cnt_before 384 local host_oops_cnt_after 385 local host_warn_cnt_after 386 local vm_oops_cnt_after 387 local vm_warn_cnt_after 388 local name 389 local rc 390 391 host_oops_cnt_before=$(dmesg | grep -c -i 'Oops') 392 host_warn_cnt_before=$(dmesg --level=warn | wc -l) 393 vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops') 394 vm_warn_cnt_before=$(vm_ssh -- dmesg --level=warn | wc -l) 395 396 name=$(echo "${1}" | awk '{ print $1 }') 397 eval test_"${name}" 398 rc=$? 399 400 host_oops_cnt_after=$(dmesg | grep -i 'Oops' | wc -l) 401 if [[ ${host_oops_cnt_after} -gt ${host_oops_cnt_before} ]]; then 402 echo "FAIL: kernel oops detected on host" | log_host "${name}" 403 rc=$KSFT_FAIL 404 fi 405 406 host_warn_cnt_after=$(dmesg --level=warn | wc -l) 407 if [[ ${host_warn_cnt_after} -gt ${host_warn_cnt_before} ]]; then 408 echo "FAIL: kernel warning detected on host" | log_host "${name}" 409 rc=$KSFT_FAIL 410 fi 411 412 vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l) 413 if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then 414 echo "FAIL: kernel oops detected on vm" | log_host "${name}" 415 rc=$KSFT_FAIL 416 fi 417 418 vm_warn_cnt_after=$(vm_ssh -- dmesg --level=warn | wc -l) 419 if [[ ${vm_warn_cnt_after} -gt ${vm_warn_cnt_before} ]]; then 420 echo "FAIL: kernel warning detected on vm" | log_host "${name}" 421 rc=$KSFT_FAIL 422 fi 423 424 return "${rc}" 425} 426 427QEMU="qemu-system-$(uname -m)" 428 429while getopts :hvsq:b o 430do 431 case $o in 432 v) VERBOSE=1;; 433 b) BUILD=1;; 434 q) QEMU=$OPTARG;; 435 h|*) usage;; 436 esac 437done 438shift $((OPTIND-1)) 439 440trap cleanup EXIT 441 442if [[ ${#} -eq 0 ]]; then 443 ARGS=("${TEST_NAMES[@]}") 444else 445 ARGS=("$@") 446fi 447 448check_args "${ARGS[@]}" 449check_deps 450check_vng 451handle_build 452 453echo "1..${#ARGS[@]}" 454 455log_setup "Booting up VM" 456vm_start 457vm_wait_for_ssh 458log_setup "VM booted up" 459 460cnt_pass=0 461cnt_fail=0 462cnt_skip=0 463cnt_total=0 464for arg in "${ARGS[@]}"; do 465 run_test "${arg}" 466 rc=$? 467 if [[ ${rc} -eq $KSFT_PASS ]]; then 468 cnt_pass=$(( cnt_pass + 1 )) 469 echo "ok ${cnt_total} ${arg}" 470 elif [[ ${rc} -eq $KSFT_SKIP ]]; then 471 cnt_skip=$(( cnt_skip + 1 )) 472 echo "ok ${cnt_total} ${arg} # SKIP" 473 elif [[ ${rc} -eq $KSFT_FAIL ]]; then 474 cnt_fail=$(( cnt_fail + 1 )) 475 echo "not ok ${cnt_total} ${arg} # exit=$rc" 476 fi 477 cnt_total=$(( cnt_total + 1 )) 478done 479 480echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}" 481echo "Log: ${LOG}" 482 483if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then 484 exit "$KSFT_PASS" 485else 486 exit "$KSFT_FAIL" 487fi 488