1 // SPDX-License-Identifier: GPL-2.0
2 #include <alloca.h>
3 #include <fcntl.h>
4 #include <inttypes.h>
5 #include <string.h>
6 #include "../../../../../include/linux/kernel.h"
7 #include "../../../../../include/linux/stringify.h"
8 #include "aolib.h"
9 
10 const unsigned int test_server_port = 7010;
__test_listen_socket(int backlog,void * addr,size_t addr_sz)11 int __test_listen_socket(int backlog, void *addr, size_t addr_sz)
12 {
13 	int err, sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP);
14 	long flags;
15 
16 	if (sk < 0)
17 		test_error("socket()");
18 
19 	err = setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE, veth_name,
20 			 strlen(veth_name) + 1);
21 	if (err < 0)
22 		test_error("setsockopt(SO_BINDTODEVICE)");
23 
24 	if (bind(sk, (struct sockaddr *)addr, addr_sz) < 0)
25 		test_error("bind()");
26 
27 	flags = fcntl(sk, F_GETFL);
28 	if ((flags < 0) || (fcntl(sk, F_SETFL, flags | O_NONBLOCK) < 0))
29 		test_error("fcntl()");
30 
31 	if (listen(sk, backlog))
32 		test_error("listen()");
33 
34 	return sk;
35 }
36 
test_wait_fd(int sk,time_t sec,bool write)37 int test_wait_fd(int sk, time_t sec, bool write)
38 {
39 	struct timeval tv = { .tv_sec = sec };
40 	struct timeval *ptv = NULL;
41 	fd_set fds, efds;
42 	int ret;
43 	socklen_t slen = sizeof(ret);
44 
45 	FD_ZERO(&fds);
46 	FD_SET(sk, &fds);
47 	FD_ZERO(&efds);
48 	FD_SET(sk, &efds);
49 
50 	if (sec)
51 		ptv = &tv;
52 
53 	errno = 0;
54 	if (write)
55 		ret = select(sk + 1, NULL, &fds, &efds, ptv);
56 	else
57 		ret = select(sk + 1, &fds, NULL, &efds, ptv);
58 	if (ret < 0)
59 		return -errno;
60 	if (ret == 0) {
61 		errno = ETIMEDOUT;
62 		return -ETIMEDOUT;
63 	}
64 
65 	if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &slen))
66 		return -errno;
67 	if (ret)
68 		return -ret;
69 	return 0;
70 }
71 
__test_connect_socket(int sk,const char * device,void * addr,size_t addr_sz,time_t timeout)72 int __test_connect_socket(int sk, const char *device,
73 			  void *addr, size_t addr_sz, time_t timeout)
74 {
75 	long flags;
76 	int err;
77 
78 	if (device != NULL) {
79 		err = setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE, device,
80 				 strlen(device) + 1);
81 		if (err < 0)
82 			test_error("setsockopt(SO_BINDTODEVICE, %s)", device);
83 	}
84 
85 	if (!timeout) {
86 		err = connect(sk, addr, addr_sz);
87 		if (err) {
88 			err = -errno;
89 			goto out;
90 		}
91 		return 0;
92 	}
93 
94 	flags = fcntl(sk, F_GETFL);
95 	if ((flags < 0) || (fcntl(sk, F_SETFL, flags | O_NONBLOCK) < 0))
96 		test_error("fcntl()");
97 
98 	if (connect(sk, addr, addr_sz) < 0) {
99 		if (errno != EINPROGRESS) {
100 			err = -errno;
101 			goto out;
102 		}
103 		if (timeout < 0)
104 			return sk;
105 		err = test_wait_fd(sk, timeout, 1);
106 		if (err)
107 			goto out;
108 	}
109 	return sk;
110 
111 out:
112 	close(sk);
113 	return err;
114 }
115 
__test_set_md5(int sk,void * addr,size_t addr_sz,uint8_t prefix,int vrf,const char * password)116 int __test_set_md5(int sk, void *addr, size_t addr_sz, uint8_t prefix,
117 		   int vrf, const char *password)
118 {
119 	size_t pwd_len = strlen(password);
120 	struct tcp_md5sig md5sig = {};
121 
122 	md5sig.tcpm_keylen = pwd_len;
123 	memcpy(md5sig.tcpm_key, password, pwd_len);
124 	md5sig.tcpm_flags = TCP_MD5SIG_FLAG_PREFIX;
125 	md5sig.tcpm_prefixlen = prefix;
126 	if (vrf >= 0) {
127 		md5sig.tcpm_flags |= TCP_MD5SIG_FLAG_IFINDEX;
128 		md5sig.tcpm_ifindex = (uint8_t)vrf;
129 	}
130 	memcpy(&md5sig.tcpm_addr, addr, addr_sz);
131 
132 	errno = 0;
133 	return setsockopt(sk, IPPROTO_TCP, TCP_MD5SIG_EXT,
134 			&md5sig, sizeof(md5sig));
135 }
136 
137 
test_prepare_key_sockaddr(struct tcp_ao_add * ao,const char * alg,void * addr,size_t addr_sz,bool set_current,bool set_rnext,uint8_t prefix,uint8_t vrf,uint8_t sndid,uint8_t rcvid,uint8_t maclen,uint8_t keyflags,uint8_t keylen,const char * key)138 int test_prepare_key_sockaddr(struct tcp_ao_add *ao, const char *alg,
139 		void *addr, size_t addr_sz, bool set_current, bool set_rnext,
140 		uint8_t prefix, uint8_t vrf, uint8_t sndid, uint8_t rcvid,
141 		uint8_t maclen, uint8_t keyflags,
142 		uint8_t keylen, const char *key)
143 {
144 	memset(ao, 0, sizeof(struct tcp_ao_add));
145 
146 	ao->set_current	= !!set_current;
147 	ao->set_rnext	= !!set_rnext;
148 	ao->prefix	= prefix;
149 	ao->sndid	= sndid;
150 	ao->rcvid	= rcvid;
151 	ao->maclen	= maclen;
152 	ao->keyflags	= keyflags;
153 	ao->keylen	= keylen;
154 	ao->ifindex	= vrf;
155 
156 	memcpy(&ao->addr, addr, addr_sz);
157 
158 	if (strlen(alg) > 64)
159 		return -ENOBUFS;
160 	strncpy(ao->alg_name, alg, 64);
161 
162 	memcpy(ao->key, key,
163 	       (keylen > TCP_AO_MAXKEYLEN) ? TCP_AO_MAXKEYLEN : keylen);
164 	return 0;
165 }
166 
test_get_ao_keys_nr(int sk)167 static int test_get_ao_keys_nr(int sk)
168 {
169 	struct tcp_ao_getsockopt tmp = {};
170 	socklen_t tmp_sz = sizeof(tmp);
171 	int ret;
172 
173 	tmp.nkeys  = 1;
174 	tmp.get_all = 1;
175 
176 	ret = getsockopt(sk, IPPROTO_TCP, TCP_AO_GET_KEYS, &tmp, &tmp_sz);
177 	if (ret)
178 		return -errno;
179 	return (int)tmp.nkeys;
180 }
181 
test_get_one_ao(int sk,struct tcp_ao_getsockopt * out,void * addr,size_t addr_sz,uint8_t prefix,uint8_t sndid,uint8_t rcvid)182 int test_get_one_ao(int sk, struct tcp_ao_getsockopt *out,
183 		void *addr, size_t addr_sz, uint8_t prefix,
184 		uint8_t sndid, uint8_t rcvid)
185 {
186 	struct tcp_ao_getsockopt tmp = {};
187 	socklen_t tmp_sz = sizeof(tmp);
188 	int ret;
189 
190 	memcpy(&tmp.addr, addr, addr_sz);
191 	tmp.prefix = prefix;
192 	tmp.sndid  = sndid;
193 	tmp.rcvid  = rcvid;
194 	tmp.nkeys  = 1;
195 
196 	ret = getsockopt(sk, IPPROTO_TCP, TCP_AO_GET_KEYS, &tmp, &tmp_sz);
197 	if (ret)
198 		return ret;
199 	if (tmp.nkeys != 1)
200 		return -E2BIG;
201 	*out = tmp;
202 	return 0;
203 }
204 
test_get_ao_info(int sk,struct tcp_ao_info_opt * out)205 int test_get_ao_info(int sk, struct tcp_ao_info_opt *out)
206 {
207 	socklen_t sz = sizeof(*out);
208 
209 	out->reserved = 0;
210 	out->reserved2 = 0;
211 	if (getsockopt(sk, IPPROTO_TCP, TCP_AO_INFO, out, &sz))
212 		return -errno;
213 	if (sz != sizeof(*out))
214 		return -EMSGSIZE;
215 	return 0;
216 }
217 
test_set_ao_info(int sk,struct tcp_ao_info_opt * in)218 int test_set_ao_info(int sk, struct tcp_ao_info_opt *in)
219 {
220 	socklen_t sz = sizeof(*in);
221 
222 	in->reserved = 0;
223 	in->reserved2 = 0;
224 	if (setsockopt(sk, IPPROTO_TCP, TCP_AO_INFO, in, sz))
225 		return -errno;
226 	return 0;
227 }
228 
test_cmp_getsockopt_setsockopt(const struct tcp_ao_add * a,const struct tcp_ao_getsockopt * b)229 int test_cmp_getsockopt_setsockopt(const struct tcp_ao_add *a,
230 				   const struct tcp_ao_getsockopt *b)
231 {
232 	bool is_kdf_aes_128_cmac = false;
233 	bool is_cmac_aes = false;
234 
235 	if (!strcmp("cmac(aes128)", a->alg_name)) {
236 		is_kdf_aes_128_cmac = (a->keylen != 16);
237 		is_cmac_aes = true;
238 	}
239 
240 #define __cmp_ao(member)						\
241 do {									\
242 	if (b->member != a->member) {					\
243 		test_fail("getsockopt(): " __stringify(member) " %u != %u",	\
244 				b->member, a->member);			\
245 		return -1;						\
246 	}								\
247 } while(0)
248 	__cmp_ao(sndid);
249 	__cmp_ao(rcvid);
250 	__cmp_ao(prefix);
251 	__cmp_ao(keyflags);
252 	__cmp_ao(ifindex);
253 	if (a->maclen) {
254 		__cmp_ao(maclen);
255 	} else if (b->maclen != 12) {
256 		test_fail("getsockopt(): expected default maclen 12, but it's %u",
257 				b->maclen);
258 		return -1;
259 	}
260 	if (!is_kdf_aes_128_cmac) {
261 		__cmp_ao(keylen);
262 	} else if (b->keylen != 16) {
263 		test_fail("getsockopt(): expected keylen 16 for cmac(aes128), but it's %u",
264 				b->keylen);
265 		return -1;
266 	}
267 #undef __cmp_ao
268 	if (!is_kdf_aes_128_cmac && memcmp(b->key, a->key, a->keylen)) {
269 		test_fail("getsockopt(): returned key is different `%s' != `%s'",
270 				b->key, a->key);
271 		return -1;
272 	}
273 	if (memcmp(&b->addr, &a->addr, sizeof(b->addr))) {
274 		test_fail("getsockopt(): returned address is different");
275 		return -1;
276 	}
277 	if (!is_cmac_aes && strcmp(b->alg_name, a->alg_name)) {
278 		test_fail("getsockopt(): returned algorithm %s is different than %s", b->alg_name, a->alg_name);
279 		return -1;
280 	}
281 	if (is_cmac_aes && strcmp(b->alg_name, "cmac(aes)")) {
282 		test_fail("getsockopt(): returned algorithm %s is different than cmac(aes)", b->alg_name);
283 		return -1;
284 	}
285 	/* For a established key rotation test don't add a key with
286 	 * set_current = 1, as it's likely to change by peer's request;
287 	 * rather use setsockopt(TCP_AO_INFO)
288 	 */
289 	if (a->set_current != b->is_current) {
290 		test_fail("getsockopt(): returned key is not Current_key");
291 		return -1;
292 	}
293 	if (a->set_rnext != b->is_rnext) {
294 		test_fail("getsockopt(): returned key is not RNext_key");
295 		return -1;
296 	}
297 
298 	return 0;
299 }
300 
test_cmp_getsockopt_setsockopt_ao(const struct tcp_ao_info_opt * a,const struct tcp_ao_info_opt * b)301 int test_cmp_getsockopt_setsockopt_ao(const struct tcp_ao_info_opt *a,
302 				      const struct tcp_ao_info_opt *b)
303 {
304 	/* No check for ::current_key, as it may change by the peer */
305 	if (a->ao_required != b->ao_required) {
306 		test_fail("getsockopt(): returned ao doesn't have ao_required");
307 		return -1;
308 	}
309 	if (a->accept_icmps != b->accept_icmps) {
310 		test_fail("getsockopt(): returned ao doesn't accept ICMPs");
311 		return -1;
312 	}
313 	if (a->set_rnext && a->rnext != b->rnext) {
314 		test_fail("getsockopt(): RNext KeyID has changed");
315 		return -1;
316 	}
317 #define __cmp_cnt(member)						\
318 do {									\
319 	if (b->member != a->member) {					\
320 		test_fail("getsockopt(): " __stringify(member) " %llu != %llu",	\
321 				b->member, a->member);			\
322 		return -1;						\
323 	}								\
324 } while(0)
325 	if (a->set_counters) {
326 		__cmp_cnt(pkt_good);
327 		__cmp_cnt(pkt_bad);
328 		__cmp_cnt(pkt_key_not_found);
329 		__cmp_cnt(pkt_ao_required);
330 		__cmp_cnt(pkt_dropped_icmp);
331 	}
332 #undef __cmp_cnt
333 	return 0;
334 }
335 
test_get_tcp_ao_counters(int sk,struct tcp_ao_counters * out)336 int test_get_tcp_ao_counters(int sk, struct tcp_ao_counters *out)
337 {
338 	struct tcp_ao_getsockopt *key_dump;
339 	socklen_t key_dump_sz = sizeof(*key_dump);
340 	struct tcp_ao_info_opt info = {};
341 	bool c1, c2, c3, c4, c5;
342 	struct netstat *ns;
343 	int err, nr_keys;
344 
345 	memset(out, 0, sizeof(*out));
346 
347 	/* per-netns */
348 	ns = netstat_read();
349 	out->netns_ao_good = netstat_get(ns, "TCPAOGood", &c1);
350 	out->netns_ao_bad = netstat_get(ns, "TCPAOBad", &c2);
351 	out->netns_ao_key_not_found = netstat_get(ns, "TCPAOKeyNotFound", &c3);
352 	out->netns_ao_required = netstat_get(ns, "TCPAORequired", &c4);
353 	out->netns_ao_dropped_icmp = netstat_get(ns, "TCPAODroppedIcmps", &c5);
354 	netstat_free(ns);
355 	if (c1 || c2 || c3 || c4 || c5)
356 		return -EOPNOTSUPP;
357 
358 	err = test_get_ao_info(sk, &info);
359 	if (err)
360 		return err;
361 
362 	/* per-socket */
363 	out->ao_info_pkt_good		= info.pkt_good;
364 	out->ao_info_pkt_bad		= info.pkt_bad;
365 	out->ao_info_pkt_key_not_found	= info.pkt_key_not_found;
366 	out->ao_info_pkt_ao_required	= info.pkt_ao_required;
367 	out->ao_info_pkt_dropped_icmp	= info.pkt_dropped_icmp;
368 
369 	/* per-key */
370 	nr_keys = test_get_ao_keys_nr(sk);
371 	if (nr_keys < 0)
372 		return nr_keys;
373 	if (nr_keys == 0)
374 		test_error("test_get_ao_keys_nr() == 0");
375 	out->nr_keys = (size_t)nr_keys;
376 	key_dump = calloc(nr_keys, key_dump_sz);
377 	if (!key_dump)
378 		return -errno;
379 
380 	key_dump[0].nkeys = nr_keys;
381 	key_dump[0].get_all = 1;
382 	err = getsockopt(sk, IPPROTO_TCP, TCP_AO_GET_KEYS,
383 			 key_dump, &key_dump_sz);
384 	if (err) {
385 		free(key_dump);
386 		return -errno;
387 	}
388 
389 	out->key_cnts = calloc(nr_keys, sizeof(out->key_cnts[0]));
390 	if (!out->key_cnts) {
391 		free(key_dump);
392 		return -errno;
393 	}
394 
395 	while (nr_keys--) {
396 		out->key_cnts[nr_keys].sndid = key_dump[nr_keys].sndid;
397 		out->key_cnts[nr_keys].rcvid = key_dump[nr_keys].rcvid;
398 		out->key_cnts[nr_keys].pkt_good = key_dump[nr_keys].pkt_good;
399 		out->key_cnts[nr_keys].pkt_bad = key_dump[nr_keys].pkt_bad;
400 	}
401 	free(key_dump);
402 
403 	return 0;
404 }
405 
__test_tcp_ao_counters_cmp(const char * tst_name,struct tcp_ao_counters * before,struct tcp_ao_counters * after,test_cnt expected)406 int __test_tcp_ao_counters_cmp(const char *tst_name,
407 			       struct tcp_ao_counters *before,
408 			       struct tcp_ao_counters *after,
409 			       test_cnt expected)
410 {
411 #define __cmp_ao(cnt, expecting_inc)					\
412 do {									\
413 	if (before->cnt > after->cnt) {					\
414 		test_fail("%s: Decreased counter " __stringify(cnt) " %" PRIu64 " > %" PRIu64, \
415 			  tst_name ?: "", before->cnt, after->cnt);		\
416 		return -1;						\
417 	}								\
418 	if ((before->cnt != after->cnt) != (expecting_inc)) {		\
419 		test_fail("%s: Counter " __stringify(cnt) " was %sexpected to increase %" PRIu64 " => %" PRIu64, \
420 			  tst_name ?: "", (expecting_inc) ? "" : "not ",	\
421 			  before->cnt, after->cnt);			\
422 		return -1;						\
423 	}								\
424 } while(0)
425 
426 	errno = 0;
427 	/* per-netns */
428 	__cmp_ao(netns_ao_good, !!(expected & TEST_CNT_NS_GOOD));
429 	__cmp_ao(netns_ao_bad, !!(expected & TEST_CNT_NS_BAD));
430 	__cmp_ao(netns_ao_key_not_found,
431 		 !!(expected & TEST_CNT_NS_KEY_NOT_FOUND));
432 	__cmp_ao(netns_ao_required, !!(expected & TEST_CNT_NS_AO_REQUIRED));
433 	__cmp_ao(netns_ao_dropped_icmp,
434 		 !!(expected & TEST_CNT_NS_DROPPED_ICMP));
435 	/* per-socket */
436 	__cmp_ao(ao_info_pkt_good, !!(expected & TEST_CNT_SOCK_GOOD));
437 	__cmp_ao(ao_info_pkt_bad, !!(expected & TEST_CNT_SOCK_BAD));
438 	__cmp_ao(ao_info_pkt_key_not_found,
439 		 !!(expected & TEST_CNT_SOCK_KEY_NOT_FOUND));
440 	__cmp_ao(ao_info_pkt_ao_required, !!(expected & TEST_CNT_SOCK_AO_REQUIRED));
441 	__cmp_ao(ao_info_pkt_dropped_icmp,
442 		 !!(expected & TEST_CNT_SOCK_DROPPED_ICMP));
443 	return 0;
444 #undef __cmp_ao
445 }
446 
test_tcp_ao_key_counters_cmp(const char * tst_name,struct tcp_ao_counters * before,struct tcp_ao_counters * after,test_cnt expected,int sndid,int rcvid)447 int test_tcp_ao_key_counters_cmp(const char *tst_name,
448 				 struct tcp_ao_counters *before,
449 				 struct tcp_ao_counters *after,
450 				 test_cnt expected,
451 				 int sndid, int rcvid)
452 {
453 	size_t i;
454 #define __cmp_ao(i, cnt, expecting_inc)					\
455 do {									\
456 	if (before->key_cnts[i].cnt > after->key_cnts[i].cnt) {		\
457 		test_fail("%s: Decreased counter " __stringify(cnt) " %" PRIu64 " > %" PRIu64 " for key %u:%u", \
458 			  tst_name ?: "", before->key_cnts[i].cnt,	\
459 			  after->key_cnts[i].cnt,			\
460 			  before->key_cnts[i].sndid,			\
461 			  before->key_cnts[i].rcvid);			\
462 		return -1;						\
463 	}								\
464 	if ((before->key_cnts[i].cnt != after->key_cnts[i].cnt) != (expecting_inc)) {		\
465 		test_fail("%s: Counter " __stringify(cnt) " was %sexpected to increase %" PRIu64 " => %" PRIu64 " for key %u:%u", \
466 			  tst_name ?: "", (expecting_inc) ? "" : "not ",\
467 			  before->key_cnts[i].cnt,			\
468 			  after->key_cnts[i].cnt,			\
469 			  before->key_cnts[i].sndid,			\
470 			  before->key_cnts[i].rcvid);			\
471 		return -1;						\
472 	}								\
473 } while(0)
474 
475 	if (before->nr_keys != after->nr_keys) {
476 		test_fail("%s: Keys changed on the socket %zu != %zu",
477 			  tst_name, before->nr_keys, after->nr_keys);
478 		return -1;
479 	}
480 
481 	/* per-key */
482 	i = before->nr_keys;
483 	while (i--) {
484 		if (sndid >= 0 && before->key_cnts[i].sndid != sndid)
485 			continue;
486 		if (rcvid >= 0 && before->key_cnts[i].rcvid != rcvid)
487 			continue;
488 		__cmp_ao(i, pkt_good, !!(expected & TEST_CNT_KEY_GOOD));
489 		__cmp_ao(i, pkt_bad, !!(expected & TEST_CNT_KEY_BAD));
490 	}
491 	return 0;
492 #undef __cmp_ao
493 }
494 
test_tcp_ao_counters_free(struct tcp_ao_counters * cnts)495 void test_tcp_ao_counters_free(struct tcp_ao_counters *cnts)
496 {
497 	free(cnts->key_cnts);
498 }
499 
500 #define TEST_BUF_SIZE 4096
test_server_run(int sk,ssize_t quota,time_t timeout_sec)501 ssize_t test_server_run(int sk, ssize_t quota, time_t timeout_sec)
502 {
503 	ssize_t total = 0;
504 
505 	do {
506 		char buf[TEST_BUF_SIZE];
507 		ssize_t bytes, sent;
508 		int ret;
509 
510 		ret = test_wait_fd(sk, timeout_sec, 0);
511 		if (ret)
512 			return ret;
513 
514 		bytes = recv(sk, buf, sizeof(buf), 0);
515 
516 		if (bytes < 0)
517 			test_error("recv(): %zd", bytes);
518 		if (bytes == 0)
519 			break;
520 
521 		ret = test_wait_fd(sk, timeout_sec, 1);
522 		if (ret)
523 			return ret;
524 
525 		sent = send(sk, buf, bytes, 0);
526 		if (sent == 0)
527 			break;
528 		if (sent != bytes)
529 			test_error("send()");
530 		total += bytes;
531 	} while (!quota || total < quota);
532 
533 	return total;
534 }
535 
test_client_loop(int sk,char * buf,size_t buf_sz,const size_t msg_len,time_t timeout_sec)536 ssize_t test_client_loop(int sk, char *buf, size_t buf_sz,
537 			 const size_t msg_len, time_t timeout_sec)
538 {
539 	char msg[msg_len];
540 	int nodelay = 1;
541 	size_t i;
542 
543 	if (setsockopt(sk, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)))
544 		test_error("setsockopt(TCP_NODELAY)");
545 
546 	for (i = 0; i < buf_sz; i += min(msg_len, buf_sz - i)) {
547 		size_t sent, bytes = min(msg_len, buf_sz - i);
548 		int ret;
549 
550 		ret = test_wait_fd(sk, timeout_sec, 1);
551 		if (ret)
552 			return ret;
553 
554 		sent = send(sk, buf + i, bytes, 0);
555 		if (sent == 0)
556 			break;
557 		if (sent != bytes)
558 			test_error("send()");
559 
560 		bytes = 0;
561 		do {
562 			ssize_t got;
563 
564 			ret = test_wait_fd(sk, timeout_sec, 0);
565 			if (ret)
566 				return ret;
567 
568 			got = recv(sk, msg + bytes, sizeof(msg) - bytes, 0);
569 			if (got <= 0)
570 				return i;
571 			bytes += got;
572 		} while (bytes < sent);
573 		if (bytes > sent)
574 			test_error("recv(): %zd > %zd", bytes, sent);
575 		if (memcmp(buf + i, msg, bytes) != 0) {
576 			test_fail("received message differs");
577 			return -1;
578 		}
579 	}
580 	return i;
581 }
582 
test_client_verify(int sk,const size_t msg_len,const size_t nr,time_t timeout_sec)583 int test_client_verify(int sk, const size_t msg_len, const size_t nr,
584 		       time_t timeout_sec)
585 {
586 	size_t buf_sz = msg_len * nr;
587 	char *buf = alloca(buf_sz);
588 	ssize_t ret;
589 
590 	randomize_buffer(buf, buf_sz);
591 	ret = test_client_loop(sk, buf, buf_sz, msg_len, timeout_sec);
592 	if (ret < 0)
593 		return (int)ret;
594 	return ret != buf_sz ? -1 : 0;
595 }
596