mirror of https://github.com/torvalds/linux.git
selftests: af_unix: Add tests for ECONNRESET and EOF semantics
Add selftests to verify and document Linux’s intended behaviour for UNIX domain sockets (SOCK_STREAM and SOCK_DGRAM) when a peer closes. The tests verify that: 1. SOCK_STREAM returns EOF when the peer closes normally. 2. SOCK_STREAM returns ECONNRESET if the peer closes with unread data. 3. SOCK_SEQPACKET returns EOF when the peer closes normally. 4. SOCK_SEQPACKET returns ECONNRESET if the peer closes with unread data. 5. SOCK_DGRAM does not return ECONNRESET when the peer closes. This follows up on review feedback suggesting a selftest to clarify Linux’s semantics. Suggested-by: Kuniyuki Iwashima <kuniyu@google.com> Signed-off-by: Sunday Adelodun <adelodunolaoluwa@yahoo.com> Link: https://patch.msgid.link/20251113112802.44657-1-adelodunolaoluwa@yahoo.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
parent
4e1b0afa61
commit
45a1cd8346
|
|
@ -65,3 +65,4 @@ udpgso
|
|||
udpgso_bench_rx
|
||||
udpgso_bench_tx
|
||||
unix_connect
|
||||
unix_connreset
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ TEST_GEN_PROGS := \
|
|||
scm_pidfd \
|
||||
scm_rights \
|
||||
unix_connect \
|
||||
unix_connreset \
|
||||
# end of TEST_GEN_PROGS
|
||||
|
||||
include ../../lib.mk
|
||||
|
|
|
|||
|
|
@ -0,0 +1,177 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Selftest for AF_UNIX socket close and ECONNRESET behaviour.
|
||||
*
|
||||
* This test verifies:
|
||||
* 1. SOCK_STREAM returns EOF when the peer closes normally.
|
||||
* 2. SOCK_STREAM returns ECONNRESET if peer closes with unread data.
|
||||
* 3. SOCK_SEQPACKET returns EOF when the peer closes normally.
|
||||
* 4. SOCK_SEQPACKET returns ECONNRESET if the peer closes with unread data.
|
||||
* 5. SOCK_DGRAM does not return ECONNRESET when the peer closes.
|
||||
*
|
||||
* These tests document the intended Linux behaviour.
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include "../../kselftest_harness.h"
|
||||
|
||||
#define SOCK_PATH "/tmp/af_unix_connreset.sock"
|
||||
|
||||
static void remove_socket_file(void)
|
||||
{
|
||||
unlink(SOCK_PATH);
|
||||
}
|
||||
|
||||
FIXTURE(unix_sock)
|
||||
{
|
||||
int server;
|
||||
int client;
|
||||
int child;
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT(unix_sock)
|
||||
{
|
||||
int socket_type;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(unix_sock, stream) {
|
||||
.socket_type = SOCK_STREAM,
|
||||
.name = "SOCK_STREAM",
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(unix_sock, dgram) {
|
||||
.socket_type = SOCK_DGRAM,
|
||||
.name = "SOCK_DGRAM",
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(unix_sock, seqpacket) {
|
||||
.socket_type = SOCK_SEQPACKET,
|
||||
.name = "SOCK_SEQPACKET",
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(unix_sock)
|
||||
{
|
||||
struct sockaddr_un addr = {};
|
||||
int err;
|
||||
|
||||
addr.sun_family = AF_UNIX;
|
||||
strcpy(addr.sun_path, SOCK_PATH);
|
||||
remove_socket_file();
|
||||
|
||||
self->server = socket(AF_UNIX, variant->socket_type, 0);
|
||||
ASSERT_LT(-1, self->server);
|
||||
|
||||
err = bind(self->server, (struct sockaddr *)&addr, sizeof(addr));
|
||||
ASSERT_EQ(0, err);
|
||||
|
||||
if (variant->socket_type == SOCK_STREAM ||
|
||||
variant->socket_type == SOCK_SEQPACKET) {
|
||||
err = listen(self->server, 1);
|
||||
ASSERT_EQ(0, err);
|
||||
}
|
||||
|
||||
self->client = socket(AF_UNIX, variant->socket_type | SOCK_NONBLOCK, 0);
|
||||
ASSERT_LT(-1, self->client);
|
||||
|
||||
err = connect(self->client, (struct sockaddr *)&addr, sizeof(addr));
|
||||
ASSERT_EQ(0, err);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(unix_sock)
|
||||
{
|
||||
if (variant->socket_type == SOCK_STREAM ||
|
||||
variant->socket_type == SOCK_SEQPACKET)
|
||||
close(self->child);
|
||||
|
||||
close(self->client);
|
||||
close(self->server);
|
||||
remove_socket_file();
|
||||
}
|
||||
|
||||
/* Test 1: peer closes normally */
|
||||
TEST_F(unix_sock, eof)
|
||||
{
|
||||
char buf[16] = {};
|
||||
ssize_t n;
|
||||
|
||||
if (variant->socket_type == SOCK_STREAM ||
|
||||
variant->socket_type == SOCK_SEQPACKET) {
|
||||
self->child = accept(self->server, NULL, NULL);
|
||||
ASSERT_LT(-1, self->child);
|
||||
|
||||
close(self->child);
|
||||
} else {
|
||||
close(self->server);
|
||||
}
|
||||
|
||||
n = recv(self->client, buf, sizeof(buf), 0);
|
||||
|
||||
if (variant->socket_type == SOCK_STREAM ||
|
||||
variant->socket_type == SOCK_SEQPACKET) {
|
||||
ASSERT_EQ(0, n);
|
||||
} else {
|
||||
ASSERT_EQ(-1, n);
|
||||
ASSERT_EQ(EAGAIN, errno);
|
||||
}
|
||||
}
|
||||
|
||||
/* Test 2: peer closes with unread data */
|
||||
TEST_F(unix_sock, reset_unread_behavior)
|
||||
{
|
||||
char buf[16] = {};
|
||||
ssize_t n;
|
||||
|
||||
/* Send data that will remain unread */
|
||||
send(self->client, "hello", 5, 0);
|
||||
|
||||
if (variant->socket_type == SOCK_DGRAM) {
|
||||
/* No real connection, just close the server */
|
||||
close(self->server);
|
||||
} else {
|
||||
self->child = accept(self->server, NULL, NULL);
|
||||
ASSERT_LT(-1, self->child);
|
||||
|
||||
/* Peer closes before client reads */
|
||||
close(self->child);
|
||||
}
|
||||
|
||||
n = recv(self->client, buf, sizeof(buf), 0);
|
||||
ASSERT_EQ(-1, n);
|
||||
|
||||
if (variant->socket_type == SOCK_STREAM ||
|
||||
variant->socket_type == SOCK_SEQPACKET) {
|
||||
ASSERT_EQ(ECONNRESET, errno);
|
||||
} else {
|
||||
ASSERT_EQ(EAGAIN, errno);
|
||||
}
|
||||
}
|
||||
|
||||
/* Test 3: closing unaccepted (embryo) server socket should reset client. */
|
||||
TEST_F(unix_sock, reset_closed_embryo)
|
||||
{
|
||||
char buf[16] = {};
|
||||
ssize_t n;
|
||||
|
||||
if (variant->socket_type == SOCK_DGRAM)
|
||||
SKIP(return, "This test only applies to SOCK_STREAM and SOCK_SEQPACKET");
|
||||
|
||||
/* Close server without accept()ing */
|
||||
close(self->server);
|
||||
|
||||
n = recv(self->client, buf, sizeof(buf), 0);
|
||||
|
||||
ASSERT_EQ(-1, n);
|
||||
ASSERT_EQ(ECONNRESET, errno);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
||||
Loading…
Reference in New Issue