1 // SPDX-License-Identifier: GPL-2.0-only 2 /* MSG_ZEROCOPY feature tests for vsock 3 * 4 * Copyright (C) 2023 SberDevices. 5 * 6 * Author: Arseniy Krasnov <avkrasnov@salutedevices.com> 7 */ 8 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 #include <sys/mman.h> 13 #include <unistd.h> 14 #include <poll.h> 15 #include <linux/errqueue.h> 16 #include <linux/kernel.h> 17 #include <errno.h> 18 19 #include "control.h" 20 #include "vsock_test_zerocopy.h" 21 #include "msg_zerocopy_common.h" 22 23 #ifndef PAGE_SIZE 24 #define PAGE_SIZE 4096 25 #endif 26 27 #define VSOCK_TEST_DATA_MAX_IOV 3 28 29 struct vsock_test_data { 30 /* This test case if for SOCK_STREAM only. */ 31 bool stream_only; 32 /* Data must be zerocopied. This field is checked against 33 * field 'ee_code' of the 'struct sock_extended_err', which 34 * contains bit to detect that zerocopy transmission was 35 * fallbacked to copy mode. 36 */ 37 bool zerocopied; 38 /* Enable SO_ZEROCOPY option on the socket. Without enabled 39 * SO_ZEROCOPY, every MSG_ZEROCOPY transmission will behave 40 * like without MSG_ZEROCOPY flag. 41 */ 42 bool so_zerocopy; 43 /* 'errno' after 'sendmsg()' call. */ 44 int sendmsg_errno; 45 /* Number of valid elements in 'vecs'. */ 46 int vecs_cnt; 47 struct iovec vecs[VSOCK_TEST_DATA_MAX_IOV]; 48 }; 49 50 static struct vsock_test_data test_data_array[] = { 51 /* Last element has non-page aligned size. */ 52 { 53 .zerocopied = true, 54 .so_zerocopy = true, 55 .sendmsg_errno = 0, 56 .vecs_cnt = 3, 57 { 58 { NULL, PAGE_SIZE }, 59 { NULL, PAGE_SIZE }, 60 { NULL, 200 } 61 } 62 }, 63 /* All elements have page aligned base and size. */ 64 { 65 .zerocopied = true, 66 .so_zerocopy = true, 67 .sendmsg_errno = 0, 68 .vecs_cnt = 3, 69 { 70 { NULL, PAGE_SIZE }, 71 { NULL, PAGE_SIZE * 2 }, 72 { NULL, PAGE_SIZE * 3 } 73 } 74 }, 75 /* All elements have page aligned base and size. But 76 * data length is bigger than 64Kb. 77 */ 78 { 79 .zerocopied = true, 80 .so_zerocopy = true, 81 .sendmsg_errno = 0, 82 .vecs_cnt = 3, 83 { 84 { NULL, PAGE_SIZE * 16 }, 85 { NULL, PAGE_SIZE * 16 }, 86 { NULL, PAGE_SIZE * 16 } 87 } 88 }, 89 /* Middle element has both non-page aligned base and size. */ 90 { 91 .zerocopied = true, 92 .so_zerocopy = true, 93 .sendmsg_errno = 0, 94 .vecs_cnt = 3, 95 { 96 { NULL, PAGE_SIZE }, 97 { (void *)1, 100 }, 98 { NULL, PAGE_SIZE } 99 } 100 }, 101 /* Middle element is unmapped. */ 102 { 103 .zerocopied = false, 104 .so_zerocopy = true, 105 .sendmsg_errno = ENOMEM, 106 .vecs_cnt = 3, 107 { 108 { NULL, PAGE_SIZE }, 109 { MAP_FAILED, PAGE_SIZE }, 110 { NULL, PAGE_SIZE } 111 } 112 }, 113 /* Valid data, but SO_ZEROCOPY is off. This 114 * will trigger fallback to copy. 115 */ 116 { 117 .zerocopied = false, 118 .so_zerocopy = false, 119 .sendmsg_errno = 0, 120 .vecs_cnt = 1, 121 { 122 { NULL, PAGE_SIZE } 123 } 124 }, 125 /* Valid data, but message is bigger than peer's 126 * buffer, so this will trigger fallback to copy. 127 * This test is for SOCK_STREAM only, because 128 * for SOCK_SEQPACKET, 'sendmsg()' returns EMSGSIZE. 129 */ 130 { 131 .stream_only = true, 132 .zerocopied = false, 133 .so_zerocopy = true, 134 .sendmsg_errno = 0, 135 .vecs_cnt = 1, 136 { 137 { NULL, 100 * PAGE_SIZE } 138 } 139 }, 140 }; 141 142 #define POLL_TIMEOUT_MS 100 143 144 static void test_client(const struct test_opts *opts, 145 const struct vsock_test_data *test_data, 146 bool sock_seqpacket) 147 { 148 struct pollfd fds = { 0 }; 149 struct msghdr msg = { 0 }; 150 ssize_t sendmsg_res; 151 struct iovec *iovec; 152 int fd; 153 154 if (sock_seqpacket) 155 fd = vsock_seqpacket_connect(opts->peer_cid, 1234); 156 else 157 fd = vsock_stream_connect(opts->peer_cid, 1234); 158 159 if (fd < 0) { 160 perror("connect"); 161 exit(EXIT_FAILURE); 162 } 163 164 if (test_data->so_zerocopy) 165 enable_so_zerocopy(fd); 166 167 iovec = alloc_test_iovec(test_data->vecs, test_data->vecs_cnt); 168 169 msg.msg_iov = iovec; 170 msg.msg_iovlen = test_data->vecs_cnt; 171 172 errno = 0; 173 174 sendmsg_res = sendmsg(fd, &msg, MSG_ZEROCOPY); 175 if (errno != test_data->sendmsg_errno) { 176 fprintf(stderr, "expected 'errno' == %i, got %i\n", 177 test_data->sendmsg_errno, errno); 178 exit(EXIT_FAILURE); 179 } 180 181 if (!errno) { 182 if (sendmsg_res != iovec_bytes(iovec, test_data->vecs_cnt)) { 183 fprintf(stderr, "expected 'sendmsg()' == %li, got %li\n", 184 iovec_bytes(iovec, test_data->vecs_cnt), 185 sendmsg_res); 186 exit(EXIT_FAILURE); 187 } 188 } 189 190 fds.fd = fd; 191 fds.events = 0; 192 193 if (poll(&fds, 1, POLL_TIMEOUT_MS) < 0) { 194 perror("poll"); 195 exit(EXIT_FAILURE); 196 } 197 198 if (fds.revents & POLLERR) { 199 vsock_recv_completion(fd, &test_data->zerocopied); 200 } else if (test_data->so_zerocopy && !test_data->sendmsg_errno) { 201 /* If we don't have data in the error queue, but 202 * SO_ZEROCOPY was enabled and 'sendmsg()' was 203 * successful - this is an error. 204 */ 205 fprintf(stderr, "POLLERR expected\n"); 206 exit(EXIT_FAILURE); 207 } 208 209 if (!test_data->sendmsg_errno) 210 control_writeulong(iovec_hash_djb2(iovec, test_data->vecs_cnt)); 211 else 212 control_writeulong(0); 213 214 control_writeln("DONE"); 215 free_test_iovec(test_data->vecs, iovec, test_data->vecs_cnt); 216 close(fd); 217 } 218 219 void test_stream_msgzcopy_client(const struct test_opts *opts) 220 { 221 int i; 222 223 for (i = 0; i < ARRAY_SIZE(test_data_array); i++) 224 test_client(opts, &test_data_array[i], false); 225 } 226 227 void test_seqpacket_msgzcopy_client(const struct test_opts *opts) 228 { 229 int i; 230 231 for (i = 0; i < ARRAY_SIZE(test_data_array); i++) { 232 if (test_data_array[i].stream_only) 233 continue; 234 235 test_client(opts, &test_data_array[i], true); 236 } 237 } 238 239 static void test_server(const struct test_opts *opts, 240 const struct vsock_test_data *test_data, 241 bool sock_seqpacket) 242 { 243 unsigned long remote_hash; 244 unsigned long local_hash; 245 ssize_t total_bytes_rec; 246 unsigned char *data; 247 size_t data_len; 248 int fd; 249 250 if (sock_seqpacket) 251 fd = vsock_seqpacket_accept(VMADDR_CID_ANY, 1234, NULL); 252 else 253 fd = vsock_stream_accept(VMADDR_CID_ANY, 1234, NULL); 254 255 if (fd < 0) { 256 perror("accept"); 257 exit(EXIT_FAILURE); 258 } 259 260 data_len = iovec_bytes(test_data->vecs, test_data->vecs_cnt); 261 262 data = malloc(data_len); 263 if (!data) { 264 perror("malloc"); 265 exit(EXIT_FAILURE); 266 } 267 268 total_bytes_rec = 0; 269 270 while (total_bytes_rec != data_len) { 271 ssize_t bytes_rec; 272 273 bytes_rec = read(fd, data + total_bytes_rec, 274 data_len - total_bytes_rec); 275 if (bytes_rec <= 0) 276 break; 277 278 total_bytes_rec += bytes_rec; 279 } 280 281 if (test_data->sendmsg_errno == 0) 282 local_hash = hash_djb2(data, data_len); 283 else 284 local_hash = 0; 285 286 free(data); 287 288 /* Waiting for some result. */ 289 remote_hash = control_readulong(); 290 if (remote_hash != local_hash) { 291 fprintf(stderr, "hash mismatch\n"); 292 exit(EXIT_FAILURE); 293 } 294 295 control_expectln("DONE"); 296 close(fd); 297 } 298 299 void test_stream_msgzcopy_server(const struct test_opts *opts) 300 { 301 int i; 302 303 for (i = 0; i < ARRAY_SIZE(test_data_array); i++) 304 test_server(opts, &test_data_array[i], false); 305 } 306 307 void test_seqpacket_msgzcopy_server(const struct test_opts *opts) 308 { 309 int i; 310 311 for (i = 0; i < ARRAY_SIZE(test_data_array); i++) { 312 if (test_data_array[i].stream_only) 313 continue; 314 315 test_server(opts, &test_data_array[i], true); 316 } 317 } 318 319 void test_stream_msgzcopy_empty_errq_client(const struct test_opts *opts) 320 { 321 struct msghdr msg = { 0 }; 322 char cmsg_data[128]; 323 ssize_t res; 324 int fd; 325 326 fd = vsock_stream_connect(opts->peer_cid, 1234); 327 if (fd < 0) { 328 perror("connect"); 329 exit(EXIT_FAILURE); 330 } 331 332 msg.msg_control = cmsg_data; 333 msg.msg_controllen = sizeof(cmsg_data); 334 335 res = recvmsg(fd, &msg, MSG_ERRQUEUE); 336 if (res != -1) { 337 fprintf(stderr, "expected 'recvmsg(2)' failure, got %zi\n", 338 res); 339 exit(EXIT_FAILURE); 340 } 341 342 control_writeln("DONE"); 343 close(fd); 344 } 345 346 void test_stream_msgzcopy_empty_errq_server(const struct test_opts *opts) 347 { 348 int fd; 349 350 fd = vsock_stream_accept(VMADDR_CID_ANY, 1234, NULL); 351 if (fd < 0) { 352 perror("accept"); 353 exit(EXIT_FAILURE); 354 } 355 356 control_expectln("DONE"); 357 close(fd); 358 } 359