1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Selftest for AF_UNIX socket close and ECONNRESET behaviour.
4 *
5 * This test verifies:
6 * 1. SOCK_STREAM returns EOF when the peer closes normally.
7 * 2. SOCK_STREAM returns ECONNRESET if peer closes with unread data.
8 * 3. SOCK_SEQPACKET returns EOF when the peer closes normally.
9 * 4. SOCK_SEQPACKET returns ECONNRESET if the peer closes with unread data.
10 * 5. SOCK_DGRAM does not return ECONNRESET when the peer closes.
11 *
12 * These tests document the intended Linux behaviour.
13 *
14 */
15
16 #define _GNU_SOURCE
17 #include <string.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20 #include <errno.h>
21 #include <sys/socket.h>
22 #include <sys/un.h>
23 #include "../../kselftest_harness.h"
24
25 #define SOCK_PATH "/tmp/af_unix_connreset.sock"
26
remove_socket_file(void)27 static void remove_socket_file(void)
28 {
29 unlink(SOCK_PATH);
30 }
31
FIXTURE(unix_sock)32 FIXTURE(unix_sock)
33 {
34 int server;
35 int client;
36 int child;
37 };
38
FIXTURE_VARIANT(unix_sock)39 FIXTURE_VARIANT(unix_sock)
40 {
41 int socket_type;
42 const char *name;
43 };
44
FIXTURE_VARIANT_ADD(unix_sock,stream)45 FIXTURE_VARIANT_ADD(unix_sock, stream) {
46 .socket_type = SOCK_STREAM,
47 .name = "SOCK_STREAM",
48 };
49
FIXTURE_VARIANT_ADD(unix_sock,dgram)50 FIXTURE_VARIANT_ADD(unix_sock, dgram) {
51 .socket_type = SOCK_DGRAM,
52 .name = "SOCK_DGRAM",
53 };
54
FIXTURE_VARIANT_ADD(unix_sock,seqpacket)55 FIXTURE_VARIANT_ADD(unix_sock, seqpacket) {
56 .socket_type = SOCK_SEQPACKET,
57 .name = "SOCK_SEQPACKET",
58 };
59
FIXTURE_SETUP(unix_sock)60 FIXTURE_SETUP(unix_sock)
61 {
62 struct sockaddr_un addr = {};
63 int err;
64
65 addr.sun_family = AF_UNIX;
66 strcpy(addr.sun_path, SOCK_PATH);
67 remove_socket_file();
68
69 self->server = socket(AF_UNIX, variant->socket_type, 0);
70 ASSERT_LT(-1, self->server);
71
72 err = bind(self->server, (struct sockaddr *)&addr, sizeof(addr));
73 ASSERT_EQ(0, err);
74
75 if (variant->socket_type == SOCK_STREAM ||
76 variant->socket_type == SOCK_SEQPACKET) {
77 err = listen(self->server, 1);
78 ASSERT_EQ(0, err);
79 }
80
81 self->client = socket(AF_UNIX, variant->socket_type | SOCK_NONBLOCK, 0);
82 ASSERT_LT(-1, self->client);
83
84 err = connect(self->client, (struct sockaddr *)&addr, sizeof(addr));
85 ASSERT_EQ(0, err);
86 }
87
FIXTURE_TEARDOWN(unix_sock)88 FIXTURE_TEARDOWN(unix_sock)
89 {
90 if (variant->socket_type == SOCK_STREAM ||
91 variant->socket_type == SOCK_SEQPACKET)
92 close(self->child);
93
94 close(self->client);
95 close(self->server);
96 remove_socket_file();
97 }
98
99 /* Test 1: peer closes normally */
TEST_F(unix_sock,eof)100 TEST_F(unix_sock, eof)
101 {
102 char buf[16] = {};
103 ssize_t n;
104
105 if (variant->socket_type == SOCK_STREAM ||
106 variant->socket_type == SOCK_SEQPACKET) {
107 self->child = accept(self->server, NULL, NULL);
108 ASSERT_LT(-1, self->child);
109
110 close(self->child);
111 } else {
112 close(self->server);
113 }
114
115 n = recv(self->client, buf, sizeof(buf), 0);
116
117 if (variant->socket_type == SOCK_STREAM ||
118 variant->socket_type == SOCK_SEQPACKET) {
119 ASSERT_EQ(0, n);
120 } else {
121 ASSERT_EQ(-1, n);
122 ASSERT_EQ(EAGAIN, errno);
123 }
124 }
125
126 /* Test 2: peer closes with unread data */
TEST_F(unix_sock,reset_unread_behavior)127 TEST_F(unix_sock, reset_unread_behavior)
128 {
129 char buf[16] = {};
130 ssize_t n;
131
132 /* Send data that will remain unread */
133 send(self->client, "hello", 5, 0);
134
135 if (variant->socket_type == SOCK_DGRAM) {
136 /* No real connection, just close the server */
137 close(self->server);
138 } else {
139 self->child = accept(self->server, NULL, NULL);
140 ASSERT_LT(-1, self->child);
141
142 /* Peer closes before client reads */
143 close(self->child);
144 }
145
146 n = recv(self->client, buf, sizeof(buf), 0);
147 ASSERT_EQ(-1, n);
148
149 if (variant->socket_type == SOCK_STREAM ||
150 variant->socket_type == SOCK_SEQPACKET) {
151 ASSERT_EQ(ECONNRESET, errno);
152 } else {
153 ASSERT_EQ(EAGAIN, errno);
154 }
155 }
156
157 /* Test 3: closing unaccepted (embryo) server socket should reset client. */
TEST_F(unix_sock,reset_closed_embryo)158 TEST_F(unix_sock, reset_closed_embryo)
159 {
160 char buf[16] = {};
161 ssize_t n;
162
163 if (variant->socket_type == SOCK_DGRAM) {
164 snprintf(_metadata->results->reason,
165 sizeof(_metadata->results->reason),
166 "Test only applies to SOCK_STREAM and SOCK_SEQPACKET");
167 exit(KSFT_XFAIL);
168 }
169
170 /* Close server without accept()ing */
171 close(self->server);
172
173 n = recv(self->client, buf, sizeof(buf), 0);
174
175 ASSERT_EQ(-1, n);
176 ASSERT_EQ(ECONNRESET, errno);
177 }
178
179 TEST_HARNESS_MAIN
180
181