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# * socat 11# 12# shellcheck disable=SC2317,SC2119 13 14readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" 15readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../) 16 17source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh 18 19readonly VSOCK_TEST="${SCRIPT_DIR}"/vsock_test 20readonly TEST_GUEST_PORT=51000 21readonly TEST_HOST_PORT=50000 22readonly TEST_HOST_PORT_LISTENER=50001 23readonly SSH_GUEST_PORT=22 24readonly SSH_HOST_PORT=2222 25readonly VSOCK_CID=1234 26readonly WAIT_PERIOD=3 27readonly WAIT_PERIOD_MAX=60 28readonly WAIT_QEMU=5 29readonly PIDFILE_TEMPLATE=/tmp/vsock_vmtest_XXXX.pid 30declare -A PIDFILES 31 32# virtme-ng offers a netdev for ssh when using "--ssh", but we also need a 33# control port forwarded for vsock_test. Because virtme-ng doesn't support 34# adding an additional port to forward to the device created from "--ssh" and 35# virtme-init mistakenly sets identical IPs to the ssh device and additional 36# devices, we instead opt out of using --ssh, add the device manually, and also 37# add the kernel cmdline options that virtme-init uses to setup the interface. 38readonly QEMU_TEST_PORT_FWD="hostfwd=tcp::${TEST_HOST_PORT}-:${TEST_GUEST_PORT}" 39readonly QEMU_SSH_PORT_FWD="hostfwd=tcp::${SSH_HOST_PORT}-:${SSH_GUEST_PORT}" 40readonly KERNEL_CMDLINE="\ 41 virtme.dhcp net.ifnames=0 biosdevname=0 \ 42 virtme.ssh virtme_ssh_channel=tcp virtme_ssh_user=$USER \ 43" 44readonly LOG=$(mktemp /tmp/vsock_vmtest_XXXX.log) 45 46# Namespace tests must use the ns_ prefix. This is checked in check_netns() and 47# is used to determine if a test needs namespace setup before test execution. 48readonly TEST_NAMES=( 49 vm_server_host_client 50 vm_client_host_server 51 vm_loopback 52 ns_host_vsock_ns_mode_ok 53 ns_host_vsock_child_ns_mode_ok 54 ns_global_same_cid_fails 55 ns_local_same_cid_ok 56 ns_global_local_same_cid_ok 57 ns_local_global_same_cid_ok 58 ns_diff_global_host_connect_to_global_vm_ok 59 ns_diff_global_host_connect_to_local_vm_fails 60 ns_diff_global_vm_connect_to_global_host_ok 61 ns_diff_global_vm_connect_to_local_host_fails 62 ns_diff_local_host_connect_to_local_vm_fails 63 ns_diff_local_vm_connect_to_local_host_fails 64 ns_diff_global_to_local_loopback_local_fails 65 ns_diff_local_to_global_loopback_fails 66 ns_diff_local_to_local_loopback_fails 67 ns_diff_global_to_global_loopback_ok 68 ns_same_local_loopback_ok 69 ns_same_local_host_connect_to_local_vm_ok 70 ns_same_local_vm_connect_to_local_host_ok 71) 72readonly TEST_DESCS=( 73 # vm_server_host_client 74 "Run vsock_test in server mode on the VM and in client mode on the host." 75 76 # vm_client_host_server 77 "Run vsock_test in client mode on the VM and in server mode on the host." 78 79 # vm_loopback 80 "Run vsock_test using the loopback transport in the VM." 81 82 # ns_host_vsock_ns_mode_ok 83 "Check /proc/sys/net/vsock/ns_mode strings on the host." 84 85 # ns_host_vsock_child_ns_mode_ok 86 "Check /proc/sys/net/vsock/ns_mode is read-only and child_ns_mode is writable." 87 88 # ns_global_same_cid_fails 89 "Check QEMU fails to start two VMs with same CID in two different global namespaces." 90 91 # ns_local_same_cid_ok 92 "Check QEMU successfully starts two VMs with same CID in two different local namespaces." 93 94 # ns_global_local_same_cid_ok 95 "Check QEMU successfully starts one VM in a global ns and then another VM in a local ns with the same CID." 96 97 # ns_local_global_same_cid_ok 98 "Check QEMU successfully starts one VM in a local ns and then another VM in a global ns with the same CID." 99 100 # ns_diff_global_host_connect_to_global_vm_ok 101 "Run vsock_test client in global ns with server in VM in another global ns." 102 103 # ns_diff_global_host_connect_to_local_vm_fails 104 "Run socat to test a process in a global ns fails to connect to a VM in a local ns." 105 106 # ns_diff_global_vm_connect_to_global_host_ok 107 "Run vsock_test client in VM in a global ns with server in another global ns." 108 109 # ns_diff_global_vm_connect_to_local_host_fails 110 "Run socat to test a VM in a global ns fails to connect to a host process in a local ns." 111 112 # ns_diff_local_host_connect_to_local_vm_fails 113 "Run socat to test a host process in a local ns fails to connect to a VM in another local ns." 114 115 # ns_diff_local_vm_connect_to_local_host_fails 116 "Run socat to test a VM in a local ns fails to connect to a host process in another local ns." 117 118 # ns_diff_global_to_local_loopback_local_fails 119 "Run socat to test a loopback vsock in a global ns fails to connect to a vsock in a local ns." 120 121 # ns_diff_local_to_global_loopback_fails 122 "Run socat to test a loopback vsock in a local ns fails to connect to a vsock in a global ns." 123 124 # ns_diff_local_to_local_loopback_fails 125 "Run socat to test a loopback vsock in a local ns fails to connect to a vsock in another local ns." 126 127 # ns_diff_global_to_global_loopback_ok 128 "Run socat to test a loopback vsock in a global ns successfully connects to a vsock in another global ns." 129 130 # ns_same_local_loopback_ok 131 "Run socat to test a loopback vsock in a local ns successfully connects to a vsock in the same ns." 132 133 # ns_same_local_host_connect_to_local_vm_ok 134 "Run vsock_test client in a local ns with server in VM in same ns." 135 136 # ns_same_local_vm_connect_to_local_host_ok 137 "Run vsock_test client in VM in a local ns with server in same ns." 138) 139 140readonly USE_SHARED_VM=( 141 vm_server_host_client 142 vm_client_host_server 143 vm_loopback 144) 145readonly NS_MODES=("local" "global") 146 147VERBOSE=0 148 149usage() { 150 local name 151 local desc 152 local i 153 154 echo 155 echo "$0 [OPTIONS] [TEST]..." 156 echo "If no TEST argument is given, all tests will be run." 157 echo 158 echo "Options" 159 echo " -b: build the kernel from the current source tree and use it for guest VMs" 160 echo " -q: set the path to or name of qemu binary" 161 echo " -v: verbose output" 162 echo 163 echo "Available tests" 164 165 for ((i = 0; i < ${#TEST_NAMES[@]}; i++)); do 166 name=${TEST_NAMES[${i}]} 167 desc=${TEST_DESCS[${i}]} 168 printf "\t%-55s%-35s\n" "${name}" "${desc}" 169 done 170 echo 171 172 exit 1 173} 174 175die() { 176 echo "$*" >&2 177 exit "${KSFT_FAIL}" 178} 179 180check_result() { 181 local rc arg 182 183 rc=$1 184 arg=$2 185 186 cnt_total=$(( cnt_total + 1 )) 187 188 if [[ ${rc} -eq ${KSFT_PASS} ]]; then 189 cnt_pass=$(( cnt_pass + 1 )) 190 echo "ok ${cnt_total} ${arg}" 191 elif [[ ${rc} -eq ${KSFT_SKIP} ]]; then 192 cnt_skip=$(( cnt_skip + 1 )) 193 echo "ok ${cnt_total} ${arg} # SKIP" 194 elif [[ ${rc} -eq ${KSFT_FAIL} ]]; then 195 cnt_fail=$(( cnt_fail + 1 )) 196 echo "not ok ${cnt_total} ${arg} # exit=${rc}" 197 fi 198} 199 200add_namespaces() { 201 local orig_mode 202 orig_mode=$(cat /proc/sys/net/vsock/child_ns_mode) 203 204 for mode in "${NS_MODES[@]}"; do 205 echo "${mode}" > /proc/sys/net/vsock/child_ns_mode 206 ip netns add "${mode}0" 2>/dev/null 207 ip netns add "${mode}1" 2>/dev/null 208 done 209 210 echo "${orig_mode}" > /proc/sys/net/vsock/child_ns_mode 211} 212 213init_namespaces() { 214 for mode in "${NS_MODES[@]}"; do 215 # we need lo for qemu port forwarding 216 ip netns exec "${mode}0" ip link set dev lo up 217 ip netns exec "${mode}1" ip link set dev lo up 218 done 219} 220 221del_namespaces() { 222 for mode in "${NS_MODES[@]}"; do 223 ip netns del "${mode}0" &>/dev/null 224 ip netns del "${mode}1" &>/dev/null 225 log_host "removed ns ${mode}0" 226 log_host "removed ns ${mode}1" 227 done 228} 229 230vm_ssh() { 231 local ns_exec 232 233 if [[ "${1}" == init_ns ]]; then 234 ns_exec="" 235 else 236 ns_exec="ip netns exec ${1}" 237 fi 238 239 shift 240 241 ${ns_exec} ssh -q -o UserKnownHostsFile=/dev/null -p "${SSH_HOST_PORT}" localhost "$@" 242 243 return $? 244} 245 246cleanup() { 247 terminate_pidfiles "${!PIDFILES[@]}" 248 del_namespaces 249} 250 251check_args() { 252 local found 253 254 for arg in "$@"; do 255 found=0 256 for name in "${TEST_NAMES[@]}"; do 257 if [[ "${name}" = "${arg}" ]]; then 258 found=1 259 break 260 fi 261 done 262 263 if [[ "${found}" -eq 0 ]]; then 264 echo "${arg} is not an available test" >&2 265 usage 266 fi 267 done 268 269 for arg in "$@"; do 270 if ! command -v > /dev/null "test_${arg}"; then 271 echo "Test ${arg} not found" >&2 272 usage 273 fi 274 done 275} 276 277check_deps() { 278 for dep in vng ${QEMU} busybox pkill ssh ss socat; do 279 if [[ ! -x $(command -v "${dep}") ]]; then 280 echo -e "skip: dependency ${dep} not found!\n" 281 exit "${KSFT_SKIP}" 282 fi 283 done 284 285 if [[ ! -x $(command -v "${VSOCK_TEST}") ]]; then 286 printf "skip: %s not found!" "${VSOCK_TEST}" 287 printf " Please build the kselftest vsock target.\n" 288 exit "${KSFT_SKIP}" 289 fi 290} 291 292check_netns() { 293 local tname=$1 294 295 # If the test requires NS support, check if NS support exists 296 # using /proc/self/ns 297 if [[ "${tname}" =~ ^ns_ ]] && 298 [[ ! -e /proc/self/ns ]]; then 299 log_host "No NS support detected for test ${tname}" 300 return 1 301 fi 302 303 return 0 304} 305 306check_vng() { 307 local tested_versions 308 local version 309 local ok 310 311 tested_versions=("1.33" "1.36" "1.37") 312 version="$(vng --version)" 313 314 ok=0 315 for tv in "${tested_versions[@]}"; do 316 if [[ "${version}" == *"${tv}"* ]]; then 317 ok=1 318 break 319 fi 320 done 321 322 if [[ ! "${ok}" -eq 1 ]]; then 323 printf "warning: vng version '%s' has not been tested and may " "${version}" >&2 324 printf "not function properly.\n\tThe following versions have been tested: " >&2 325 echo "${tested_versions[@]}" >&2 326 fi 327} 328 329check_socat() { 330 local support_string 331 332 support_string="$(socat -V)" 333 334 if [[ "${support_string}" != *"WITH_VSOCK 1"* ]]; then 335 die "err: socat is missing vsock support" 336 fi 337 338 if [[ "${support_string}" != *"WITH_UNIX 1"* ]]; then 339 die "err: socat is missing unix support" 340 fi 341} 342 343handle_build() { 344 if [[ ! "${BUILD}" -eq 1 ]]; then 345 return 346 fi 347 348 if [[ ! -d "${KERNEL_CHECKOUT}" ]]; then 349 echo "-b requires vmtest.sh called from the kernel source tree" >&2 350 exit 1 351 fi 352 353 pushd "${KERNEL_CHECKOUT}" &>/dev/null 354 355 if ! vng --kconfig --config "${SCRIPT_DIR}"/config; then 356 die "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})" 357 fi 358 359 if ! make -j$(nproc); then 360 die "failed to build kernel from source tree (${KERNEL_CHECKOUT})" 361 fi 362 363 popd &>/dev/null 364} 365 366create_pidfile() { 367 local pidfile 368 369 pidfile=$(mktemp "${PIDFILE_TEMPLATE}") 370 PIDFILES["${pidfile}"]=1 371 372 echo "${pidfile}" 373} 374 375terminate_pidfiles() { 376 local pidfile 377 378 for pidfile in "$@"; do 379 if [[ -s "${pidfile}" ]]; then 380 pkill -SIGTERM -F "${pidfile}" > /dev/null 2>&1 381 fi 382 383 if [[ -e "${pidfile}" ]]; then 384 rm -f "${pidfile}" 385 fi 386 387 unset "PIDFILES[${pidfile}]" 388 done 389} 390 391terminate_pids() { 392 local pid 393 394 for pid in "$@"; do 395 kill -SIGTERM "${pid}" &>/dev/null || : 396 done 397} 398 399vm_start() { 400 local pidfile=$1 401 local ns=$2 402 local logfile=/dev/null 403 local verbose_opt="" 404 local kernel_opt="" 405 local qemu_opts="" 406 local ns_exec="" 407 local qemu 408 409 qemu=$(command -v "${QEMU}") 410 411 if [[ "${VERBOSE}" -eq 1 ]]; then 412 verbose_opt="--verbose" 413 logfile=/dev/stdout 414 fi 415 416 qemu_opts="\ 417 -netdev user,id=n0,${QEMU_TEST_PORT_FWD},${QEMU_SSH_PORT_FWD} \ 418 -device virtio-net-pci,netdev=n0 \ 419 -device vhost-vsock-pci,guest-cid=${VSOCK_CID} \ 420 --pidfile ${pidfile} 421 " 422 423 if [[ "${BUILD}" -eq 1 ]]; then 424 kernel_opt="${KERNEL_CHECKOUT}" 425 fi 426 427 if [[ "${ns}" != "init_ns" ]]; then 428 ns_exec="ip netns exec ${ns}" 429 fi 430 431 ${ns_exec} vng \ 432 --run \ 433 ${kernel_opt} \ 434 ${verbose_opt} \ 435 --qemu-opts="${qemu_opts}" \ 436 --qemu="${qemu}" \ 437 --user root \ 438 --append "${KERNEL_CMDLINE}" \ 439 --rw &> ${logfile} & 440 441 timeout "${WAIT_QEMU}" \ 442 bash -c 'while [[ ! -s '"${pidfile}"' ]]; do sleep 1; done; exit 0' 443} 444 445vm_wait_for_ssh() { 446 local ns=$1 447 local i 448 449 i=0 450 while true; do 451 if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then 452 die "Timed out waiting for guest ssh" 453 fi 454 455 if vm_ssh "${ns}" -- true; then 456 break 457 fi 458 i=$(( i + 1 )) 459 sleep ${WAIT_PERIOD} 460 done 461} 462 463# derived from selftests/net/net_helper.sh 464wait_for_listener() 465{ 466 local port=$1 467 local interval=$2 468 local max_intervals=$3 469 local protocol=$4 470 local i 471 472 for i in $(seq "${max_intervals}"); do 473 case "${protocol}" in 474 tcp) 475 if ss --listening --tcp --numeric | grep -q ":${port} "; then 476 break 477 fi 478 ;; 479 vsock) 480 if ss --listening --vsock --numeric | grep -q ":${port} "; then 481 break 482 fi 483 ;; 484 unix) 485 # For unix sockets, port is actually the socket path 486 if ss --listening --unix | grep -q "${port}"; then 487 break 488 fi 489 ;; 490 *) 491 echo "Unknown protocol: ${protocol}" >&2 492 break 493 ;; 494 esac 495 sleep "${interval}" 496 done 497} 498 499vm_wait_for_listener() { 500 local ns=$1 501 local port=$2 502 local protocol=$3 503 504 vm_ssh "${ns}" <<EOF 505$(declare -f wait_for_listener) 506wait_for_listener ${port} ${WAIT_PERIOD} ${WAIT_PERIOD_MAX} ${protocol} 507EOF 508} 509 510host_wait_for_listener() { 511 local ns=$1 512 local port=$2 513 local protocol=$3 514 515 if [[ "${ns}" == "init_ns" ]]; then 516 wait_for_listener "${port}" "${WAIT_PERIOD}" "${WAIT_PERIOD_MAX}" "${protocol}" 517 else 518 ip netns exec "${ns}" bash <<-EOF 519 $(declare -f wait_for_listener) 520 wait_for_listener ${port} ${WAIT_PERIOD} ${WAIT_PERIOD_MAX} ${protocol} 521 EOF 522 fi 523} 524 525vm_dmesg_oops_count() { 526 local ns=$1 527 528 vm_ssh "${ns}" -- dmesg 2>/dev/null | grep -c -i 'Oops' 529} 530 531vm_dmesg_warn_count() { 532 local ns=$1 533 534 vm_ssh "${ns}" -- dmesg --level=warn 2>/dev/null | grep -c -i 'vsock' 535} 536 537vm_dmesg_check() { 538 local pidfile=$1 539 local ns=$2 540 local oops_before=$3 541 local warn_before=$4 542 local oops_after warn_after 543 544 oops_after=$(vm_dmesg_oops_count "${ns}") 545 if [[ "${oops_after}" -gt "${oops_before}" ]]; then 546 echo "FAIL: kernel oops detected on vm in ns ${ns}" | log_host 547 return 1 548 fi 549 550 warn_after=$(vm_dmesg_warn_count "${ns}") 551 if [[ "${warn_after}" -gt "${warn_before}" ]]; then 552 echo "FAIL: kernel warning detected on vm in ns ${ns}" | log_host 553 return 1 554 fi 555 556 return 0 557} 558 559vm_vsock_test() { 560 local ns=$1 561 local host=$2 562 local cid=$3 563 local port=$4 564 local rc 565 566 # log output and use pipefail to respect vsock_test errors 567 set -o pipefail 568 if [[ "${host}" != server ]]; then 569 vm_ssh "${ns}" -- "${VSOCK_TEST}" \ 570 --mode=client \ 571 --control-host="${host}" \ 572 --peer-cid="${cid}" \ 573 --control-port="${port}" \ 574 2>&1 | log_guest 575 rc=$? 576 else 577 vm_ssh "${ns}" -- "${VSOCK_TEST}" \ 578 --mode=server \ 579 --peer-cid="${cid}" \ 580 --control-port="${port}" \ 581 2>&1 | log_guest & 582 rc=$? 583 584 if [[ $rc -ne 0 ]]; then 585 set +o pipefail 586 return $rc 587 fi 588 589 vm_wait_for_listener "${ns}" "${port}" "tcp" 590 rc=$? 591 fi 592 set +o pipefail 593 594 return $rc 595} 596 597host_vsock_test() { 598 local ns=$1 599 local host=$2 600 local cid=$3 601 local port=$4 602 shift 4 603 local extra_args=("$@") 604 local rc 605 606 local cmd="${VSOCK_TEST}" 607 if [[ "${ns}" != "init_ns" ]]; then 608 cmd="ip netns exec ${ns} ${cmd}" 609 fi 610 611 # log output and use pipefail to respect vsock_test errors 612 set -o pipefail 613 if [[ "${host}" != server ]]; then 614 ${cmd} \ 615 --mode=client \ 616 --peer-cid="${cid}" \ 617 --control-host="${host}" \ 618 --control-port="${port}" \ 619 "${extra_args[@]}" 2>&1 | log_host 620 rc=$? 621 else 622 ${cmd} \ 623 --mode=server \ 624 --peer-cid="${cid}" \ 625 --control-port="${port}" \ 626 "${extra_args[@]}" 2>&1 | log_host & 627 rc=$? 628 629 if [[ $rc -ne 0 ]]; then 630 set +o pipefail 631 return $rc 632 fi 633 634 host_wait_for_listener "${ns}" "${port}" "tcp" 635 rc=$? 636 fi 637 set +o pipefail 638 639 return $rc 640} 641 642log() { 643 local redirect 644 local prefix 645 646 if [[ ${VERBOSE} -eq 0 ]]; then 647 redirect=/dev/null 648 else 649 redirect=/dev/stdout 650 fi 651 652 prefix="${LOG_PREFIX:-}" 653 654 if [[ "$#" -eq 0 ]]; then 655 if [[ -n "${prefix}" ]]; then 656 awk -v prefix="${prefix}" '{printf "%s: %s\n", prefix, $0}' 657 else 658 cat 659 fi 660 else 661 if [[ -n "${prefix}" ]]; then 662 echo "${prefix}: " "$@" 663 else 664 echo "$@" 665 fi 666 fi | tee -a "${LOG}" > "${redirect}" 667} 668 669log_host() { 670 LOG_PREFIX=host log "$@" 671} 672 673log_guest() { 674 LOG_PREFIX=guest log "$@" 675} 676 677ns_get_mode() { 678 local ns=$1 679 680 ip netns exec "${ns}" cat /proc/sys/net/vsock/ns_mode 2>/dev/null 681} 682 683test_ns_host_vsock_ns_mode_ok() { 684 for mode in "${NS_MODES[@]}"; do 685 local actual 686 687 actual=$(ns_get_mode "${mode}0") 688 if [[ "${actual}" != "${mode}" ]]; then 689 log_host "expected mode ${mode}, got ${actual}" 690 return "${KSFT_FAIL}" 691 fi 692 done 693 694 return "${KSFT_PASS}" 695} 696 697test_ns_diff_global_host_connect_to_global_vm_ok() { 698 local oops_before warn_before 699 local pids pid pidfile 700 local ns0 ns1 port 701 declare -a pids 702 local unixfile 703 ns0="global0" 704 ns1="global1" 705 port=1234 706 local rc 707 708 init_namespaces 709 710 pidfile="$(create_pidfile)" 711 712 if ! vm_start "${pidfile}" "${ns0}"; then 713 return "${KSFT_FAIL}" 714 fi 715 716 vm_wait_for_ssh "${ns0}" 717 oops_before=$(vm_dmesg_oops_count "${ns0}") 718 warn_before=$(vm_dmesg_warn_count "${ns0}") 719 720 unixfile=$(mktemp -u /tmp/XXXX.sock) 721 ip netns exec "${ns1}" \ 722 socat TCP-LISTEN:"${TEST_HOST_PORT}",fork \ 723 UNIX-CONNECT:"${unixfile}" & 724 pids+=($!) 725 host_wait_for_listener "${ns1}" "${TEST_HOST_PORT}" "tcp" 726 727 ip netns exec "${ns0}" socat UNIX-LISTEN:"${unixfile}",fork \ 728 TCP-CONNECT:localhost:"${TEST_HOST_PORT}" & 729 pids+=($!) 730 host_wait_for_listener "${ns0}" "${unixfile}" "unix" 731 732 vm_vsock_test "${ns0}" "server" 2 "${TEST_GUEST_PORT}" 733 vm_wait_for_listener "${ns0}" "${TEST_GUEST_PORT}" "tcp" 734 host_vsock_test "${ns1}" "127.0.0.1" "${VSOCK_CID}" "${TEST_HOST_PORT}" 735 rc=$? 736 737 vm_dmesg_check "${pidfile}" "${ns0}" "${oops_before}" "${warn_before}" 738 dmesg_rc=$? 739 740 terminate_pids "${pids[@]}" 741 terminate_pidfiles "${pidfile}" 742 743 if [[ "${rc}" -ne 0 ]] || [[ "${dmesg_rc}" -ne 0 ]]; then 744 return "${KSFT_FAIL}" 745 fi 746 747 return "${KSFT_PASS}" 748} 749 750test_ns_diff_global_host_connect_to_local_vm_fails() { 751 local oops_before warn_before 752 local ns0="global0" 753 local ns1="local0" 754 local port=12345 755 local dmesg_rc 756 local pidfile 757 local result 758 local pid 759 760 init_namespaces 761 762 outfile=$(mktemp) 763 764 pidfile="$(create_pidfile)" 765 if ! vm_start "${pidfile}" "${ns1}"; then 766 log_host "failed to start vm (cid=${VSOCK_CID}, ns=${ns0})" 767 return "${KSFT_FAIL}" 768 fi 769 770 vm_wait_for_ssh "${ns1}" 771 oops_before=$(vm_dmesg_oops_count "${ns1}") 772 warn_before=$(vm_dmesg_warn_count "${ns1}") 773 774 vm_ssh "${ns1}" -- socat VSOCK-LISTEN:"${port}" STDOUT > "${outfile}" & 775 vm_wait_for_listener "${ns1}" "${port}" "vsock" 776 echo TEST | ip netns exec "${ns0}" \ 777 socat STDIN VSOCK-CONNECT:"${VSOCK_CID}":"${port}" 2>/dev/null 778 779 vm_dmesg_check "${pidfile}" "${ns1}" "${oops_before}" "${warn_before}" 780 dmesg_rc=$? 781 782 terminate_pidfiles "${pidfile}" 783 result=$(cat "${outfile}") 784 rm -f "${outfile}" 785 786 if [[ "${result}" == "TEST" ]] || [[ "${dmesg_rc}" -ne 0 ]]; then 787 return "${KSFT_FAIL}" 788 fi 789 790 return "${KSFT_PASS}" 791} 792 793test_ns_diff_global_vm_connect_to_global_host_ok() { 794 local oops_before warn_before 795 local ns0="global0" 796 local ns1="global1" 797 local port=12345 798 local unixfile 799 local dmesg_rc 800 local pidfile 801 local pids 802 local rc 803 804 init_namespaces 805 806 declare -a pids 807 808 log_host "Setup socat bridge from ns ${ns0} to ns ${ns1} over port ${port}" 809 810 unixfile=$(mktemp -u /tmp/XXXX.sock) 811 812 ip netns exec "${ns0}" \ 813 socat TCP-LISTEN:"${port}" UNIX-CONNECT:"${unixfile}" & 814 pids+=($!) 815 host_wait_for_listener "${ns0}" "${port}" "tcp" 816 817 ip netns exec "${ns1}" \ 818 socat UNIX-LISTEN:"${unixfile}" TCP-CONNECT:127.0.0.1:"${port}" & 819 pids+=($!) 820 host_wait_for_listener "${ns1}" "${unixfile}" "unix" 821 822 log_host "Launching ${VSOCK_TEST} in ns ${ns1}" 823 host_vsock_test "${ns1}" "server" "${VSOCK_CID}" "${port}" 824 825 pidfile="$(create_pidfile)" 826 if ! vm_start "${pidfile}" "${ns0}"; then 827 log_host "failed to start vm (cid=${cid}, ns=${ns0})" 828 terminate_pids "${pids[@]}" 829 rm -f "${unixfile}" 830 return "${KSFT_FAIL}" 831 fi 832 833 vm_wait_for_ssh "${ns0}" 834 835 oops_before=$(vm_dmesg_oops_count "${ns0}") 836 warn_before=$(vm_dmesg_warn_count "${ns0}") 837 838 vm_vsock_test "${ns0}" "10.0.2.2" 2 "${port}" 839 rc=$? 840 841 vm_dmesg_check "${pidfile}" "${ns0}" "${oops_before}" "${warn_before}" 842 dmesg_rc=$? 843 844 terminate_pidfiles "${pidfile}" 845 terminate_pids "${pids[@]}" 846 rm -f "${unixfile}" 847 848 if [[ "${rc}" -ne 0 ]] || [[ "${dmesg_rc}" -ne 0 ]]; then 849 return "${KSFT_FAIL}" 850 fi 851 852 return "${KSFT_PASS}" 853 854} 855 856test_ns_diff_global_vm_connect_to_local_host_fails() { 857 local ns0="global0" 858 local ns1="local0" 859 local port=12345 860 local oops_before warn_before 861 local dmesg_rc 862 local pidfile 863 local result 864 local pid 865 866 init_namespaces 867 868 log_host "Launching socat in ns ${ns1}" 869 outfile=$(mktemp) 870 871 ip netns exec "${ns1}" socat VSOCK-LISTEN:"${port}" STDOUT &> "${outfile}" & 872 pid=$! 873 host_wait_for_listener "${ns1}" "${port}" "vsock" 874 875 pidfile="$(create_pidfile)" 876 if ! vm_start "${pidfile}" "${ns0}"; then 877 log_host "failed to start vm (cid=${cid}, ns=${ns0})" 878 terminate_pids "${pid}" 879 rm -f "${outfile}" 880 return "${KSFT_FAIL}" 881 fi 882 883 vm_wait_for_ssh "${ns0}" 884 885 oops_before=$(vm_dmesg_oops_count "${ns0}") 886 warn_before=$(vm_dmesg_warn_count "${ns0}") 887 888 vm_ssh "${ns0}" -- \ 889 bash -c "echo TEST | socat STDIN VSOCK-CONNECT:2:${port}" 2>&1 | log_guest 890 891 vm_dmesg_check "${pidfile}" "${ns0}" "${oops_before}" "${warn_before}" 892 dmesg_rc=$? 893 894 terminate_pidfiles "${pidfile}" 895 terminate_pids "${pid}" 896 897 result=$(cat "${outfile}") 898 rm -f "${outfile}" 899 900 if [[ "${result}" != TEST ]] && [[ "${dmesg_rc}" -eq 0 ]]; then 901 return "${KSFT_PASS}" 902 fi 903 904 return "${KSFT_FAIL}" 905} 906 907test_ns_diff_local_host_connect_to_local_vm_fails() { 908 local ns0="local0" 909 local ns1="local1" 910 local port=12345 911 local oops_before warn_before 912 local dmesg_rc 913 local pidfile 914 local result 915 local pid 916 917 init_namespaces 918 919 outfile=$(mktemp) 920 921 pidfile="$(create_pidfile)" 922 if ! vm_start "${pidfile}" "${ns1}"; then 923 log_host "failed to start vm (cid=${cid}, ns=${ns0})" 924 return "${KSFT_FAIL}" 925 fi 926 927 vm_wait_for_ssh "${ns1}" 928 oops_before=$(vm_dmesg_oops_count "${ns1}") 929 warn_before=$(vm_dmesg_warn_count "${ns1}") 930 931 vm_ssh "${ns1}" -- socat VSOCK-LISTEN:"${port}" STDOUT > "${outfile}" & 932 vm_wait_for_listener "${ns1}" "${port}" "vsock" 933 934 echo TEST | ip netns exec "${ns0}" \ 935 socat STDIN VSOCK-CONNECT:"${VSOCK_CID}":"${port}" 2>/dev/null 936 937 vm_dmesg_check "${pidfile}" "${ns1}" "${oops_before}" "${warn_before}" 938 dmesg_rc=$? 939 940 terminate_pidfiles "${pidfile}" 941 942 result=$(cat "${outfile}") 943 rm -f "${outfile}" 944 945 if [[ "${result}" != TEST ]] && [[ "${dmesg_rc}" -eq 0 ]]; then 946 return "${KSFT_PASS}" 947 fi 948 949 return "${KSFT_FAIL}" 950} 951 952test_ns_diff_local_vm_connect_to_local_host_fails() { 953 local oops_before warn_before 954 local ns0="local0" 955 local ns1="local1" 956 local port=12345 957 local dmesg_rc 958 local pidfile 959 local result 960 local pid 961 962 init_namespaces 963 964 log_host "Launching socat in ns ${ns1}" 965 outfile=$(mktemp) 966 ip netns exec "${ns1}" socat VSOCK-LISTEN:"${port}" STDOUT &> "${outfile}" & 967 pid=$! 968 host_wait_for_listener "${ns1}" "${port}" "vsock" 969 970 pidfile="$(create_pidfile)" 971 if ! vm_start "${pidfile}" "${ns0}"; then 972 log_host "failed to start vm (cid=${cid}, ns=${ns0})" 973 rm -f "${outfile}" 974 return "${KSFT_FAIL}" 975 fi 976 977 vm_wait_for_ssh "${ns0}" 978 oops_before=$(vm_dmesg_oops_count "${ns0}") 979 warn_before=$(vm_dmesg_warn_count "${ns0}") 980 981 vm_ssh "${ns0}" -- \ 982 bash -c "echo TEST | socat STDIN VSOCK-CONNECT:2:${port}" 2>&1 | log_guest 983 984 vm_dmesg_check "${pidfile}" "${ns0}" "${oops_before}" "${warn_before}" 985 dmesg_rc=$? 986 987 terminate_pidfiles "${pidfile}" 988 terminate_pids "${pid}" 989 990 result=$(cat "${outfile}") 991 rm -f "${outfile}" 992 993 if [[ "${result}" != TEST ]] && [[ "${dmesg_rc}" -eq 0 ]]; then 994 return "${KSFT_PASS}" 995 fi 996 997 return "${KSFT_FAIL}" 998} 999 1000__test_loopback_two_netns() { 1001 local ns0=$1 1002 local ns1=$2 1003 local port=12345 1004 local result 1005 local pid 1006 1007 modprobe vsock_loopback &> /dev/null || : 1008 1009 log_host "Launching socat in ns ${ns1}" 1010 outfile=$(mktemp) 1011 1012 ip netns exec "${ns1}" socat VSOCK-LISTEN:"${port}" STDOUT > "${outfile}" 2>/dev/null & 1013 pid=$! 1014 host_wait_for_listener "${ns1}" "${port}" "vsock" 1015 1016 log_host "Launching socat in ns ${ns0}" 1017 echo TEST | ip netns exec "${ns0}" socat STDIN VSOCK-CONNECT:1:"${port}" 2>/dev/null 1018 terminate_pids "${pid}" 1019 1020 result=$(cat "${outfile}") 1021 rm -f "${outfile}" 1022 1023 if [[ "${result}" == TEST ]]; then 1024 return 0 1025 fi 1026 1027 return 1 1028} 1029 1030test_ns_diff_global_to_local_loopback_local_fails() { 1031 init_namespaces 1032 1033 if ! __test_loopback_two_netns "global0" "local0"; then 1034 return "${KSFT_PASS}" 1035 fi 1036 1037 return "${KSFT_FAIL}" 1038} 1039 1040test_ns_diff_local_to_global_loopback_fails() { 1041 init_namespaces 1042 1043 if ! __test_loopback_two_netns "local0" "global0"; then 1044 return "${KSFT_PASS}" 1045 fi 1046 1047 return "${KSFT_FAIL}" 1048} 1049 1050test_ns_diff_local_to_local_loopback_fails() { 1051 init_namespaces 1052 1053 if ! __test_loopback_two_netns "local0" "local1"; then 1054 return "${KSFT_PASS}" 1055 fi 1056 1057 return "${KSFT_FAIL}" 1058} 1059 1060test_ns_diff_global_to_global_loopback_ok() { 1061 init_namespaces 1062 1063 if __test_loopback_two_netns "global0" "global1"; then 1064 return "${KSFT_PASS}" 1065 fi 1066 1067 return "${KSFT_FAIL}" 1068} 1069 1070test_ns_same_local_loopback_ok() { 1071 init_namespaces 1072 1073 if __test_loopback_two_netns "local0" "local0"; then 1074 return "${KSFT_PASS}" 1075 fi 1076 1077 return "${KSFT_FAIL}" 1078} 1079 1080test_ns_same_local_host_connect_to_local_vm_ok() { 1081 local oops_before warn_before 1082 local ns="local0" 1083 local port=1234 1084 local dmesg_rc 1085 local pidfile 1086 local rc 1087 1088 init_namespaces 1089 1090 pidfile="$(create_pidfile)" 1091 1092 if ! vm_start "${pidfile}" "${ns}"; then 1093 return "${KSFT_FAIL}" 1094 fi 1095 1096 vm_wait_for_ssh "${ns}" 1097 oops_before=$(vm_dmesg_oops_count "${ns}") 1098 warn_before=$(vm_dmesg_warn_count "${ns}") 1099 1100 vm_vsock_test "${ns}" "server" 2 "${TEST_GUEST_PORT}" 1101 1102 # Skip test 29 (transport release use-after-free): This test attempts 1103 # binding both G2H and H2G CIDs. Because virtio-vsock (G2H) doesn't 1104 # support local namespaces the test will fail when 1105 # transport_g2h->stream_allow() returns false. This edge case only 1106 # happens for vsock_test in client mode on the host in a local 1107 # namespace. This is a false positive. 1108 host_vsock_test "${ns}" "127.0.0.1" "${VSOCK_CID}" "${TEST_HOST_PORT}" --skip=29 1109 rc=$? 1110 1111 vm_dmesg_check "${pidfile}" "${ns}" "${oops_before}" "${warn_before}" 1112 dmesg_rc=$? 1113 1114 terminate_pidfiles "${pidfile}" 1115 1116 if [[ "${rc}" -ne 0 ]] || [[ "${dmesg_rc}" -ne 0 ]]; then 1117 return "${KSFT_FAIL}" 1118 fi 1119 1120 return "${KSFT_PASS}" 1121} 1122 1123test_ns_same_local_vm_connect_to_local_host_ok() { 1124 local oops_before warn_before 1125 local ns="local0" 1126 local port=1234 1127 local dmesg_rc 1128 local pidfile 1129 local rc 1130 1131 init_namespaces 1132 1133 pidfile="$(create_pidfile)" 1134 1135 if ! vm_start "${pidfile}" "${ns}"; then 1136 return "${KSFT_FAIL}" 1137 fi 1138 1139 vm_wait_for_ssh "${ns}" 1140 oops_before=$(vm_dmesg_oops_count "${ns}") 1141 warn_before=$(vm_dmesg_warn_count "${ns}") 1142 1143 host_vsock_test "${ns}" "server" "${VSOCK_CID}" "${port}" 1144 vm_vsock_test "${ns}" "10.0.2.2" 2 "${port}" 1145 rc=$? 1146 1147 vm_dmesg_check "${pidfile}" "${ns}" "${oops_before}" "${warn_before}" 1148 dmesg_rc=$? 1149 1150 terminate_pidfiles "${pidfile}" 1151 1152 if [[ "${rc}" -ne 0 ]] || [[ "${dmesg_rc}" -ne 0 ]]; then 1153 return "${KSFT_FAIL}" 1154 fi 1155 1156 return "${KSFT_PASS}" 1157} 1158 1159namespaces_can_boot_same_cid() { 1160 local ns0=$1 1161 local ns1=$2 1162 local pidfile1 pidfile2 1163 local rc 1164 1165 pidfile1="$(create_pidfile)" 1166 1167 # The first VM should be able to start. If it can't then we have 1168 # problems and need to return non-zero. 1169 if ! vm_start "${pidfile1}" "${ns0}"; then 1170 return 1 1171 fi 1172 1173 pidfile2="$(create_pidfile)" 1174 vm_start "${pidfile2}" "${ns1}" 1175 rc=$? 1176 terminate_pidfiles "${pidfile1}" "${pidfile2}" 1177 1178 return "${rc}" 1179} 1180 1181test_ns_global_same_cid_fails() { 1182 init_namespaces 1183 1184 if namespaces_can_boot_same_cid "global0" "global1"; then 1185 return "${KSFT_FAIL}" 1186 fi 1187 1188 return "${KSFT_PASS}" 1189} 1190 1191test_ns_local_global_same_cid_ok() { 1192 init_namespaces 1193 1194 if namespaces_can_boot_same_cid "local0" "global0"; then 1195 return "${KSFT_PASS}" 1196 fi 1197 1198 return "${KSFT_FAIL}" 1199} 1200 1201test_ns_global_local_same_cid_ok() { 1202 init_namespaces 1203 1204 if namespaces_can_boot_same_cid "global0" "local0"; then 1205 return "${KSFT_PASS}" 1206 fi 1207 1208 return "${KSFT_FAIL}" 1209} 1210 1211test_ns_local_same_cid_ok() { 1212 init_namespaces 1213 1214 if namespaces_can_boot_same_cid "local0" "local1"; then 1215 return "${KSFT_PASS}" 1216 fi 1217 1218 return "${KSFT_FAIL}" 1219} 1220 1221test_ns_host_vsock_child_ns_mode_ok() { 1222 local orig_mode 1223 local rc 1224 1225 orig_mode=$(cat /proc/sys/net/vsock/child_ns_mode) 1226 1227 rc="${KSFT_PASS}" 1228 for mode in "${NS_MODES[@]}"; do 1229 local ns="${mode}0" 1230 1231 if echo "${mode}" 2>/dev/null > /proc/sys/net/vsock/ns_mode; then 1232 log_host "ns_mode should be read-only but write succeeded" 1233 rc="${KSFT_FAIL}" 1234 continue 1235 fi 1236 1237 if ! echo "${mode}" > /proc/sys/net/vsock/child_ns_mode; then 1238 log_host "child_ns_mode should be writable to ${mode}" 1239 rc="${KSFT_FAIL}" 1240 continue 1241 fi 1242 done 1243 1244 echo "${orig_mode}" > /proc/sys/net/vsock/child_ns_mode 1245 1246 return "${rc}" 1247} 1248 1249test_vm_server_host_client() { 1250 if ! vm_vsock_test "init_ns" "server" 2 "${TEST_GUEST_PORT}"; then 1251 return "${KSFT_FAIL}" 1252 fi 1253 1254 if ! host_vsock_test "init_ns" "127.0.0.1" "${VSOCK_CID}" "${TEST_HOST_PORT}"; then 1255 return "${KSFT_FAIL}" 1256 fi 1257 1258 return "${KSFT_PASS}" 1259} 1260 1261test_vm_client_host_server() { 1262 if ! host_vsock_test "init_ns" "server" "${VSOCK_CID}" "${TEST_HOST_PORT_LISTENER}"; then 1263 return "${KSFT_FAIL}" 1264 fi 1265 1266 if ! vm_vsock_test "init_ns" "10.0.2.2" 2 "${TEST_HOST_PORT_LISTENER}"; then 1267 return "${KSFT_FAIL}" 1268 fi 1269 1270 return "${KSFT_PASS}" 1271} 1272 1273test_vm_loopback() { 1274 local port=60000 # non-forwarded local port 1275 1276 vm_ssh "init_ns" -- modprobe vsock_loopback &> /dev/null || : 1277 1278 if ! vm_vsock_test "init_ns" "server" 1 "${port}"; then 1279 return "${KSFT_FAIL}" 1280 fi 1281 1282 1283 if ! vm_vsock_test "init_ns" "127.0.0.1" 1 "${port}"; then 1284 return "${KSFT_FAIL}" 1285 fi 1286 1287 return "${KSFT_PASS}" 1288} 1289 1290shared_vm_test() { 1291 local tname 1292 1293 tname="${1}" 1294 1295 for testname in "${USE_SHARED_VM[@]}"; do 1296 if [[ "${tname}" == "${testname}" ]]; then 1297 return 0 1298 fi 1299 done 1300 1301 return 1 1302} 1303 1304shared_vm_tests_requested() { 1305 for arg in "$@"; do 1306 if shared_vm_test "${arg}"; then 1307 return 0 1308 fi 1309 done 1310 1311 return 1 1312} 1313 1314run_shared_vm_tests() { 1315 local arg 1316 1317 for arg in "$@"; do 1318 if ! shared_vm_test "${arg}"; then 1319 continue 1320 fi 1321 1322 if ! check_netns "${arg}"; then 1323 check_result "${KSFT_SKIP}" "${arg}" 1324 continue 1325 fi 1326 1327 run_shared_vm_test "${arg}" 1328 check_result "$?" "${arg}" 1329 done 1330} 1331 1332run_shared_vm_test() { 1333 local host_oops_cnt_before 1334 local host_warn_cnt_before 1335 local vm_oops_cnt_before 1336 local vm_warn_cnt_before 1337 local host_oops_cnt_after 1338 local host_warn_cnt_after 1339 local vm_oops_cnt_after 1340 local vm_warn_cnt_after 1341 local name 1342 local rc 1343 1344 host_oops_cnt_before=$(dmesg | grep -c -i 'Oops') 1345 host_warn_cnt_before=$(dmesg --level=warn | grep -c -i 'vsock') 1346 vm_oops_cnt_before=$(vm_dmesg_oops_count "init_ns") 1347 vm_warn_cnt_before=$(vm_dmesg_warn_count "init_ns") 1348 1349 name=$(echo "${1}" | awk '{ print $1 }') 1350 eval test_"${name}" 1351 rc=$? 1352 1353 host_oops_cnt_after=$(dmesg | grep -i 'Oops' | wc -l) 1354 if [[ ${host_oops_cnt_after} -gt ${host_oops_cnt_before} ]]; then 1355 echo "FAIL: kernel oops detected on host" | log_host 1356 rc=$KSFT_FAIL 1357 fi 1358 1359 host_warn_cnt_after=$(dmesg --level=warn | grep -c -i 'vsock') 1360 if [[ ${host_warn_cnt_after} -gt ${host_warn_cnt_before} ]]; then 1361 echo "FAIL: kernel warning detected on host" | log_host 1362 rc=$KSFT_FAIL 1363 fi 1364 1365 vm_oops_cnt_after=$(vm_dmesg_oops_count "init_ns") 1366 if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then 1367 echo "FAIL: kernel oops detected on vm" | log_host 1368 rc=$KSFT_FAIL 1369 fi 1370 1371 vm_warn_cnt_after=$(vm_dmesg_warn_count "init_ns") 1372 if [[ ${vm_warn_cnt_after} -gt ${vm_warn_cnt_before} ]]; then 1373 echo "FAIL: kernel warning detected on vm" | log_host 1374 rc=$KSFT_FAIL 1375 fi 1376 1377 return "${rc}" 1378} 1379 1380run_ns_tests() { 1381 for arg in "${ARGS[@]}"; do 1382 if shared_vm_test "${arg}"; then 1383 continue 1384 fi 1385 1386 if ! check_netns "${arg}"; then 1387 check_result "${KSFT_SKIP}" "${arg}" 1388 continue 1389 fi 1390 1391 add_namespaces 1392 1393 name=$(echo "${arg}" | awk '{ print $1 }') 1394 log_host "Executing test_${name}" 1395 1396 host_oops_before=$(dmesg 2>/dev/null | grep -c -i 'Oops') 1397 host_warn_before=$(dmesg --level=warn 2>/dev/null | grep -c -i 'vsock') 1398 eval test_"${name}" 1399 rc=$? 1400 1401 host_oops_after=$(dmesg 2>/dev/null | grep -c -i 'Oops') 1402 if [[ "${host_oops_after}" -gt "${host_oops_before}" ]]; then 1403 echo "FAIL: kernel oops detected on host" | log_host 1404 check_result "${KSFT_FAIL}" "${name}" 1405 del_namespaces 1406 continue 1407 fi 1408 1409 host_warn_after=$(dmesg --level=warn 2>/dev/null | grep -c -i 'vsock') 1410 if [[ "${host_warn_after}" -gt "${host_warn_before}" ]]; then 1411 echo "FAIL: kernel warning detected on host" | log_host 1412 check_result "${KSFT_FAIL}" "${name}" 1413 del_namespaces 1414 continue 1415 fi 1416 1417 check_result "${rc}" "${name}" 1418 1419 del_namespaces 1420 done 1421} 1422 1423BUILD=0 1424QEMU="qemu-system-$(uname -m)" 1425 1426while getopts :hvsq:b o 1427do 1428 case $o in 1429 v) VERBOSE=1;; 1430 b) BUILD=1;; 1431 q) QEMU=$OPTARG;; 1432 h|*) usage;; 1433 esac 1434done 1435shift $((OPTIND-1)) 1436 1437trap cleanup EXIT 1438 1439if [[ ${#} -eq 0 ]]; then 1440 ARGS=("${TEST_NAMES[@]}") 1441else 1442 ARGS=("$@") 1443fi 1444 1445check_args "${ARGS[@]}" 1446check_deps 1447check_vng 1448check_socat 1449handle_build 1450 1451echo "1..${#ARGS[@]}" 1452 1453cnt_pass=0 1454cnt_fail=0 1455cnt_skip=0 1456cnt_total=0 1457 1458if shared_vm_tests_requested "${ARGS[@]}"; then 1459 log_host "Booting up VM" 1460 pidfile="$(create_pidfile)" 1461 vm_start "${pidfile}" "init_ns" 1462 vm_wait_for_ssh "init_ns" 1463 log_host "VM booted up" 1464 1465 run_shared_vm_tests "${ARGS[@]}" 1466 terminate_pidfiles "${pidfile}" 1467fi 1468 1469run_ns_tests "${ARGS[@]}" 1470 1471echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}" 1472echo "Log: ${LOG}" 1473 1474if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then 1475 exit "$KSFT_PASS" 1476else 1477 exit "$KSFT_FAIL" 1478fi 1479