197403Sobrien/*-
2169691Skan * Copyright (c) 2005 Robert N. M. Watson
3169691Skan * All rights reserved.
497403Sobrien *
5132720Skan * Redistribution and use in source and binary forms, with or without
697403Sobrien * modification, are permitted provided that the following conditions
7132720Skan * are met:
897403Sobrien * 1. Redistributions of source code must retain the above copyright
997403Sobrien *    notice, this list of conditions and the following disclaimer.
1097403Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1197403Sobrien *    notice, this list of conditions and the following disclaimer in the
12132720Skan *    documentation and/or other materials provided with the distribution.
1397403Sobrien *
1497403Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1597403Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1697403Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1797403Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18132720Skan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19169691Skan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20169691Skan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2197403Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2297403Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2397403Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2497403Sobrien * SUCH DAMAGE.
2597403Sobrien *
2697403Sobrien * $FreeBSD$
2797403Sobrien */
2897403Sobrien
2997403Sobrien#include <sys/types.h>
3097403Sobrien#include <sys/socket.h>
3197403Sobrien#include <sys/sysctl.h>
3297403Sobrien
3397403Sobrien#include <err.h>
34169691Skan#include <errno.h>
3597403Sobrien#include <stdio.h>
36169691Skan#include <stdlib.h>
3797403Sobrien#include <unistd.h>
38169691Skan
3997403Sobrien/*
40102782Skan * This regression test is intended to validate that the backlog parameter
4197403Sobrien * set by listen() is properly set, can be retrieved using SO_LISTENQLIMIT,
42169691Skan * and that it can be updated by later calls to listen().  We also check that
4397403Sobrien * SO_LISTENQLIMIT cannot be set.
44169691Skan *
45169691Skan * Future things to test:
46169691Skan *
47169691Skan * - That if we change the value of kern.ipc.somaxconn, the limits really
48169691Skan *   do change.
49169691Skan *
50169691Skan * - That limits are, approximately, enforced and implemented.
51169691Skan *
52169691Skan * - All this on multiple socket types -- i.e., PF_LOCAL.
53169691Skan *
54169691Skan * - That we also test SO_LISTENQLEN and SO_LISTENINCQLEN.
55169691Skan */
5697403Sobrien
5797403Sobrien/*
5897403Sobrien * We retrieve kern.ipc.somaxconn before running the tests in order to use a
5997403Sobrien * run-time set value of SOMAXCONN, rather than compile-time set.  We assume
6097403Sobrien * that no other process will be simultaneously frobbing it, and these tests
6197403Sobrien * may fail if that assumption is not held.
6297403Sobrien */
6397403Sobrienstatic int	somaxconn;
6497403Sobrien
6597403Sobrien/*
6697403Sobrien * Retrieve the current socket listen queue limit using SO_LISTENQLIMIT.
6797403Sobrien */
6897403Sobrienstatic int
6997403Sobriensocket_get_backlog(int sock, int *backlogp, const char *testclass,
7097403Sobrien    const char *test, const char *testfunc)
7197403Sobrien{
7297403Sobrien	socklen_t len;
7397403Sobrien	int i;
7497403Sobrien
7597403Sobrien	len = sizeof(i);
7697403Sobrien	if (getsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &i, &len) < 0) {
7797403Sobrien		warn("%s: %s: %s: socket_get_backlog: getsockopt("
7897403Sobrien		    "SOL_SOCKET, SO_LISTENQLIMIT)", testclass, test,
7997403Sobrien		    testfunc);
8097403Sobrien		return (-1);
81259694Spfg	}
82259694Spfg
83259694Spfg	if (len != sizeof(i)) {
84259694Spfg		warnx("%s: %s: %s: socket_get_backlog: getsockopt("
85259694Spfg		    "SOL_SOCKET, SO_LISTENQLIMIT): returned size %d",
86259694Spfg		    testclass, test, testfunc, len);
87259694Spfg		return (-1);
88259694Spfg	}
8997403Sobrien
9097403Sobrien	*backlogp = i;
9197403Sobrien
9297403Sobrien	return (0);
9397403Sobrien}
9497403Sobrien
9597403Sobrien/*
9697403Sobrien * Create a socket, check the queue limit on creation, perform a listen(),
9797403Sobrien * and make sure that the limit was set as expected by listen().
9897403Sobrien */
9997403Sobrienstatic int
100169691Skansocket_listen(int domain, int type, int protocol, int backlog,
10197403Sobrien    int create_backlog_assertion, int listen_backlog_assertion, int *sockp,
102169691Skan    const char *domainstring, const char *typestring, const char *testclass,
103169691Skan    const char *test)
10497403Sobrien{
10597403Sobrien	int backlog_retrieved, sock;
10697403Sobrien
107169691Skan	sock = socket(domain, type, protocol);
10897403Sobrien	if (sock < 0) {
10997403Sobrien		warn("%s: %s: socket_listen: socket(%s, %s)", testclass,
11097403Sobrien		    test, domainstring, typestring);
11197403Sobrien		close(sock);
112169691Skan		return (-1);
11397403Sobrien	}
11497403Sobrien
11597403Sobrien	if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
116169691Skan	    "socket_listen") < 0) {
11797403Sobrien		close(sock);
11897403Sobrien		return (-1);
11997403Sobrien	}
12097403Sobrien
12197403Sobrien	if (backlog_retrieved != create_backlog_assertion) {
12297403Sobrien		warnx("%s: %s: socket_listen: create backlog is %d not %d",
12397403Sobrien		    testclass, test, backlog_retrieved,
12497403Sobrien		    create_backlog_assertion);
12597403Sobrien		close(sock);
12697403Sobrien		return (-1);
12797403Sobrien	}
12897403Sobrien
12997403Sobrien	if (listen(sock, backlog) < 0) {
13097403Sobrien		warn("%s: %s: socket_listen: listen(, %d)", testclass, test,
13197403Sobrien		    backlog);
13297403Sobrien		close(sock);
13397403Sobrien		return (-1);
134169691Skan	}
13597403Sobrien
13697403Sobrien	if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
13797403Sobrien	    "socket_listen") < 0) {
13897403Sobrien		close(sock);
139169691Skan		return (-1);
140169691Skan	}
141169691Skan
142169691Skan	if (backlog_retrieved != listen_backlog_assertion) {
143169691Skan		warnx("%s: %s: socket_listen: listen backlog is %d not %d",
14497403Sobrien		    testclass, test, backlog_retrieved,
145169691Skan		    listen_backlog_assertion);
146169691Skan		close(sock);
14797403Sobrien		return (-1);
14897403Sobrien	}
14997403Sobrien
15097403Sobrien	*sockp = sock;
15197403Sobrien	return (0);
152169691Skan}
15397403Sobrien
15497403Sobrien/*
15597403Sobrien * This test creates sockets and tests default states before and after
15697403Sobrien * listen().  Specifically, we expect a queue limit of 0 before listen, and
15797403Sobrien * then various settings for after listen().  If the passed backlog was
158169691Skan * either < 0 or > somaxconn, it should be set to somaxconn; otherwise, the
15997403Sobrien * passed queue depth.
16097403Sobrien */
161169691Skanstatic void
16297403Sobrientest_defaults(void)
16397403Sobrien{
16497403Sobrien	int sock;
165169691Skan
16697403Sobrien	/*
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 __unused, int type __unused,
222    int protocol __unused, int backlog,
223    int update_backlog, int listen_backlog_assertion,
224    int update_backlog_assertion, int *sockp, const char *domainstring,
225    const char *typestring, const char *testclass, const char *test)
226{
227	int backlog_retrieved, sock;
228
229	sock = socket(PF_INET, SOCK_STREAM, 0);
230	if (sock < 0) {
231		warn("%s: %s: socket_listen_update: socket(%s, %s)",
232		    testclass, test, domainstring, typestring);
233		return (-1);
234	}
235
236	if (listen(sock, backlog) < 0) {
237		warn("%s: %s: socket_listen_update: initial listen(, %d)",
238		    testclass, test, backlog);
239		close(sock);
240		return (-1);
241	}
242
243	if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
244	    "socket_listen_update") < 0) {
245		close(sock);
246		return (-1);
247	}
248
249	if (backlog_retrieved != listen_backlog_assertion) {
250		warnx("%s: %s: socket_listen_update: initial backlog is %d "
251		    "not %d", testclass, test, backlog_retrieved,
252		    listen_backlog_assertion);
253		close(sock);
254		return (-1);
255	}
256
257	if (listen(sock, update_backlog) < 0) {
258		warn("%s: %s: socket_listen_update: update listen(, %d)",
259		    testclass, test, update_backlog);
260		close(sock);
261		return (-1);
262	}
263
264	if (socket_get_backlog(sock, &backlog_retrieved, testclass, test,
265	    "socket_listen_update") < 0) {
266		close(sock);
267		return (-1);
268	}
269
270	if (backlog_retrieved != update_backlog_assertion) {
271		warnx("%s: %s: socket_listen_update: updated backlog is %d "
272		    "not %d", testclass, test, backlog_retrieved,
273		    update_backlog_assertion);
274		close(sock);
275		return (-1);
276	}
277
278	*sockp = sock;
279	return (0);
280}
281
282/*
283 * This test tests using listen() to update the queue depth after a socket
284 * has already been marked as listening.  We test several cases: setting the
285 * socket < 0, 0, 1, somaxconn, and somaxconn + 1.
286 */
287static void
288test_listen_update(void)
289{
290	int sock;
291
292	/*
293	 * Set to 5, update to -1, which should give somaxconn.
294	 */
295	if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, -1, 5, somaxconn,
296	    &sock, "PF_INET", "SOCK_STREAM", "test_listen_update",
297	    "update_5,-1") < 0)
298		exit(-1);
299	close(sock);
300
301	/*
302	 * Set to 5, update to 0, which should give 0.
303	 */
304	if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, 0, 5, 0, &sock,
305	    "PF_INET", "SOCK_STREAM", "test_listen_update", "update_5,0")
306	    < 0)
307		exit(-1);
308	close(sock);
309
310	/*
311	 * Set to 5, update to 1, which should give 1.
312	 */
313	if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, 1, 5, 1, &sock,
314	    "PF_INET", "SOCK_STREAM", "test_listen_update", "update_5,1")
315	    < 0)
316		exit(-1);
317	close(sock);
318
319	/*
320	 * Set to 5, update to somaxconn, which should give somaxconn.
321	 */
322	if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, somaxconn, 5,
323	    somaxconn, &sock, "PF_INET", "SOCK_STREAM", "test_listen_update",
324	    "update_5,somaxconn") < 0)
325		exit(-1);
326	close(sock);
327
328	/*
329	 * Set to 5, update to somaxconn+1, which should give somaxconn.
330	 */
331	if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, somaxconn+1, 5,
332	    somaxconn, &sock, "PF_INET", "SOCK_STREAM", "test_listen_update",
333	    "update_5,somaxconn+1") < 0)
334		exit(-1);
335	close(sock);
336}
337
338/*
339 * SO_LISTENQLIMIT is a read-only socket option, so make sure we get an error
340 * if we try to write it.
341 */
342static void
343test_set_qlimit(void)
344{
345	int i, ret, sock;
346
347	sock = socket(PF_INET, SOCK_STREAM, 0);
348	if (sock < 0)
349		err(-1, "test_set_qlimit: socket(PF_INET, SOCK_STREAM)");
350
351	i = 0;
352	ret = setsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &i, sizeof(i));
353	if (ret < 0 && errno != ENOPROTOOPT) {
354		warn("test_set_qlimit: setsockopt(SOL_SOCKET, "
355		    "SO_LISTENQLIMIT, 0): unexpected error");
356		close(sock);
357	}
358
359	if (ret == 0) {
360		warnx("test_set_qlimit: setsockopt(SOL_SOCKET, "
361		    "SO_LISTENQLIMIT, 0) succeeded");
362		close(sock);
363		exit(-1);
364	}
365	close(sock);
366}
367
368int
369main(void)
370{
371	size_t len;
372
373	len = sizeof(somaxconn);
374	if (sysctlbyname("kern.ipc.somaxconn", &somaxconn, &len, NULL, 0)
375	    < 0)
376		err(-1, "sysctlbyname(kern.ipc.somaxconn)");
377
378	test_defaults();
379	test_listen_update();
380	test_set_qlimit();
381
382	return (0);
383}
384