Path: blob/master/tools/testing/vsock/vsock_test_zerocopy.c
49255 views
// SPDX-License-Identifier: GPL-2.0-only1/* MSG_ZEROCOPY feature tests for vsock2*3* Copyright (C) 2023 SberDevices.4*5* Author: Arseniy Krasnov <[email protected]>6*/78#include <stdio.h>9#include <stdlib.h>10#include <string.h>11#include <sys/ioctl.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 <linux/sockios.h>18#include <linux/time64.h>19#include <errno.h>2021#include "control.h"22#include "timeout.h"23#include "vsock_test_zerocopy.h"24#include "msg_zerocopy_common.h"2526#ifndef PAGE_SIZE27#define PAGE_SIZE 409628#endif2930#define VSOCK_TEST_DATA_MAX_IOV 33132struct vsock_test_data {33/* This test case if for SOCK_STREAM only. */34bool stream_only;35/* Data must be zerocopied. This field is checked against36* field 'ee_code' of the 'struct sock_extended_err', which37* contains bit to detect that zerocopy transmission was38* fallbacked to copy mode.39*/40bool zerocopied;41/* Enable SO_ZEROCOPY option on the socket. Without enabled42* SO_ZEROCOPY, every MSG_ZEROCOPY transmission will behave43* like without MSG_ZEROCOPY flag.44*/45bool so_zerocopy;46/* 'errno' after 'sendmsg()' call. */47int sendmsg_errno;48/* Number of valid elements in 'vecs'. */49int vecs_cnt;50struct iovec vecs[VSOCK_TEST_DATA_MAX_IOV];51};5253static struct vsock_test_data test_data_array[] = {54/* Last element has non-page aligned size. */55{56.zerocopied = true,57.so_zerocopy = true,58.sendmsg_errno = 0,59.vecs_cnt = 3,60{61{ NULL, PAGE_SIZE },62{ NULL, PAGE_SIZE },63{ NULL, 200 }64}65},66/* All elements have page aligned base and size. */67{68.zerocopied = true,69.so_zerocopy = true,70.sendmsg_errno = 0,71.vecs_cnt = 3,72{73{ NULL, PAGE_SIZE },74{ NULL, PAGE_SIZE * 2 },75{ NULL, PAGE_SIZE * 3 }76}77},78/* All elements have page aligned base and size. But79* data length is bigger than 64Kb.80*/81{82.zerocopied = true,83.so_zerocopy = true,84.sendmsg_errno = 0,85.vecs_cnt = 3,86{87{ NULL, PAGE_SIZE * 16 },88{ NULL, PAGE_SIZE * 16 },89{ NULL, PAGE_SIZE * 16 }90}91},92/* Middle element has both non-page aligned base and size. */93{94.zerocopied = true,95.so_zerocopy = true,96.sendmsg_errno = 0,97.vecs_cnt = 3,98{99{ NULL, PAGE_SIZE },100{ (void *)1, 100 },101{ NULL, PAGE_SIZE }102}103},104/* Middle element is unmapped. */105{106.zerocopied = false,107.so_zerocopy = true,108.sendmsg_errno = ENOMEM,109.vecs_cnt = 3,110{111{ NULL, PAGE_SIZE },112{ MAP_FAILED, PAGE_SIZE },113{ NULL, PAGE_SIZE }114}115},116/* Valid data, but SO_ZEROCOPY is off. This117* will trigger fallback to copy.118*/119{120.zerocopied = false,121.so_zerocopy = false,122.sendmsg_errno = 0,123.vecs_cnt = 1,124{125{ NULL, PAGE_SIZE }126}127},128/* Valid data, but message is bigger than peer's129* buffer, so this will trigger fallback to copy.130* This test is for SOCK_STREAM only, because131* for SOCK_SEQPACKET, 'sendmsg()' returns EMSGSIZE.132*/133{134.stream_only = true,135.zerocopied = false,136.so_zerocopy = true,137.sendmsg_errno = 0,138.vecs_cnt = 1,139{140{ NULL, 100 * PAGE_SIZE }141}142},143};144145#define POLL_TIMEOUT_MS 100146147static void test_client(const struct test_opts *opts,148const struct vsock_test_data *test_data,149bool sock_seqpacket)150{151struct pollfd fds = { 0 };152struct msghdr msg = { 0 };153ssize_t sendmsg_res;154struct iovec *iovec;155int fd;156157if (sock_seqpacket)158fd = vsock_seqpacket_connect(opts->peer_cid, opts->peer_port);159else160fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);161162if (fd < 0) {163perror("connect");164exit(EXIT_FAILURE);165}166167if (test_data->so_zerocopy)168enable_so_zerocopy_check(fd);169170iovec = alloc_test_iovec(test_data->vecs, test_data->vecs_cnt);171172msg.msg_iov = iovec;173msg.msg_iovlen = test_data->vecs_cnt;174175errno = 0;176177sendmsg_res = sendmsg(fd, &msg, MSG_ZEROCOPY);178if (errno != test_data->sendmsg_errno) {179fprintf(stderr, "expected 'errno' == %i, got %i\n",180test_data->sendmsg_errno, errno);181exit(EXIT_FAILURE);182}183184if (!errno) {185if (sendmsg_res != iovec_bytes(iovec, test_data->vecs_cnt)) {186fprintf(stderr, "expected 'sendmsg()' == %li, got %li\n",187iovec_bytes(iovec, test_data->vecs_cnt),188sendmsg_res);189exit(EXIT_FAILURE);190}191}192193fds.fd = fd;194fds.events = 0;195196if (poll(&fds, 1, POLL_TIMEOUT_MS) < 0) {197perror("poll");198exit(EXIT_FAILURE);199}200201if (fds.revents & POLLERR) {202vsock_recv_completion(fd, &test_data->zerocopied);203} else if (test_data->so_zerocopy && !test_data->sendmsg_errno) {204/* If we don't have data in the error queue, but205* SO_ZEROCOPY was enabled and 'sendmsg()' was206* successful - this is an error.207*/208fprintf(stderr, "POLLERR expected\n");209exit(EXIT_FAILURE);210}211212if (!test_data->sendmsg_errno)213control_writeulong(iovec_hash_djb2(iovec, test_data->vecs_cnt));214else215control_writeulong(0);216217control_writeln("DONE");218free_test_iovec(test_data->vecs, iovec, test_data->vecs_cnt);219close(fd);220}221222void test_stream_msgzcopy_client(const struct test_opts *opts)223{224int i;225226for (i = 0; i < ARRAY_SIZE(test_data_array); i++)227test_client(opts, &test_data_array[i], false);228}229230void test_seqpacket_msgzcopy_client(const struct test_opts *opts)231{232int i;233234for (i = 0; i < ARRAY_SIZE(test_data_array); i++) {235if (test_data_array[i].stream_only)236continue;237238test_client(opts, &test_data_array[i], true);239}240}241242static void test_server(const struct test_opts *opts,243const struct vsock_test_data *test_data,244bool sock_seqpacket)245{246unsigned long remote_hash;247unsigned long local_hash;248ssize_t total_bytes_rec;249unsigned char *data;250size_t data_len;251int fd;252253if (sock_seqpacket)254fd = vsock_seqpacket_accept(VMADDR_CID_ANY, opts->peer_port, NULL);255else256fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);257258if (fd < 0) {259perror("accept");260exit(EXIT_FAILURE);261}262263data_len = iovec_bytes(test_data->vecs, test_data->vecs_cnt);264265data = malloc(data_len);266if (!data) {267perror("malloc");268exit(EXIT_FAILURE);269}270271total_bytes_rec = 0;272273while (total_bytes_rec != data_len) {274ssize_t bytes_rec;275276bytes_rec = read(fd, data + total_bytes_rec,277data_len - total_bytes_rec);278if (bytes_rec <= 0)279break;280281total_bytes_rec += bytes_rec;282}283284if (test_data->sendmsg_errno == 0)285local_hash = hash_djb2(data, data_len);286else287local_hash = 0;288289free(data);290291/* Waiting for some result. */292remote_hash = control_readulong();293if (remote_hash != local_hash) {294fprintf(stderr, "hash mismatch\n");295exit(EXIT_FAILURE);296}297298control_expectln("DONE");299close(fd);300}301302void test_stream_msgzcopy_server(const struct test_opts *opts)303{304int i;305306for (i = 0; i < ARRAY_SIZE(test_data_array); i++)307test_server(opts, &test_data_array[i], false);308}309310void test_seqpacket_msgzcopy_server(const struct test_opts *opts)311{312int i;313314for (i = 0; i < ARRAY_SIZE(test_data_array); i++) {315if (test_data_array[i].stream_only)316continue;317318test_server(opts, &test_data_array[i], true);319}320}321322void test_stream_msgzcopy_empty_errq_client(const struct test_opts *opts)323{324struct msghdr msg = { 0 };325char cmsg_data[128];326ssize_t res;327int fd;328329fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);330if (fd < 0) {331perror("connect");332exit(EXIT_FAILURE);333}334335msg.msg_control = cmsg_data;336msg.msg_controllen = sizeof(cmsg_data);337338res = recvmsg(fd, &msg, MSG_ERRQUEUE);339if (res != -1) {340fprintf(stderr, "expected 'recvmsg(2)' failure, got %zi\n",341res);342exit(EXIT_FAILURE);343}344345control_writeln("DONE");346close(fd);347}348349void test_stream_msgzcopy_empty_errq_server(const struct test_opts *opts)350{351int fd;352353fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);354if (fd < 0) {355perror("accept");356exit(EXIT_FAILURE);357}358359control_expectln("DONE");360close(fd);361}362363#define GOOD_COPY_LEN 128 /* net/vmw_vsock/virtio_transport_common.c */364365void test_stream_msgzcopy_mangle_client(const struct test_opts *opts)366{367char sbuf1[PAGE_SIZE + 1], sbuf2[GOOD_COPY_LEN];368unsigned long hash;369struct pollfd fds;370int fd, i;371372fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);373if (fd < 0) {374perror("connect");375exit(EXIT_FAILURE);376}377378enable_so_zerocopy_check(fd);379380memset(sbuf1, 'x', sizeof(sbuf1));381send_buf(fd, sbuf1, sizeof(sbuf1), 0, sizeof(sbuf1));382383for (i = 0; i < sizeof(sbuf2); i++)384sbuf2[i] = rand() & 0xff;385386send_buf(fd, sbuf2, sizeof(sbuf2), MSG_ZEROCOPY, sizeof(sbuf2));387388hash = hash_djb2(sbuf2, sizeof(sbuf2));389control_writeulong(hash);390391fds.fd = fd;392fds.events = 0;393394if (poll(&fds, 1, TIMEOUT * MSEC_PER_SEC) != 1 ||395!(fds.revents & POLLERR)) {396perror("poll");397exit(EXIT_FAILURE);398}399400close(fd);401}402403void test_stream_msgzcopy_mangle_server(const struct test_opts *opts)404{405unsigned long local_hash, remote_hash;406char rbuf[PAGE_SIZE + 1];407int fd;408409fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);410if (fd < 0) {411perror("accept");412exit(EXIT_FAILURE);413}414415/* Wait, don't race the (buggy) skbs coalescence. */416vsock_ioctl_int(fd, SIOCINQ, PAGE_SIZE + 1 + GOOD_COPY_LEN);417418/* Discard the first packet. */419recv_buf(fd, rbuf, PAGE_SIZE + 1, 0, PAGE_SIZE + 1);420421recv_buf(fd, rbuf, GOOD_COPY_LEN, 0, GOOD_COPY_LEN);422remote_hash = control_readulong();423local_hash = hash_djb2(rbuf, GOOD_COPY_LEN);424425if (local_hash != remote_hash) {426fprintf(stderr, "Data received corrupted\n");427exit(EXIT_FAILURE);428}429430close(fd);431}432433434