xref: /linux/tools/testing/selftests/net/udp_splice_checksum.c (revision 1221246d3bd08d8b5228777e1a9b3361ca946e64)
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