/* * Copyright (c) 2017-2024 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * Testing Framework for skywalk wrappers and syscalls * * This version forks many subchildren and sequences them via * control messages on fd 3 (MPTEST_SEQ_FILENO) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "skywalk_test_driver.h" #include "skywalk_test_common.h" #include "skywalk_test_utils.h" static uint64_t skywalk_features; extern char **environ; bool skywalk_in_driver; static struct timeval inittime; static struct skywalk_mptest *curr_test; #define test_exit(ecode) \ { \ T_LOG("%s:%d := Test exiting with error code %d\n", __func__, __LINE__, (ecode)); \ T_END; \ } /* * Print extra stats (e.g. AQM, ARP) that doesn't have to be done by the child. */ static void print_extra_stats(struct skywalk_mptest *test) { struct protox *tp; skt_aqstatpr("feth0"); /* netstat -qq -I feth0 */ skt_aqstatpr("feth1"); /* netstat -qq -I feth1 */ if ((!strncmp(test->skt_testname, "xferrdudpping", strlen("xferrdudpping")))) { skt_aqstatpr("rd0"); /* netstat -qq -I rd0 */ } /* netstat -sp arp */ tp = &protox[0]; skt_printproto(tp, tp->pr_name); } void skywalk_mptest_driver_SIGINT_handler(int sig) { signal(sig, SIG_IGN); if (curr_test != NULL) { if (curr_test->skt_fini != NULL) { curr_test->skt_fini(); } } exit(0); } void skywalk_mptest_driver_init(void) { size_t len; assert(!skywalk_in_driver); // only call init once skywalk_in_driver = true; /* Query the kernel for available skywalk features */ len = sizeof(skywalk_features); sysctlbyname("kern.skywalk.features", &skywalk_features, &len, NULL, 0); gettimeofday(&inittime, NULL); curr_test = NULL; signal(SIGINT, skywalk_mptest_driver_SIGINT_handler); } static struct skywalk_mptest_check *sk_checks[] = { &skt_filternative_check, &skt_filtercompat_check, }; bool skywalk_mptest_supported(const char *name) { int i; for (i = 0; i < sizeof(sk_checks) / sizeof(sk_checks[0]); i++) { if (strcmp(name, sk_checks[i]->skt_testname) == 0) { return sk_checks[i]->skt_supported(); } } return TRUE; } int skywalk_mptest_driver_run(struct skywalk_mptest *skt, bool ignoreskip) { posix_spawnattr_t attrs; int error; int childfail; bool childarg; skywalk_mptest_driver_init(); /* Initialize posix_spawn attributes */ posix_spawnattr_init(&attrs); if ((error = posix_spawnattr_setflags(&attrs, POSIX_SPAWN_SETEXEC)) != 0) { T_LOG("posix_spawnattr_setflags: %s", strerror(error)); test_exit(1); } /* Run Tests */ T_LOG("Test \"%s\":\t%s", skt->skt_testname, skt->skt_testdesc); if ((skt->skt_required_features & skywalk_features) != skt->skt_required_features) { T_LOG("Required features: 0x%llx, actual features 0x%llx", skt->skt_required_features, skywalk_features); if (!ignoreskip) { T_LOG("Test Result: SKIPPED\n-------------------"); return 0; } else { T_LOG("Proceeding with skipped test"); } } if (!skywalk_mptest_supported(skt->skt_testname)) { T_LOG("Test is not supported on this device"); if (!ignoreskip) { T_LOG("Test Result: SKIPPED\n-------------------"); return 0; } else { T_LOG("Proceeding with skipped test"); } } if (skt->skt_init) { skt->skt_init(); } curr_test = skt; pid_t pids[skt->skt_nchildren]; int fds[skt->skt_nchildren]; /* If the child arg isn't set, then set it */ childarg = !skt->skt_argv[3]; T_LOG("Spawning %d children", skt->skt_nchildren); for (int j = 0; j < skt->skt_nchildren; j++) { int pfd[2]; char argbuf[11]; if (childarg) { snprintf(argbuf, sizeof(argbuf), "%d", j); skt->skt_argv[3] = "--child"; skt->skt_argv[4] = argbuf; } //T_LOG("Spawning:"); //for (int k = 0; skt->skt_argv[k]; k++) { // T_LOG(" %s", skt->skt_argv[k]); //} //T_LOG("\n"); if (socketpair(AF_LOCAL, SOCK_STREAM, 0, pfd) == -1) { SKT_LOG("socketpair: %s", strerror(errno)); test_exit(1); } /* Fork and exec child */ if ((pids[j] = fork()) == -1) { SKT_LOG("fork: %s", strerror(errno)); test_exit(1); } if (pids[j] == 0) { /* XXX If a parent process test init expects to share an fd with a * child process, care must be taken to make sure this doesn't * accidentally close it. So far, no test does this. Another * option would be to pass an fd number to the child's argv instead * of assuming the hard coded fd number. */ error = close(pfd[1]); assert(!error); dup2(pfd[0], MPTEST_SEQ_FILENO); if (pfd[0] != MPTEST_SEQ_FILENO) { error = close(pfd[0]); assert(!error); } int argc = 0; /* XXX */ int ret = skt->skt_main(argc, skt->skt_argv); exit(ret); /* Not reached */ abort(); } error = close(pfd[0]); assert(!error); int fdflags = fcntl(pfd[1], F_GETFD); if (fdflags == -1) { SKT_LOG("fcntl(F_GETFD): %s", strerror(errno)); test_exit(1); } error = fcntl(pfd[1], F_SETFD, fdflags | FD_CLOEXEC); assert(!error); fds[j] = pfd[1]; } /* Wait for input from children */ for (int j = 0; j < skt->skt_nchildren; j++) { ssize_t ret; char buf[1]; if ((ret = read(fds[j], buf, sizeof(buf))) == -1) { SKT_LOG("read: %s", strerror(errno)); test_exit(1); } assert(ret == 1); } // sleep for debug introspection //T_LOG("sleep 100\n"); //sleep(100); /* Send output to children */ for (int j = 0; j < skt->skt_nchildren; j++) { ssize_t ret; char buf[1] = { 0 }; if ((ret = write(fds[j], buf, sizeof(buf))) == -1) { SKT_LOG("write: %s", strerror(errno)); test_exit(1); } assert(ret == 1); error = close(fds[j]); assert(!error); } childfail = 0; for (int j = 0; j < skt->skt_nchildren; j++) { int child; pid_t childpid; int child_status; /* Wait for children and check results */ if ((childpid = wait(&child_status)) == -1) { SKT_LOG("wait: %s", strerror(errno)); test_exit(1); } /* Which child was this? */ for (child = 0; child < skt->skt_nchildren; child++) { if (childpid == pids[child]) { pids[child] = 0; break; } } if (child == skt->skt_nchildren) { T_LOG("Received unexpected child status from pid %d\n", childpid); test_exit(1); } if (WIFEXITED(child_status)) { if (WEXITSTATUS(child_status)) { T_LOG("Child %d (pid %d) exited with status %d", child, childpid, WEXITSTATUS(child_status)); } } if (WIFSIGNALED(child_status)) { T_LOG("Child %d (pid %d) signaled with signal %d coredump %d", child, childpid, WTERMSIG(child_status), WCOREDUMP(child_status)); } if (!WIFEXITED(child_status) || WEXITSTATUS(child_status)) { childfail++; /* Wait for a second and then send sigterm to remaining children * in case they may be stuck */ if (childfail == 1) { print_extra_stats(skt); sleep(1); for (int k = 0; k < skt->skt_nchildren; k++) { if (pids[k]) { error = kill(pids[k], SIGTERM); if (error == 0) { T_LOG("Delivered SIGTERM to child %d pid %d", k, pids[k]); } else if (errno != ESRCH) { SKT_LOG("kill(%d, SIGTERM): %s", pids[k], strerror(errno)); test_exit(1); } } } } } } if (skt->skt_fini) { skt->skt_fini(); } curr_test = NULL; if (childfail) { T_FAIL("Test %s: %s", skt->skt_testname, skt->skt_testdesc); if ((skt->skt_required_features & SK_FEATURE_NEXUS_KERNEL_PIPE) != 0 && geteuid() != 0) { T_LOG("%sPlease try running test as root%s", BOLD, NORMAL); } } else { T_PASS("Test %s: %s", skt->skt_testname, skt->skt_testdesc); } posix_spawnattr_destroy(&attrs); return 0; }