1 /* SPDX-License-Identifier: MIT */
2 /*
3 * Description: Check that {g,s}etsockopt CMD operations on sockets are
4 * consistent.
5 *
6 * The tests basically do the same socket operation using regular system calls
7 * and io_uring commands, and then compare the results.
8 */
9
10 #include <stdio.h>
11 #include <assert.h>
12 #include <string.h>
13 #include <unistd.h>
14 #include <linux/tcp.h>
15
16 #include "liburing.h"
17 #include "helpers.h"
18
19 #define USERDATA 0xff42ff
20 #define MSG "foobarbaz"
21
22 static int no_sock_opt;
23
24 struct fds {
25 int tx;
26 int rx;
27 };
28
create_sockets(void)29 static struct fds create_sockets(void)
30 {
31 struct fds retval;
32 int fd[2];
33
34 t_create_socket_pair(fd, true);
35
36 retval.tx = fd[0];
37 retval.rx = fd[1];
38
39 return retval;
40 }
41
create_ring(void)42 static struct io_uring create_ring(void)
43 {
44 struct io_uring ring;
45 int ring_flags = 0;
46 int err;
47
48 err = io_uring_queue_init(32, &ring, ring_flags);
49 assert(err == 0);
50
51 return ring;
52 }
53
submit_cmd_sqe(struct io_uring * ring,int32_t fd,int op,int level,int optname,void * optval,int optlen,bool async)54 static int submit_cmd_sqe(struct io_uring *ring, int32_t fd,
55 int op, int level, int optname,
56 void *optval, int optlen,
57 bool async)
58 {
59 struct io_uring_sqe *sqe;
60 int err;
61
62 assert(fd > 0);
63
64 sqe = io_uring_get_sqe(ring);
65 assert(sqe != NULL);
66
67 io_uring_prep_cmd_sock(sqe, op, fd, level, optname, optval, optlen);
68 sqe->user_data = USERDATA;
69 if (async)
70 sqe->flags |= IOSQE_ASYNC;
71
72 /* Submitting SQE */
73 err = io_uring_submit_and_wait(ring, 1);
74 if (err != 1)
75 fprintf(stderr, "Failure: io_uring_submit_and_wait returned %d\n", err);
76
77 return err;
78 }
79
receive_cqe(struct io_uring * ring)80 static int receive_cqe(struct io_uring *ring)
81 {
82 struct io_uring_cqe *cqe;
83 int err;
84
85 err = io_uring_wait_cqe(ring, &cqe);
86 assert(err == 0);
87 assert(cqe->user_data == USERDATA);
88 io_uring_cqe_seen(ring, cqe);
89
90 /* Return the result of the operation */
91 return cqe->res;
92 }
93
94 /*
95 * Run getsock operation using SO_RCVBUF using io_uring cmd operation and
96 * getsockopt(2) and compare the results.
97 */
run_get_rcvbuf(struct io_uring * ring,struct fds * sockfds,bool async)98 static int run_get_rcvbuf(struct io_uring *ring, struct fds *sockfds, bool async)
99 {
100 int sval, uval, ulen, err;
101 unsigned int slen;
102
103 /* System call values */
104 slen = sizeof(sval);
105 /* io_uring values */
106 ulen = sizeof(uval);
107
108 /* get through io_uring cmd */
109 err = submit_cmd_sqe(ring, sockfds->rx, SOCKET_URING_OP_GETSOCKOPT,
110 SOL_SOCKET, SO_RCVBUF, &uval, ulen, async);
111 assert(err == 1);
112
113 /* Wait for the CQE */
114 err = receive_cqe(ring);
115 if (err == -EOPNOTSUPP)
116 return T_EXIT_SKIP;
117 if (err < 0) {
118 fprintf(stderr, "Error received. %d\n", err);
119 return T_EXIT_FAIL;
120 }
121 /* The output of CQE->res contains the length */
122 ulen = err;
123
124 /* Executes the same operation using system call */
125 err = getsockopt(sockfds->rx, SOL_SOCKET, SO_RCVBUF, &sval, &slen);
126 assert(err == 0);
127
128 /* Make sure that io_uring operation returns the same value as the systemcall */
129 assert(ulen == slen);
130 assert(uval == sval);
131
132 return T_EXIT_PASS;
133 }
134
135 /*
136 * Run getsock operation using SO_PEERNAME using io_uring cmd operation
137 * and getsockopt(2) and compare the results.
138 */
run_get_peername(struct io_uring * ring,struct fds * sockfds,bool async)139 static int run_get_peername(struct io_uring *ring, struct fds *sockfds, bool async)
140 {
141 struct sockaddr sval, uval = {};
142 socklen_t slen = sizeof(sval);
143 socklen_t ulen = sizeof(uval);
144 int err;
145
146 /* Get values from the systemcall */
147 err = getsockopt(sockfds->tx, SOL_SOCKET, SO_PEERNAME, &sval, &slen);
148 assert(err == 0);
149
150 /* Getting SO_PEERNAME */
151 err = submit_cmd_sqe(ring, sockfds->rx, SOCKET_URING_OP_GETSOCKOPT,
152 SOL_SOCKET, SO_PEERNAME, &uval, ulen, async);
153 assert(err == 1);
154
155 /* Wait for the CQE */
156 err = receive_cqe(ring);
157 if (err == -EOPNOTSUPP || err == -EINVAL) {
158 no_sock_opt = 1;
159 return T_EXIT_SKIP;
160 }
161
162 if (err < 0) {
163 fprintf(stderr, "%s: Error in the CQE: %d\n", __func__, err);
164 return T_EXIT_FAIL;
165 }
166
167 /* The length comes from cqe->res, which is returned from receive_cqe() */
168 ulen = err;
169
170 /* Make sure that io_uring operation returns the same values as the systemcall */
171 assert(sval.sa_family == uval.sa_family);
172 assert(slen == ulen);
173
174 return T_EXIT_PASS;
175 }
176
177 /*
178 * Run getsockopt tests. Basically comparing io_uring output and systemcall results
179 */
run_getsockopt_test(struct io_uring * ring,struct fds * sockfds)180 static int run_getsockopt_test(struct io_uring *ring, struct fds *sockfds)
181 {
182 int err;
183
184 err = run_get_peername(ring, sockfds, false);
185 if (err)
186 return err;
187
188 err = run_get_peername(ring, sockfds, true);
189 if (err)
190 return err;
191
192 err = run_get_rcvbuf(ring, sockfds, false);
193 if (err)
194 return err;
195
196 return run_get_rcvbuf(ring, sockfds, true);
197 }
198
199 /*
200 * Given a `val` value, set it in SO_REUSEPORT using io_uring cmd, and read using
201 * getsockopt(2), and make sure they match.
202 */
run_setsockopt_reuseport(struct io_uring * ring,struct fds * sockfds,int val,bool async)203 static int run_setsockopt_reuseport(struct io_uring *ring, struct fds *sockfds,
204 int val, bool async)
205 {
206 unsigned int slen, ulen;
207 int sval, uval = val;
208 int err;
209
210 slen = sizeof(sval);
211 ulen = sizeof(uval);
212
213 /* Setting SO_REUSEPORT */
214 err = submit_cmd_sqe(ring, sockfds->rx, SOCKET_URING_OP_SETSOCKOPT,
215 SOL_SOCKET, SO_REUSEPORT, &uval, ulen, async);
216 assert(err == 1);
217
218 err = receive_cqe(ring);
219 if (err == -EOPNOTSUPP)
220 return T_EXIT_SKIP;
221
222 /* Get values from the systemcall */
223 err = getsockopt(sockfds->rx, SOL_SOCKET, SO_REUSEPORT, &sval, &slen);
224 assert(err == 0);
225
226 /* Make sure the set using io_uring cmd matches what systemcall returns */
227 assert(uval == sval);
228 assert(ulen == slen);
229
230 return T_EXIT_PASS;
231 }
232
233 /*
234 * Given a `val` value, set the TCP_USER_TIMEOUT using io_uring and read using
235 * getsockopt(2). Make sure they match
236 */
run_setsockopt_usertimeout(struct io_uring * ring,struct fds * sockfds,int val,bool async)237 static int run_setsockopt_usertimeout(struct io_uring *ring, struct fds *sockfds,
238 int val, bool async)
239 {
240 int optname = TCP_USER_TIMEOUT;
241 int level = IPPROTO_TCP;
242 unsigned int slen, ulen;
243 int sval, uval, err;
244
245 slen = sizeof(uval);
246 ulen = sizeof(uval);
247
248 uval = val;
249
250 /* Setting timeout */
251 err = submit_cmd_sqe(ring, sockfds->rx, SOCKET_URING_OP_SETSOCKOPT,
252 level, optname, &uval, ulen, async);
253 assert(err == 1);
254
255 err = receive_cqe(ring);
256 if (err == -EOPNOTSUPP)
257 return T_EXIT_SKIP;
258 if (err < 0) {
259 fprintf(stderr, "%s: Got an error: %d\n", __func__, err);
260 return T_EXIT_FAIL;
261 }
262
263 /* Get the value from the systemcall, to make sure it was set */
264 err = getsockopt(sockfds->rx, level, optname, &sval, &slen);
265 assert(err == 0);
266 assert(uval == sval);
267
268 return T_EXIT_PASS;
269 }
270
271 /* Test setsockopt() for SOL_SOCKET */
run_setsockopt_test(struct io_uring * ring,struct fds * sockfds)272 static int run_setsockopt_test(struct io_uring *ring, struct fds *sockfds)
273 {
274 int err, i;
275 int j;
276
277 for (j = 0; j < 2; j++) {
278 bool async = j & 1;
279
280 for (i = 0; i <= 1; i++) {
281 err = run_setsockopt_reuseport(ring, sockfds, i, async);
282 if (err)
283 return err;
284 }
285
286 for (i = 1; i <= 10; i++) {
287 err = run_setsockopt_usertimeout(ring, sockfds, i, async);
288 if (err)
289 return err;
290 }
291 }
292
293 return err;
294 }
295
296 /* Send data through the sockets */
send_data(struct fds * s)297 static void send_data(struct fds *s)
298 {
299 int written_bytes;
300 /* Send data sing the sockstruct->send */
301 written_bytes = write(s->tx, MSG, strlen(MSG));
302 assert(written_bytes == strlen(MSG));
303 }
304
main(int argc,char * argv[])305 int main(int argc, char *argv[])
306 {
307 struct fds sockfds;
308 struct io_uring ring;
309 int err;
310
311 if (argc > 1)
312 return T_EXIT_SKIP;
313
314 /* Simply io_uring ring creation */
315 ring = create_ring();
316
317 /* Create sockets */
318 sockfds = create_sockets();
319
320 send_data(&sockfds);
321
322 err = run_getsockopt_test(&ring, &sockfds);
323 if (err) {
324 if (err == T_EXIT_SKIP) {
325 fprintf(stderr, "Skipping tests.\n");
326 return T_EXIT_SKIP;
327 }
328 fprintf(stderr, "Failed to run test: %d\n", err);
329 return err;
330 }
331 if (no_sock_opt)
332 return T_EXIT_SKIP;
333
334 err = run_setsockopt_test(&ring, &sockfds);
335 if (err) {
336 if (err == T_EXIT_SKIP) {
337 fprintf(stderr, "Skipping tests.\n");
338 return T_EXIT_SKIP;
339 }
340 fprintf(stderr, "Failed to run test: %d\n", err);
341 return err;
342 }
343
344 io_uring_queue_exit(&ring);
345 return err;
346 }
347