1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* io_uring tests for vsock |
3 | * |
4 | * Copyright (C) 2023 SberDevices. |
5 | * |
6 | * Author: Arseniy Krasnov <avkrasnov@salutedevices.com> |
7 | */ |
8 | |
9 | #include <getopt.h> |
10 | #include <stdio.h> |
11 | #include <stdlib.h> |
12 | #include <string.h> |
13 | #include <liburing.h> |
14 | #include <unistd.h> |
15 | #include <sys/mman.h> |
16 | #include <linux/kernel.h> |
17 | #include <error.h> |
18 | |
19 | #include "util.h" |
20 | #include "control.h" |
21 | #include "msg_zerocopy_common.h" |
22 | |
23 | #ifndef PAGE_SIZE |
24 | #define PAGE_SIZE 4096 |
25 | #endif |
26 | |
27 | #define RING_ENTRIES_NUM 4 |
28 | |
29 | #define VSOCK_TEST_DATA_MAX_IOV 3 |
30 | |
31 | struct vsock_io_uring_test { |
32 | /* Number of valid elements in 'vecs'. */ |
33 | int vecs_cnt; |
34 | struct iovec vecs[VSOCK_TEST_DATA_MAX_IOV]; |
35 | }; |
36 | |
37 | static struct vsock_io_uring_test test_data_array[] = { |
38 | /* All elements have page aligned base and size. */ |
39 | { |
40 | .vecs_cnt = 3, |
41 | { |
42 | { NULL, PAGE_SIZE }, |
43 | { NULL, 2 * PAGE_SIZE }, |
44 | { NULL, 3 * PAGE_SIZE }, |
45 | } |
46 | }, |
47 | /* Middle element has both non-page aligned base and size. */ |
48 | { |
49 | .vecs_cnt = 3, |
50 | { |
51 | { NULL, PAGE_SIZE }, |
52 | { (void *)1, 200 }, |
53 | { NULL, 3 * PAGE_SIZE }, |
54 | } |
55 | } |
56 | }; |
57 | |
58 | static void vsock_io_uring_client(const struct test_opts *opts, |
59 | const struct vsock_io_uring_test *test_data, |
60 | bool msg_zerocopy) |
61 | { |
62 | struct io_uring_sqe *sqe; |
63 | struct io_uring_cqe *cqe; |
64 | struct io_uring ring; |
65 | struct iovec *iovec; |
66 | struct msghdr msg; |
67 | int fd; |
68 | |
69 | fd = vsock_stream_connect(cid: opts->peer_cid, port: opts->peer_port); |
70 | if (fd < 0) { |
71 | perror("connect" ); |
72 | exit(EXIT_FAILURE); |
73 | } |
74 | |
75 | if (msg_zerocopy) |
76 | enable_so_zerocopy(fd); |
77 | |
78 | iovec = alloc_test_iovec(test_iovec: test_data->vecs, iovnum: test_data->vecs_cnt); |
79 | |
80 | if (io_uring_queue_init(RING_ENTRIES_NUM, &ring, 0)) |
81 | error(1, errno, "io_uring_queue_init" ); |
82 | |
83 | if (io_uring_register_buffers(&ring, iovec, test_data->vecs_cnt)) |
84 | error(1, errno, "io_uring_register_buffers" ); |
85 | |
86 | memset(&msg, 0, sizeof(msg)); |
87 | msg.msg_iov = iovec; |
88 | msg.msg_iovlen = test_data->vecs_cnt; |
89 | sqe = io_uring_get_sqe(&ring); |
90 | |
91 | if (msg_zerocopy) |
92 | io_uring_prep_sendmsg_zc(sqe, fd, &msg, 0); |
93 | else |
94 | io_uring_prep_sendmsg(sqe, fd, &msg, 0); |
95 | |
96 | if (io_uring_submit(&ring) != 1) |
97 | error(1, errno, "io_uring_submit" ); |
98 | |
99 | if (io_uring_wait_cqe(&ring, &cqe)) |
100 | error(1, errno, "io_uring_wait_cqe" ); |
101 | |
102 | io_uring_cqe_seen(&ring, cqe); |
103 | |
104 | control_writeulong(value: iovec_hash_djb2(iov: iovec, iovnum: test_data->vecs_cnt)); |
105 | |
106 | control_writeln(str: "DONE" ); |
107 | io_uring_queue_exit(&ring); |
108 | free_test_iovec(test_iovec: test_data->vecs, iovec, iovnum: test_data->vecs_cnt); |
109 | close(fd); |
110 | } |
111 | |
112 | static void vsock_io_uring_server(const struct test_opts *opts, |
113 | const struct vsock_io_uring_test *test_data) |
114 | { |
115 | unsigned long remote_hash; |
116 | unsigned long local_hash; |
117 | struct io_uring ring; |
118 | size_t data_len; |
119 | size_t recv_len; |
120 | void *data; |
121 | int fd; |
122 | |
123 | fd = vsock_stream_accept(VMADDR_CID_ANY, port: opts->peer_port, NULL); |
124 | if (fd < 0) { |
125 | perror("accept" ); |
126 | exit(EXIT_FAILURE); |
127 | } |
128 | |
129 | data_len = iovec_bytes(iov: test_data->vecs, iovnum: test_data->vecs_cnt); |
130 | |
131 | data = malloc(data_len); |
132 | if (!data) { |
133 | perror("malloc" ); |
134 | exit(EXIT_FAILURE); |
135 | } |
136 | |
137 | if (io_uring_queue_init(RING_ENTRIES_NUM, &ring, 0)) |
138 | error(1, errno, "io_uring_queue_init" ); |
139 | |
140 | recv_len = 0; |
141 | |
142 | while (recv_len < data_len) { |
143 | struct io_uring_sqe *sqe; |
144 | struct io_uring_cqe *cqe; |
145 | struct iovec iovec; |
146 | |
147 | sqe = io_uring_get_sqe(&ring); |
148 | iovec.iov_base = data + recv_len; |
149 | iovec.iov_len = data_len; |
150 | |
151 | io_uring_prep_readv(sqe, fd, &iovec, 1, 0); |
152 | |
153 | if (io_uring_submit(&ring) != 1) |
154 | error(1, errno, "io_uring_submit" ); |
155 | |
156 | if (io_uring_wait_cqe(&ring, &cqe)) |
157 | error(1, errno, "io_uring_wait_cqe" ); |
158 | |
159 | recv_len += cqe->res; |
160 | io_uring_cqe_seen(&ring, cqe); |
161 | } |
162 | |
163 | if (recv_len != data_len) { |
164 | fprintf(stderr, "expected %zu, got %zu\n" , data_len, |
165 | recv_len); |
166 | exit(EXIT_FAILURE); |
167 | } |
168 | |
169 | local_hash = hash_djb2(data, len: data_len); |
170 | |
171 | remote_hash = control_readulong(); |
172 | if (remote_hash != local_hash) { |
173 | fprintf(stderr, "hash mismatch\n" ); |
174 | exit(EXIT_FAILURE); |
175 | } |
176 | |
177 | control_expectln(str: "DONE" ); |
178 | io_uring_queue_exit(&ring); |
179 | free(data); |
180 | } |
181 | |
182 | void test_stream_uring_server(const struct test_opts *opts) |
183 | { |
184 | int i; |
185 | |
186 | for (i = 0; i < ARRAY_SIZE(test_data_array); i++) |
187 | vsock_io_uring_server(opts, test_data: &test_data_array[i]); |
188 | } |
189 | |
190 | void test_stream_uring_client(const struct test_opts *opts) |
191 | { |
192 | int i; |
193 | |
194 | for (i = 0; i < ARRAY_SIZE(test_data_array); i++) |
195 | vsock_io_uring_client(opts, test_data: &test_data_array[i], msg_zerocopy: false); |
196 | } |
197 | |
198 | void test_stream_uring_msg_zc_server(const struct test_opts *opts) |
199 | { |
200 | int i; |
201 | |
202 | for (i = 0; i < ARRAY_SIZE(test_data_array); i++) |
203 | vsock_io_uring_server(opts, test_data: &test_data_array[i]); |
204 | } |
205 | |
206 | void test_stream_uring_msg_zc_client(const struct test_opts *opts) |
207 | { |
208 | int i; |
209 | |
210 | for (i = 0; i < ARRAY_SIZE(test_data_array); i++) |
211 | vsock_io_uring_client(opts, test_data: &test_data_array[i], msg_zerocopy: true); |
212 | } |
213 | |
214 | static struct test_case test_cases[] = { |
215 | { |
216 | .name = "SOCK_STREAM io_uring test" , |
217 | .run_server = test_stream_uring_server, |
218 | .run_client = test_stream_uring_client, |
219 | }, |
220 | { |
221 | .name = "SOCK_STREAM io_uring MSG_ZEROCOPY test" , |
222 | .run_server = test_stream_uring_msg_zc_server, |
223 | .run_client = test_stream_uring_msg_zc_client, |
224 | }, |
225 | {}, |
226 | }; |
227 | |
228 | static const char optstring[] = "" ; |
229 | static const struct option longopts[] = { |
230 | { |
231 | .name = "control-host" , |
232 | .has_arg = required_argument, |
233 | .val = 'H', |
234 | }, |
235 | { |
236 | .name = "control-port" , |
237 | .has_arg = required_argument, |
238 | .val = 'P', |
239 | }, |
240 | { |
241 | .name = "mode" , |
242 | .has_arg = required_argument, |
243 | .val = 'm', |
244 | }, |
245 | { |
246 | .name = "peer-cid" , |
247 | .has_arg = required_argument, |
248 | .val = 'p', |
249 | }, |
250 | { |
251 | .name = "peer-port" , |
252 | .has_arg = required_argument, |
253 | .val = 'q', |
254 | }, |
255 | { |
256 | .name = "help" , |
257 | .has_arg = no_argument, |
258 | .val = '?', |
259 | }, |
260 | {}, |
261 | }; |
262 | |
263 | static void usage(void) |
264 | { |
265 | fprintf(stderr, "Usage: vsock_uring_test [--help] [--control-host=<host>] --control-port=<port> --mode=client|server --peer-cid=<cid> [--peer-port=<port>]\n" |
266 | "\n" |
267 | " Server: vsock_uring_test --control-port=1234 --mode=server --peer-cid=3\n" |
268 | " Client: vsock_uring_test --control-host=192.168.0.1 --control-port=1234 --mode=client --peer-cid=2\n" |
269 | "\n" |
270 | "Run transmission tests using io_uring. Usage is the same as\n" |
271 | "in ./vsock_test\n" |
272 | "\n" |
273 | "Options:\n" |
274 | " --help This help message\n" |
275 | " --control-host <host> Server IP address to connect to\n" |
276 | " --control-port <port> Server port to listen on/connect to\n" |
277 | " --mode client|server Server or client mode\n" |
278 | " --peer-cid <cid> CID of the other side\n" |
279 | " --peer-port <port> AF_VSOCK port used for the test [default: %d]\n" , |
280 | DEFAULT_PEER_PORT |
281 | ); |
282 | exit(EXIT_FAILURE); |
283 | } |
284 | |
285 | int main(int argc, char **argv) |
286 | { |
287 | const char *control_host = NULL; |
288 | const char *control_port = NULL; |
289 | struct test_opts opts = { |
290 | .mode = TEST_MODE_UNSET, |
291 | .peer_cid = VMADDR_CID_ANY, |
292 | .peer_port = DEFAULT_PEER_PORT, |
293 | }; |
294 | |
295 | init_signals(); |
296 | |
297 | for (;;) { |
298 | int opt = getopt_long(argc, argv, optstring, longopts, NULL); |
299 | |
300 | if (opt == -1) |
301 | break; |
302 | |
303 | switch (opt) { |
304 | case 'H': |
305 | control_host = optarg; |
306 | break; |
307 | case 'm': |
308 | if (strcmp(optarg, "client" ) == 0) { |
309 | opts.mode = TEST_MODE_CLIENT; |
310 | } else if (strcmp(optarg, "server" ) == 0) { |
311 | opts.mode = TEST_MODE_SERVER; |
312 | } else { |
313 | fprintf(stderr, "--mode must be \"client\" or \"server\"\n" ); |
314 | return EXIT_FAILURE; |
315 | } |
316 | break; |
317 | case 'p': |
318 | opts.peer_cid = parse_cid(str: optarg); |
319 | break; |
320 | case 'q': |
321 | opts.peer_port = parse_port(str: optarg); |
322 | break; |
323 | case 'P': |
324 | control_port = optarg; |
325 | break; |
326 | case '?': |
327 | default: |
328 | usage(); |
329 | } |
330 | } |
331 | |
332 | if (!control_port) |
333 | usage(); |
334 | if (opts.mode == TEST_MODE_UNSET) |
335 | usage(); |
336 | if (opts.peer_cid == VMADDR_CID_ANY) |
337 | usage(); |
338 | |
339 | if (!control_host) { |
340 | if (opts.mode != TEST_MODE_SERVER) |
341 | usage(); |
342 | control_host = "0.0.0.0" ; |
343 | } |
344 | |
345 | control_init(control_host, control_port, |
346 | server: opts.mode == TEST_MODE_SERVER); |
347 | |
348 | run_tests(test_cases, opts: &opts); |
349 | |
350 | control_cleanup(); |
351 | |
352 | return 0; |
353 | } |
354 | |