2023-12-05 06:57:04

by Arseniy Krasnov

[permalink] [raw]
Subject: [PATCH net-next v6 4/4] vsock/test: two tests to check credit update logic

Both tests are almost same, only differs in two 'if' conditions, so
implemented in a single function. Tests check, that credit update
message is sent:

1) During setting SO_RCVLOWAT value of the socket.
2) When number of 'rx_bytes' become smaller than SO_RCVLOWAT value.

Signed-off-by: Arseniy Krasnov <[email protected]>
---
Changelog:
v1 -> v2:
* Update commit message by removing 'This patch adds XXX' manner.
* Update commit message by adding details about dependency for this
test from kernel internal define VIRTIO_VSOCK_MAX_PKT_BUF_SIZE.
* Add comment for this dependency in 'vsock_test.c' where this define
is duplicated.
v2 -> v3:
* Replace synchronization based on control TCP socket with vsock
data socket - this is needed to allow sender transmit data only
when new buffer size of receiver is visible to sender. Otherwise
there is race and test fails sometimes.
v3 -> v4:
* Replace 'recv_buf()' to 'recv(MSG_DONTWAIT)' in last read operation
in server part. This is needed to ensure that 'poll()' wake up us
when number of bytes ready to read is equal to SO_RCVLOWAT value.
v4 -> v5:
* Use 'recv_buf(MSG_DONTWAIT)' instead of 'recv(MSG_DONTWAIT)'.
v5 -> v6:
* Add second test which checks, that credit update is sent during
reading data from socket.
* Update commit message.

tools/testing/vsock/vsock_test.c | 175 +++++++++++++++++++++++++++++++
1 file changed, 175 insertions(+)

diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index 01fa816868bc..66246d81d654 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -1232,6 +1232,171 @@ static void test_double_bind_connect_client(const struct test_opts *opts)
}
}

+#define RCVLOWAT_CREDIT_UPD_BUF_SIZE (1024 * 128)
+/* This define is the same as in 'include/linux/virtio_vsock.h':
+ * it is used to decide when to send credit update message during
+ * reading from rx queue of a socket. Value and its usage in
+ * kernel is important for this test.
+ */
+#define VIRTIO_VSOCK_MAX_PKT_BUF_SIZE (1024 * 64)
+
+static void test_stream_rcvlowat_def_cred_upd_client(const struct test_opts *opts)
+{
+ size_t buf_size;
+ void *buf;
+ int fd;
+
+ fd = vsock_stream_connect(opts->peer_cid, 1234);
+ if (fd < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Send 1 byte more than peer's buffer size. */
+ buf_size = RCVLOWAT_CREDIT_UPD_BUF_SIZE + 1;
+
+ buf = malloc(buf_size);
+ if (!buf) {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Wait until peer sets needed buffer size. */
+ recv_byte(fd, 1, 0);
+
+ if (send(fd, buf, buf_size, 0) != buf_size) {
+ perror("send failed");
+ exit(EXIT_FAILURE);
+ }
+
+ free(buf);
+ close(fd);
+}
+
+static void test_stream_credit_update_test(const struct test_opts *opts,
+ bool low_rx_bytes_test)
+{
+ size_t recv_buf_size;
+ struct pollfd fds;
+ size_t buf_size;
+ void *buf;
+ int fd;
+
+ fd = vsock_stream_accept(VMADDR_CID_ANY, 1234, NULL);
+ if (fd < 0) {
+ perror("accept");
+ exit(EXIT_FAILURE);
+ }
+
+ buf_size = RCVLOWAT_CREDIT_UPD_BUF_SIZE;
+
+ if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
+ &buf_size, sizeof(buf_size))) {
+ perror("setsockopt(SO_VM_SOCKETS_BUFFER_SIZE)");
+ exit(EXIT_FAILURE);
+ }
+
+ if (low_rx_bytes_test) {
+ /* Set new SO_RCVLOWAT here. This enables sending credit
+ * update when number of bytes if our rx queue become <
+ * SO_RCVLOWAT value.
+ */
+ recv_buf_size = 1 + VIRTIO_VSOCK_MAX_PKT_BUF_SIZE;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVLOWAT,
+ &recv_buf_size, sizeof(recv_buf_size))) {
+ perror("setsockopt(SO_RCVLOWAT)");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* Send one dummy byte here, because 'setsockopt()' above also
+ * sends special packet which tells sender to update our buffer
+ * size. This 'send_byte()' will serialize such packet with data
+ * reads in a loop below. Sender starts transmission only when
+ * it receives this single byte.
+ */
+ send_byte(fd, 1, 0);
+
+ buf = malloc(buf_size);
+ if (!buf) {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Wait until there will be 128KB of data in rx queue. */
+ while (1) {
+ ssize_t res;
+
+ res = recv(fd, buf, buf_size, MSG_PEEK);
+ if (res == buf_size)
+ break;
+
+ if (res <= 0) {
+ fprintf(stderr, "unexpected 'recv()' return: %zi\n", res);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* There is 128KB of data in the socket's rx queue, dequeue first
+ * 64KB, credit update is sent if 'low_rx_bytes_test' == true.
+ * Otherwise, credit update is sent in 'if (!low_rx_bytes_test)'.
+ */
+ recv_buf_size = VIRTIO_VSOCK_MAX_PKT_BUF_SIZE;
+ recv_buf(fd, buf, recv_buf_size, 0, recv_buf_size);
+
+ if (!low_rx_bytes_test) {
+ recv_buf_size++;
+
+ /* Updating SO_RCVLOWAT will send credit update. */
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVLOWAT,
+ &recv_buf_size, sizeof(recv_buf_size))) {
+ perror("setsockopt(SO_RCVLOWAT)");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ fds.fd = fd;
+ fds.events = POLLIN | POLLRDNORM | POLLERR |
+ POLLRDHUP | POLLHUP;
+
+ /* This 'poll()' will return once we receive last byte
+ * sent by client.
+ */
+ if (poll(&fds, 1, -1) < 0) {
+ perror("poll");
+ exit(EXIT_FAILURE);
+ }
+
+ if (fds.revents & POLLERR) {
+ fprintf(stderr, "'poll()' error\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (fds.revents & (POLLIN | POLLRDNORM)) {
+ recv_buf(fd, buf, recv_buf_size, MSG_DONTWAIT, recv_buf_size);
+ } else {
+ /* These flags must be set, as there is at
+ * least 64KB of data ready to read.
+ */
+ fprintf(stderr, "POLLIN | POLLRDNORM expected\n");
+ exit(EXIT_FAILURE);
+ }
+
+ free(buf);
+ close(fd);
+}
+
+static void test_stream_cred_upd_on_low_rx_bytes(const struct test_opts *opts)
+{
+ test_stream_credit_update_test(opts, true);
+}
+
+static void test_stream_cred_upd_on_set_rcvlowat(const struct test_opts *opts)
+{
+ test_stream_credit_update_test(opts, false);
+}
+
static struct test_case test_cases[] = {
{
.name = "SOCK_STREAM connection reset",
@@ -1342,6 +1507,16 @@ static struct test_case test_cases[] = {
.run_client = test_double_bind_connect_client,
.run_server = test_double_bind_connect_server,
},
+ {
+ .name = "SOCK_STREAM virtio credit update + SO_RCVLOWAT",
+ .run_client = test_stream_rcvlowat_def_cred_upd_client,
+ .run_server = test_stream_cred_upd_on_set_rcvlowat,
+ },
+ {
+ .name = "SOCK_STREAM virtio credit update + low rx_bytes",
+ .run_client = test_stream_rcvlowat_def_cred_upd_client,
+ .run_server = test_stream_cred_upd_on_low_rx_bytes,
+ },
{},
};

--
2.25.1


2023-12-05 10:56:55

by Stefano Garzarella

[permalink] [raw]
Subject: Re: [PATCH net-next v6 4/4] vsock/test: two tests to check credit update logic

On Tue, Dec 05, 2023 at 09:48:06AM +0300, Arseniy Krasnov wrote:
>Both tests are almost same, only differs in two 'if' conditions, so
>implemented in a single function. Tests check, that credit update
>message is sent:
>
>1) During setting SO_RCVLOWAT value of the socket.
>2) When number of 'rx_bytes' become smaller than SO_RCVLOWAT value.
>
>Signed-off-by: Arseniy Krasnov <[email protected]>
>---
> Changelog:
> v1 -> v2:
> * Update commit message by removing 'This patch adds XXX' manner.
> * Update commit message by adding details about dependency for this
> test from kernel internal define VIRTIO_VSOCK_MAX_PKT_BUF_SIZE.
> * Add comment for this dependency in 'vsock_test.c' where this define
> is duplicated.
> v2 -> v3:
> * Replace synchronization based on control TCP socket with vsock
> data socket - this is needed to allow sender transmit data only
> when new buffer size of receiver is visible to sender. Otherwise
> there is race and test fails sometimes.
> v3 -> v4:
> * Replace 'recv_buf()' to 'recv(MSG_DONTWAIT)' in last read operation
> in server part. This is needed to ensure that 'poll()' wake up us
> when number of bytes ready to read is equal to SO_RCVLOWAT value.
> v4 -> v5:
> * Use 'recv_buf(MSG_DONTWAIT)' instead of 'recv(MSG_DONTWAIT)'.
> v5 -> v6:
> * Add second test which checks, that credit update is sent during
> reading data from socket.
> * Update commit message.
>
> tools/testing/vsock/vsock_test.c | 175 +++++++++++++++++++++++++++++++
> 1 file changed, 175 insertions(+)

Reviewed-by: Stefano Garzarella <[email protected]>

>
>diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
>index 01fa816868bc..66246d81d654 100644
>--- a/tools/testing/vsock/vsock_test.c
>+++ b/tools/testing/vsock/vsock_test.c
>@@ -1232,6 +1232,171 @@ static void test_double_bind_connect_client(const struct test_opts *opts)
> }
> }
>
>+#define RCVLOWAT_CREDIT_UPD_BUF_SIZE (1024 * 128)
>+/* This define is the same as in 'include/linux/virtio_vsock.h':
>+ * it is used to decide when to send credit update message during
>+ * reading from rx queue of a socket. Value and its usage in
>+ * kernel is important for this test.
>+ */
>+#define VIRTIO_VSOCK_MAX_PKT_BUF_SIZE (1024 * 64)
>+
>+static void test_stream_rcvlowat_def_cred_upd_client(const struct test_opts *opts)
>+{
>+ size_t buf_size;
>+ void *buf;
>+ int fd;
>+
>+ fd = vsock_stream_connect(opts->peer_cid, 1234);
>+ if (fd < 0) {
>+ perror("connect");
>+ exit(EXIT_FAILURE);
>+ }
>+
>+ /* Send 1 byte more than peer's buffer size. */
>+ buf_size = RCVLOWAT_CREDIT_UPD_BUF_SIZE + 1;
>+
>+ buf = malloc(buf_size);
>+ if (!buf) {
>+ perror("malloc");
>+ exit(EXIT_FAILURE);
>+ }
>+
>+ /* Wait until peer sets needed buffer size. */
>+ recv_byte(fd, 1, 0);
>+
>+ if (send(fd, buf, buf_size, 0) != buf_size) {
>+ perror("send failed");
>+ exit(EXIT_FAILURE);
>+ }
>+
>+ free(buf);
>+ close(fd);
>+}
>+
>+static void test_stream_credit_update_test(const struct test_opts *opts,
>+ bool low_rx_bytes_test)
>+{
>+ size_t recv_buf_size;
>+ struct pollfd fds;
>+ size_t buf_size;
>+ void *buf;
>+ int fd;
>+
>+ fd = vsock_stream_accept(VMADDR_CID_ANY, 1234, NULL);
>+ if (fd < 0) {
>+ perror("accept");
>+ exit(EXIT_FAILURE);
>+ }
>+
>+ buf_size = RCVLOWAT_CREDIT_UPD_BUF_SIZE;
>+
>+ if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
>+ &buf_size, sizeof(buf_size))) {
>+ perror("setsockopt(SO_VM_SOCKETS_BUFFER_SIZE)");
>+ exit(EXIT_FAILURE);
>+ }
>+
>+ if (low_rx_bytes_test) {
>+ /* Set new SO_RCVLOWAT here. This enables sending credit
>+ * update when number of bytes if our rx queue become <
>+ * SO_RCVLOWAT value.
>+ */
>+ recv_buf_size = 1 + VIRTIO_VSOCK_MAX_PKT_BUF_SIZE;
>+
>+ if (setsockopt(fd, SOL_SOCKET, SO_RCVLOWAT,
>+ &recv_buf_size, sizeof(recv_buf_size))) {
>+ perror("setsockopt(SO_RCVLOWAT)");
>+ exit(EXIT_FAILURE);
>+ }
>+ }
>+
>+ /* Send one dummy byte here, because 'setsockopt()' above also
>+ * sends special packet which tells sender to update our buffer
>+ * size. This 'send_byte()' will serialize such packet with data
>+ * reads in a loop below. Sender starts transmission only when
>+ * it receives this single byte.
>+ */
>+ send_byte(fd, 1, 0);
>+
>+ buf = malloc(buf_size);
>+ if (!buf) {
>+ perror("malloc");
>+ exit(EXIT_FAILURE);
>+ }
>+
>+ /* Wait until there will be 128KB of data in rx queue. */
>+ while (1) {
>+ ssize_t res;
>+
>+ res = recv(fd, buf, buf_size, MSG_PEEK);
>+ if (res == buf_size)
>+ break;
>+
>+ if (res <= 0) {
>+ fprintf(stderr, "unexpected 'recv()' return: %zi\n", res);
>+ exit(EXIT_FAILURE);
>+ }
>+ }
>+
>+ /* There is 128KB of data in the socket's rx queue, dequeue first
>+ * 64KB, credit update is sent if 'low_rx_bytes_test' == true.
>+ * Otherwise, credit update is sent in 'if (!low_rx_bytes_test)'.
>+ */
>+ recv_buf_size = VIRTIO_VSOCK_MAX_PKT_BUF_SIZE;
>+ recv_buf(fd, buf, recv_buf_size, 0, recv_buf_size);
>+
>+ if (!low_rx_bytes_test) {
>+ recv_buf_size++;
>+
>+ /* Updating SO_RCVLOWAT will send credit update. */
>+ if (setsockopt(fd, SOL_SOCKET, SO_RCVLOWAT,
>+ &recv_buf_size, sizeof(recv_buf_size))) {
>+ perror("setsockopt(SO_RCVLOWAT)");
>+ exit(EXIT_FAILURE);
>+ }
>+ }
>+
>+ fds.fd = fd;
>+ fds.events = POLLIN | POLLRDNORM | POLLERR |
>+ POLLRDHUP | POLLHUP;
>+
>+ /* This 'poll()' will return once we receive last byte
>+ * sent by client.
>+ */
>+ if (poll(&fds, 1, -1) < 0) {
>+ perror("poll");
>+ exit(EXIT_FAILURE);
>+ }
>+
>+ if (fds.revents & POLLERR) {
>+ fprintf(stderr, "'poll()' error\n");
>+ exit(EXIT_FAILURE);
>+ }
>+
>+ if (fds.revents & (POLLIN | POLLRDNORM)) {
>+ recv_buf(fd, buf, recv_buf_size, MSG_DONTWAIT, recv_buf_size);
>+ } else {
>+ /* These flags must be set, as there is at
>+ * least 64KB of data ready to read.
>+ */
>+ fprintf(stderr, "POLLIN | POLLRDNORM expected\n");
>+ exit(EXIT_FAILURE);
>+ }
>+
>+ free(buf);
>+ close(fd);
>+}
>+
>+static void test_stream_cred_upd_on_low_rx_bytes(const struct test_opts *opts)
>+{
>+ test_stream_credit_update_test(opts, true);
>+}
>+
>+static void test_stream_cred_upd_on_set_rcvlowat(const struct test_opts *opts)
>+{
>+ test_stream_credit_update_test(opts, false);
>+}
>+
> static struct test_case test_cases[] = {
> {
> .name = "SOCK_STREAM connection reset",
>@@ -1342,6 +1507,16 @@ static struct test_case test_cases[] = {
> .run_client = test_double_bind_connect_client,
> .run_server = test_double_bind_connect_server,
> },
>+ {
>+ .name = "SOCK_STREAM virtio credit update + SO_RCVLOWAT",
>+ .run_client = test_stream_rcvlowat_def_cred_upd_client,
>+ .run_server = test_stream_cred_upd_on_set_rcvlowat,
>+ },
>+ {
>+ .name = "SOCK_STREAM virtio credit update + low rx_bytes",
>+ .run_client = test_stream_rcvlowat_def_cred_upd_client,
>+ .run_server = test_stream_cred_upd_on_low_rx_bytes,
>+ },
> {},
> };
>
>--
>2.25.1
>