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