1*1221246dSAlireza Asgari // SPDX-License-Identifier: GPL-2.0-only 2*1221246dSAlireza Asgari /* 3*1221246dSAlireza Asgari * Exercise UDP software checksums over multiple spliced pipe fragments. 4*1221246dSAlireza Asgari * All traffic stays on loopback; no privileges or external services are needed. 5*1221246dSAlireza Asgari * Exit status: 0 = pass, 1 = failure, 4 = all cases skipped. 6*1221246dSAlireza Asgari */ 7*1221246dSAlireza Asgari #define _GNU_SOURCE 8*1221246dSAlireza Asgari 9*1221246dSAlireza Asgari #include <arpa/inet.h> 10*1221246dSAlireza Asgari #include <errno.h> 11*1221246dSAlireza Asgari #include <fcntl.h> 12*1221246dSAlireza Asgari #include <poll.h> 13*1221246dSAlireza Asgari #include <stdbool.h> 14*1221246dSAlireza Asgari #include <stdio.h> 15*1221246dSAlireza Asgari #include <stdlib.h> 16*1221246dSAlireza Asgari #include <string.h> 17*1221246dSAlireza Asgari #include <sys/socket.h> 18*1221246dSAlireza Asgari #include <time.h> 19*1221246dSAlireza Asgari #include <unistd.h> 20*1221246dSAlireza Asgari 21*1221246dSAlireza Asgari #include "kselftest.h" 22*1221246dSAlireza Asgari 23*1221246dSAlireza Asgari #define TIMEOUT_MS 200 24*1221246dSAlireza Asgari 25*1221246dSAlireza Asgari struct test_case { 26*1221246dSAlireza Asgari const char *name; 27*1221246dSAlireza Asgari const char *body; 28*1221246dSAlireza Asgari const char *prefix; 29*1221246dSAlireza Asgari size_t fragments[3]; 30*1221246dSAlireza Asgari bool ordinary; 31*1221246dSAlireza Asgari bool page_tail; 32*1221246dSAlireza Asgari }; 33*1221246dSAlireza Asgari 34*1221246dSAlireza Asgari static const struct test_case cases[] = { 35*1221246dSAlireza Asgari { "ordinary-control", "abcDEFGH", "", { 3, 5 }, true, false }, 36*1221246dSAlireza Asgari { "uniform-odd-fragments", "BBBBBBBB", "", { 3, 5 }, false, false }, 37*1221246dSAlireza Asgari { "even-fragments", "abcdEFGH", "", { 4, 4 }, false, false }, 38*1221246dSAlireza Asgari { "odd-fragments", "abcDEFGH", "", { 3, 5 }, false, false }, 39*1221246dSAlireza Asgari { "odd-prefix-odd-fragments", "abcDEFGH", "P", { 3, 5 }, false, false }, 40*1221246dSAlireza Asgari { "even-prefix-odd-fragments", "abcDEFGH", "PQ", { 3, 5 }, false, false }, 41*1221246dSAlireza Asgari { "page-tail-odd-fragments", "abcDEFGH", "", { 3, 5 }, false, true }, 42*1221246dSAlireza Asgari { "three-fragments", "abcDEFGhijkl", "", { 3, 4, 5 }, false, false }, 43*1221246dSAlireza Asgari { "odd-prefix-three-fragments", 44*1221246dSAlireza Asgari "abcDEFGhijkl", 45*1221246dSAlireza Asgari "P", 46*1221246dSAlireza Asgari { 3, 4, 5 }, 47*1221246dSAlireza Asgari false, 48*1221246dSAlireza Asgari false }, 49*1221246dSAlireza Asgari }; 50*1221246dSAlireza Asgari 51*1221246dSAlireza Asgari static const char *const families[] = { "IPv4", "IPv6", "IPv4-mapped" }; 52*1221246dSAlireza Asgari 53*1221246dSAlireza Asgari static int error(const char *what) 54*1221246dSAlireza Asgari { 55*1221246dSAlireza Asgari ksft_perror(what); 56*1221246dSAlireza Asgari return 1; 57*1221246dSAlireza Asgari } 58*1221246dSAlireza Asgari 59*1221246dSAlireza Asgari static bool unavailable(void) 60*1221246dSAlireza Asgari { 61*1221246dSAlireza Asgari return errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT || 62*1221246dSAlireza Asgari errno == EADDRNOTAVAIL; 63*1221246dSAlireza Asgari } 64*1221246dSAlireza Asgari 65*1221246dSAlireza Asgari static long long now_ms(void) 66*1221246dSAlireza Asgari { 67*1221246dSAlireza Asgari struct timespec ts; 68*1221246dSAlireza Asgari 69*1221246dSAlireza Asgari if (clock_gettime(CLOCK_MONOTONIC, &ts)) 70*1221246dSAlireza Asgari ksft_exit_fail_perror("clock_gettime"); 71*1221246dSAlireza Asgari return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 72*1221246dSAlireza Asgari } 73*1221246dSAlireza Asgari 74*1221246dSAlireza Asgari static int receive_one(int fd, const char *expected, size_t length) 75*1221246dSAlireza Asgari { 76*1221246dSAlireza Asgari struct pollfd pfd = { .fd = fd, .events = POLLIN }; 77*1221246dSAlireza Asgari long long deadline = now_ms() + TIMEOUT_MS; 78*1221246dSAlireza Asgari int remaining, ret; 79*1221246dSAlireza Asgari char received[64]; 80*1221246dSAlireza Asgari ssize_t count; 81*1221246dSAlireza Asgari 82*1221246dSAlireza Asgari for (;;) { 83*1221246dSAlireza Asgari remaining = deadline - now_ms(); 84*1221246dSAlireza Asgari if (remaining < 0) 85*1221246dSAlireza Asgari remaining = 0; 86*1221246dSAlireza Asgari ret = poll(&pfd, 1, remaining); 87*1221246dSAlireza Asgari if (ret < 0) { 88*1221246dSAlireza Asgari if (errno == EINTR) 89*1221246dSAlireza Asgari continue; 90*1221246dSAlireza Asgari return error("poll"); 91*1221246dSAlireza Asgari } 92*1221246dSAlireza Asgari if (!ret) { 93*1221246dSAlireza Asgari ksft_print_msg("no datagram within %d ms after send\n", 94*1221246dSAlireza Asgari TIMEOUT_MS); 95*1221246dSAlireza Asgari return 1; 96*1221246dSAlireza Asgari } 97*1221246dSAlireza Asgari count = recv(fd, received, sizeof(received), 98*1221246dSAlireza Asgari MSG_DONTWAIT | MSG_TRUNC); 99*1221246dSAlireza Asgari if (count < 0) { 100*1221246dSAlireza Asgari if (errno == EINTR || errno == EAGAIN) 101*1221246dSAlireza Asgari continue; 102*1221246dSAlireza Asgari return error("recv"); 103*1221246dSAlireza Asgari } 104*1221246dSAlireza Asgari break; 105*1221246dSAlireza Asgari } 106*1221246dSAlireza Asgari if (count != (ssize_t)length || memcmp(received, expected, length)) { 107*1221246dSAlireza Asgari ksft_print_msg("received %zd bytes; expected %zu matching\n", 108*1221246dSAlireza Asgari count, length); 109*1221246dSAlireza Asgari return 1; 110*1221246dSAlireza Asgari } 111*1221246dSAlireza Asgari count = recv(fd, received, sizeof(received), MSG_DONTWAIT | MSG_TRUNC); 112*1221246dSAlireza Asgari if (count >= 0) { 113*1221246dSAlireza Asgari ksft_print_msg("unexpected extra datagram (%zd bytes)\n", 114*1221246dSAlireza Asgari count); 115*1221246dSAlireza Asgari return 1; 116*1221246dSAlireza Asgari } 117*1221246dSAlireza Asgari if (errno != EAGAIN) 118*1221246dSAlireza Asgari return error("extra-datagram check"); 119*1221246dSAlireza Asgari return 0; 120*1221246dSAlireza Asgari } 121*1221246dSAlireza Asgari 122*1221246dSAlireza Asgari /* Separate source pipes prevent adjacent small writes merging into one buffer. */ 123*1221246dSAlireza Asgari static int append_fragment(int dest, const char *data, size_t length) 124*1221246dSAlireza Asgari { 125*1221246dSAlireza Asgari int source[2], ret = 1; 126*1221246dSAlireza Asgari 127*1221246dSAlireza Asgari if (pipe2(source, O_NONBLOCK | O_CLOEXEC)) 128*1221246dSAlireza Asgari return error("source pipe"); 129*1221246dSAlireza Asgari if (write(source[1], data, length) != (ssize_t)length) { 130*1221246dSAlireza Asgari error("source write"); 131*1221246dSAlireza Asgari goto out; 132*1221246dSAlireza Asgari } 133*1221246dSAlireza Asgari if (splice(source[0], NULL, dest, NULL, length, SPLICE_F_NONBLOCK) != 134*1221246dSAlireza Asgari (ssize_t)length) { 135*1221246dSAlireza Asgari error("pipe-to-pipe splice"); 136*1221246dSAlireza Asgari goto out; 137*1221246dSAlireza Asgari } 138*1221246dSAlireza Asgari ret = 0; 139*1221246dSAlireza Asgari out: 140*1221246dSAlireza Asgari close(source[0]); 141*1221246dSAlireza Asgari close(source[1]); 142*1221246dSAlireza Asgari return ret; 143*1221246dSAlireza Asgari } 144*1221246dSAlireza Asgari 145*1221246dSAlireza Asgari static int pipe_capacity(int fd, unsigned int slots, const char **skip_reason) 146*1221246dSAlireza Asgari { 147*1221246dSAlireza Asgari long required = slots * sysconf(_SC_PAGESIZE); 148*1221246dSAlireza Asgari int capacity = fcntl(fd, F_GETPIPE_SZ); 149*1221246dSAlireza Asgari 150*1221246dSAlireza Asgari if (capacity < 0) 151*1221246dSAlireza Asgari return error("query pipe capacity"); 152*1221246dSAlireza Asgari if (capacity >= required || 153*1221246dSAlireza Asgari fcntl(fd, F_SETPIPE_SZ, required) >= required) 154*1221246dSAlireza Asgari return 0; 155*1221246dSAlireza Asgari ksft_print_msg("cannot reserve %u pipe slots: %s\n", slots, 156*1221246dSAlireza Asgari strerror(errno)); 157*1221246dSAlireza Asgari *skip_reason = "required pipe capacity unavailable"; 158*1221246dSAlireza Asgari return KSFT_SKIP; 159*1221246dSAlireza Asgari } 160*1221246dSAlireza Asgari 161*1221246dSAlireza Asgari /* Leave three bytes at a page's end, followed by five bytes on the next page. */ 162*1221246dSAlireza Asgari static int append_page_tail(int dest, const char *body, 163*1221246dSAlireza Asgari const char **skip_reason) 164*1221246dSAlireza Asgari { 165*1221246dSAlireza Asgari long page = sysconf(_SC_PAGESIZE); 166*1221246dSAlireza Asgari int source[2], ret = 1; 167*1221246dSAlireza Asgari char *data; 168*1221246dSAlireza Asgari 169*1221246dSAlireza Asgari if (page < 8) { 170*1221246dSAlireza Asgari ksft_print_msg("invalid page size %ld\n", page); 171*1221246dSAlireza Asgari return 1; 172*1221246dSAlireza Asgari } 173*1221246dSAlireza Asgari data = malloc(page + 5); 174*1221246dSAlireza Asgari if (!data) 175*1221246dSAlireza Asgari return error("malloc"); 176*1221246dSAlireza Asgari memset(data, 'x', page - 3); 177*1221246dSAlireza Asgari memcpy(data + page - 3, body, 8); 178*1221246dSAlireza Asgari if (pipe2(source, O_NONBLOCK | O_CLOEXEC)) { 179*1221246dSAlireza Asgari error("page-tail source pipe"); 180*1221246dSAlireza Asgari goto free_data; 181*1221246dSAlireza Asgari } 182*1221246dSAlireza Asgari ret = pipe_capacity(source[1], 2, skip_reason); 183*1221246dSAlireza Asgari if (ret) 184*1221246dSAlireza Asgari goto out; 185*1221246dSAlireza Asgari ret = 1; 186*1221246dSAlireza Asgari if (write(source[1], data, page + 5) != page + 5) { 187*1221246dSAlireza Asgari error("page-tail write"); 188*1221246dSAlireza Asgari goto out; 189*1221246dSAlireza Asgari } 190*1221246dSAlireza Asgari if (read(source[0], data, page - 3) != page - 3) { 191*1221246dSAlireza Asgari error("page-tail discard"); 192*1221246dSAlireza Asgari goto out; 193*1221246dSAlireza Asgari } 194*1221246dSAlireza Asgari if (splice(source[0], NULL, dest, NULL, 8, SPLICE_F_NONBLOCK) != 8) { 195*1221246dSAlireza Asgari error("page-tail pipe-to-pipe splice"); 196*1221246dSAlireza Asgari goto out; 197*1221246dSAlireza Asgari } 198*1221246dSAlireza Asgari ret = 0; 199*1221246dSAlireza Asgari out: 200*1221246dSAlireza Asgari close(source[0]); 201*1221246dSAlireza Asgari close(source[1]); 202*1221246dSAlireza Asgari free_data: 203*1221246dSAlireza Asgari free(data); 204*1221246dSAlireza Asgari return ret; 205*1221246dSAlireza Asgari } 206*1221246dSAlireza Asgari 207*1221246dSAlireza Asgari static int run_case(unsigned int family, bool connected, 208*1221246dSAlireza Asgari const struct test_case *test, const char **skip_reason) 209*1221246dSAlireza Asgari { 210*1221246dSAlireza Asgari size_t body_length = strlen(test->body), offset = 0; 211*1221246dSAlireza Asgari int tx_family = family == 0 ? AF_INET : AF_INET6; 212*1221246dSAlireza Asgari int rx_family = family == 1 ? AF_INET6 : AF_INET; 213*1221246dSAlireza Asgari int rx = -1, tx = -1, pipefd[2] = { -1, -1 }; 214*1221246dSAlireza Asgari size_t prefix_length = strlen(test->prefix); 215*1221246dSAlireza Asgari struct sockaddr_storage address = { 0 }; 216*1221246dSAlireza Asgari socklen_t address_length; 217*1221246dSAlireza Asgari struct sockaddr_in6 *v6; 218*1221246dSAlireza Asgari struct sockaddr_in *v4; 219*1221246dSAlireza Asgari int ret = 1, zero = 0; 220*1221246dSAlireza Asgari char expected[64]; 221*1221246dSAlireza Asgari unsigned int i; 222*1221246dSAlireza Asgari ssize_t count; 223*1221246dSAlireza Asgari 224*1221246dSAlireza Asgari v4 = (struct sockaddr_in *)&address; 225*1221246dSAlireza Asgari v6 = (struct sockaddr_in6 *)&address; 226*1221246dSAlireza Asgari *skip_reason = "address family unavailable"; 227*1221246dSAlireza Asgari memcpy(expected, test->prefix, prefix_length); 228*1221246dSAlireza Asgari memcpy(expected + prefix_length, test->body, body_length); 229*1221246dSAlireza Asgari rx = socket(rx_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); 230*1221246dSAlireza Asgari if (rx < 0) { 231*1221246dSAlireza Asgari ret = unavailable() ? KSFT_SKIP : error("receive socket"); 232*1221246dSAlireza Asgari goto out; 233*1221246dSAlireza Asgari } 234*1221246dSAlireza Asgari if (rx_family == AF_INET) { 235*1221246dSAlireza Asgari v4->sin_family = AF_INET; 236*1221246dSAlireza Asgari v4->sin_addr.s_addr = htonl(INADDR_LOOPBACK); 237*1221246dSAlireza Asgari address_length = sizeof(*v4); 238*1221246dSAlireza Asgari } else { 239*1221246dSAlireza Asgari v6->sin6_family = AF_INET6; 240*1221246dSAlireza Asgari v6->sin6_addr = in6addr_loopback; 241*1221246dSAlireza Asgari address_length = sizeof(*v6); 242*1221246dSAlireza Asgari } 243*1221246dSAlireza Asgari if (bind(rx, (struct sockaddr *)&address, address_length)) { 244*1221246dSAlireza Asgari ret = unavailable() ? KSFT_SKIP : error("bind loopback"); 245*1221246dSAlireza Asgari goto out; 246*1221246dSAlireza Asgari } 247*1221246dSAlireza Asgari if (getsockname(rx, (struct sockaddr *)&address, &address_length)) { 248*1221246dSAlireza Asgari error("getsockname"); 249*1221246dSAlireza Asgari goto out; 250*1221246dSAlireza Asgari } 251*1221246dSAlireza Asgari if (family == 2) { 252*1221246dSAlireza Asgari unsigned short port = v4->sin_port; 253*1221246dSAlireza Asgari 254*1221246dSAlireza Asgari memset(&address, 0, sizeof(address)); 255*1221246dSAlireza Asgari v6->sin6_family = AF_INET6; 256*1221246dSAlireza Asgari v6->sin6_port = port; 257*1221246dSAlireza Asgari inet_pton(AF_INET6, "::ffff:127.0.0.1", &v6->sin6_addr); 258*1221246dSAlireza Asgari address_length = sizeof(*v6); 259*1221246dSAlireza Asgari } 260*1221246dSAlireza Asgari tx = socket(tx_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); 261*1221246dSAlireza Asgari if (tx < 0) { 262*1221246dSAlireza Asgari ret = unavailable() ? KSFT_SKIP : error("send socket"); 263*1221246dSAlireza Asgari goto out; 264*1221246dSAlireza Asgari } 265*1221246dSAlireza Asgari if (family == 2 && 266*1221246dSAlireza Asgari setsockopt(tx, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero))) { 267*1221246dSAlireza Asgari error("disable IPV6_V6ONLY"); 268*1221246dSAlireza Asgari goto out; 269*1221246dSAlireza Asgari } 270*1221246dSAlireza Asgari if (connected && 271*1221246dSAlireza Asgari connect(tx, (struct sockaddr *)&address, address_length)) { 272*1221246dSAlireza Asgari error("connect"); 273*1221246dSAlireza Asgari goto out; 274*1221246dSAlireza Asgari } 275*1221246dSAlireza Asgari if (test->ordinary) { 276*1221246dSAlireza Asgari count = sendto(tx, expected, prefix_length + body_length, 0, 277*1221246dSAlireza Asgari (struct sockaddr *)&address, address_length); 278*1221246dSAlireza Asgari if (count != (ssize_t)(prefix_length + body_length)) { 279*1221246dSAlireza Asgari error("ordinary sendto"); 280*1221246dSAlireza Asgari goto out; 281*1221246dSAlireza Asgari } 282*1221246dSAlireza Asgari } else { 283*1221246dSAlireza Asgari if (pipe2(pipefd, O_NONBLOCK | O_CLOEXEC)) { 284*1221246dSAlireza Asgari error("assembled pipe"); 285*1221246dSAlireza Asgari goto out; 286*1221246dSAlireza Asgari } 287*1221246dSAlireza Asgari ret = pipe_capacity(pipefd[1], test->fragments[2] ? 3 : 2, 288*1221246dSAlireza Asgari skip_reason); 289*1221246dSAlireza Asgari if (ret) 290*1221246dSAlireza Asgari goto out; 291*1221246dSAlireza Asgari ret = 1; 292*1221246dSAlireza Asgari if (test->page_tail) { 293*1221246dSAlireza Asgari ret = append_page_tail(pipefd[1], test->body, 294*1221246dSAlireza Asgari skip_reason); 295*1221246dSAlireza Asgari if (ret) 296*1221246dSAlireza Asgari goto out; 297*1221246dSAlireza Asgari ret = 1; 298*1221246dSAlireza Asgari } else { 299*1221246dSAlireza Asgari for (i = 0; i < ARRAY_SIZE(test->fragments); i++) { 300*1221246dSAlireza Asgari if (!test->fragments[i]) 301*1221246dSAlireza Asgari break; 302*1221246dSAlireza Asgari if (append_fragment(pipefd[1], 303*1221246dSAlireza Asgari test->body + offset, 304*1221246dSAlireza Asgari test->fragments[i])) 305*1221246dSAlireza Asgari goto out; 306*1221246dSAlireza Asgari offset += test->fragments[i]; 307*1221246dSAlireza Asgari } 308*1221246dSAlireza Asgari } 309*1221246dSAlireza Asgari /* Even an empty prefix establishes the destination and UDP cork. */ 310*1221246dSAlireza Asgari count = sendto(tx, test->prefix, prefix_length, MSG_MORE, 311*1221246dSAlireza Asgari (struct sockaddr *)&address, address_length); 312*1221246dSAlireza Asgari if (count != (ssize_t)prefix_length) { 313*1221246dSAlireza Asgari error("prime sendto(MSG_MORE)"); 314*1221246dSAlireza Asgari goto out; 315*1221246dSAlireza Asgari } 316*1221246dSAlireza Asgari count = splice(pipefd[0], NULL, tx, NULL, body_length, 317*1221246dSAlireza Asgari SPLICE_F_NONBLOCK | SPLICE_F_MORE); 318*1221246dSAlireza Asgari if (count != (ssize_t)body_length) { 319*1221246dSAlireza Asgari ksft_print_msg("socket splice: %zd, expected %zu: %s\n", 320*1221246dSAlireza Asgari count, body_length, 321*1221246dSAlireza Asgari count < 0 ? strerror(errno) : 322*1221246dSAlireza Asgari "short transfer"); 323*1221246dSAlireza Asgari goto out; 324*1221246dSAlireza Asgari } 325*1221246dSAlireza Asgari if (sendto(tx, "", 0, 0, (struct sockaddr *)&address, 326*1221246dSAlireza Asgari address_length)) { 327*1221246dSAlireza Asgari error("commit sendto"); 328*1221246dSAlireza Asgari goto out; 329*1221246dSAlireza Asgari } 330*1221246dSAlireza Asgari } 331*1221246dSAlireza Asgari ret = receive_one(rx, expected, prefix_length + body_length); 332*1221246dSAlireza Asgari out: 333*1221246dSAlireza Asgari if (pipefd[0] >= 0) { 334*1221246dSAlireza Asgari close(pipefd[0]); 335*1221246dSAlireza Asgari close(pipefd[1]); 336*1221246dSAlireza Asgari } 337*1221246dSAlireza Asgari if (tx >= 0) 338*1221246dSAlireza Asgari close(tx); 339*1221246dSAlireza Asgari if (rx >= 0) 340*1221246dSAlireza Asgari close(rx); 341*1221246dSAlireza Asgari return ret; 342*1221246dSAlireza Asgari } 343*1221246dSAlireza Asgari 344*1221246dSAlireza Asgari int main(void) 345*1221246dSAlireza Asgari { 346*1221246dSAlireza Asgari unsigned int family, connected, i; 347*1221246dSAlireza Asgari const char *connection, *skip; 348*1221246dSAlireza Asgari int ret; 349*1221246dSAlireza Asgari 350*1221246dSAlireza Asgari ksft_print_header(); 351*1221246dSAlireza Asgari ksft_set_plan(ARRAY_SIZE(families) * 2 * ARRAY_SIZE(cases)); 352*1221246dSAlireza Asgari for (family = 0; family < ARRAY_SIZE(families); family++) { 353*1221246dSAlireza Asgari for (connected = 0; connected < 2; connected++) { 354*1221246dSAlireza Asgari connection = connected ? "connected" : "unconnected"; 355*1221246dSAlireza Asgari for (i = 0; i < ARRAY_SIZE(cases); i++) { 356*1221246dSAlireza Asgari ret = run_case(family, connected, &cases[i], 357*1221246dSAlireza Asgari &skip); 358*1221246dSAlireza Asgari if (ret == KSFT_SKIP) 359*1221246dSAlireza Asgari ksft_test_result_skip("%s %s %s: %s\n", 360*1221246dSAlireza Asgari families[family], 361*1221246dSAlireza Asgari connection, 362*1221246dSAlireza Asgari cases[i].name, 363*1221246dSAlireza Asgari skip); 364*1221246dSAlireza Asgari else 365*1221246dSAlireza Asgari ksft_test_result(!ret, "%s %s %s\n", 366*1221246dSAlireza Asgari families[family], 367*1221246dSAlireza Asgari connection, 368*1221246dSAlireza Asgari cases[i].name); 369*1221246dSAlireza Asgari } 370*1221246dSAlireza Asgari } 371*1221246dSAlireza Asgari } 372*1221246dSAlireza Asgari if (!ksft_get_pass_cnt() && !ksft_get_fail_cnt()) { 373*1221246dSAlireza Asgari /* ksft_exit_skip() would add a result beyond the plan. */ 374*1221246dSAlireza Asgari ksft_print_cnts(); 375*1221246dSAlireza Asgari return KSFT_SKIP; 376*1221246dSAlireza Asgari } 377*1221246dSAlireza Asgari ksft_finished(); 378*1221246dSAlireza Asgari } 379