xref: /freebsd/tools/regression/sockets/listen_backlog/listen_backlog.c (revision b3e7694832e81d7a904a10f525f8797b753bf0d3)
1 /*-
2  * Copyright (c) 2005 Robert N. M. Watson
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/types.h>
28 #include <sys/socket.h>
29 #include <sys/sysctl.h>
30 
31 #include <err.h>
32 #include <errno.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 
37 /*
38  * This regression test is intended to validate that the backlog parameter
39  * set by listen() is properly set, can be retrieved using SO_LISTENQLIMIT,
40  * and that it can be updated by later calls to listen().  We also check that
41  * SO_LISTENQLIMIT cannot be set.
42  *
43  * Future things to test:
44  *
45  * - That if we change the value of kern.ipc.soacceptqueue, the limits really
46  *   do change.
47  *
48  * - That limits are, approximately, enforced and implemented.
49  *
50  * - All this on multiple socket types -- i.e., PF_LOCAL.
51  *
52  * - That we also test SO_LISTENQLEN and SO_LISTENINCQLEN.
53  */
54 
55 /*
56  * We retrieve kern.ipc.soacceptqueue before running the tests in order to use a
57  * run-time set value of SOMAXCONN, rather than compile-time set.  We assume
58  * that no other process will be simultaneously frobbing it, and these tests
59  * may fail if that assumption is not held.
60  */
61 static int	somaxconn;
62 
63 /*
64  * Retrieve the current socket listen queue limit using SO_LISTENQLIMIT.
65  */
66 static int
socket_get_backlog(int sock,int * backlogp,const char * testclass,const char * test,const char * testfunc)67 socket_get_backlog(int sock, int *backlogp, const char *testclass,
68     const char *test, const char *testfunc)
69 {
70 	socklen_t len;
71 	int i;
72 
73 	len = sizeof(i);
74 	if (getsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &i, &len) < 0) {
75 		warn("%s: %s: %s: socket_get_backlog: getsockopt("
76 		    "SOL_SOCKET, SO_LISTENQLIMIT)", testclass, test,
77 		    testfunc);
78 		return (-1);
79 	}
80 
81 	if (len != sizeof(i)) {
82 		warnx("%s: %s: %s: socket_get_backlog: getsockopt("
83 		    "SOL_SOCKET, SO_LISTENQLIMIT): returned size %d",
84 		    testclass, test, testfunc, len);
85 		return (-1);
86 	}
87 
88 	*backlogp = i;
89 
90 	return (0);
91 }
92 
93 /*
94  * Create a socket, check the queue limit on creation, perform a listen(),
95  * and make sure that the limit was set as expected by listen().
96  */
97 static int
socket_listen(int domain,int type,int protocol,int backlog,int create_backlog_assertion,int listen_backlog_assertion,int * sockp,const char * domainstring,const char * typestring,const char * testclass,const char * test)98 socket_listen(int domain, int type, int protocol, int backlog,
99     int create_backlog_assertion, int listen_backlog_assertion, int *sockp,
100     const char *domainstring, const char *typestring, const char *testclass,
101     const char *test)
102 {
103 	int backlog_retrieved, sock;
104 
105 	sock = socket(domain, type, protocol);
106 	if (sock < 0) {
107 		warn("%s: %s: socket_listen: socket(%s, %s)", testclass,
108 		    test, domainstring, typestring);
109 		close(sock);
110 		return (-1);
111 	}
112 
113 	if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
114 	    "socket_listen") < 0) {
115 		close(sock);
116 		return (-1);
117 	}
118 
119 	if (backlog_retrieved != create_backlog_assertion) {
120 		warnx("%s: %s: socket_listen: create backlog is %d not %d",
121 		    testclass, test, backlog_retrieved,
122 		    create_backlog_assertion);
123 		close(sock);
124 		return (-1);
125 	}
126 
127 	if (listen(sock, backlog) < 0) {
128 		warn("%s: %s: socket_listen: listen(, %d)", testclass, test,
129 		    backlog);
130 		close(sock);
131 		return (-1);
132 	}
133 
134 	if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
135 	    "socket_listen") < 0) {
136 		close(sock);
137 		return (-1);
138 	}
139 
140 	if (backlog_retrieved != listen_backlog_assertion) {
141 		warnx("%s: %s: socket_listen: listen backlog is %d not %d",
142 		    testclass, test, backlog_retrieved,
143 		    listen_backlog_assertion);
144 		close(sock);
145 		return (-1);
146 	}
147 
148 	*sockp = sock;
149 	return (0);
150 }
151 
152 /*
153  * This test creates sockets and tests default states before and after
154  * listen().  Specifically, we expect a queue limit of 0 before listen, and
155  * then various settings for after listen().  If the passed backlog was
156  * either < 0 or > somaxconn, it should be set to somaxconn; otherwise, the
157  * passed queue depth.
158  */
159 static void
test_defaults(void)160 test_defaults(void)
161 {
162 	int sock;
163 
164 	/*
165 	 * First pass.  Confirm the default is 0.  Listen with a backlog of
166 	 * 0 and confirm it gets set that way.
167 	 */
168 	if (socket_listen(PF_INET, SOCK_STREAM, 0, 0, 0, 0, &sock, "PF_INET",
169 	    "SOCK_STREAM", "test_defaults", "default_0_listen_0") < 0)
170 		exit(-1);
171 	close(sock);
172 
173 	/*
174 	 * Second pass.  Listen with a backlog of -1 and make sure it is set
175 	 * to somaxconn.
176 	 */
177 	if (socket_listen(PF_INET, SOCK_STREAM, 0, -1, 0, somaxconn, &sock,
178 	    "PF_INET", "SOCK_STREAM", "test_defaults", "default_0_listen_-1")
179 	    < 0)
180 		exit(-1);
181 	close(sock);
182 
183 	/*
184 	 * Third pass.  Listen with a backlog of 1 and make sure it is set to
185 	 * 1.
186 	 */
187 	if (socket_listen(PF_INET, SOCK_STREAM, 0, 1, 0, 1, &sock, "PF_INET",
188 	    "SOCK_STREAM", "test_defaults", "default_0_listen_1") < 0)
189 		exit(-1);
190 	close(sock);
191 
192 	/*
193 	 * Fourth pass.  Listen with a backlog of somaxconn and make sure it
194 	 * is set to somaxconn.
195 	 */
196 	if (socket_listen(PF_INET, SOCK_STREAM, 0, somaxconn, 0, somaxconn,
197 	    &sock, "PF_INET", "SOCK_STREAM", "test_defaults",
198 	    "default_0_listen_somaxconn") < 0)
199 		exit(-1);
200 	close(sock);
201 
202 	/*
203 	 * Fifth pass.  Listen with a backlog of somaxconn+1 and make sure it
204 	 * is set to somaxconn.
205 	 */
206 	if (socket_listen(PF_INET, SOCK_STREAM, 0, somaxconn+1, 0, somaxconn,
207 	    &sock, "PF_INET", "SOCK_STREAM", "test_defaults",
208 	    "default_0_listen_somaxconn+1") < 0)
209 		exit(-1);
210 	close(sock);
211 }
212 
213 /*
214  * Create a socket, set the initial listen() state, then update the queue
215  * depth using listen().  Check that the backlog is as expected after both
216  * the first and second listen().
217  */
218 static int
socket_listen_update(int domain __unused,int type __unused,int protocol __unused,int backlog,int update_backlog,int listen_backlog_assertion,int update_backlog_assertion,int * sockp,const char * domainstring,const char * typestring,const char * testclass,const char * test)219 socket_listen_update(int domain __unused, int type __unused,
220     int protocol __unused, int backlog,
221     int update_backlog, int listen_backlog_assertion,
222     int update_backlog_assertion, int *sockp, const char *domainstring,
223     const char *typestring, const char *testclass, const char *test)
224 {
225 	int backlog_retrieved, sock;
226 
227 	sock = socket(PF_INET, SOCK_STREAM, 0);
228 	if (sock < 0) {
229 		warn("%s: %s: socket_listen_update: socket(%s, %s)",
230 		    testclass, test, domainstring, typestring);
231 		return (-1);
232 	}
233 
234 	if (listen(sock, backlog) < 0) {
235 		warn("%s: %s: socket_listen_update: initial listen(, %d)",
236 		    testclass, test, backlog);
237 		close(sock);
238 		return (-1);
239 	}
240 
241 	if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
242 	    "socket_listen_update") < 0) {
243 		close(sock);
244 		return (-1);
245 	}
246 
247 	if (backlog_retrieved != listen_backlog_assertion) {
248 		warnx("%s: %s: socket_listen_update: initial backlog is %d "
249 		    "not %d", testclass, test, backlog_retrieved,
250 		    listen_backlog_assertion);
251 		close(sock);
252 		return (-1);
253 	}
254 
255 	if (listen(sock, update_backlog) < 0) {
256 		warn("%s: %s: socket_listen_update: update listen(, %d)",
257 		    testclass, test, update_backlog);
258 		close(sock);
259 		return (-1);
260 	}
261 
262 	if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
263 	    "socket_listen_update") < 0) {
264 		close(sock);
265 		return (-1);
266 	}
267 
268 	if (backlog_retrieved != update_backlog_assertion) {
269 		warnx("%s: %s: socket_listen_update: updated backlog is %d "
270 		    "not %d", testclass, test, backlog_retrieved,
271 		    update_backlog_assertion);
272 		close(sock);
273 		return (-1);
274 	}
275 
276 	*sockp = sock;
277 	return (0);
278 }
279 
280 /*
281  * This test tests using listen() to update the queue depth after a socket
282  * has already been marked as listening.  We test several cases: setting the
283  * socket < 0, 0, 1, somaxconn, and somaxconn + 1.
284  */
285 static void
test_listen_update(void)286 test_listen_update(void)
287 {
288 	int sock;
289 
290 	/*
291 	 * Set to 5, update to -1, which should give somaxconn.
292 	 */
293 	if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, -1, 5, somaxconn,
294 	    &sock, "PF_INET", "SOCK_STREAM", "test_listen_update",
295 	    "update_5,-1") < 0)
296 		exit(-1);
297 	close(sock);
298 
299 	/*
300 	 * Set to 5, update to 0, which should give 0.
301 	 */
302 	if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, 0, 5, 0, &sock,
303 	    "PF_INET", "SOCK_STREAM", "test_listen_update", "update_5,0")
304 	    < 0)
305 		exit(-1);
306 	close(sock);
307 
308 	/*
309 	 * Set to 5, update to 1, which should give 1.
310 	 */
311 	if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, 1, 5, 1, &sock,
312 	    "PF_INET", "SOCK_STREAM", "test_listen_update", "update_5,1")
313 	    < 0)
314 		exit(-1);
315 	close(sock);
316 
317 	/*
318 	 * Set to 5, update to somaxconn, which should give somaxconn.
319 	 */
320 	if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, somaxconn, 5,
321 	    somaxconn, &sock, "PF_INET", "SOCK_STREAM", "test_listen_update",
322 	    "update_5,somaxconn") < 0)
323 		exit(-1);
324 	close(sock);
325 
326 	/*
327 	 * Set to 5, update to somaxconn+1, which should give somaxconn.
328 	 */
329 	if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, somaxconn+1, 5,
330 	    somaxconn, &sock, "PF_INET", "SOCK_STREAM", "test_listen_update",
331 	    "update_5,somaxconn+1") < 0)
332 		exit(-1);
333 	close(sock);
334 }
335 
336 /*
337  * SO_LISTENQLIMIT is a read-only socket option, so make sure we get an error
338  * if we try to write it.
339  */
340 static void
test_set_qlimit(void)341 test_set_qlimit(void)
342 {
343 	int i, ret, sock;
344 
345 	sock = socket(PF_INET, SOCK_STREAM, 0);
346 	if (sock < 0)
347 		err(-1, "test_set_qlimit: socket(PF_INET, SOCK_STREAM)");
348 
349 	i = 0;
350 	ret = setsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &i, sizeof(i));
351 	if (ret < 0 && errno != ENOPROTOOPT) {
352 		warn("test_set_qlimit: setsockopt(SOL_SOCKET, "
353 		    "SO_LISTENQLIMIT, 0): unexpected error");
354 		close(sock);
355 	}
356 
357 	if (ret == 0) {
358 		warnx("test_set_qlimit: setsockopt(SOL_SOCKET, "
359 		    "SO_LISTENQLIMIT, 0) succeeded");
360 		close(sock);
361 		exit(-1);
362 	}
363 	close(sock);
364 }
365 
366 int
main(void)367 main(void)
368 {
369 	size_t len;
370 
371 	len = sizeof(somaxconn);
372 	if (sysctlbyname("kern.ipc.soacceptqueue", &somaxconn, &len, NULL, 0)
373 	    < 0)
374 		err(-1, "sysctlbyname(kern.ipc.soacceptqueue)");
375 
376 	test_defaults();
377 	test_listen_update();
378 	test_set_qlimit();
379 
380 	return (0);
381 }
382