diff options
Diffstat (limited to 'tests')
37 files changed, 8711 insertions, 3 deletions
diff --git a/tests/sys/Makefile b/tests/sys/Makefile index 066c918..8f37490 100644 --- a/tests/sys/Makefile +++ b/tests/sys/Makefile @@ -12,6 +12,9 @@ TESTS_SUBDIRS+= kqueue TESTS_SUBDIRS+= mqueue TESTS_SUBDIRS+= netinet TESTS_SUBDIRS+= opencrypto +TESTS_SUBDIRS+= posixshm +TESTS_SUBDIRS+= socket +TESTS_SUBDIRS+= vfs TESTS_SUBDIRS+= vm # Items not integrated into kyua runs by default diff --git a/tests/sys/Makefile.inc b/tests/sys/Makefile.inc new file mode 100644 index 0000000..f341842 --- /dev/null +++ b/tests/sys/Makefile.inc @@ -0,0 +1,3 @@ +# $FreeBSD$ + +WARNS?= 6 diff --git a/tests/sys/aio/Makefile b/tests/sys/aio/Makefile index 851252d..3117462 100644 --- a/tests/sys/aio/Makefile +++ b/tests/sys/aio/Makefile @@ -2,8 +2,8 @@ TESTSDIR= ${TESTSBASE}/sys/aio -PLAIN_TESTS_C+= aio_kqueue_test -PLAIN_TESTS_C+= lio_kqueue_test +PLAIN_TESTS_C+= aio_kqueue +PLAIN_TESTS_C+= lio_kqueue ATF_TESTS_C+= aio_test DPADD.aio_test+= ${LIBUTIL} diff --git a/tests/sys/file/ftruncate_test.c b/tests/sys/file/ftruncate_test.c index 7eaba14..b657aca 100644 --- a/tests/sys/file/ftruncate_test.c +++ b/tests/sys/file/ftruncate_test.c @@ -57,7 +57,7 @@ static off_t lengths[] = {0, 1, 2, 3, 4, 127, 128, 129, 511, 512, 513, 1023, static int lengths_count = sizeof(lengths) / sizeof(off_t); int -main(int argc, char *argv[]) +main(void) { int error, fd, fds[2], i, read_only_fd; char path[PATH_MAX]; diff --git a/tests/sys/posixshm/Makefile b/tests/sys/posixshm/Makefile new file mode 100644 index 0000000..cb27345 --- /dev/null +++ b/tests/sys/posixshm/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +TESTSDIR= ${TESTSBASE}/sys/posixshm + +TAP_TESTS_C= posixshm_test +SRCS.posixshm_test= posixshm.c test.c + +WARNS?= 6 + +.include <bsd.test.mk> diff --git a/tests/sys/posixshm/posixshm.c b/tests/sys/posixshm/posixshm.c new file mode 100644 index 0000000..e21f45a --- /dev/null +++ b/tests/sys/posixshm/posixshm.c @@ -0,0 +1,627 @@ +/*- + * Copyright (c) 2006 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/mman.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/wait.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "test.h" + +#define TEST_PATH "/tmp/posixshm_regression_test" + +/* + * Attempt a shm_open() that should fail with an expected error of 'error'. + */ +static void +shm_open_should_fail(const char *path, int flags, mode_t mode, int error) +{ + int fd; + + fd = shm_open(path, flags, mode); + if (fd >= 0) { + fail_err("shm_open() didn't fail"); + close(fd); + return; + } + if (errno != error) { + fail_errno("shm_open"); + return; + } + pass(); +} + +/* + * Attempt a shm_unlink() that should fail with an expected error of 'error'. + */ +static void +shm_unlink_should_fail(const char *path, int error) +{ + + if (shm_unlink(path) >= 0) { + fail_err("shm_unlink() didn't fail"); + return; + } + if (errno != error) { + fail_errno("shm_unlink"); + return; + } + pass(); +} + +/* + * Open the test object and write '1' to the first byte. Returns valid fd + * on success and -1 on failure. + */ +static int +scribble_object(void) +{ + char *page; + int fd; + + fd = shm_open(TEST_PATH, O_CREAT | O_EXCL | O_RDWR, 0777); + if (fd < 0 && errno == EEXIST) { + if (shm_unlink(TEST_PATH) < 0) { + fail_errno("shm_unlink"); + return (-1); + } + fd = shm_open(TEST_PATH, O_CREAT | O_EXCL | O_RDWR, 0777); + } + if (fd < 0) { + fail_errno("shm_open"); + return (-1); + } + if (ftruncate(fd, getpagesize()) < 0) { + fail_errno("ftruncate"); + close(fd); + shm_unlink(TEST_PATH); + return (-1); + } + + page = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, + 0); + if (page == MAP_FAILED) { + fail_errno("mmap"); + close(fd); + shm_unlink(TEST_PATH); + return (-1); + } + + page[0] = '1'; + + if (munmap(page, getpagesize()) < 0) { + fail_errno("munmap"); + close(fd); + shm_unlink(TEST_PATH); + return (-1); + } + + return (fd); +} + +static void +remap_object(void) +{ + char *page; + int fd; + + fd = scribble_object(); + if (fd < 0) + return; + + page = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, + 0); + if (page == MAP_FAILED) { + fail_errno("mmap(2)"); + close(fd); + shm_unlink(TEST_PATH); + return; + } + + if (page[0] != '1') { + fail_err("missing data"); + close(fd); + shm_unlink(TEST_PATH); + return; + } + + close(fd); + if (munmap(page, getpagesize()) < 0) { + fail_errno("munmap"); + shm_unlink(TEST_PATH); + return; + } + + if (shm_unlink(TEST_PATH) < 0) { + fail_errno("shm_unlink"); + return; + } + + pass(); +} +TEST(remap_object, "remap object"); + +static void +reopen_object(void) +{ + char *page; + int fd; + + fd = scribble_object(); + if (fd < 0) + return; + close(fd); + + fd = shm_open(TEST_PATH, O_RDONLY, 0777); + if (fd < 0) { + fail_errno("shm_open(2)"); + shm_unlink(TEST_PATH); + return; + } + page = mmap(0, getpagesize(), PROT_READ, MAP_SHARED, fd, 0); + if (page == MAP_FAILED) { + fail_errno("mmap(2)"); + close(fd); + shm_unlink(TEST_PATH); + return; + } + + if (page[0] != '1') { + fail_err("missing data"); + munmap(page, getpagesize()); + close(fd); + shm_unlink(TEST_PATH); + return; + } + + munmap(page, getpagesize()); + close(fd); + shm_unlink(TEST_PATH); + pass(); +} +TEST(reopen_object, "reopen object"); + +static void +readonly_mmap_write(void) +{ + char *page; + int fd; + + fd = shm_open(TEST_PATH, O_RDONLY | O_CREAT, 0777); + if (fd < 0) { + fail_errno("shm_open"); + return; + } + + /* PROT_WRITE should fail with EACCES. */ + page = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, + 0); + if (page != MAP_FAILED) { + fail_err("mmap(PROT_WRITE) succeeded"); + munmap(page, getpagesize()); + close(fd); + shm_unlink(TEST_PATH); + return; + } + if (errno != EACCES) { + fail_errno("mmap"); + close(fd); + shm_unlink(TEST_PATH); + return; + } + + close(fd); + shm_unlink(TEST_PATH); + pass(); +} +TEST(readonly_mmap_write, "RDONLY object"); + +static void +open_after_unlink(void) +{ + int fd; + + fd = shm_open(TEST_PATH, O_RDONLY | O_CREAT, 0777); + if (fd < 0) { + fail_errno("shm_open(1)"); + return; + } + close(fd); + + if (shm_unlink(TEST_PATH) < 0) { + fail_errno("shm_unlink"); + return; + } + + shm_open_should_fail(TEST_PATH, O_RDONLY, 0777, ENOENT); +} +TEST(open_after_unlink, "open after unlink"); + +static void +open_invalid_path(void) +{ + + shm_open_should_fail("blah", O_RDONLY, 0777, EINVAL); +} +TEST(open_invalid_path, "open invalid path"); + +static void +open_write_only(void) +{ + + shm_open_should_fail(TEST_PATH, O_WRONLY, 0777, EINVAL); +} +TEST(open_write_only, "open with O_WRONLY"); + +static void +open_extra_flags(void) +{ + + shm_open_should_fail(TEST_PATH, O_RDONLY | O_DIRECT, 0777, EINVAL); +} +TEST(open_extra_flags, "open with extra flags"); + +static void +open_anon(void) +{ + int fd; + + fd = shm_open(SHM_ANON, O_RDWR, 0777); + if (fd < 0) { + fail_errno("shm_open"); + return; + } + close(fd); + pass(); +} +TEST(open_anon, "open anonymous object"); + +static void +open_anon_readonly(void) +{ + + shm_open_should_fail(SHM_ANON, O_RDONLY, 0777, EINVAL); +} +TEST(open_anon_readonly, "open SHM_ANON with O_RDONLY"); + +static void +open_bad_path_pointer(void) +{ + + shm_open_should_fail((char *)1024, O_RDONLY, 0777, EFAULT); +} +TEST(open_bad_path_pointer, "open bad path pointer"); + +static void +open_path_too_long(void) +{ + char *page; + + page = malloc(MAXPATHLEN + 1); + memset(page, 'a', MAXPATHLEN); + page[MAXPATHLEN] = '\0'; + shm_open_should_fail(page, O_RDONLY, 0777, ENAMETOOLONG); + free(page); +} +TEST(open_path_too_long, "open pathname too long"); + +static void +open_nonexisting_object(void) +{ + + shm_open_should_fail("/notreallythere", O_RDONLY, 0777, ENOENT); +} +TEST(open_nonexisting_object, "open nonexistent object"); + +static void +exclusive_create_existing_object(void) +{ + int fd; + + fd = shm_open("/tmp/notreallythere", O_RDONLY | O_CREAT, 0777); + if (fd < 0) { + fail_errno("shm_open(O_CREAT)"); + return; + } + close(fd); + + shm_open_should_fail("/tmp/notreallythere", O_RDONLY | O_CREAT | O_EXCL, + 0777, EEXIST); + + shm_unlink("/tmp/notreallythere"); +} +TEST(exclusive_create_existing_object, "O_EXCL of existing object"); + +static void +trunc_resets_object(void) +{ + struct stat sb; + int fd; + + /* Create object and set size to 1024. */ + fd = shm_open(TEST_PATH, O_RDWR | O_CREAT, 0777); + if (fd < 0) { + fail_errno("shm_open(1)"); + return; + } + if (ftruncate(fd, 1024) < 0) { + fail_errno("ftruncate"); + close(fd); + return; + } + if (fstat(fd, &sb) < 0) { + fail_errno("fstat(1)"); + close(fd); + return; + } + if (sb.st_size != 1024) { + fail_err("size %d != 1024", (int)sb.st_size); + close(fd); + return; + } + close(fd); + + /* Open with O_TRUNC which should reset size to 0. */ + fd = shm_open(TEST_PATH, O_RDWR | O_TRUNC, 0777); + if (fd < 0) { + fail_errno("shm_open(2)"); + return; + } + if (fstat(fd, &sb) < 0) { + fail_errno("fstat(2)"); + close(fd); + return; + } + if (sb.st_size != 0) { + fail_err("size after O_TRUNC %d != 0", (int)sb.st_size); + close(fd); + return; + } + close(fd); + if (shm_unlink(TEST_PATH) < 0) { + fail_errno("shm_unlink"); + return; + } + pass(); +} +TEST(trunc_resets_object, "O_TRUNC resets size"); + +static void +unlink_bad_path_pointer(void) +{ + + shm_unlink_should_fail((char *)1024, EFAULT); +} +TEST(unlink_bad_path_pointer, "unlink bad path pointer"); + +static void +unlink_path_too_long(void) +{ + char *page; + + page = malloc(MAXPATHLEN + 1); + memset(page, 'a', MAXPATHLEN); + page[MAXPATHLEN] = '\0'; + shm_unlink_should_fail(page, ENAMETOOLONG); + free(page); +} +TEST(unlink_path_too_long, "unlink pathname too long"); + +static void +test_object_resize(void) +{ + pid_t pid; + struct stat sb; + char *page; + int fd, status; + + /* Start off with a size of a single page. */ + fd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0777); + if (fd < 0) { + fail_errno("shm_open"); + return; + } + if (ftruncate(fd, getpagesize()) < 0) { + fail_errno("ftruncate(1)"); + close(fd); + return; + } + if (fstat(fd, &sb) < 0) { + fail_errno("fstat(1)"); + close(fd); + return; + } + if (sb.st_size != getpagesize()) { + fail_err("first resize failed"); + close(fd); + return; + } + + /* Write a '1' to the first byte. */ + page = mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, + 0); + if (page == MAP_FAILED) { + fail_errno("mmap(1)"); + close(fd); + return; + } + + page[0] = '1'; + + if (munmap(page, getpagesize()) < 0) { + fail_errno("munmap(1)"); + close(fd); + return; + } + + /* Grow the object to 2 pages. */ + if (ftruncate(fd, getpagesize() * 2) < 0) { + fail_errno("ftruncate(2)"); + close(fd); + return; + } + if (fstat(fd, &sb) < 0) { + fail_errno("fstat(2)"); + close(fd); + return; + } + if (sb.st_size != getpagesize() * 2) { + fail_err("second resize failed"); + close(fd); + return; + } + + /* Check for '1' at the first byte. */ + page = mmap(0, getpagesize() * 2, PROT_READ | PROT_WRITE, MAP_SHARED, + fd, 0); + if (page == MAP_FAILED) { + fail_errno("mmap(2)"); + close(fd); + return; + } + + if (page[0] != '1') { + fail_err("missing data at 0"); + close(fd); + return; + } + + /* Write a '2' at the start of the second page. */ + page[getpagesize()] = '2'; + + /* Shrink the object back to 1 page. */ + if (ftruncate(fd, getpagesize()) < 0) { + fail_errno("ftruncate(3)"); + close(fd); + return; + } + if (fstat(fd, &sb) < 0) { + fail_errno("fstat(3)"); + close(fd); + return; + } + if (sb.st_size != getpagesize()) { + fail_err("third resize failed"); + close(fd); + return; + } + + /* + * Fork a child process to make sure the second page is no + * longer valid. + */ + pid = fork(); + if (pid < 0) { + fail_errno("fork"); + close(fd); + return; + } + + if (pid == 0) { + struct rlimit lim; + char c; + + /* Don't generate a core dump. */ + getrlimit(RLIMIT_CORE, &lim); + lim.rlim_cur = 0; + setrlimit(RLIMIT_CORE, &lim); + + /* + * The previous ftruncate(2) shrunk the backing object + * so that this address is no longer valid, so reading + * from it should trigger a SIGSEGV. + */ + c = page[getpagesize()]; + fprintf(stderr, "child: page 1: '%c'\n", c); + exit(0); + } + if (wait(&status) < 0) { + fail_errno("wait"); + close(fd); + return; + } + if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGSEGV) { + fail_err("child terminated with status %x", status); + close(fd); + return; + } + + /* Grow the object back to 2 pages. */ + if (ftruncate(fd, getpagesize() * 2) < 0) { + fail_errno("ftruncate(4)"); + close(fd); + return; + } + if (fstat(fd, &sb) < 0) { + fail_errno("fstat(4)"); + close(fd); + return; + } + if (sb.st_size != getpagesize() * 2) { + fail_err("second resize failed"); + close(fd); + return; + } + + /* + * Note that the mapping at 'page' for the second page is + * still valid, and now that the shm object has been grown + * back up to 2 pages, there is now memory backing this page + * so the read will work. However, the data should be zero + * rather than '2' as the old data was thrown away when the + * object was shrunk and the new pages when an object are + * grown are zero-filled. + */ + if (page[getpagesize()] != 0) { + fail_err("invalid data at %d", getpagesize()); + close(fd); + return; + } + + close(fd); + pass(); +} +TEST(test_object_resize, "object resize"); + +int +main(void) +{ + + run_tests(); + return (0); +} diff --git a/tests/sys/posixshm/test.c b/tests/sys/posixshm/test.c new file mode 100644 index 0000000..8583a0d --- /dev/null +++ b/tests/sys/posixshm/test.c @@ -0,0 +1,128 @@ +/*- + * Copyright (c) 2008 Yahoo!, Inc. + * All rights reserved. + * Written by: John Baldwin <jhb@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <stdarg.h> +#include <stdio.h> + +#include "test.h" + +static int test_index; +static struct regression_test *test; +static int test_acknowleged; + +SET_DECLARE(regression_tests_set, struct regression_test); + +/* + * Outputs a test summary of the following: + * + * <status> <test #> [name] [# <fmt> [fmt args]] + */ +static void +vprint_status(const char *status, const char *fmt, va_list ap) +{ + + printf("%s %d", status, test_index); + if (test->rt_name) + printf(" - %s", test->rt_name); + if (fmt) { + printf(" # "); + vprintf(fmt, ap); + } + printf("\n"); + test_acknowleged = 1; +} + +static void +print_status(const char *status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprint_status(status, fmt, ap); + va_end(ap); +} + +void +pass(void) +{ + + print_status("ok", NULL); +} + +void +fail(void) +{ + + print_status("not ok", NULL); +} + +void +fail_err(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprint_status("not ok", fmt, ap); + va_end(ap); +} + +void +skip(const char *reason) +{ + + print_status("ok", "skip %s", reason); +} + +void +todo(const char *reason) +{ + + print_status("not ok", "TODO %s", reason); +} + +void +run_tests(void) +{ + struct regression_test **testp; + + printf("1..%td\n", SET_COUNT(regression_tests_set)); + test_index = 1; + SET_FOREACH(testp, regression_tests_set) { + test_acknowleged = 0; + test = *testp; + test->rt_function(); + if (!test_acknowleged) + print_status("not ok", "unknown status"); + test_index++; + } +} diff --git a/tests/sys/posixshm/test.h b/tests/sys/posixshm/test.h new file mode 100644 index 0000000..505679b --- /dev/null +++ b/tests/sys/posixshm/test.h @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 2008 Yahoo!, Inc. + * All rights reserved. + * Written by: John Baldwin <jhb@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef __TEST_H__ +#define __TEST_H__ + +#include <sys/linker_set.h> + +struct regression_test { + void (*rt_function)(void); + const char *rt_name; +}; + +#define TEST(function, name) \ + static struct regression_test _regtest_##function = { \ + (function), \ + (name) \ + }; \ + DATA_SET(regression_tests_set, _regtest_##function) + +void fail(void); +void fail_err(const char *fmt, ...); +void pass(void); +void run_tests(void); +void skip(const char *reason); +void todo(const char *reason); + +#define fail_errno(tag) fail_err("%s: %s", (tag), strerror(errno)) + +#endif /* !__TEST_H__ */ diff --git a/tests/sys/socket/Makefile b/tests/sys/socket/Makefile new file mode 100644 index 0000000..a119863 --- /dev/null +++ b/tests/sys/socket/Makefile @@ -0,0 +1,46 @@ +# $FreeBSD$ +# +# Some of these tests fail on 11.0-CURRENT @ r280355 with DEBUG bits on + +TESTSDIR= ${TESTSBASE}/sys/socket + +BINDIR= ${TESTSDIR} + +# See comment below +#PROGS+= unix_cmsg + +TAP_TESTS_C+= accept_fd_leak_test +ATF_TESTS_C+= accf_data_attach_test +PLAIN_TESTS_C+= fstat_test +PLAIN_TESTS_C+= kqueue_test +PLAIN_TESTS_C+= listen_backlog_test +PLAIN_TESTS_C+= listenclose_test +PLAIN_TESTS_C+= pr_atomic_test +PLAIN_TESTS_C+= reconnect_test +PLAIN_TESTS_C+= rtsocket_test +PLAIN_TESTS_C+= sblock_test +TAP_TESTS_C+= sendfile_test +PLAIN_TESTS_C+= shutdown_test +PLAIN_TESTS_C+= sigpipe_test +TAP_TESTS_C+= so_setfib_test +PLAIN_TESTS_C+= socketpair_test +PLAIN_TESTS_C+= unix_bindconnect_test +PLAIN_TESTS_C+= unix_close_race_test +# Lots of failures; see https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=199478 +#TAP_TESTS_SH+= unix_cmsg_test +PLAIN_TESTS_C+= unix_gc_test +ATF_TESTS_C+= unix_passfd_test +PLAIN_TESTS_C+= unix_sendto_race_test +PLAIN_TESTS_C+= unix_socket_test +PLAIN_TESTS_C+= unix_sorflush_test +ATF_TESTS_C+= zerosend_test + +DPADD.sendfile_test+= ${LIBMD} +LDADD.sendfile_test+= -lmd + +# XXX: unix_cmsg_test and unix_passfd_test need to be fixed +NO_WCAST_ALIGN= + +WARNS?= 6 + +.include <bsd.test.mk> diff --git a/tests/sys/socket/README.unix_cmsg b/tests/sys/socket/README.unix_cmsg new file mode 100644 index 0000000..df51723 --- /dev/null +++ b/tests/sys/socket/README.unix_cmsg @@ -0,0 +1,160 @@ +$FreeBSD$ + +About unix_cmsg +=============== + +This program is a collection of regression tests for ancillary data +(control information) for PF_LOCAL sockets (local domain or Unix domain +sockets). There are tests for stream and datagram sockets. + +Usually each test does following steps: creates Server, forks Client, +Client sends something to Server, Server verifies whether everything is +correct in received message(s). + +It is better to change the owner of unix_cmsg to some safe user +(eg. nobody:nogroup) and set SUID and SGID bits, else some tests that +check credentials can give correct results for wrong implementation. + +It is better to run this program by a user that belongs to more +than 16 groups. + +Available options +================= + +usage: unix_cmsg [-dh] [-n num] [-s size] [-t type] [-z value] [testno] + + Options are: + -d Output debugging information + -h Output the help message and exit + -n num Number of messages to send + -s size Specify size of data for IPC + -t type Specify socket type (stream, dgram) for tests + -z value Do not send data in a message (bit 0x1), do not send + data array associated with a cmsghdr structure (bit 0x2) + testno Run one test by its number (require the -t option) + +Description +=========== + +If Client sends something to Server, then it sends 5 messages by default. +Number of messages can be changed in the -n command line option. Number +of messages will be given as N in the following descriptions. + +If Client sends something to Server, then it sends some data (few bytes) +in each message by default. The size of this data can be changed by the -s +command line option. The "-s 0" command line option means, that Client will +send zero bytes represented by { NULL, 0 } value of struct iovec{}, referenced +by the msg_iov field from struct msghdr{}. The "-z 1" or "-z 3" command line +option means, that Client will send zero bytes represented by the NULL value +in the msg_iov field from struct msghdr{}. + +If Client sends some ancillary data object, then this ancillary data object +always has associated data array by default. The "-z 2" or "-z 3" option +means, that Client will not send associated data array if possible. + +For SOCK_STREAM sockets: +----------------------- + + 1: Sending, receiving cmsgcred + + Client connects to Server and sends N messages with SCM_CREDS ancillary + data object. Server should receive N messages, each message should + have SCM_CREDS ancillary data object followed by struct cmsgcred{}. + + 2: Receiving sockcred (listening socket) + + Server creates a listening stream socket and sets the LOCAL_CREDS + socket option for it. Client connects to Server two times, each time + it sends N messages. Server accepts two connections and receives N + messages from each connection. The first message from each connection + should have SCM_CREDS ancillary data object followed by struct sockcred{}, + next messages from the same connection should not have ancillary data. + + 3: Receiving sockcred (accepted socket) + + Client connects to Server. Server accepts connection and sets the + LOCAL_CREDS socket option for just accepted socket. Client sends N + messages to Server. Server should receive N messages, the first + message should have SCM_CREDS ancillary data object followed by + struct sockcred{}, next messages should not have ancillary data. + + 4: Sending cmsgcred, receiving sockcred + + Server creates a listening stream socket and sets the LOCAL_CREDS + socket option for it. Client connects to Server and sends N messages + with SCM_CREDS ancillary data object. Server should receive N messages, + the first message should have SCM_CREDS ancillary data object followed + by struct sockcred{}, each of next messages should have SCM_CREDS + ancillary data object followed by struct cmsgcred{}. + + 5: Sending, receiving timeval + + Client connects to Server and sends message with SCM_TIMESTAMP ancillary + data object. Server should receive one message with SCM_TIMESTAMP + ancillary data object followed by struct timeval{}. + + 6: Sending, receiving bintime + + Client connects to Server and sends message with SCM_BINTIME ancillary + data object. Server should receive one message with SCM_BINTIME + ancillary data object followed by struct bintime{}. + + 7: Checking cmsghdr.cmsg_len + + Client connects to Server and tries to send several messages with + SCM_CREDS ancillary data object that has wrong cmsg_len field in its + struct cmsghdr{}. All these attempts should fail, since cmsg_len + in all requests is less than CMSG_LEN(0). + + 8: Check LOCAL_PEERCRED socket option + + This test does not use ancillary data, but can be implemented here. + Client connects to Server. Both Client and Server verify that + credentials of the peer are correct using LOCAL_PEERCRED socket option. + +For SOCK_DGRAM sockets: +---------------------- + + 1: Sending, receiving cmsgcred + + Client connects to Server and sends N messages with SCM_CREDS ancillary + data object. Server should receive N messages, each message should + have SCM_CREDS ancillary data object followed by struct cmsgcred{}. + + 2: Receiving sockcred + + Server creates datagram socket and sets the LOCAL_CREDS socket option + for it. Client sends N messages to Server. Server should receive N + messages, each message should have SCM_CREDS ancillary data object + followed by struct sockcred{}. + + 3: Sending cmsgcred, receiving sockcred + + Server creates datagram socket and sets the LOCAL_CREDS socket option + for it. Client sends N messages with SCM_CREDS ancillary data object + to Server. Server should receive N messages, the first message should + have SCM_CREDS ancillary data object followed by struct sockcred{}, + each of next messages should have SCM_CREDS ancillary data object + followed by struct cmsgcred{}. + + 4: Sending, receiving timeval + + Client sends one message with SCM_TIMESTAMP ancillary data object + to Server. Server should receive one message with SCM_TIMESTAMP + ancillary data object followed by struct timeval{}. + + 5: Sending, receiving bintime + + Client sends one message with SCM_BINTIME ancillary data object + to Server. Server should receive one message with SCM_BINTIME + ancillary data object followed by struct bintime{}. + + 6: Checking cmsghdr.cmsg_len + + Client tries to send Server several messages with SCM_CREDS ancillary + data object that has wrong cmsg_len field in its struct cmsghdr{}. + All these attempts should fail, since cmsg_len in all requests is less + than CMSG_LEN(0). + +- Andrey Simonenko +andreysimonenko@users.sourceforge.net diff --git a/tests/sys/socket/accept_fd_leak_test.c b/tests/sys/socket/accept_fd_leak_test.c new file mode 100644 index 0000000..659c22d --- /dev/null +++ b/tests/sys/socket/accept_fd_leak_test.c @@ -0,0 +1,215 @@ +/*- + * Copyright (c) 2004 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define BIND_ATTEMPTS 10 +#define LOOPS 500 +#define NUM_ATTEMPTS 1000 + +static volatile int quit; + +static void +child_died(int sig __unused) +{ + + quit = 1; +} + +/* + * This test is intended to detect a leak of a file descriptor in the process + * following a failed non-blocking accept. It measures an available fd + * baseline, then performs 1000 failing accepts, then checks to see what the + * next fd is. It relies on sequential fd allocation, and will test for it + * briefly before beginning (not 100% reliable, but a good start). + */ +int +main(void) +{ + struct sockaddr_in sin; + socklen_t size; + pid_t child; + int fd1, fd2, fd3, i, listen_port, s, status; + + printf("1..2\n"); + + /* + * Check for sequential fd allocation, and give up early if not. + */ + fd1 = dup(STDIN_FILENO); + fd2 = dup(STDIN_FILENO); + if (fd2 != fd1 + 1) + errx(-1, "Non-sequential fd allocation\n"); + + s = socket(PF_INET, SOCK_STREAM, 0); + if (s == -1) + errx(-1, "socket: %s", strerror(errno)); + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + srandomdev(); + + for (i = 0; i < BIND_ATTEMPTS; i++) { + /* Pick a random unprivileged port 1025-65535 */ + listen_port = MAX((int)random() % 65535, 1025); + sin.sin_port = htons(listen_port); + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == 0) + break; + warn("bind with %d failed", listen_port); + usleep(1000); + } + if (i >= BIND_ATTEMPTS) { + printf("Bail out!\n"); + exit(1); + } + + if (listen(s, -1) != 0) + errx(-1, "listen: %s", strerror(errno)); + + i = fcntl(s, F_GETFL); + if (i == -1) + errx(-1, "ioctl(F_GETFL): %s", strerror(errno)); + i |= O_NONBLOCK; + if (fcntl(s, F_SETFL, i) != 0) + errx(-1, "ioctl(F_SETFL): %s", strerror(errno)); + i = fcntl(s, F_GETFL); + if (i == -1) + errx(-1, "ioctl(F_GETFL): %s", strerror(errno)); + if ((i & O_NONBLOCK) != O_NONBLOCK) + errx(-1, "Failed to set O_NONBLOCK (i=0x%x)\n", i); + + for (i = 0; i < LOOPS; i++) { + size = sizeof(sin); + if (accept(s, (struct sockaddr *)&sin, &size) != -1) + errx(-1, "accept succeeded\n"); + if (errno != EAGAIN) + errx(-1, "accept: %s", strerror(errno)); + } + + /* + * Allocate a file descriptor and make sure it's fd2+2. 2 because + * we allocate an fd for the socket. + */ + fd3 = dup(STDIN_FILENO); + if (fd3 != fd2 + 2) + printf("not ok 1 - (%d, %d, %d)\n", fd1, fd2, fd3); + else + printf("ok 1\n"); + + /* + * Try failing accept's w/o non-blocking where the destination + * address pointer is invalid. + */ + close(fd3); + signal(SIGCHLD, child_died); + child = fork(); + if (child < 0) + errx(-1, "fork: %s", strerror(errno)); + + /* + * Child process does `NUM_ATTEMPTS` connects. + */ + if (child == 0) { + close(fd1); + close(fd2); + close(s); + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(listen_port); + + for (i = 0; i < NUM_ATTEMPTS; i++) { + s = socket(PF_INET, SOCK_STREAM, 0); + if (s == -1) + errx(-1, "socket: %s", strerror(errno)); + if (connect(s, (struct sockaddr *)&sin, + sizeof(sin)) < 0) + errx(-1, "connect: %s", strerror(errno)); + close(s); + } + _exit(0); + } + + /* Reset back to a blocking socket. */ + i = fcntl(s, F_GETFL); + if (i == -1) + errx(-1, "ioctl(F_GETFL): %s", strerror(errno)); + i &= ~O_NONBLOCK; + if (fcntl(s, F_SETFL, i) != 0) + errx(-1, "ioctl(F_SETFL): %s", strerror(errno)); + i = fcntl(s, F_GETFL); + if (i == -1) + errx(-1, "ioctl(F_GETFL): %s", strerror(errno)); + if (i & O_NONBLOCK) + errx(-1, "Failed to clear O_NONBLOCK (i=0x%x)\n", i); + + /* Do `NUM_ATTEMPTS` accepts with an invalid pointer. */ + for (i = 0; !quit && i < NUM_ATTEMPTS; i++) { + size = sizeof(sin); + if (accept(s, (struct sockaddr *)(uintptr_t)(0x100), + &size) != -1) + errx(-1, "accept succeeded\n"); + if (errno != EFAULT) + errx(-1, "accept: %s", strerror(errno)); + } + + if (waitpid(child, &status, 0) < 0) + errx(-1, "waitpid: %s", strerror(errno)); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + warnx("child process died"); + + /* + * Allocate a file descriptor and make sure it's fd2+2. 2 because + * we allocate an fd for the socket. + */ + fd3 = dup(STDIN_FILENO); + if (fd3 != fd2 + 2) + printf("not ok 2 - (%d, %d, %d)\n", fd1, fd2, fd3); + else + printf("ok 2\n"); + + return (0); +} diff --git a/tests/sys/socket/accf_data_attach_test.c b/tests/sys/socket/accf_data_attach_test.c new file mode 100644 index 0000000..07d1bae --- /dev/null +++ b/tests/sys/socket/accf_data_attach_test.c @@ -0,0 +1,184 @@ +/*- + * Copyright (c) 2004 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <atf-c.h> + +#define ACCF_NAME "dataready" + +/* + * A number of small tests to confirm that attaching ACCF_DATA accept filters + * to inet4 ports works as expected. We test: + * + * - That no accept filter is attached on a newly created socket. + * - That bind() has no affect on the accept filter state. + * - That we can't attach an accept filter to a socket that isn't in the + * listen state. + * - That after we fail to attach the filter, querying the kernel shows no + * filter attached. + * - That we can attach an accept filter to a socket that is in the listen + * state. + * - That once an accept filter is attached, we can query to make sure it is + * attached. + * - That once an accept filter is attached, we can remove it and query to + * make sure it is removed. + */ +ATF_TC_WITHOUT_HEAD(accf_data_attach_test); +ATF_TC_BODY(accf_data_attach_test, tc) +{ + struct accept_filter_arg afa; + struct sockaddr_in sin; + socklen_t len; + int lso, ret; + + /* + * Step 0. Open socket(). + */ + lso = socket(PF_INET, SOCK_STREAM, 0); + ATF_REQUIRE_MSG(lso != -1, "socket failed: %s", strerror(errno)); + + /* + * Step 1. After socket(). Should return EINVAL, since no accept + * filter should be attached. + */ + bzero(&afa, sizeof(afa)); + len = sizeof(afa); + ATF_REQUIRE_ERRNO(EINVAL, + getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len) == -1); + + /* + * Step 2. Bind(). Ideally this will succeed. + */ + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_port = htons(8080); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + ATF_REQUIRE_MSG(bind(lso, (struct sockaddr *)&sin, sizeof(sin)) == 0, + "bind failed: %s", strerror(errno)); + + /* + * Step 3: After bind(). getsockopt() should return EINVAL, since no + * accept filter should be attached. + */ + len = sizeof(afa); + ATF_REQUIRE_ERRNO(EINVAL, + getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len) == -1); + + /* + * Step 4: Setsockopt() before listen(). Should fail, since it's not + * yet a listen() socket. + */ + bzero(&afa, sizeof(afa)); + strcpy(afa.af_name, ACCF_NAME); + ATF_REQUIRE_MSG(setsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, + sizeof(afa)) != 0, "setsockopt succeeded unexpectedly"); + + /* + * Step 5: Getsockopt() after pre-listen() setsockopt(). Should + * fail with EINVAL, since setsockopt() should have failed. + */ + len = sizeof(afa); + ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len); + ATF_REQUIRE_ERRNO(EINVAL, ret != 0); + + /* + * Step 6: listen(). + */ + ATF_REQUIRE_MSG(listen(lso, 1) == 0, + "listen failed: %s", strerror(errno)); + + /* + * Step 7: Getsockopt() after listen(). Should fail with EINVAL, + * since we have not installed accept filter yet. + */ + len = sizeof(afa); + ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len); + ATF_REQUIRE_MSG(ret == -1 && errno == EINVAL, + "getsockopt after listen failed: %s", strerror(errno)); + + atf_tc_expect_fail("XXX(ngie): step 8 always fails on my system for some odd reason"); + + /* + * Step 8: After listen(). This call to setsockopt() should succeed. + */ + bzero(&afa, sizeof(afa)); + strcpy(afa.af_name, ACCF_NAME); + ret = setsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)); + //ATF_REQUIRE_MSG(ret == 0, + ATF_REQUIRE_MSG(ret == 0, + "setsockopt after listen failed: %s", strerror(errno)); + + /* + * Step 9: After setsockopt(). Should succeed and identify + * ACCF_NAME. + */ + bzero(&afa, sizeof(afa)); + len = sizeof(afa); + ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len); + ATF_REQUIRE_MSG(ret == 0, + "getsockopt after listen/setsockopt failed: %s", strerror(errno)); + ATF_REQUIRE_EQ(len, sizeof(afa)); + ATF_REQUIRE_STREQ(afa.af_name, ACCF_NAME); + + /* + * Step 10: Remove accept filter. After removing the accept filter + * getsockopt() should fail with EINVAL. + */ + ret = setsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, NULL, 0); + ATF_REQUIRE_MSG(ret == 0, + "setsockopt failed to remove accept filter: %s", strerror(errno)); + bzero(&afa, sizeof(afa)); + len = sizeof(afa); + ret = getsockopt(lso, SOL_SOCKET, SO_ACCEPTFILTER, &afa, &len); + ATF_REQUIRE_MSG(ret == -1 && errno == EINVAL, + "getsockopt failed after removing the accept filter: %s", + strerror(errno)); + + close(lso); + +} + +ATF_TP_ADD_TCS(tp) +{ + + ATF_TP_ADD_TC(tp, accf_data_attach_test); + + return (atf_no_error()); +} diff --git a/tests/sys/socket/fstat_test.c b/tests/sys/socket/fstat_test.c new file mode 100644 index 0000000..6ea931d --- /dev/null +++ b/tests/sys/socket/fstat_test.c @@ -0,0 +1,71 @@ +/*- + * Copyright (c) 2008 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <err.h> +#include <unistd.h> + +/* + * Basic test to make sure that fstat(2) returns success on various socket + * types. In the future we should also validate the fields, confirming + * expected results such as the effect of shutdown(2) on permissions, etc. + */ + +static void +dotest(int domain, int type, int protocol) +{ + struct stat sb; + int sock; + + sock = socket(domain, type, protocol); + if (sock < 0) + err(-1, "socket(%d, %d, %d)", domain, type, protocol); + + if (fstat(sock, &sb) < 0) + err(-1, "fstat on socket(%d, %d, %d)", domain, type, + protocol); + + close(sock); +} + +int +main(void) +{ + + dotest(PF_INET, SOCK_DGRAM, 0); + dotest(PF_INET, SOCK_STREAM, 0); + dotest(PF_INET6, SOCK_DGRAM, 0); + dotest(PF_INET6, SOCK_STREAM, 0); + dotest(PF_LOCAL, SOCK_DGRAM, 0); + dotest(PF_LOCAL, SOCK_STREAM, 0); + + return (0); +} diff --git a/tests/sys/socket/kqueue_test.c b/tests/sys/socket/kqueue_test.c new file mode 100644 index 0000000..f73704a --- /dev/null +++ b/tests/sys/socket/kqueue_test.c @@ -0,0 +1,368 @@ +/*- + * Copyright (c) 2004 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static int curtest = 1; + +/*- + * This test uses UNIX domain socket pairs to perform some basic exercising + * of kqueue functionality on sockets. In particular, testing that for read + * and write filters, we see the correct detection of whether reads and + * writes should actually be able to occur. + * + * TODO: + * - Test read/write filters for listen/accept sockets. + * - Handle the XXXRW below regarding datagram sockets. + * - Test that watermark/buffer size "data" fields returned by kqueue are + * correct. + * - Check that kqueue does something sensible when the remote endpoing is + * closed. + */ + +#define OK(testname) printf("ok %d - %s\n", curtest, testname); \ + curtest++; + +static void +fail(int error, const char *func, const char *socktype, const char *rest) +{ + + printf("not ok %d\n", curtest); + + if (socktype == NULL) + printf("# %s(): %s\n", func, strerror(error)); + else if (rest == NULL) + printf("# %s(%s): %s\n", func, socktype, + strerror(error)); + else + printf("# %s(%s, %s): %s\n", func, socktype, rest, + strerror(error)); + exit(-1); +} + +static void +fail_assertion(const char *func, const char *socktype, const char *rest, + const char *assertion) +{ + + printf("not ok %d - %s\n", curtest, assertion); + + if (socktype == NULL) + printf("# %s(): assertion %s failed\n", func, + assertion); + else if (rest == NULL) + printf("# %s(%s): assertion %s failed\n", func, + socktype, assertion); + else + printf("# %s(%s, %s): assertion %s failed\n", func, + socktype, rest, assertion); + exit(-1); +} + +/* + * Test read kevent on a socket pair: check to make sure endpoint 0 isn't + * readable when we start, then write to endpoint 1 and confirm that endpoint + * 0 is now readable. Drain the write, then check that it's not readable + * again. Use non-blocking kqueue operations and socket operations. + */ +static void +test_evfilt_read(int kq, int fd[2], const char *socktype) +{ + struct timespec ts; + struct kevent ke; + ssize_t len; + char ch; + int i; + + EV_SET(&ke, fd[0], EVFILT_READ, EV_ADD, 0, 0, NULL); + if (kevent(kq, &ke, 1, NULL, 0, NULL) == -1) + fail(errno, "kevent", socktype, "EVFILT_READ, EV_ADD"); + OK("EVFILT_READ, EV_ADD"); + + /* + * Confirm not readable to begin with, no I/O yet. + */ + ts.tv_sec = 0; + ts.tv_nsec = 0; + i = kevent(kq, NULL, 0, &ke, 1, &ts); + if (i == -1) + fail(errno, "kevent", socktype, "EVFILT_READ"); + OK("EVFILT_READ"); + if (i != 0) + fail_assertion("kevent", socktype, "EVFILT_READ", + "empty socket unreadable"); + OK("empty socket unreadable"); + + /* + * Write a byte to one end. + */ + ch = 'a'; + len = write(fd[1], &ch, sizeof(ch)); + if (len == -1) + fail(errno, "write", socktype, NULL); + OK("write one byte"); + if (len != sizeof(ch)) + fail_assertion("write", socktype, NULL, "write length"); + OK("write one byte length"); + + /* + * Other end should now be readable. + */ + ts.tv_sec = 0; + ts.tv_nsec = 0; + i = kevent(kq, NULL, 0, &ke, 1, &ts); + if (i == -1) + fail(errno, "kevent", socktype, "EVFILT_READ"); + OK("EVFILT_READ"); + if (i != 1) + fail_assertion("kevent", socktype, "EVFILT_READ", + "non-empty socket unreadable"); + OK("non-empty socket unreadable"); + + /* + * Read a byte to clear the readable state. + */ + len = read(fd[0], &ch, sizeof(ch)); + if (len == -1) + fail(errno, "read", socktype, NULL); + OK("read one byte"); + if (len != sizeof(ch)) + fail_assertion("read", socktype, NULL, "read length"); + OK("read one byte length"); + + /* + * Now re-check for readability. + */ + ts.tv_sec = 0; + ts.tv_nsec = 0; + i = kevent(kq, NULL, 0, &ke, 1, &ts); + if (i == -1) + fail(errno, "kevent", socktype, "EVFILT_READ"); + OK("EVFILT_READ"); + if (i != 0) + fail_assertion("kevent", socktype, "EVFILT_READ", + "empty socket unreadable"); + OK("empty socket unreadable"); + + EV_SET(&ke, fd[0], EVFILT_READ, EV_DELETE, 0, 0, NULL); + if (kevent(kq, &ke, 1, NULL, 0, NULL) == -1) + fail(errno, "kevent", socktype, "EVFILT_READ, EV_DELETE"); + OK("EVFILT_READ, EV_DELETE"); +} + +static void +test_evfilt_write(int kq, int fd[2], const char *socktype) +{ + struct timespec ts; + struct kevent ke; + ssize_t len; + char ch; + int i; + + EV_SET(&ke, fd[0], EVFILT_WRITE, EV_ADD, 0, 0, NULL); + if (kevent(kq, &ke, 1, NULL, 0, NULL) == -1) + fail(errno, "kevent", socktype, "EVFILT_WRITE, EV_ADD"); + OK("EVFILE_WRITE, EV_ADD"); + + /* + * Confirm writable to begin with, no I/O yet. + */ + ts.tv_sec = 0; + ts.tv_nsec = 0; + i = kevent(kq, NULL, 0, &ke, 1, &ts); + if (i == -1) + fail(errno, "kevent", socktype, "EVFILT_WRITE"); + OK("EVFILE_WRITE"); + if (i != 1) + fail_assertion("kevent", socktype, "EVFILT_WRITE", + "empty socket unwritable"); + OK("empty socket unwritable"); + + /* + * Write bytes into the socket until we can't write anymore. + */ + ch = 'a'; + while ((len = write(fd[0], &ch, sizeof(ch))) == sizeof(ch)) {}; + if (len == -1 && errno != EAGAIN && errno != ENOBUFS) + fail(errno, "write", socktype, NULL); + OK("write"); + if (len != -1 && len != sizeof(ch)) + fail_assertion("write", socktype, NULL, "write length"); + OK("write length"); + + /* + * Check to make sure the socket is no longer writable. + */ + ts.tv_sec = 0; + ts.tv_nsec = 0; + i = kevent(kq, NULL, 0, &ke, 1, &ts); + if (i == -1) + fail(errno, "kevent", socktype, "EVFILT_WRITE"); + OK("EVFILT_WRITE"); + if (i != 0) + fail_assertion("kevent", socktype, "EVFILT_WRITE", + "full socket writable"); + OK("full socket writable"); + + EV_SET(&ke, fd[0], EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + if (kevent(kq, &ke, 1, NULL, 0, NULL) == -1) + fail(errno, "kevent", socktype, "EVFILT_WRITE, EV_DELETE"); + OK("EVFILT_WRITE, EV_DELETE"); +} + +/* + * Basic registration exercise for kqueue(2). Create several types/brands of + * sockets, and confirm that we can register for various events on them. + */ +int +main(void) +{ + int kq, sv[2]; + + printf("1..49\n"); + + kq = kqueue(); + if (kq == -1) + fail(errno, "kqueue", NULL, NULL); + OK("kqueue()"); + + /* + * Create a UNIX domain datagram socket, and attach/test/detach a + * read filter on it. + */ + if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) == -1) + fail(errno, "socketpair", "PF_UNIX, SOCK_DGRAM", NULL); + OK("socketpair() 1"); + + if (fcntl(sv[0], F_SETFL, O_NONBLOCK) != 0) + fail(errno, "fcntl", "PF_UNIX, SOCK_DGRAM", "O_NONBLOCK"); + OK("fcntl() 1"); + if (fcntl(sv[1], F_SETFL, O_NONBLOCK) != 0) + fail(errno, "fcntl", "PF_UNIX, SOCK_DGRAM", "O_NONBLOCK"); + OK("fnctl() 2"); + + test_evfilt_read(kq, sv, "PF_UNIX, SOCK_DGRAM"); + + if (close(sv[0]) == -1) + fail(errno, "close", "PF_UNIX/SOCK_DGRAM", "sv[0]"); + OK("close() 1"); + if (close(sv[1]) == -1) + fail(errno, "close", "PF_UNIX/SOCK_DGRAM", "sv[1]"); + OK("close() 2"); + +#if 0 + /* + * XXXRW: We disable the write test in the case of datagram sockets, + * as kqueue can't tell when the remote socket receive buffer is + * full, whereas the UNIX domain socket implementation can tell and + * returns ENOBUFS. + */ + /* + * Create a UNIX domain datagram socket, and attach/test/detach a + * write filter on it. + */ + if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) == -1) + fail(errno, "socketpair", "PF_UNIX, SOCK_DGRAM", NULL); + + if (fcntl(sv[0], F_SETFL, O_NONBLOCK) != 0) + fail(errno, "fcntl", "PF_UNIX, SOCK_DGRAM", "O_NONBLOCK"); + if (fcntl(sv[1], F_SETFL, O_NONBLOCK) != 0) + fail(errno, "fcntl", "PF_UNIX, SOCK_DGRAM", "O_NONBLOCK"); + + test_evfilt_write(kq, sv, "PF_UNIX, SOCK_DGRAM"); + + if (close(sv[0]) == -1) + fail(errno, "close", "PF_UNIX/SOCK_DGRAM", "sv[0]"); + if (close(sv[1]) == -1) + fail(errno, "close", "PF_UNIX/SOCK_DGRAM", "sv[1]"); +#endif + + /* + * Create a UNIX domain stream socket, and attach/test/detach a + * read filter on it. + */ + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) + fail(errno, "socketpair", "PF_UNIX, SOCK_STREAM", NULL); + OK("socketpair() 2"); + + if (fcntl(sv[0], F_SETFL, O_NONBLOCK) != 0) + fail(errno, "fcntl", "PF_UNIX, SOCK_STREAM", "O_NONBLOCK"); + OK("fcntl() 3"); + if (fcntl(sv[1], F_SETFL, O_NONBLOCK) != 0) + fail(errno, "fcntl", "PF_UNIX, SOCK_STREAM", "O_NONBLOCK"); + OK("fcntl() 4"); + + test_evfilt_read(kq, sv, "PF_UNIX, SOCK_STREAM"); + + if (close(sv[0]) == -1) + fail(errno, "close", "PF_UNIX/SOCK_STREAM", "sv[0]"); + OK("close() 3"); + if (close(sv[1]) == -1) + fail(errno, "close", "PF_UNIX/SOCK_STREAM", "sv[1]"); + OK("close() 4"); + + /* + * Create a UNIX domain stream socket, and attach/test/detach a + * write filter on it. + */ + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) + fail(errno, "socketpair", "PF_UNIX, SOCK_STREAM", NULL); + OK("socketpair() 3"); + + if (fcntl(sv[0], F_SETFL, O_NONBLOCK) != 0) + fail(errno, "fcntl", "PF_UNIX, SOCK_STREAM", "O_NONBLOCK"); + OK("fcntl() 5"); + if (fcntl(sv[1], F_SETFL, O_NONBLOCK) != 0) + fail(errno, "fcntl", "PF_UNIX, SOCK_STREAM", "O_NONBLOCK"); + OK("fcntl() 6"); + + test_evfilt_write(kq, sv, "PF_UNIX, SOCK_STREAM"); + + if (close(sv[0]) == -1) + fail(errno, "close", "PF_UNIX/SOCK_STREAM", "sv[0]"); + OK("close() 5"); + if (close(sv[1]) == -1) + fail(errno, "close", "PF_UNIX/SOCK_STREAM", "sv[1]"); + OK("close() 6"); + + if (close(kq) == -1) + fail(errno, "close", "kq", NULL); + OK("close() 7"); + + return (0); +} diff --git a/tests/sys/socket/listen_backlog_test.c b/tests/sys/socket/listen_backlog_test.c new file mode 100644 index 0000000..2276393 --- /dev/null +++ b/tests/sys/socket/listen_backlog_test.c @@ -0,0 +1,383 @@ +/*- + * Copyright (c) 2005 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/sysctl.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +/* + * This regression test is intended to validate that the backlog parameter + * set by listen() is properly set, can be retrieved using SO_LISTENQLIMIT, + * and that it can be updated by later calls to listen(). We also check that + * SO_LISTENQLIMIT cannot be set. + * + * Future things to test: + * + * - That if we change the value of kern.ipc.somaxconn, the limits really + * do change. + * + * - That limits are, approximately, enforced and implemented. + * + * - All this on multiple socket types -- i.e., PF_LOCAL. + * + * - That we also test SO_LISTENQLEN and SO_LISTENINCQLEN. + */ + +/* + * We retrieve kern.ipc.somaxconn before running the tests in order to use a + * run-time set value of SOMAXCONN, rather than compile-time set. We assume + * that no other process will be simultaneously frobbing it, and these tests + * may fail if that assumption is not held. + */ +static int somaxconn; + +/* + * Retrieve the current socket listen queue limit using SO_LISTENQLIMIT. + */ +static int +socket_get_backlog(int sock, int *backlogp, const char *testclass, + const char *test, const char *testfunc) +{ + socklen_t len; + int i; + + len = sizeof(i); + if (getsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &i, &len) < 0) { + warn("%s: %s: %s: socket_get_backlog: getsockopt(" + "SOL_SOCKET, SO_LISTENQLIMIT)", testclass, test, + testfunc); + return (-1); + } + + if (len != sizeof(i)) { + warnx("%s: %s: %s: socket_get_backlog: getsockopt(" + "SOL_SOCKET, SO_LISTENQLIMIT): returned size %d", + testclass, test, testfunc, len); + return (-1); + } + + *backlogp = i; + + return (0); +} + +/* + * Create a socket, check the queue limit on creation, perform a listen(), + * and make sure that the limit was set as expected by listen(). + */ +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) +{ + int backlog_retrieved, sock; + + sock = socket(domain, type, protocol); + if (sock < 0) { + warn("%s: %s: socket_listen: socket(%s, %s)", testclass, + test, domainstring, typestring); + close(sock); + return (-1); + } + + if (socket_get_backlog(sock, &backlog_retrieved, testclass, test, + "socket_listen") < 0) { + close(sock); + return (-1); + } + + if (backlog_retrieved != create_backlog_assertion) { + warnx("%s: %s: socket_listen: create backlog is %d not %d", + testclass, test, backlog_retrieved, + create_backlog_assertion); + close(sock); + return (-1); + } + + if (listen(sock, backlog) < 0) { + warn("%s: %s: socket_listen: listen(, %d)", testclass, test, + backlog); + close(sock); + return (-1); + } + + if (socket_get_backlog(sock, &backlog_retrieved, testclass, test, + "socket_listen") < 0) { + close(sock); + return (-1); + } + + if (backlog_retrieved != listen_backlog_assertion) { + warnx("%s: %s: socket_listen: listen backlog is %d not %d", + testclass, test, backlog_retrieved, + listen_backlog_assertion); + close(sock); + return (-1); + } + + *sockp = sock; + return (0); +} + +/* + * This test creates sockets and tests default states before and after + * listen(). Specifically, we expect a queue limit of 0 before listen, and + * then various settings for after listen(). If the passed backlog was + * either < 0 or > somaxconn, it should be set to somaxconn; otherwise, the + * passed queue depth. + */ +static void +test_defaults(void) +{ + int sock; + + /* + * First pass. Confirm the default is 0. Listen with a backlog of + * 0 and confirm it gets set that way. + */ + if (socket_listen(PF_INET, SOCK_STREAM, 0, 0, 0, 0, &sock, "PF_INET", + "SOCK_STREAM", "test_defaults", "default_0_listen_0") < 0) + exit(-1); + close(sock); + + /* + * Second pass. Listen with a backlog of -1 and make sure it is set + * to somaxconn. + */ + if (socket_listen(PF_INET, SOCK_STREAM, 0, -1, 0, somaxconn, &sock, + "PF_INET", "SOCK_STREAM", "test_defaults", "default_0_listen_-1") + < 0) + exit(-1); + close(sock); + + /* + * Third pass. Listen with a backlog of 1 and make sure it is set to + * 1. + */ + if (socket_listen(PF_INET, SOCK_STREAM, 0, 1, 0, 1, &sock, "PF_INET", + "SOCK_STREAM", "test_defaults", "default_0_listen_1") < 0) + exit(-1); + close(sock); + + /* + * Fourth pass. Listen with a backlog of somaxconn and make sure it + * is set to somaxconn. + */ + if (socket_listen(PF_INET, SOCK_STREAM, 0, somaxconn, 0, somaxconn, + &sock, "PF_INET", "SOCK_STREAM", "test_defaults", + "default_0_listen_somaxconn") < 0) + exit(-1); + close(sock); + + /* + * Fifth pass. Listen with a backlog of somaxconn+1 and make sure it + * is set to somaxconn. + */ + if (socket_listen(PF_INET, SOCK_STREAM, 0, somaxconn+1, 0, somaxconn, + &sock, "PF_INET", "SOCK_STREAM", "test_defaults", + "default_0_listen_somaxconn+1") < 0) + exit(-1); + close(sock); +} + +/* + * Create a socket, set the initial listen() state, then update the queue + * depth using listen(). Check that the backlog is as expected after both + * the first and second listen(). + */ +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) +{ + int backlog_retrieved, sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) { + warn("%s: %s: socket_listen_update: socket(%s, %s)", + testclass, test, domainstring, typestring); + return (-1); + } + + if (listen(sock, backlog) < 0) { + warn("%s: %s: socket_listen_update: initial listen(, %d)", + testclass, test, backlog); + close(sock); + return (-1); + } + + if (socket_get_backlog(sock, &backlog_retrieved, testclass, test, + "socket_listen_update") < 0) { + close(sock); + return (-1); + } + + if (backlog_retrieved != listen_backlog_assertion) { + warnx("%s: %s: socket_listen_update: initial backlog is %d " + "not %d", testclass, test, backlog_retrieved, + listen_backlog_assertion); + close(sock); + return (-1); + } + + if (listen(sock, update_backlog) < 0) { + warn("%s: %s: socket_listen_update: update listen(, %d)", + testclass, test, update_backlog); + close(sock); + return (-1); + } + + if (socket_get_backlog(sock, &backlog_retrieved, testclass, test, + "socket_listen_update") < 0) { + close(sock); + return (-1); + } + + if (backlog_retrieved != update_backlog_assertion) { + warnx("%s: %s: socket_listen_update: updated backlog is %d " + "not %d", testclass, test, backlog_retrieved, + update_backlog_assertion); + close(sock); + return (-1); + } + + *sockp = sock; + return (0); +} + +/* + * This test tests using listen() to update the queue depth after a socket + * has already been marked as listening. We test several cases: setting the + * socket < 0, 0, 1, somaxconn, and somaxconn + 1. + */ +static void +test_listen_update(void) +{ + int sock; + + /* + * Set to 5, update to -1, which should give somaxconn. + */ + if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, -1, 5, somaxconn, + &sock, "PF_INET", "SOCK_STREAM", "test_listen_update", + "update_5,-1") < 0) + exit(-1); + close(sock); + + /* + * Set to 5, update to 0, which should give 0. + */ + if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, 0, 5, 0, &sock, + "PF_INET", "SOCK_STREAM", "test_listen_update", "update_5,0") + < 0) + exit(-1); + close(sock); + + /* + * Set to 5, update to 1, which should give 1. + */ + if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, 1, 5, 1, &sock, + "PF_INET", "SOCK_STREAM", "test_listen_update", "update_5,1") + < 0) + exit(-1); + close(sock); + + /* + * Set to 5, update to somaxconn, which should give somaxconn. + */ + if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, somaxconn, 5, + somaxconn, &sock, "PF_INET", "SOCK_STREAM", "test_listen_update", + "update_5,somaxconn") < 0) + exit(-1); + close(sock); + + /* + * Set to 5, update to somaxconn+1, which should give somaxconn. + */ + if (socket_listen_update(PF_INET, SOCK_STREAM, 0, 5, somaxconn+1, 5, + somaxconn, &sock, "PF_INET", "SOCK_STREAM", "test_listen_update", + "update_5,somaxconn+1") < 0) + exit(-1); + close(sock); +} + +/* + * SO_LISTENQLIMIT is a read-only socket option, so make sure we get an error + * if we try to write it. + */ +static void +test_set_qlimit(void) +{ + int i, ret, sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) + err(-1, "test_set_qlimit: socket(PF_INET, SOCK_STREAM)"); + + i = 0; + ret = setsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &i, sizeof(i)); + if (ret < 0 && errno != ENOPROTOOPT) { + warn("test_set_qlimit: setsockopt(SOL_SOCKET, " + "SO_LISTENQLIMIT, 0): unexpected error"); + close(sock); + } + + if (ret == 0) { + warnx("test_set_qlimit: setsockopt(SOL_SOCKET, " + "SO_LISTENQLIMIT, 0) succeeded"); + close(sock); + exit(-1); + } + close(sock); +} + +int +main(void) +{ + size_t len; + + len = sizeof(somaxconn); + if (sysctlbyname("kern.ipc.somaxconn", &somaxconn, &len, NULL, 0) + < 0) + err(-1, "sysctlbyname(kern.ipc.somaxconn)"); + + test_defaults(); + test_listen_update(); + test_set_qlimit(); + + return (0); +} diff --git a/tests/sys/socket/listenclose_test.c b/tests/sys/socket/listenclose_test.c new file mode 100644 index 0000000..f920846 --- /dev/null +++ b/tests/sys/socket/listenclose_test.c @@ -0,0 +1,111 @@ +/*- + * Copyright (c) 2004-2005 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +/* + * The listenclose regression test is designed to catch kernel bugs that may + * trigger as a result of performing a close on a listen() socket with as-yet + * unaccepted connections in its queues. This results in the connections + * being aborted, which is a not-often-followed code path. To do this, we + * create a local TCP socket, build a non-blocking connection to it, and then + * close the accept socket. The connection must be non-blocking or the + * program will block and as such connect() will not return as accept() is + * never called. + */ + +int +main(void) +{ + int listen_sock, connect_sock; + struct sockaddr_in sin; + socklen_t len; + u_short port; + int arg; + + listen_sock = socket(PF_INET, SOCK_STREAM, 0); + if (listen_sock == -1) + errx(-1, + "socket(PF_INET, SOCK_STREAM, 0) for listen socket: %s", + strerror(errno)); + + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = 0; + + if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + errx(-1, "bind(%s, %d) for listen socket: %s", + inet_ntoa(sin.sin_addr), 0, strerror(errno)); + + len = sizeof(sin); + if (getsockname(listen_sock, (struct sockaddr *)&sin, &len) < 0) + errx(-1, "getsockname() for listen socket: %s", + strerror(errno)); + port = sin.sin_port; + + if (listen(listen_sock, -1) < 0) + errx(-1, "listen() for listen socket: %s", strerror(errno)); + + connect_sock = socket(PF_INET, SOCK_STREAM, 0); + if (connect_sock == -1) + errx(-1, "socket(PF_INET, SOCK_STREAM, 0) for connect " + "socket: %s", strerror(errno)); + + arg = O_NONBLOCK; + if (fcntl(connect_sock, F_SETFL, &arg) < 0) + errx(-1, "socket(PF_INET, SOCK_STREAM, 0) for connect socket" + ": %s", strerror(errno)); + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = port; + + if (connect(connect_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + errx(-1, "connect() for connect socket: %s", strerror(errno)); + close(connect_sock); + close(listen_sock); + + return (0); +} diff --git a/tests/sys/socket/pr_atomic_test.c b/tests/sys/socket/pr_atomic_test.c new file mode 100644 index 0000000..e902cf7 --- /dev/null +++ b/tests/sys/socket/pr_atomic_test.c @@ -0,0 +1,109 @@ +/*- + * Copyright (c) 2006 Bruce M. Simpson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Regression test for uiomove in kernel; specifically for PR kern/38495. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <stdlib.h> +#include <signal.h> +#include <setjmp.h> +#include <string.h> +#include <err.h> +#include <errno.h> +#include <unistd.h> + +static char socket_path[] = "tmp.XXXXXX"; + +static jmp_buf myjmpbuf; + +static void handle_sigalrm(int signo __unused) +{ + longjmp(myjmpbuf, 1); +} + +int +main(void) +{ + struct sockaddr_un un; + pid_t pid; + int s; + + if (mkstemp(socket_path) == -1) + err(1, "mkstemp"); + s = socket(PF_LOCAL, SOCK_DGRAM, 0); + if (s == -1) + errx(-1, "socket"); + memset(&un, 0, sizeof(un)); + un.sun_family = AF_LOCAL; + unlink(socket_path); + strcpy(un.sun_path, socket_path); + if (bind(s, (struct sockaddr *)&un, sizeof(un)) == -1) + errx(-1, "bind"); + pid = fork(); + if (pid == -1) + errx(-1, "fork"); + if (pid == 0) { + int conn; + char buf[] = "AAAAAAAAA"; + + close(s); + conn = socket(AF_LOCAL, SOCK_DGRAM, 0); + if (conn == -1) + errx(-1,"socket"); + if (sendto(conn, buf, sizeof(buf), 0, (struct sockaddr *)&un, + sizeof(un)) != sizeof(buf)) + errx(-1,"sendto"); + close(conn); + _exit(0); + } + + sleep(5); + + /* Make sure the data is there when we try to receive it. */ + if (recvfrom(s, (void *)-1, 1, 0, NULL, NULL) != -1) + errx(-1,"recvfrom succeeded when failure expected"); + + (void)signal(SIGALRM, handle_sigalrm); + if (setjmp(myjmpbuf) == 0) { + /* + * This recvfrom will panic an unpatched system, and block + * a patched one. + */ + alarm(5); + (void)recvfrom(s, (void *)-1, 1, 0, NULL, NULL); + } + + /* We should reach here via longjmp() and all should be well. */ + + return (0); +} diff --git a/tests/sys/socket/reconnect_test.c b/tests/sys/socket/reconnect_test.c new file mode 100644 index 0000000..f596092 --- /dev/null +++ b/tests/sys/socket/reconnect_test.c @@ -0,0 +1,132 @@ +/*- + * Copyright (c) 2005 Maxim Sobolev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * The reconnect regression test is designed to catch kernel bug that may + * prevent changing association of already associated datagram unix domain + * socket when server side of connection has been closed. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/un.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> + +static char uds_name1[] = "reconnect.XXXXXXXX"; +static char uds_name2[] = "reconnect.XXXXXXXX"; + +#define sstosa(ss) ((struct sockaddr *)(ss)) + +static void +prepare_ifsun(struct sockaddr_un *ifsun, const char *path) +{ + + memset(ifsun, '\0', sizeof(*ifsun)); +#if !defined(__linux__) && !defined(__solaris__) + ifsun->sun_len = strlen(path); +#endif + ifsun->sun_family = AF_LOCAL; + strcpy(ifsun->sun_path, path); +} + +static int +create_uds_server(const char *path) +{ + struct sockaddr_un ifsun; + int sock; + + prepare_ifsun(&ifsun, path); + + unlink(ifsun.sun_path); + + sock = socket(PF_LOCAL, SOCK_DGRAM, 0); + if (sock == -1) + err(1, "can't create socket"); + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &sock, sizeof(sock)); + if (bind(sock, sstosa(&ifsun), sizeof(ifsun)) < 0) + err(1, "can't bind to a socket"); + + return sock; +} + +static void +connect_uds_server(int sock, const char *path) +{ + struct sockaddr_un ifsun; + int e; + + prepare_ifsun(&ifsun, path); + + e = connect(sock, sstosa(&ifsun), sizeof(ifsun)); + if (e < 0) + err(1, "can't connect to a socket"); +} + +static void +cleanup(void) +{ + + unlink(uds_name1); + unlink(uds_name2); +} + +int +main(void) +{ + int s_sock1, s_sock2, c_sock; + + atexit(cleanup); + + if (mkstemp(uds_name1) == -1) + err(1, "mkstemp"); + unlink(uds_name1); + s_sock1 = create_uds_server(uds_name1); + + if (mkstemp(uds_name2) == -1) + err(1, "mkstemp"); + unlink(uds_name2); + s_sock2 = create_uds_server(uds_name2); + + c_sock = socket(PF_LOCAL, SOCK_DGRAM, 0); + if (c_sock < 0) + err(1, "can't create socket"); + + connect_uds_server(c_sock, uds_name1); + close(s_sock1); + connect_uds_server(c_sock, uds_name2); + + exit (0); +} diff --git a/tests/sys/socket/rtsocket_test.c b/tests/sys/socket/rtsocket_test.c new file mode 100644 index 0000000..f189cdc --- /dev/null +++ b/tests/sys/socket/rtsocket_test.c @@ -0,0 +1,101 @@ +/*- + * Copyright (c) 2006 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Simple routing socket regression test: create and destroy a raw routing + * socket, and make sure that dgram and stream don't work, socketpair, etc. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <net/route.h> + +#include <err.h> +#include <errno.h> +#include <unistd.h> + +int +main(void) +{ + int sock, socks[2]; + + sock = socket(PF_ROUTE, SOCK_STREAM, 0); + if (sock >= 0) { + close(sock); + errx(-1, "socket(PF_ROUTE, SOCK_STREAM, 0) returned %d", + sock); + } + + if (errno != EPROTOTYPE) + err(-1, "socket(PF_ROUTE, SOCK_STREAM, 0)"); + + sock = socket(PF_ROUTE, SOCK_DGRAM, 0); + if (sock >= 0) { + close(sock); + errx(-1, "socket(PF_ROUTE, SOCK_DGRAM, 0) returned %d", + sock); + } + + if (errno != EPROTOTYPE) + err(-1, "socket(PF_ROUTE, SOCK_DGRAM, 0)"); + + sock = socket(PF_ROUTE, SOCK_RAW, 0); + if (sock < 0) + err(-1, "socket(PF_ROUTE, SOCK_RAW, 0)"); + close(sock); + + if (socketpair(PF_ROUTE, SOCK_STREAM, 0, socks) == 0) { + close(socks[0]); + close(socks[1]); + errx(-1, + "socketpair(PF_ROUTE, SOCK_STREAM, 0, socks) success"); + } + + if (errno != EPROTOTYPE) + err(-1, "socketpair(PF_ROUTE, SOCK_STREAM, 0, socks)"); + + if (socketpair(PF_ROUTE, SOCK_DGRAM, 0, socks) == 0) { + close(socks[0]); + close(socks[1]); + errx(-1, + "socketpair(PF_ROUTE, SOCK_DGRAM, 0, socks) success"); + } + + if (errno != EPROTOTYPE) + err(-1, "socketpair(PF_ROUTE, SOCK_DGRAM, 0, socks)"); + + if (socketpair(PF_ROUTE, SOCK_RAW, 0, socks) == 0) { + close(socks[0]); + close(socks[1]); + errx(-1, + "socketpair(PF_ROUTE, SOCK_STREAM, 0, socks) success"); + } + + return (0); +} diff --git a/tests/sys/socket/sblock_test.c b/tests/sys/socket/sblock_test.c new file mode 100644 index 0000000..415c4d4 --- /dev/null +++ b/tests/sys/socket/sblock_test.c @@ -0,0 +1,207 @@ +/*- + * Copyright (c) 2007 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Sockets serialize I/O in each direction in order to avoid interlacing of + * I/O by multiple processes or threcvs recving or sending the socket. This + * is done using some form of kernel lock (varies by kernel version), called + * "sblock" in FreeBSD. However, to avoid unkillable processes waiting on + * I/O that may be entirely controlled by a remote network endpoint, that + * lock acquisition must be interruptible. + * + * To test this, set up a local domain stream socket pair and a set of three + * processes. Two processes block in recv(), the first on sbwait (wait for + * I/O), and the second on the sblock waiting for the first to finish. A + * third process is responsible for signalling the second process, then + * writing to the socket. Depending on the error returned in the second + * process, we can tell whether the sblock wait was interrupted, or if + * instead the process only woke up when the write was performed. + */ + +#include <sys/socket.h> + +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +static int interrupted; +static void +signal_handler(int signum __unused) +{ + + interrupted++; +} + +/* + * Process that will perform a blocking recv on a UNIX domain socket. This + * should return one byte of data. + */ +static void +blocking_recver(int fd) +{ + ssize_t len; + char ch; + + len = recv(fd, &ch, sizeof(ch), 0); + if (len < 0) + err(-1, "FAIL: blocking_recver: recv"); + if (len == 0) + errx(-1, "FAIL: blocking_recver: recv: eof"); + if (len != 1) + errx(-1, "FAIL: blocking_recver: recv: %zd bytes", len); + if (interrupted) + errx(-1, "FAIL: blocking_recver: interrupted wrong pid"); +} + +/* + * Process that will perform a locking recv on a UNIX domain socket. + * + * This is where we figure out if the test worked or not. If it has failed, + * then recv() will return EOF, as the close() arrives before the signal, + * meaning that the wait for the sblock was not interrupted; if it has + * succeeded, we get EINTR as the signal interrupts the lock request. + */ +static void +locking_recver(int fd) +{ + ssize_t len; + char ch; + + if (sleep(1) != 0) + err(-1, "FAIL: locking_recver: sleep"); + len = recv(fd, &ch, sizeof(ch), 0); + if (len < 0 && errno != EINTR) + err(-1, "FAIL: locking_recver: recv"); + if (len < 0 && errno == EINTR) { + fprintf(stderr, "PASS\n"); + exit(0); + } + if (len == 0) + errx(-1, "FAIL: locking_recver: recv: eof"); + if (!interrupted) + errx(-1, "FAIL: locking_recver: not interrupted"); +} + +static void +signaller(pid_t locking_recver_pid, int fd) +{ + ssize_t len; + char ch; + + if (sleep(2) != 0) { + warn("signaller sleep(2)"); + return; + } + if (kill(locking_recver_pid, SIGHUP) < 0) { + warn("signaller kill(%d)", locking_recver_pid); + return; + } + if (sleep(1) != 0) { + warn("signaller sleep(1)"); + return; + } + len = send(fd, &ch, sizeof(ch), 0); + if (len < 0) { + warn("signaller send"); + return; + } + if (len != sizeof(ch)) { + warnx("signaller send ret %zd", len); + return; + } + if (close(fd) < 0) { + warn("signaller close"); + return; + } + if (sleep(1) != 0) { + warn("signaller sleep(1)"); + return; + } +} + +int +main(void) +{ + int error, fds[2], recver_fd, sender_fd; + pid_t blocking_recver_pid; + pid_t locking_recver_pid; + struct sigaction sa; + + if (sigaction(SIGHUP, NULL, &sa) < 0) + err(-1, "FAIL: sigaction(SIGHUP, NULL, &sa)"); + + sa.sa_handler = signal_handler; + if (sa.sa_flags & SA_RESTART) + printf("SIGHUP restartable by default (cleared)\n"); + sa.sa_flags &= ~SA_RESTART; + + if (sigaction(SIGHUP, &sa, NULL) < 0) + err(-1, "FAIL: sigaction(SIGHUP, &sa, NULL)"); + +#if 0 + if (signal(SIGHUP, signal_handler) == SIG_ERR) + err(-1, "FAIL: signal(SIGHUP)"); +#endif + + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) < 0) + err(-1, "FAIL: socketpair(PF_LOCAL, SOGK_STREAM, 0)"); + + sender_fd = fds[0]; + recver_fd = fds[1]; + + blocking_recver_pid = fork(); + if (blocking_recver_pid < 0) + err(-1, "FAIL: fork"); + if (blocking_recver_pid == 0) { + close(sender_fd); + blocking_recver(recver_fd); + exit(0); + } + + locking_recver_pid = fork(); + if (locking_recver_pid < 0) { + error = errno; + kill(blocking_recver_pid, SIGKILL); + errno = error; + err(-1, "FAIL: fork"); + } + if (locking_recver_pid == 0) { + close(sender_fd); + locking_recver(recver_fd); + exit(0); + } + + signaller(locking_recver_pid, sender_fd); + + kill(blocking_recver_pid, SIGKILL); + kill(locking_recver_pid, SIGKILL); + exit(0); +} diff --git a/tests/sys/socket/sendfile_test.c b/tests/sys/socket/sendfile_test.c new file mode 100644 index 0000000..18ae9ad --- /dev/null +++ b/tests/sys/socket/sendfile_test.c @@ -0,0 +1,486 @@ +/*- + * Copyright (c) 2006 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <md5.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * Simple regression test for sendfile. Creates a file sized at four pages + * and then proceeds to send it over a series of sockets, exercising a number + * of cases and performing limited validation. + */ + +#define FAIL(msg) {printf("# %s\n", msg); \ + return (-1);} + +#define FAIL_ERR(msg) {printf("# %s: %s\n", msg, strerror(errno)); \ + return (-1);} + +#define TEST_PORT 5678 +#define TEST_MAGIC 0x4440f7bb +#define TEST_PAGES 4 +#define TEST_SECONDS 30 + +struct test_header { + uint32_t th_magic; + uint32_t th_header_length; + uint32_t th_offset; + uint32_t th_length; + char th_md5[33]; +}; + +struct sendfile_test { + uint32_t hdr_length; + uint32_t offset; + uint32_t length; + uint32_t file_size; +}; + +static int file_fd; +static char path[PATH_MAX]; +static int listen_socket; +static int accept_socket; + +static int test_th(struct test_header *th, uint32_t *header_length, + uint32_t *offset, uint32_t *length); +static void signal_alarm(int signum); +static void setup_alarm(int seconds); +static void cancel_alarm(void); +static int receive_test(void); +static void run_child(void); +static int new_test_socket(int *connect_socket); +static void init_th(struct test_header *th, uint32_t header_length, + uint32_t offset, uint32_t length); +static int send_test(int connect_socket, struct sendfile_test); +static int write_test_file(size_t file_size); +static void run_parent(void); +static void cleanup(void); + + +static int +test_th(struct test_header *th, uint32_t *header_length, uint32_t *offset, + uint32_t *length) +{ + + if (th->th_magic != htonl(TEST_MAGIC)) + FAIL("magic number not found in header") + *header_length = ntohl(th->th_header_length); + *offset = ntohl(th->th_offset); + *length = ntohl(th->th_length); + return (0); +} + +static void +signal_alarm(int signum) +{ + (void)signum; + + printf("# test timeout\n"); + + if (accept_socket > 0) + close(accept_socket); + if (listen_socket > 0) + close(listen_socket); + + _exit(-1); +} + +static void +setup_alarm(int seconds) +{ + struct itimerval itv; + bzero(&itv, sizeof(itv)); + (void)seconds; + itv.it_value.tv_sec = seconds; + + signal(SIGALRM, signal_alarm); + setitimer(ITIMER_REAL, &itv, NULL); +} + +static void +cancel_alarm(void) +{ + struct itimerval itv; + bzero(&itv, sizeof(itv)); + setitimer(ITIMER_REAL, &itv, NULL); +} + +static int +receive_test(void) +{ + uint32_t header_length, offset, length, counter; + struct test_header th; + ssize_t len; + char buf[10240]; + MD5_CTX md5ctx; + char *rxmd5; + + len = read(accept_socket, &th, sizeof(th)); + if (len < 0 || (size_t)len < sizeof(th)) + FAIL_ERR("read") + + if (test_th(&th, &header_length, &offset, &length) != 0) + return (-1); + + MD5Init(&md5ctx); + + counter = 0; + while (1) { + len = read(accept_socket, buf, sizeof(buf)); + if (len < 0 || len == 0) + break; + counter += len; + MD5Update(&md5ctx, buf, len); + } + + rxmd5 = MD5End(&md5ctx, NULL); + + if ((counter != header_length+length) || + memcmp(th.th_md5, rxmd5, 33) != 0) + FAIL("receive length mismatch") + + free(rxmd5); + return (0); +} + +static void +run_child(void) +{ + struct sockaddr_in sin; + int rc = 0; + + listen_socket = socket(PF_INET, SOCK_STREAM, 0); + if (listen_socket < 0) { + printf("# socket: %s\n", strerror(errno)); + rc = -1; + } + + if (!rc) { + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(TEST_PORT); + + if (bind(listen_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + printf("# bind: %s\n", strerror(errno)); + rc = -1; + } + } + + if (!rc && listen(listen_socket, -1) < 0) { + printf("# listen: %s\n", strerror(errno)); + rc = -1; + } + + if (!rc) { + accept_socket = accept(listen_socket, NULL, NULL); + setup_alarm(TEST_SECONDS); + if (receive_test() != 0) + rc = -1; + } + + cancel_alarm(); + if (accept_socket > 0) + close(accept_socket); + if (listen_socket > 0) + close(listen_socket); + + _exit(rc); +} + +static int +new_test_socket(int *connect_socket) +{ + struct sockaddr_in sin; + int rc = 0; + + *connect_socket = socket(PF_INET, SOCK_STREAM, 0); + if (*connect_socket < 0) + FAIL_ERR("socket") + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(TEST_PORT); + + if (connect(*connect_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) + FAIL_ERR("connect") + + return (rc); +} + +static void +init_th(struct test_header *th, uint32_t header_length, uint32_t offset, + uint32_t length) +{ + bzero(th, sizeof(*th)); + th->th_magic = htonl(TEST_MAGIC); + th->th_header_length = htonl(header_length); + th->th_offset = htonl(offset); + th->th_length = htonl(length); + + MD5FileChunk(path, th->th_md5, offset, length); +} + +static int +send_test(int connect_socket, struct sendfile_test test) +{ + struct test_header th; + struct sf_hdtr hdtr, *hdtrp; + struct iovec headers; + char *header; + ssize_t len; + int length; + off_t off; + + len = lseek(file_fd, 0, SEEK_SET); + if (len != 0) + FAIL_ERR("lseek") + + struct stat st; + if (fstat(file_fd, &st) < 0) + FAIL_ERR("fstat") + length = st.st_size - test.offset; + if (test.length > 0 && test.length < (uint32_t)length) + length = test.length; + + init_th(&th, test.hdr_length, test.offset, length); + + len = write(connect_socket, &th, sizeof(th)); + if (len != sizeof(th)) + return (-1); + + if (test.hdr_length != 0) { + header = malloc(test.hdr_length); + if (header == NULL) + FAIL_ERR("malloc") + + hdtrp = &hdtr; + bzero(&headers, sizeof(headers)); + headers.iov_base = header; + headers.iov_len = test.hdr_length; + bzero(&hdtr, sizeof(hdtr)); + hdtr.headers = &headers; + hdtr.hdr_cnt = 1; + hdtr.trailers = NULL; + hdtr.trl_cnt = 0; + } else { + hdtrp = NULL; + header = NULL; + } + + if (sendfile(file_fd, connect_socket, test.offset, test.length, + hdtrp, &off, 0) < 0) { + if (header != NULL) + free(header); + FAIL_ERR("sendfile") + } + + if (length == 0) { + struct stat sb; + + if (fstat(file_fd, &sb) == 0) + length = sb.st_size - test.offset; + } + + if (header != NULL) + free(header); + + if (off != length) + FAIL("offset != length") + + return (0); +} + +static int +write_test_file(size_t file_size) +{ + char *page_buffer; + ssize_t len; + static size_t current_file_size = 0; + + if (file_size == current_file_size) + return (0); + else if (file_size < current_file_size) { + if (ftruncate(file_fd, file_size) != 0) + FAIL_ERR("ftruncate"); + current_file_size = file_size; + return (0); + } + + page_buffer = malloc(file_size); + if (page_buffer == NULL) + FAIL_ERR("malloc") + bzero(page_buffer, file_size); + + len = write(file_fd, page_buffer, file_size); + if (len < 0) + FAIL_ERR("write") + + len = lseek(file_fd, 0, SEEK_SET); + if (len < 0) + FAIL_ERR("lseek") + if (len != 0) + FAIL("len != 0") + + free(page_buffer); + current_file_size = file_size; + return (0); +} + +static void +run_parent(void) +{ + int connect_socket; + int status; + int test_num; + int test_count; + int pid; + size_t desired_file_size = 0; + + const int pagesize = getpagesize(); + + struct sendfile_test tests[] = { + { .hdr_length = 0, .offset = 0, .length = 1 }, + { .hdr_length = 0, .offset = 0, .length = pagesize }, + { .hdr_length = 0, .offset = 1, .length = 1 }, + { .hdr_length = 0, .offset = 1, .length = pagesize }, + { .hdr_length = 0, .offset = pagesize, .length = pagesize }, + { .hdr_length = 0, .offset = 0, .length = 2*pagesize }, + { .hdr_length = 0, .offset = 0, .length = 0 }, + { .hdr_length = 0, .offset = pagesize, .length = 0 }, + { .hdr_length = 0, .offset = 2*pagesize, .length = 0 }, + { .hdr_length = 0, .offset = TEST_PAGES*pagesize, .length = 0 }, + { .hdr_length = 0, .offset = 0, .length = pagesize, + .file_size = 1 } + }; + + test_count = sizeof(tests) / sizeof(tests[0]); + printf("1..%d\n", test_count); + + for (test_num = 1; test_num <= test_count; test_num++) { + + desired_file_size = tests[test_num - 1].file_size; + if (desired_file_size == 0) + desired_file_size = TEST_PAGES * pagesize; + if (write_test_file(desired_file_size) != 0) { + printf("not ok %d\n", test_num); + continue; + } + + pid = fork(); + if (pid == -1) { + printf("not ok %d\n", test_num); + continue; + } + + if (pid == 0) + run_child(); + + usleep(250000); + + if (new_test_socket(&connect_socket) != 0) { + printf("not ok %d\n", test_num); + kill(pid, SIGALRM); + close(connect_socket); + continue; + } + + if (send_test(connect_socket, tests[test_num-1]) != 0) { + printf("not ok %d\n", test_num); + kill(pid, SIGALRM); + close(connect_socket); + continue; + } + + close(connect_socket); + if (waitpid(pid, &status, 0) == pid) { + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + printf("%s %d\n", "ok", test_num); + else + printf("%s %d\n", "not ok", test_num); + } + else { + printf("not ok %d\n", test_num); + } + } +} + +static void +cleanup(void) +{ + + unlink(path); +} + +int +main(int argc, char *argv[]) +{ + int pagesize; + + path[0] = '\0'; + + pagesize = getpagesize(); + + if (argc == 1) { + snprintf(path, sizeof(path), "sendfile.XXXXXXXXXXXX"); + file_fd = mkstemp(path); + if (file_fd == -1) + FAIL_ERR("mkstemp"); + } else if (argc == 2) { + (void)strlcpy(path, argv[1], sizeof(path)); + file_fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0600); + if (file_fd == -1) + FAIL_ERR("open"); + } else { + FAIL("usage: sendfile [path]"); + } + + atexit(cleanup); + + run_parent(); + return (0); +} diff --git a/tests/sys/socket/shutdown_test.c b/tests/sys/socket/shutdown_test.c new file mode 100644 index 0000000..3d23c94 --- /dev/null +++ b/tests/sys/socket/shutdown_test.c @@ -0,0 +1,110 @@ +/*- + * Copyright (C) 2005 The FreeBSD Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +int +main(void) +{ + struct sockaddr_in sock; + socklen_t len; + int listen_sock, connect_sock; + u_short port; + + listen_sock = -1; + + /* Shutdown(2) on an invalid file descriptor has to return EBADF. */ + if ((shutdown(listen_sock, SHUT_RDWR) != -1) && (errno != EBADF)) + errx(-1, "shutdown() for invalid file descriptor does not " + "return EBADF"); + + listen_sock = socket(PF_INET, SOCK_STREAM, 0); + if (listen_sock == -1) + errx(-1, + "socket(PF_INET, SOCK_STREAM, 0) for listen socket: %s", + strerror(errno)); + + bzero(&sock, sizeof(sock)); + sock.sin_len = sizeof(sock); + sock.sin_family = AF_INET; + sock.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sock.sin_port = 0; + + if (bind(listen_sock, (struct sockaddr *)&sock, sizeof(sock)) < 0) + errx(-1, "bind(%s, %d) for listen socket: %s", + inet_ntoa(sock.sin_addr), sock.sin_port, strerror(errno)); + + len = sizeof(sock); + if (getsockname(listen_sock, (struct sockaddr *)&sock, &len) < 0) + errx(-1, "getsockname() for listen socket: %s", + strerror(errno)); + port = sock.sin_port; + + if (listen(listen_sock, -1) < 0) + errx(-1, "listen() for listen socket: %s", strerror(errno)); + + connect_sock = socket(PF_INET, SOCK_STREAM, 0); + if (connect_sock == -1) + errx(-1, "socket(PF_INET, SOCK_STREAM, 0) for connect " + "socket: %s", strerror(errno)); + + bzero(&sock, sizeof(sock)); + sock.sin_len = sizeof(sock); + sock.sin_family = AF_INET; + sock.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sock.sin_port = port; + + if (connect(connect_sock, (struct sockaddr *)&sock, sizeof(sock)) < 0) + errx(-1, "connect() for connect socket: %s", strerror(errno)); + /* Try to pass an invalid flags. */ + if ((shutdown(connect_sock, SHUT_RD - 1) != -1) && (errno != EINVAL)) + errx(-1, "shutdown(SHUT_RD - 1) does not return EINVAL"); + if ((shutdown(connect_sock, SHUT_RDWR + 1) != -1) && (errno != EINVAL)) + errx(-1, "shutdown(SHUT_RDWR + 1) does not return EINVAL"); + + if (shutdown(connect_sock, SHUT_RD) < 0) + errx(-1, "shutdown(SHUT_RD) for connect socket: %s", + strerror(errno)); + if (shutdown(connect_sock, SHUT_WR) < 0) + errx(-1, "shutdown(SHUT_WR) for connect socket: %s", + strerror(errno)); + + close(connect_sock); + close(listen_sock); + + return (0); +} diff --git a/tests/sys/socket/sigpipe_test.c b/tests/sys/socket/sigpipe_test.c new file mode 100644 index 0000000..a9562f2 --- /dev/null +++ b/tests/sys/socket/sigpipe_test.c @@ -0,0 +1,329 @@ +/*- + * Copyright (c) 2005 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * This regression test is intended to verify whether or not SIGPIPE is + * properly generated in several simple test cases, as well as testing + * whether SO_NOSIGPIPE disables SIGPIPE, if available on the system. + * SIGPIPE is generated if a write or send is attempted on a socket that has + * been shutdown for write. This test runs several test cases with UNIX + * domain sockets and TCP sockets to confirm that either EPIPE or SIGPIPE is + * properly returned. + * + * For the purposes of testing TCP, an unused port number must be specified. + */ +static void usage(void) __dead2; +static void +usage(void) +{ + + errx(-1, "usage: sigpipe [tcpport]"); +} + +/* + * Signal catcher. Set a global flag that can be tested by the caller. + */ +static int signaled; +static int +got_signal(void) +{ + + return (signaled); +} + +static void +signal_handler(int signum __unused) +{ + + signaled = 1; +} + +static void +signal_setup(const char *testname) +{ + + signaled = 0; + if (signal(SIGPIPE, signal_handler) == SIG_ERR) + err(-1, "%s: signal(SIGPIPE)", testname); +} + +static void +test_send(const char *testname, int sock) +{ + ssize_t len; + char ch; + + ch = 0; + len = send(sock, &ch, sizeof(ch), 0); + if (len < 0) { + if (errno == EPIPE) + return; + err(-1, "%s: send", testname); + } + errx(-1, "%s: send: returned %zd", testname, len); +} + +static void +test_write(const char *testname, int sock) +{ + ssize_t len; + char ch; + + ch = 0; + len = write(sock, &ch, sizeof(ch)); + if (len < 0) { + if (errno == EPIPE) + return; + err(-1, "%s: write", testname); + } + errx(-1, "%s: write: returned %zd", testname, len); +} + +static void +test_send_wantsignal(const char *testname, int sock1, int sock2) +{ + + if (shutdown(sock2, SHUT_WR) < 0) + err(-1, "%s: shutdown", testname); + signal_setup(testname); + test_send(testname, sock2); + if (!got_signal()) + errx(-1, "%s: send: didn't receive SIGPIPE", testname); + close(sock1); + close(sock2); +} + +#ifdef SO_NOSIGPIPE +static void +test_send_dontsignal(const char *testname, int sock1, int sock2) +{ + int i; + + i = 1; + if (setsockopt(sock2, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)) < 0) + err(-1, "%s: setsockopt(SOL_SOCKET, SO_NOSIGPIPE)", testname); + if (shutdown(sock2, SHUT_WR) < 0) + err(-1, "%s: shutdown", testname); + signal_setup(testname); + test_send(testname, sock2); + if (got_signal()) + errx(-1, "%s: send: got SIGPIPE", testname); + close(sock1); + close(sock2); +} +#endif + +static void +test_write_wantsignal(const char *testname, int sock1, int sock2) +{ + + if (shutdown(sock2, SHUT_WR) < 0) + err(-1, "%s: shutdown", testname); + signal_setup(testname); + test_write(testname, sock2); + if (!got_signal()) + errx(-1, "%s: write: didn't receive SIGPIPE", testname); + close(sock1); + close(sock2); +} + +#ifdef SO_NOSIGPIPE +static void +test_write_dontsignal(const char *testname, int sock1, int sock2) +{ + int i; + + i = 1; + if (setsockopt(sock2, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)) < 0) + err(-1, "%s: setsockopt(SOL_SOCKET, SO_NOSIGPIPE)", testname); + if (shutdown(sock2, SHUT_WR) < 0) + err(-1, "%s: shutdown", testname); + signal_setup(testname); + test_write(testname, sock2); + if (got_signal()) + errx(-1, "%s: write: got SIGPIPE", testname); + close(sock1); + close(sock2); +} +#endif + +static int listen_sock; +static void +tcp_setup(u_short port) +{ + struct sockaddr_in sin; + + listen_sock = socket(PF_INET, SOCK_STREAM, 0); + if (listen_sock < 0) + err(-1, "tcp_setup: listen"); + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(port); + + if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + err(-1, "tcp_setup: bind"); + + if (listen(listen_sock, -1) < 0) + err(-1, "tcp_setup: listen"); +} + +static void +tcp_teardown(void) +{ + + close(listen_sock); +} + +static void +tcp_pair(u_short port, int sock[2]) +{ + int accept_sock, connect_sock; + struct sockaddr_in sin; + socklen_t len; + + connect_sock = socket(PF_INET, SOCK_STREAM, 0); + if (connect_sock < 0) + err(-1, "tcp_pair: socket"); + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(port); + + if (connect(connect_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) + err(-1, "tcp_pair: connect"); + + sleep(1); /* Time for TCP to settle. */ + + len = sizeof(sin); + accept_sock = accept(listen_sock, (struct sockaddr *)&sin, &len); + if (accept_sock < 0) + err(-1, "tcp_pair: accept"); + + sleep(1); /* Time for TCP to settle. */ + + sock[0] = accept_sock; + sock[1] = connect_sock; +} + +int +main(int argc, char *argv[]) +{ + char *dummy; + int sock[2]; + long port; + + if (argc == 1) { + srandomdev(); + + /* Pick a random unprivileged port 1025-65535 */ + port = MAX((int)random() % 65535, 1025); + } else if (argc == 2) { + port = strtol(argv[1], &dummy, 10); + if (port < 0 || port > 65535 || *dummy != '\0') + usage(); + } else + usage(); + +#ifndef SO_NOSIGPIPE + warnx("sigpipe: SO_NOSIGPIPE not defined, skipping some tests"); +#endif + + /* + * UNIX domain socketpair(). + */ + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0) + err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)"); + test_send_wantsignal("test_send_wantsignal(PF_LOCAL)", sock[0], + sock[1]); + +#ifdef SO_NOSIGPIPE + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0) + err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)"); + test_send_dontsignal("test_send_dontsignal(PF_LOCAL)", sock[0], + sock[1]); +#endif + + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0) + err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)"); + test_write_wantsignal("test_write_wantsignal(PF_LOCAL)", sock[0], + sock[1]); + +#ifdef SO_NOSIGPIPE + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sock) < 0) + err(-1, "socketpair(PF_LOCAL, SOCK_STREAM)"); + test_write_dontsignal("test_write_dontsignal(PF_LOCAL)", sock[0], + sock[1]); +#endif + + /* + * TCP. + */ + tcp_setup(port); + tcp_pair(port, sock); + test_send_wantsignal("test_send_wantsignal(PF_INET)", sock[0], + sock[1]); + +#ifdef SO_NOSIGPIPE + tcp_pair(port, sock); + test_send_dontsignal("test_send_dontsignal(PF_INET)", sock[0], + sock[1]); +#endif + + tcp_pair(port, sock); + test_write_wantsignal("test_write_wantsignal(PF_INET)", sock[0], + sock[1]); + +#ifdef SO_NOSIGPIPE + tcp_pair(port, sock); + test_write_dontsignal("test_write_dontsignal(PF_INET)", sock[0], + sock[1]); +#endif + tcp_teardown(); + + fprintf(stderr, "PASS\n"); + return (0); +} diff --git a/tests/sys/socket/so_setfib_test.c b/tests/sys/socket/so_setfib_test.c new file mode 100644 index 0000000..040ad8f --- /dev/null +++ b/tests/sys/socket/so_setfib_test.c @@ -0,0 +1,199 @@ +/*- + * Copyright (c) 2012 Cisco Systems, Inc. + * All rights reserved. + * + * This software was developed by Bjoern Zeeb under contract to + * Cisco Systems, Inc.. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Regression test on SO_SETFIB setsockopt(2). + * + * Check that the expected domain(9) families all handle the socket option + * correctly and do proper bounds checks. + * + * Test plan: + * 1. Get system wide number of FIBs from sysctl and convert to index (-= 1). + * 2. For each protocol family (INET, INET6, ROUTE and LOCAL) open socketes of + * type (STREAM, DGRAM and RAW) as supported. + * 3. Do a sequence of -2, -1, 0, .. n, n+1, n+2 SO_SETFIB sockopt calls, + * expecting the first two and last two to fail (valid 0 ... n). + * 4. Try 3 random numbers. Calculate result based on valid range. + * 5. Repeat for next domain family and type from (2) on. + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/sysctl.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static struct t_dom { + int domain; + const char *name; +} t_dom[] = { +#ifdef INET6 + { .domain = PF_INET6, .name = "PF_INET6" }, +#endif +#ifdef INET + { .domain = PF_INET, .name = "PF_INET" }, +#endif + { .domain = PF_ROUTE, .name = "PF_ROUTE" }, + { .domain = PF_LOCAL, .name = "PF_LOCAL" }, +}; + +static struct t_type { + int type; + const char *name; +} t_type[] = { + { .type = SOCK_STREAM, .name = "SOCK_STREAM" }, + { .type = SOCK_DGRAM, .name = "SOCK_DGRAM" }, + { .type = SOCK_RAW, .name = "SOCK_RAW" }, +}; + +/* + * Number of FIBs as read from net.fibs sysctl - 1. Initialize to clear out of + * bounds value to not accidentally run on a limited range. + */ +static int rt_numfibs = -42; + +/* Number of test case. */ +static int testno = 1; + + +/* + * Try the setsockopt with given FIB number i on socket s. + * Handle result given on error and valid range and errno. + */ +static void +so_setfib(int s, int i, u_int dom, u_int type) +{ + int error; + + error = setsockopt(s, SOL_SOCKET, SO_SETFIB, &i, sizeof(i)); + /* For out of bounds we expect an error. */ + if (error == -1 && (i < 0 || i > rt_numfibs)) + printf("ok %d %s_%s_%d\n", testno, t_dom[dom].name, + t_type[type].name, i); + else if (error != -1 && (i < 0 || i > rt_numfibs)) + printf("not ok %d %s_%s_%d # setsockopt(%d, SOL_SOCKET, " + "SO_SETFIB, %d, ..) unexpectedly succeeded\n", testno, + t_dom[dom].name, t_type[type].name, i, s, i); + else if (error == 0) + printf("ok %d %s_%s_%d\n", testno, t_dom[dom].name, + t_type[type].name, i); + else if (errno != EINVAL) + printf("not ok %d %s_%s_%d # setsockopt(%d, SOL_SOCKET, " + "SO_SETFIB, %d, ..) unexpected error: %s\n", testno, + t_dom[dom].name, t_type[type].name, i, s, i, + strerror(errno)); + else + printf("not ok %d %s_%s_%d\n", testno, t_dom[dom].name, + t_type[type].name, i); + + /* Test run done, next please. */ + testno++; +} + +/* + * Main test. Open socket given domain family and type. For each FIB, out of + * bounds FIB numbers and 3 random FIB numbers set the socket option. + */ +static void +t(u_int dom, u_int type) +{ + int i, s; + + /* PF_ROUTE only supports RAW socket types, while PF_LOCAL does not. */ + if (t_dom[dom].domain == PF_ROUTE && t_type[type].type != SOCK_RAW) + return; + if (t_dom[dom].domain == PF_LOCAL && t_type[type].type == SOCK_RAW) + return; + + /* Open socket for given combination. */ + s = socket(t_dom[dom].domain, t_type[type].type, 0); + if (s == -1) { + printf("not ok %d %s_%s # socket(): %s\n", testno, + t_dom[dom].name, t_type[type].name, strerror(errno)); + testno++; + return; + } + + /* Test FIBs -2, -1, 0, .. n, n + 1, n + 2. */ + for (i = -2; i <= (rt_numfibs + 2); i++) + so_setfib(s, i, dom, type); + + /* Test 3 random FIB numbers. */ + for (i = 0; i < 3; i++) + so_setfib(s, (int)random(), dom, type); + + /* Close socket. */ + close(s); +} + +/* + * Returns 0 if no program error, 1 on sysctlbyname error. + * Test results are communicated by printf("[not ]ok <n> .."). + */ +int +main(int argc __unused, char *argv[] __unused) +{ + u_int i, j; + size_t s; + + if (geteuid() != 0) { + printf("1..0 # SKIP: must be root\n"); + return (0); + } + + /* Initalize randomness. */ + srandomdev(); + + /* Get number of FIBs supported by kernel. */ + s = sizeof(rt_numfibs); + if (sysctlbyname("net.fibs", &rt_numfibs, &s, NULL, 0) == -1) + err(1, "sysctlbyname(net.fibs, ..)"); + + printf("1..%zu\n", (nitems(t_dom) - 1) * nitems(t_type) * (2 + rt_numfibs + 2 + 3)); + + /* Adjust from number to index. */ + rt_numfibs -= 1; + + /* Run tests. */ + for (i = 0; i < sizeof(t_dom) / sizeof(struct t_dom); i++) + for (j = 0; j < sizeof(t_type) / sizeof(struct t_type); j++) + t(i, j); + + return (0); +} + +/* end */ diff --git a/tests/sys/socket/socketpair_test.c b/tests/sys/socket/socketpair_test.c new file mode 100644 index 0000000..779878a --- /dev/null +++ b/tests/sys/socket/socketpair_test.c @@ -0,0 +1,161 @@ +/*- + * Copyright (c) 2004 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * Open, then close a set of UNIX domain socket pairs for datagram and + * stream. + * + * Confirm that we can't open INET datagram or stream socket pairs. + * + * More tests should be added, including confirming that sending on either + * endpoint results in data at the other, that the right kind of socket was + * created (stream vs. datagram), and that message boundaries fall in the + * right places. + */ +int +main(void) +{ + int fd1, fd2, fd3; + int sv[2]; + + /* + * UNIX domain socket pair, datagram. + */ + if (socketpair(PF_UNIX, SOCK_DGRAM, 0, sv) != 0) { + fprintf(stderr, "socketpair(PF_UNIX, SOCK_DGRAM): %s\n", + strerror(errno)); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + if (close(sv[0]) != 0) { + fprintf(stderr, "socketpair(PF_UNIX, SOCK_DGRAM) close 0: %s\n", + strerror(errno)); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + if (close(sv[1]) != 0) { + fprintf(stderr, "socketpair(PF_UNIX, SOCK_DGRAM) close 1: %s\n", + strerror(errno)); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + + /* + * UNIX domain socket pair, stream. + */ + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) != 0) { + fprintf(stderr, "socketpair(PF_UNIX, SOCK_STREAM): %s\n", + strerror(errno)); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + if (close(sv[0]) != 0) { + fprintf(stderr, "socketpair(PF_UNIX, SOCK_STREAM) close 0: %s\n", + strerror(errno)); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + if (close(sv[1]) != 0) { + fprintf(stderr, "socketpair(PF_UNIX, SOCK_STREAM) close 1: " + "%s\n", strerror(errno)); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + + /* + * Confirm that PF_INET datagram socket pair creation fails. + */ + if (socketpair(PF_INET, SOCK_DGRAM, 0, sv) == 0) { + fprintf(stderr, "socketpair(PF_INET, SOCK_DGRAM): opened\n"); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + if (errno != EOPNOTSUPP) { + fprintf(stderr, "socketpair(PF_INET, SOCK_DGRAM): %s\n", + strerror(errno)); + fprintf(stderr, "FAIL\n"); + } + + /* + * Confirm that PF_INET stream socket pair creation fails. + */ + if (socketpair(PF_INET, SOCK_STREAM, 0, sv) == 0) { + fprintf(stderr, "socketpair(PF_INET, SOCK_STREAM): opened\n"); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + if (errno != EOPNOTSUPP) { + fprintf(stderr, "socketpair(PF_INET, SOCK_STREAM): %s\n", + strerror(errno)); + fprintf(stderr, "FAIL\n"); + } + + /* + * Check for sequential fd allocation, and give up early if not. + */ + fd1 = dup(STDIN_FILENO); + fd2 = dup(STDIN_FILENO); + if (fd2 != fd1 + 1) { + fprintf(stderr, "Non-sequential fd allocation\n"); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + + /* Allocate a socketpair using a bad destination address. */ + if (socketpair(PF_UNIX, SOCK_DGRAM, 0, NULL) == 0) { + fprintf(stderr, "socketpair(PF_UNIX, SOCK_DGRAM, NULL): opened\n"); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + if (errno != EFAULT) { + fprintf(stderr, "socketpair(PF_UNIX, SOCK_DGRAM, NULL): %s\n", + strerror(errno)); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + + /* Allocate a file descriptor and make sure it's fd2+1. */ + fd3 = dup(STDIN_FILENO); + if (fd3 != fd2 + 1) { + fprintf(stderr, "socketpair(..., NULL) allocated descriptors\n"); + fprintf(stderr, "FAIL\n"); + exit(-1); + } + + fprintf(stderr, "PASS\n"); + exit(0); +} diff --git a/tests/sys/socket/unix_bindconnect_test.c b/tests/sys/socket/unix_bindconnect_test.c new file mode 100644 index 0000000..a2c3184 --- /dev/null +++ b/tests/sys/socket/unix_bindconnect_test.c @@ -0,0 +1,318 @@ +/*- + * Copyright (c) 2005 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +/* + * Simple regression test to exercise some error cases relating to the use of + * bind() and connect() on UNIX domain sockets. In particular, make sure + * that when two sockets rendezvous using the file system name space, they + * get the expected success/failure cases. + * + * TODO: + * - Check that the resulting file mode/owner are right. + * - Do the same tests with UNIX domain sockets. + * - Check the results of getsockaddr() and getpeeraddr(). + */ + +#define SOCK_NAME_ONE "socket.1" +#define SOCK_NAME_TWO "socket.2" + +#define UNWIND_MAX 1024 + +static int unwind_len; +static struct unwind { + char u_path[PATH_MAX]; +} unwind_list[UNWIND_MAX]; + +static void +push_path(const char *path) +{ + + if (unwind_len >= UNWIND_MAX) + err(-1, "push_path: one path too many (%s)", path); + + strlcpy(unwind_list[unwind_len].u_path, path, PATH_MAX); + unwind_len++; +} + +static void +unwind(void) +{ + int i; + + for (i = unwind_len - 1; i >= 0; i--) { + unlink(unwind_list[i].u_path); + rmdir(unwind_list[i].u_path); + } +} + +static int +bind_test(const char *directory_path) +{ + char socket_path[PATH_MAX]; + struct sockaddr_un sun; + int sock1, sock2; + + sock1 = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock1 < 0) { + warn("bind_test: socket(PF_UNIX, SOCK_STREAM, 0)"); + return (-1); + } + + if (snprintf(socket_path, sizeof(socket_path), "%s/%s", + directory_path, SOCK_NAME_ONE) >= PATH_MAX) { + warn("bind_test: snprintf(socket_path)"); + close(sock1); + return (-1); + } + + bzero(&sun, sizeof(sun)); + sun.sun_len = sizeof(sun); + sun.sun_family = AF_UNIX; + if (snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", socket_path) + >= (int)sizeof(sun.sun_path)) { + warn("bind_test: snprintf(sun.sun_path)"); + close(sock1); + return (-1); + } + + if (bind(sock1, (struct sockaddr *)&sun, sizeof(sun)) < 0) { + warn("bind_test: bind(sun) #1"); + close(sock1); + return (-1); + } + + push_path(socket_path); + + /* + * Once a STREAM UNIX domain socket has been bound, it can't be + * rebound. Expected error is EINVAL. + */ + if (bind(sock1, (struct sockaddr *)&sun, sizeof(sun)) == 0) { + warnx("bind_test: bind(sun) #2 succeeded"); + close(sock1); + return (-1); + } + if (errno != EINVAL) { + warn("bind_test: bind(sun) #2"); + close(sock1); + return (-1); + } + + sock2 = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock2 < 0) { + warn("bind_test: socket(PF_UNIX, SOCK_STREAM, 0)"); + close(sock1); + return (-1); + } + + /* + * Since a socket is already bound to the pathname, it can't be bound + * to a second socket. Expected error is EADDRINUSE. + */ + if (bind(sock2, (struct sockaddr *)&sun, sizeof(sun)) == 0) { + warnx("bind_test: bind(sun) #3 succeeded"); + close(sock1); + close(sock2); + return (-1); + } + if (errno != EADDRINUSE) { + warn("bind_test: bind(sun) #2"); + close(sock1); + close(sock2); + return (-1); + } + + close(sock1); + + /* + * The socket bound to the pathname has been closed, but the pathname + * can't be reused without first being unlinked. Expected error is + * EADDRINUSE. + */ + if (bind(sock2, (struct sockaddr *)&sun, sizeof(sun)) == 0) { + warnx("bind_test: bind(sun) #4 succeeded"); + close(sock2); + return (-1); + } + if (errno != EADDRINUSE) { + warn("bind_test: bind(sun) #4"); + close(sock2); + return (-1); + } + + unlink(socket_path); + + /* + * The pathname is now free, so the socket should be able to bind to + * it. + */ + if (bind(sock2, (struct sockaddr *)&sun, sizeof(sun)) < 0) { + warn("bind_test: bind(sun) #5"); + close(sock2); + return (-1); + } + + close(sock2); + return (0); +} + +static int +connect_test(const char *directory_path) +{ + char socket_path[PATH_MAX]; + struct sockaddr_un sun; + int sock1, sock2; + + sock1 = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock1 < 0) { + warn("connect_test: socket(PF_UNIX, SOCK_STREAM, 0)"); + return (-1); + } + + if (snprintf(socket_path, sizeof(socket_path), "%s/%s", + directory_path, SOCK_NAME_TWO) >= PATH_MAX) { + warn("connect_test: snprintf(socket_path)"); + close(sock1); + return (-1); + } + + bzero(&sun, sizeof(sun)); + sun.sun_len = sizeof(sun); + sun.sun_family = AF_UNIX; + if (snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", socket_path) + >= (int)sizeof(sun.sun_path)) { + warn("connect_test: snprintf(sun.sun_path)"); + close(sock1); + return (-1); + } + + /* + * Try connecting to a path that doesn't yet exist. Should fail with + * ENOENT. + */ + if (connect(sock1, (struct sockaddr *)&sun, sizeof(sun)) == 0) { + warnx("connect_test: connect(sun) #1 succeeded"); + close(sock1); + return (-1); + } + if (errno != ENOENT) { + warn("connect_test: connect(sun) #1"); + close(sock1); + return (-1); + } + + if (bind(sock1, (struct sockaddr *)&sun, sizeof(sun)) < 0) { + warn("connect_test: bind(sun) #1"); + close(sock1); + return (-1); + } + + if (listen(sock1, 3) < 0) { + warn("connect_test: listen(sock1)"); + close(sock1); + return (-1); + } + + push_path(socket_path); + + sock2 = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock2 < 0) { + warn("socket(PF_UNIX, SOCK_STREAM, 0)"); + close(sock1); + return (-1); + } + + /* + * Do a simple connect and make sure that works. + */ + if (connect(sock2, (struct sockaddr *)&sun, sizeof(sun)) < 0) { + warn("connect(sun) #2"); + close(sock1); + return (-1); + } + + close(sock2); + + close(sock1); + + sock2 = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock2 < 0) { + warn("socket(PF_UNIX, SOCK_STREAM, 0)"); + return (-1); + } + + /* + * Confirm that once the listen socket is closed, we get a + * connection refused (ECONNREFUSED) when attempting to connect to + * the pathname. + */ + if (connect(sock2, (struct sockaddr *)&sun, sizeof(sun)) == 0) { + warnx("connect(sun) #3 succeeded"); + close(sock2); + return (-1); + } + if (errno != ECONNREFUSED) { + warn("connect(sun) #3"); + close(sock2); + return (-1); + } + + close(sock2); + unlink(socket_path); + return (0); +} +int +main(void) +{ + char directory_path[PATH_MAX]; + int error; + + strlcpy(directory_path, "unix_bind.XXXXXXX", PATH_MAX); + if (mkdtemp(directory_path) == NULL) + err(-1, "mkdtemp"); + push_path(directory_path); + + error = bind_test(directory_path); + + if (error == 0) + error = connect_test(directory_path); + + unwind(); + return (error); +} diff --git a/tests/sys/socket/unix_close_race_test.c b/tests/sys/socket/unix_close_race_test.c new file mode 100644 index 0000000..89c1b20 --- /dev/null +++ b/tests/sys/socket/unix_close_race_test.c @@ -0,0 +1,143 @@ +/*- + * Copyright (c) 2010 Mikolaj Golub + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * This regression test attempts to trigger a race that occurs when both + * endpoints of a connected UNIX domain socket are closed at once. The two + * close paths may run concurrently leading to a call to sodisconnect() on an + * already-closed socket in kernel. Before it was fixed, this might lead to + * ENOTCONN being returned improperly from close(). + * + * This race is fairly timing-dependent, so it effectively requires SMP, and + * may not even trigger then. + */ + +#include <sys/types.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <strings.h> +#include <string.h> +#include <unistd.h> +#include <err.h> + +static char socket_path[] = "tmp.XXXXXXXX"; + +#define USLEEP 100 +#define LOOPS 100000 + +int +main(void) +{ + struct sockaddr_un servaddr; + int listenfd, connfd, pid; + u_int counter, ncpus; + size_t len; + + len = sizeof(ncpus); + if (sysctlbyname("kern.smp.cpus", &ncpus, &len, NULL, 0) < 0) + err(1, "kern.smp.cpus"); + if (len != sizeof(ncpus)) + errx(1, "kern.smp.cpus: invalid length"); + if (ncpus < 2) + warnx("SMP not present, test may be unable to trigger race"); + + if (mkstemp(socket_path) == -1) + err(1, "mkstemp failed"); + unlink(socket_path); + + /* + * Create a UNIX domain socket that the child will repeatedly + * accept() from, and that the parent will repeatedly connect() to. + */ + if ((listenfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) + err(1, "parent: socket error"); + (void)unlink(socket_path); + bzero(&servaddr, sizeof(servaddr)); + servaddr.sun_family = AF_LOCAL; + strcpy(servaddr.sun_path, socket_path); + if (bind(listenfd, (struct sockaddr *) &servaddr, + sizeof(servaddr)) < 0) + err(1, "parent: bind error"); + if (listen(listenfd, 1024) < 0) + err(1, "parent: listen error"); + + pid = fork(); + if (pid == -1) + err(1, "fork()"); + if (pid != 0) { + /* + * In the parent, repeatedly connect and disconnect from the + * socket, attempting to induce the race. + */ + close(listenfd); + sleep(1); + bzero(&servaddr, sizeof(servaddr)); + servaddr.sun_family = AF_LOCAL; + strcpy(servaddr.sun_path, socket_path); + for (counter = 0; counter < LOOPS; counter++) { + if ((connfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { + (void)kill(pid, SIGTERM); + err(1, "parent: socket error"); + } + if (connect(connfd, (struct sockaddr *)&servaddr, + sizeof(servaddr)) < 0) { + (void)kill(pid, SIGTERM); + err(1, "parent: connect error"); + } + if (close(connfd) < 0) { + (void)kill(pid, SIGTERM); + err(1, "parent: close error"); + } + usleep(USLEEP); + } + (void)kill(pid, SIGTERM); + } else { + /* + * In the child, loop accepting and closing. We may pick up + * the race here so report errors from close(). + */ + for ( ; ; ) { + if ((connfd = accept(listenfd, + (struct sockaddr *)NULL, NULL)) < 0) + err(1, "child: accept error"); + if (close(connfd) < 0) + err(1, "child: close error"); + } + } + printf("OK\n"); + exit(0); +} diff --git a/tests/sys/socket/unix_cmsg.c b/tests/sys/socket/unix_cmsg.c new file mode 100644 index 0000000..d91cef4 --- /dev/null +++ b/tests/sys/socket/unix_cmsg.c @@ -0,0 +1,1969 @@ +/*- + * Copyright (c) 2005 Andrey Simonenko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/ucred.h> +#include <sys/un.h> +#include <sys/wait.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <paths.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * There are tables with tests descriptions and pointers to test + * functions. Each t_*() function returns 0 if its test passed, + * -1 if its test failed, -2 if some system error occurred. + * If a test function returns -2, then a program exits. + * + * If a test function forks a client process, then it waits for its + * termination. If a return code of a client process is not equal + * to zero, or if a client process was terminated by a signal, then + * a test function returns -1 or -2 depending on exit status of + * a client process. + * + * Each function which can block, is run under TIMEOUT. If timeout + * occurs, then a test function returns -2 or a client process exits + * with a non-zero return code. + */ + +#ifndef LISTENQ +# define LISTENQ 1 +#endif + +#ifndef TIMEOUT +# define TIMEOUT 2 +#endif + +static int t_cmsgcred(void); +static int t_sockcred_1(void); +static int t_sockcred_2(void); +static int t_cmsgcred_sockcred(void); +static int t_timeval(void); +static int t_bintime(void); +static int t_cmsg_len(void); +static int t_peercred(void); + +struct test_func { + int (*func)(void); + const char *desc; +}; + +static const struct test_func test_stream_tbl[] = { + { + .func = NULL, + .desc = "All tests" + }, + { + .func = t_cmsgcred, + .desc = "Sending, receiving cmsgcred" + }, + { + .func = t_sockcred_1, + .desc = "Receiving sockcred (listening socket)" + }, + { + .func = t_sockcred_2, + .desc = "Receiving sockcred (accepted socket)" + }, + { + .func = t_cmsgcred_sockcred, + .desc = "Sending cmsgcred, receiving sockcred" + }, + { + .func = t_timeval, + .desc = "Sending, receiving timeval" + }, + { + .func = t_bintime, + .desc = "Sending, receiving bintime" + }, + { + .func = t_cmsg_len, + .desc = "Check cmsghdr.cmsg_len" + }, + { + .func = t_peercred, + .desc = "Check LOCAL_PEERCRED socket option" + } +}; + +#define TEST_STREAM_TBL_SIZE \ + (sizeof(test_stream_tbl) / sizeof(test_stream_tbl[0])) + +static const struct test_func test_dgram_tbl[] = { + { + .func = NULL, + .desc = "All tests" + }, + { + .func = t_cmsgcred, + .desc = "Sending, receiving cmsgcred" + }, + { + .func = t_sockcred_2, + .desc = "Receiving sockcred" + }, + { + .func = t_cmsgcred_sockcred, + .desc = "Sending cmsgcred, receiving sockcred" + }, + { + .func = t_timeval, + .desc = "Sending, receiving timeval" + }, + { + .func = t_bintime, + .desc = "Sending, receiving bintime" + }, + { + .func = t_cmsg_len, + .desc = "Check cmsghdr.cmsg_len" + } +}; + +#define TEST_DGRAM_TBL_SIZE \ + (sizeof(test_dgram_tbl) / sizeof(test_dgram_tbl[0])) + +static bool debug = false; +static bool server_flag = true; +static bool send_data_flag = true; +static bool send_array_flag = true; +static bool failed_flag = false; + +static int sock_type; +static const char *sock_type_str; + +static const char *proc_name; + +static char work_dir[] = _PATH_TMP "unix_cmsg.XXXXXXX"; +static int serv_sock_fd; +static struct sockaddr_un serv_addr_sun; + +static struct { + char *buf_send; + char *buf_recv; + size_t buf_size; + u_int msg_num; +} ipc_msg; + +#define IPC_MSG_NUM_DEF 5 +#define IPC_MSG_NUM_MAX 10 +#define IPC_MSG_SIZE_DEF 7 +#define IPC_MSG_SIZE_MAX 128 + +static struct { + uid_t uid; + uid_t euid; + gid_t gid; + gid_t egid; + gid_t *gid_arr; + int gid_num; +} proc_cred; + +static pid_t client_pid; + +#define SYNC_SERVER 0 +#define SYNC_CLIENT 1 +#define SYNC_RECV 0 +#define SYNC_SEND 1 + +static int sync_fd[2][2]; + +#define LOGMSG_SIZE 128 + +static void logmsg(const char *, ...) __printflike(1, 2); +static void logmsgx(const char *, ...) __printflike(1, 2); +static void dbgmsg(const char *, ...) __printflike(1, 2); +static void output(const char *, ...) __printflike(1, 2); + +static void +usage(bool verbose) +{ + u_int i; + + printf("usage: %s [-dh] [-n num] [-s size] [-t type] " + "[-z value] [testno]\n", getprogname()); + if (!verbose) + return; + printf("\n Options are:\n\ + -d Output debugging information\n\ + -h Output the help message and exit\n\ + -n num Number of messages to send\n\ + -s size Specify size of data for IPC\n\ + -t type Specify socket type (stream, dgram) for tests\n\ + -z value Do not send data in a message (bit 0x1), do not send\n\ + data array associated with a cmsghdr structure (bit 0x2)\n\ + testno Run one test by its number (require the -t option)\n\n"); + printf(" Available tests for stream sockets:\n"); + for (i = 0; i < TEST_STREAM_TBL_SIZE; ++i) + printf(" %u: %s\n", i, test_stream_tbl[i].desc); + printf("\n Available tests for datagram sockets:\n"); + for (i = 0; i < TEST_DGRAM_TBL_SIZE; ++i) + printf(" %u: %s\n", i, test_dgram_tbl[i].desc); +} + +static void +output(const char *format, ...) +{ + char buf[LOGMSG_SIZE]; + va_list ap; + + va_start(ap, format); + if (vsnprintf(buf, sizeof(buf), format, ap) < 0) + err(EXIT_FAILURE, "output: vsnprintf failed"); + write(STDOUT_FILENO, buf, strlen(buf)); + va_end(ap); +} + +static void +logmsg(const char *format, ...) +{ + char buf[LOGMSG_SIZE]; + va_list ap; + int errno_save; + + errno_save = errno; + va_start(ap, format); + if (vsnprintf(buf, sizeof(buf), format, ap) < 0) + err(EXIT_FAILURE, "logmsg: vsnprintf failed"); + if (errno_save == 0) + output("%s: %s\n", proc_name, buf); + else + output("%s: %s: %s\n", proc_name, buf, strerror(errno_save)); + va_end(ap); + errno = errno_save; +} + +static void +vlogmsgx(const char *format, va_list ap) +{ + char buf[LOGMSG_SIZE]; + + if (vsnprintf(buf, sizeof(buf), format, ap) < 0) + err(EXIT_FAILURE, "logmsgx: vsnprintf failed"); + output("%s: %s\n", proc_name, buf); + +} + +static void +logmsgx(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vlogmsgx(format, ap); + va_end(ap); +} + +static void +dbgmsg(const char *format, ...) +{ + va_list ap; + + if (debug) { + va_start(ap, format); + vlogmsgx(format, ap); + va_end(ap); + } +} + +static int +run_tests(int type, u_int testno1) +{ + const struct test_func *tf; + u_int i, testno2, failed_num; + + sock_type = type; + if (type == SOCK_STREAM) { + sock_type_str = "SOCK_STREAM"; + tf = test_stream_tbl; + i = TEST_STREAM_TBL_SIZE - 1; + } else { + sock_type_str = "SOCK_DGRAM"; + tf = test_dgram_tbl; + i = TEST_DGRAM_TBL_SIZE - 1; + } + if (testno1 == 0) { + testno1 = 1; + testno2 = i; + } else + testno2 = testno1; + + output("Running tests for %s sockets:\n", sock_type_str); + failed_num = 0; + for (i = testno1, tf += testno1; i <= testno2; ++tf, ++i) { + output(" %u: %s\n", i, tf->desc); + switch (tf->func()) { + case -1: + ++failed_num; + break; + case -2: + logmsgx("some system error or timeout occurred"); + return (-1); + } + } + + if (failed_num != 0) + failed_flag = true; + + if (testno1 != testno2) { + if (failed_num == 0) + output("-- all tests passed!\n"); + else + output("-- %u test%s failed!\n", + failed_num, failed_num == 1 ? "" : "s"); + } else { + if (failed_num == 0) + output("-- test passed!\n"); + else + output("-- test failed!\n"); + } + + return (0); +} + +static int +init(void) +{ + struct sigaction sigact; + size_t idx; + int rv; + + proc_name = "SERVER"; + + sigact.sa_handler = SIG_IGN; + sigact.sa_flags = 0; + sigemptyset(&sigact.sa_mask); + if (sigaction(SIGPIPE, &sigact, (struct sigaction *)NULL) < 0) { + logmsg("init: sigaction"); + return (-1); + } + + if (ipc_msg.buf_size == 0) + ipc_msg.buf_send = ipc_msg.buf_recv = NULL; + else { + ipc_msg.buf_send = malloc(ipc_msg.buf_size); + ipc_msg.buf_recv = malloc(ipc_msg.buf_size); + if (ipc_msg.buf_send == NULL || ipc_msg.buf_recv == NULL) { + logmsg("init: malloc"); + return (-1); + } + for (idx = 0; idx < ipc_msg.buf_size; ++idx) + ipc_msg.buf_send[idx] = (char)idx; + } + + proc_cred.uid = getuid(); + proc_cred.euid = geteuid(); + proc_cred.gid = getgid(); + proc_cred.egid = getegid(); + proc_cred.gid_num = getgroups(0, (gid_t *)NULL); + if (proc_cred.gid_num < 0) { + logmsg("init: getgroups"); + return (-1); + } + proc_cred.gid_arr = malloc(proc_cred.gid_num * + sizeof(*proc_cred.gid_arr)); + if (proc_cred.gid_arr == NULL) { + logmsg("init: malloc"); + return (-1); + } + if (getgroups(proc_cred.gid_num, proc_cred.gid_arr) < 0) { + logmsg("init: getgroups"); + return (-1); + } + + memset(&serv_addr_sun, 0, sizeof(serv_addr_sun)); + rv = snprintf(serv_addr_sun.sun_path, sizeof(serv_addr_sun.sun_path), + "%s/%s", work_dir, proc_name); + if (rv < 0) { + logmsg("init: snprintf"); + return (-1); + } + if ((size_t)rv >= sizeof(serv_addr_sun.sun_path)) { + logmsgx("init: not enough space for socket pathname"); + return (-1); + } + serv_addr_sun.sun_family = PF_LOCAL; + serv_addr_sun.sun_len = SUN_LEN(&serv_addr_sun); + + return (0); +} + +static int +client_fork(void) +{ + int fd1, fd2; + + if (pipe(sync_fd[SYNC_SERVER]) < 0 || + pipe(sync_fd[SYNC_CLIENT]) < 0) { + logmsg("client_fork: pipe"); + return (-1); + } + client_pid = fork(); + if (client_pid == (pid_t)-1) { + logmsg("client_fork: fork"); + return (-1); + } + if (client_pid == 0) { + proc_name = "CLIENT"; + server_flag = false; + fd1 = sync_fd[SYNC_SERVER][SYNC_RECV]; + fd2 = sync_fd[SYNC_CLIENT][SYNC_SEND]; + } else { + fd1 = sync_fd[SYNC_SERVER][SYNC_SEND]; + fd2 = sync_fd[SYNC_CLIENT][SYNC_RECV]; + } + if (close(fd1) < 0 || close(fd2) < 0) { + logmsg("client_fork: close"); + return (-1); + } + return (client_pid != 0); +} + +static void +client_exit(int rv) +{ + if (close(sync_fd[SYNC_SERVER][SYNC_SEND]) < 0 || + close(sync_fd[SYNC_CLIENT][SYNC_RECV]) < 0) { + logmsg("client_exit: close"); + rv = -1; + } + rv = rv == 0 ? EXIT_SUCCESS : -rv; + dbgmsg("exit: code %d", rv); + _exit(rv); +} + +static int +client_wait(void) +{ + int status; + pid_t pid; + + dbgmsg("waiting for client"); + + if (close(sync_fd[SYNC_SERVER][SYNC_RECV]) < 0 || + close(sync_fd[SYNC_CLIENT][SYNC_SEND]) < 0) { + logmsg("client_wait: close"); + return (-1); + } + + pid = waitpid(client_pid, &status, 0); + if (pid == (pid_t)-1) { + logmsg("client_wait: waitpid"); + return (-1); + } + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != EXIT_SUCCESS) { + logmsgx("client exit status is %d", + WEXITSTATUS(status)); + return (-WEXITSTATUS(status)); + } + } else { + if (WIFSIGNALED(status)) + logmsgx("abnormal termination of client, signal %d%s", + WTERMSIG(status), WCOREDUMP(status) ? + " (core file generated)" : ""); + else + logmsgx("termination of client, unknown status"); + return (-1); + } + + return (0); +} + +int +main(int argc, char *argv[]) +{ + const char *errstr; + u_int testno, zvalue; + int opt, rv; + bool dgram_flag, stream_flag; + + ipc_msg.buf_size = IPC_MSG_SIZE_DEF; + ipc_msg.msg_num = IPC_MSG_NUM_DEF; + dgram_flag = stream_flag = false; + while ((opt = getopt(argc, argv, "dhn:s:t:z:")) != -1) + switch (opt) { + case 'd': + debug = true; + break; + case 'h': + usage(true); + return (EXIT_SUCCESS); + case 'n': + ipc_msg.msg_num = strtonum(optarg, 1, + IPC_MSG_NUM_MAX, &errstr); + if (errstr != NULL) + errx(EXIT_FAILURE, "option -n: number is %s", + errstr); + break; + case 's': + ipc_msg.buf_size = strtonum(optarg, 0, + IPC_MSG_SIZE_MAX, &errstr); + if (errstr != NULL) + errx(EXIT_FAILURE, "option -s: number is %s", + errstr); + break; + case 't': + if (strcmp(optarg, "stream") == 0) + stream_flag = true; + else if (strcmp(optarg, "dgram") == 0) + dgram_flag = true; + else + errx(EXIT_FAILURE, "option -t: " + "wrong socket type"); + break; + case 'z': + zvalue = strtonum(optarg, 0, 3, &errstr); + if (errstr != NULL) + errx(EXIT_FAILURE, "option -z: number is %s", + errstr); + if (zvalue & 0x1) + send_data_flag = false; + if (zvalue & 0x2) + send_array_flag = false; + break; + default: + usage(false); + return (EXIT_FAILURE); + } + + if (optind < argc) { + if (optind + 1 != argc) + errx(EXIT_FAILURE, "too many arguments"); + testno = strtonum(argv[optind], 0, UINT_MAX, &errstr); + if (errstr != NULL) + errx(EXIT_FAILURE, "test number is %s", errstr); + if (stream_flag && testno >= TEST_STREAM_TBL_SIZE) + errx(EXIT_FAILURE, "given test %u for stream " + "sockets does not exist", testno); + if (dgram_flag && testno >= TEST_DGRAM_TBL_SIZE) + errx(EXIT_FAILURE, "given test %u for datagram " + "sockets does not exist", testno); + } else + testno = 0; + + if (!dgram_flag && !stream_flag) { + if (testno != 0) + errx(EXIT_FAILURE, "particular test number " + "can be used with the -t option only"); + dgram_flag = stream_flag = true; + } + + if (mkdtemp(work_dir) == NULL) + err(EXIT_FAILURE, "mkdtemp(%s)", work_dir); + + rv = EXIT_FAILURE; + if (init() < 0) + goto done; + + if (stream_flag) + if (run_tests(SOCK_STREAM, testno) < 0) + goto done; + if (dgram_flag) + if (run_tests(SOCK_DGRAM, testno) < 0) + goto done; + + rv = EXIT_SUCCESS; +done: + if (rmdir(work_dir) < 0) { + logmsg("rmdir(%s)", work_dir); + rv = EXIT_FAILURE; + } + return (failed_flag ? EXIT_FAILURE : rv); +} + +static int +socket_close(int fd) +{ + int rv; + + rv = 0; + if (close(fd) < 0) { + logmsg("socket_close: close"); + rv = -1; + } + if (server_flag && fd == serv_sock_fd) + if (unlink(serv_addr_sun.sun_path) < 0) { + logmsg("socket_close: unlink(%s)", + serv_addr_sun.sun_path); + rv = -1; + } + return (rv); +} + +static int +socket_create(void) +{ + struct timeval tv; + int fd; + + fd = socket(PF_LOCAL, sock_type, 0); + if (fd < 0) { + logmsg("socket_create: socket(PF_LOCAL, %s, 0)", sock_type_str); + return (-1); + } + if (server_flag) + serv_sock_fd = fd; + + tv.tv_sec = TIMEOUT; + tv.tv_usec = 0; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 || + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) { + logmsg("socket_create: setsockopt(SO_RCVTIMEO/SO_SNDTIMEO)"); + goto failed; + } + + if (server_flag) { + if (bind(fd, (struct sockaddr *)&serv_addr_sun, + serv_addr_sun.sun_len) < 0) { + logmsg("socket_create: bind(%s)", + serv_addr_sun.sun_path); + goto failed; + } + if (sock_type == SOCK_STREAM) { + int val; + + if (listen(fd, LISTENQ) < 0) { + logmsg("socket_create: listen"); + goto failed; + } + val = fcntl(fd, F_GETFL, 0); + if (val < 0) { + logmsg("socket_create: fcntl(F_GETFL)"); + goto failed; + } + if (fcntl(fd, F_SETFL, val | O_NONBLOCK) < 0) { + logmsg("socket_create: fcntl(F_SETFL)"); + goto failed; + } + } + } + + return (fd); + +failed: + if (close(fd) < 0) + logmsg("socket_create: close"); + if (server_flag) + if (unlink(serv_addr_sun.sun_path) < 0) + logmsg("socket_close: unlink(%s)", + serv_addr_sun.sun_path); + return (-1); +} + +static int +socket_connect(int fd) +{ + dbgmsg("connect"); + + if (connect(fd, (struct sockaddr *)&serv_addr_sun, + serv_addr_sun.sun_len) < 0) { + logmsg("socket_connect: connect(%s)", serv_addr_sun.sun_path); + return (-1); + } + return (0); +} + +static int +sync_recv(void) +{ + ssize_t ssize; + int fd; + char buf; + + dbgmsg("sync: wait"); + + fd = sync_fd[server_flag ? SYNC_SERVER : SYNC_CLIENT][SYNC_RECV]; + + ssize = read(fd, &buf, 1); + if (ssize < 0) { + logmsg("sync_recv: read"); + return (-1); + } + if (ssize < 1) { + logmsgx("sync_recv: read %zd of 1 byte", ssize); + return (-1); + } + + dbgmsg("sync: received"); + + return (0); +} + +static int +sync_send(void) +{ + ssize_t ssize; + int fd; + + dbgmsg("sync: send"); + + fd = sync_fd[server_flag ? SYNC_CLIENT : SYNC_SERVER][SYNC_SEND]; + + ssize = write(fd, "", 1); + if (ssize < 0) { + logmsg("sync_send: write"); + return (-1); + } + if (ssize < 1) { + logmsgx("sync_send: sent %zd of 1 byte", ssize); + return (-1); + } + + return (0); +} + +static int +message_send(int fd, const struct msghdr *msghdr) +{ + const struct cmsghdr *cmsghdr; + size_t size; + ssize_t ssize; + + size = msghdr->msg_iov != 0 ? msghdr->msg_iov->iov_len : 0; + dbgmsg("send: data size %zu", size); + dbgmsg("send: msghdr.msg_controllen %u", + (u_int)msghdr->msg_controllen); + cmsghdr = CMSG_FIRSTHDR(msghdr); + if (cmsghdr != NULL) + dbgmsg("send: cmsghdr.cmsg_len %u", + (u_int)cmsghdr->cmsg_len); + + ssize = sendmsg(fd, msghdr, 0); + if (ssize < 0) { + logmsg("message_send: sendmsg"); + return (-1); + } + if ((size_t)ssize != size) { + logmsgx("message_send: sendmsg: sent %zd of %zu bytes", + ssize, size); + return (-1); + } + + if (!send_data_flag) + if (sync_send() < 0) + return (-1); + + return (0); +} + +static int +message_sendn(int fd, struct msghdr *msghdr) +{ + u_int i; + + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); + if (message_send(fd, msghdr) < 0) + return (-1); + } + return (0); +} + +static int +message_recv(int fd, struct msghdr *msghdr) +{ + const struct cmsghdr *cmsghdr; + size_t size; + ssize_t ssize; + + if (!send_data_flag) + if (sync_recv() < 0) + return (-1); + + size = msghdr->msg_iov != NULL ? msghdr->msg_iov->iov_len : 0; + ssize = recvmsg(fd, msghdr, MSG_WAITALL); + if (ssize < 0) { + logmsg("message_recv: recvmsg"); + return (-1); + } + if ((size_t)ssize != size) { + logmsgx("message_recv: recvmsg: received %zd of %zu bytes", + ssize, size); + return (-1); + } + + dbgmsg("recv: data size %zd", ssize); + dbgmsg("recv: msghdr.msg_controllen %u", + (u_int)msghdr->msg_controllen); + cmsghdr = CMSG_FIRSTHDR(msghdr); + if (cmsghdr != NULL) + dbgmsg("recv: cmsghdr.cmsg_len %u", + (u_int)cmsghdr->cmsg_len); + + if (memcmp(ipc_msg.buf_recv, ipc_msg.buf_send, size) != 0) { + logmsgx("message_recv: received message has wrong content"); + return (-1); + } + + return (0); +} + +static int +socket_accept(int listenfd) +{ + fd_set rset; + struct timeval tv; + int fd, rv, val; + + dbgmsg("accept"); + + FD_ZERO(&rset); + FD_SET(listenfd, &rset); + tv.tv_sec = TIMEOUT; + tv.tv_usec = 0; + rv = select(listenfd + 1, &rset, (fd_set *)NULL, (fd_set *)NULL, &tv); + if (rv < 0) { + logmsg("socket_accept: select"); + return (-1); + } + if (rv == 0) { + logmsgx("socket_accept: select timeout"); + return (-1); + } + + fd = accept(listenfd, (struct sockaddr *)NULL, (socklen_t *)NULL); + if (fd < 0) { + logmsg("socket_accept: accept"); + return (-1); + } + + val = fcntl(fd, F_GETFL, 0); + if (val < 0) { + logmsg("socket_accept: fcntl(F_GETFL)"); + goto failed; + } + if (fcntl(fd, F_SETFL, val & ~O_NONBLOCK) < 0) { + logmsg("socket_accept: fcntl(F_SETFL)"); + goto failed; + } + + return (fd); + +failed: + if (close(fd) < 0) + logmsg("socket_accept: close"); + return (-1); +} + +static int +check_msghdr(const struct msghdr *msghdr, size_t size) +{ + if (msghdr->msg_flags & MSG_TRUNC) { + logmsgx("msghdr.msg_flags has MSG_TRUNC"); + return (-1); + } + if (msghdr->msg_flags & MSG_CTRUNC) { + logmsgx("msghdr.msg_flags has MSG_CTRUNC"); + return (-1); + } + if (msghdr->msg_controllen < size) { + logmsgx("msghdr.msg_controllen %u < %zu", + (u_int)msghdr->msg_controllen, size); + return (-1); + } + if (msghdr->msg_controllen > 0 && size == 0) { + logmsgx("msghdr.msg_controllen %u > 0", + (u_int)msghdr->msg_controllen); + return (-1); + } + return (0); +} + +static int +check_cmsghdr(const struct cmsghdr *cmsghdr, int type, size_t size) +{ + if (cmsghdr == NULL) { + logmsgx("cmsghdr is NULL"); + return (-1); + } + if (cmsghdr->cmsg_level != SOL_SOCKET) { + logmsgx("cmsghdr.cmsg_level %d != SOL_SOCKET", + cmsghdr->cmsg_level); + return (-1); + } + if (cmsghdr->cmsg_type != type) { + logmsgx("cmsghdr.cmsg_type %d != %d", + cmsghdr->cmsg_type, type); + return (-1); + } + if (cmsghdr->cmsg_len != CMSG_LEN(size)) { + logmsgx("cmsghdr.cmsg_len %u != %zu", + (u_int)cmsghdr->cmsg_len, CMSG_LEN(size)); + return (-1); + } + return (0); +} + +static int +check_groups(const char *gid_arr_str, const gid_t *gid_arr, + const char *gid_num_str, int gid_num, bool all_gids) +{ + int i; + + for (i = 0; i < gid_num; ++i) + dbgmsg("%s[%d] %lu", gid_arr_str, i, (u_long)gid_arr[i]); + + if (all_gids) { + if (gid_num != proc_cred.gid_num) { + logmsgx("%s %d != %d", gid_num_str, gid_num, + proc_cred.gid_num); + return (-1); + } + } else { + if (gid_num > proc_cred.gid_num) { + logmsgx("%s %d > %d", gid_num_str, gid_num, + proc_cred.gid_num); + return (-1); + } + } + if (memcmp(gid_arr, proc_cred.gid_arr, + gid_num * sizeof(*gid_arr)) != 0) { + logmsgx("%s content is wrong", gid_arr_str); + for (i = 0; i < gid_num; ++i) + if (gid_arr[i] != proc_cred.gid_arr[i]) { + logmsgx("%s[%d] %lu != %lu", + gid_arr_str, i, (u_long)gid_arr[i], + (u_long)proc_cred.gid_arr[i]); + break; + } + return (-1); + } + return (0); +} + +static int +check_xucred(const struct xucred *xucred, socklen_t len) +{ + if (len != sizeof(*xucred)) { + logmsgx("option value size %zu != %zu", + (size_t)len, sizeof(*xucred)); + return (-1); + } + + dbgmsg("xucred.cr_version %u", xucred->cr_version); + dbgmsg("xucred.cr_uid %lu", (u_long)xucred->cr_uid); + dbgmsg("xucred.cr_ngroups %d", xucred->cr_ngroups); + + if (xucred->cr_version != XUCRED_VERSION) { + logmsgx("xucred.cr_version %u != %d", + xucred->cr_version, XUCRED_VERSION); + return (-1); + } + if (xucred->cr_uid != proc_cred.euid) { + logmsgx("xucred.cr_uid %lu != %lu (EUID)", + (u_long)xucred->cr_uid, (u_long)proc_cred.euid); + return (-1); + } + if (xucred->cr_ngroups == 0) { + logmsgx("xucred.cr_ngroups == 0"); + return (-1); + } + if (xucred->cr_ngroups < 0) { + logmsgx("xucred.cr_ngroups < 0"); + return (-1); + } + if (xucred->cr_ngroups > XU_NGROUPS) { + logmsgx("xucred.cr_ngroups %hu > %u (max)", + xucred->cr_ngroups, XU_NGROUPS); + return (-1); + } + if (xucred->cr_groups[0] != proc_cred.egid) { + logmsgx("xucred.cr_groups[0] %lu != %lu (EGID)", + (u_long)xucred->cr_groups[0], (u_long)proc_cred.egid); + return (-1); + } + if (check_groups("xucred.cr_groups", xucred->cr_groups, + "xucred.cr_ngroups", xucred->cr_ngroups, false) < 0) + return (-1); + return (0); +} + +static int +check_scm_creds_cmsgcred(struct cmsghdr *cmsghdr) +{ + const struct cmsgcred *cmsgcred; + + if (check_cmsghdr(cmsghdr, SCM_CREDS, sizeof(*cmsgcred)) < 0) + return (-1); + + cmsgcred = (struct cmsgcred *)CMSG_DATA(cmsghdr); + + dbgmsg("cmsgcred.cmcred_pid %ld", (long)cmsgcred->cmcred_pid); + dbgmsg("cmsgcred.cmcred_uid %lu", (u_long)cmsgcred->cmcred_uid); + dbgmsg("cmsgcred.cmcred_euid %lu", (u_long)cmsgcred->cmcred_euid); + dbgmsg("cmsgcred.cmcred_gid %lu", (u_long)cmsgcred->cmcred_gid); + dbgmsg("cmsgcred.cmcred_ngroups %d", cmsgcred->cmcred_ngroups); + + if (cmsgcred->cmcred_pid != client_pid) { + logmsgx("cmsgcred.cmcred_pid %ld != %ld", + (long)cmsgcred->cmcred_pid, (long)client_pid); + return (-1); + } + if (cmsgcred->cmcred_uid != proc_cred.uid) { + logmsgx("cmsgcred.cmcred_uid %lu != %lu", + (u_long)cmsgcred->cmcred_uid, (u_long)proc_cred.uid); + return (-1); + } + if (cmsgcred->cmcred_euid != proc_cred.euid) { + logmsgx("cmsgcred.cmcred_euid %lu != %lu", + (u_long)cmsgcred->cmcred_euid, (u_long)proc_cred.euid); + return (-1); + } + if (cmsgcred->cmcred_gid != proc_cred.gid) { + logmsgx("cmsgcred.cmcred_gid %lu != %lu", + (u_long)cmsgcred->cmcred_gid, (u_long)proc_cred.gid); + return (-1); + } + if (cmsgcred->cmcred_ngroups == 0) { + logmsgx("cmsgcred.cmcred_ngroups == 0"); + return (-1); + } + if (cmsgcred->cmcred_ngroups < 0) { + logmsgx("cmsgcred.cmcred_ngroups %d < 0", + cmsgcred->cmcred_ngroups); + return (-1); + } + if (cmsgcred->cmcred_ngroups > CMGROUP_MAX) { + logmsgx("cmsgcred.cmcred_ngroups %d > %d", + cmsgcred->cmcred_ngroups, CMGROUP_MAX); + return (-1); + } + if (cmsgcred->cmcred_groups[0] != proc_cred.egid) { + logmsgx("cmsgcred.cmcred_groups[0] %lu != %lu (EGID)", + (u_long)cmsgcred->cmcred_groups[0], (u_long)proc_cred.egid); + return (-1); + } + if (check_groups("cmsgcred.cmcred_groups", cmsgcred->cmcred_groups, + "cmsgcred.cmcred_ngroups", cmsgcred->cmcred_ngroups, false) < 0) + return (-1); + return (0); +} + +static int +check_scm_creds_sockcred(struct cmsghdr *cmsghdr) +{ + const struct sockcred *sockcred; + + if (check_cmsghdr(cmsghdr, SCM_CREDS, + SOCKCREDSIZE(proc_cred.gid_num)) < 0) + return (-1); + + sockcred = (struct sockcred *)CMSG_DATA(cmsghdr); + + dbgmsg("sockcred.sc_uid %lu", (u_long)sockcred->sc_uid); + dbgmsg("sockcred.sc_euid %lu", (u_long)sockcred->sc_euid); + dbgmsg("sockcred.sc_gid %lu", (u_long)sockcred->sc_gid); + dbgmsg("sockcred.sc_egid %lu", (u_long)sockcred->sc_egid); + dbgmsg("sockcred.sc_ngroups %d", sockcred->sc_ngroups); + + if (sockcred->sc_uid != proc_cred.uid) { + logmsgx("sockcred.sc_uid %lu != %lu", + (u_long)sockcred->sc_uid, (u_long)proc_cred.uid); + return (-1); + } + if (sockcred->sc_euid != proc_cred.euid) { + logmsgx("sockcred.sc_euid %lu != %lu", + (u_long)sockcred->sc_euid, (u_long)proc_cred.euid); + return (-1); + } + if (sockcred->sc_gid != proc_cred.gid) { + logmsgx("sockcred.sc_gid %lu != %lu", + (u_long)sockcred->sc_gid, (u_long)proc_cred.gid); + return (-1); + } + if (sockcred->sc_egid != proc_cred.egid) { + logmsgx("sockcred.sc_egid %lu != %lu", + (u_long)sockcred->sc_egid, (u_long)proc_cred.egid); + return (-1); + } + if (sockcred->sc_ngroups == 0) { + logmsgx("sockcred.sc_ngroups == 0"); + return (-1); + } + if (sockcred->sc_ngroups < 0) { + logmsgx("sockcred.sc_ngroups %d < 0", + sockcred->sc_ngroups); + return (-1); + } + if (sockcred->sc_ngroups != proc_cred.gid_num) { + logmsgx("sockcred.sc_ngroups %d != %u", + sockcred->sc_ngroups, proc_cred.gid_num); + return (-1); + } + if (check_groups("sockcred.sc_groups", sockcred->sc_groups, + "sockcred.sc_ngroups", sockcred->sc_ngroups, true) < 0) + return (-1); + return (0); +} + +static int +check_scm_timestamp(struct cmsghdr *cmsghdr) +{ + const struct timeval *timeval; + + if (check_cmsghdr(cmsghdr, SCM_TIMESTAMP, sizeof(struct timeval)) < 0) + return (-1); + + timeval = (struct timeval *)CMSG_DATA(cmsghdr); + + dbgmsg("timeval.tv_sec %"PRIdMAX", timeval.tv_usec %"PRIdMAX, + (intmax_t)timeval->tv_sec, (intmax_t)timeval->tv_usec); + + return (0); +} + +static int +check_scm_bintime(struct cmsghdr *cmsghdr) +{ + const struct bintime *bintime; + + if (check_cmsghdr(cmsghdr, SCM_BINTIME, sizeof(struct bintime)) < 0) + return (-1); + + bintime = (struct bintime *)CMSG_DATA(cmsghdr); + + dbgmsg("bintime.sec %"PRIdMAX", bintime.frac %"PRIu64, + (intmax_t)bintime->sec, bintime->frac); + + return (0); +} + +static void +msghdr_init_generic(struct msghdr *msghdr, struct iovec *iov, void *cmsg_data) +{ + msghdr->msg_name = NULL; + msghdr->msg_namelen = 0; + if (send_data_flag) { + iov->iov_base = server_flag ? + ipc_msg.buf_recv : ipc_msg.buf_send; + iov->iov_len = ipc_msg.buf_size; + msghdr->msg_iov = iov; + msghdr->msg_iovlen = 1; + } else { + msghdr->msg_iov = NULL; + msghdr->msg_iovlen = 0; + } + msghdr->msg_control = cmsg_data; + msghdr->msg_flags = 0; +} + +static void +msghdr_init_server(struct msghdr *msghdr, struct iovec *iov, + void *cmsg_data, size_t cmsg_size) +{ + msghdr_init_generic(msghdr, iov, cmsg_data); + msghdr->msg_controllen = cmsg_size; + dbgmsg("init: data size %zu", msghdr->msg_iov != NULL ? + msghdr->msg_iov->iov_len : (size_t)0); + dbgmsg("init: msghdr.msg_controllen %u", + (u_int)msghdr->msg_controllen); +} + +static void +msghdr_init_client(struct msghdr *msghdr, struct iovec *iov, + void *cmsg_data, size_t cmsg_size, int type, size_t arr_size) +{ + struct cmsghdr *cmsghdr; + + msghdr_init_generic(msghdr, iov, cmsg_data); + if (cmsg_data != NULL) { + msghdr->msg_controllen = send_array_flag ? + cmsg_size : CMSG_SPACE(0); + cmsghdr = CMSG_FIRSTHDR(msghdr); + cmsghdr->cmsg_level = SOL_SOCKET; + cmsghdr->cmsg_type = type; + cmsghdr->cmsg_len = CMSG_LEN(send_array_flag ? arr_size : 0); + } else + msghdr->msg_controllen = 0; +} + +static int +t_generic(int (*client_func)(int), int (*server_func)(int)) +{ + int fd, rv, rv_client; + + switch (client_fork()) { + case 0: + fd = socket_create(); + if (fd < 0) + rv = -2; + else { + rv = client_func(fd); + if (socket_close(fd) < 0) + rv = -2; + } + client_exit(rv); + break; + case 1: + fd = socket_create(); + if (fd < 0) + rv = -2; + else { + rv = server_func(fd); + rv_client = client_wait(); + if (rv == 0 || (rv == -2 && rv_client != 0)) + rv = rv_client; + if (socket_close(fd) < 0) + rv = -2; + } + break; + default: + rv = -2; + } + return (rv); +} + +static int +t_cmsgcred_client(int fd) +{ + struct msghdr msghdr; + struct iovec iov[1]; + void *cmsg_data; + size_t cmsg_size; + int rv; + + if (sync_recv() < 0) + return (-2); + + rv = -2; + + cmsg_size = CMSG_SPACE(sizeof(struct cmsgcred)); + cmsg_data = malloc(cmsg_size); + if (cmsg_data == NULL) { + logmsg("malloc"); + goto done; + } + msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size, + SCM_CREDS, sizeof(struct cmsgcred)); + + if (socket_connect(fd) < 0) + goto done; + + if (message_sendn(fd, &msghdr) < 0) + goto done; + + rv = 0; +done: + free(cmsg_data); + return (rv); +} + +static int +t_cmsgcred_server(int fd1) +{ + struct msghdr msghdr; + struct iovec iov[1]; + struct cmsghdr *cmsghdr; + void *cmsg_data; + size_t cmsg_size; + u_int i; + int fd2, rv; + + if (sync_send() < 0) + return (-2); + + fd2 = -1; + rv = -2; + + cmsg_size = CMSG_SPACE(sizeof(struct cmsgcred)); + cmsg_data = malloc(cmsg_size); + if (cmsg_data == NULL) { + logmsg("malloc"); + goto done; + } + + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; + + rv = -1; + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); + + msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); + if (message_recv(fd2, &msghdr) < 0) { + rv = -2; + break; + } + + if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) + break; + + cmsghdr = CMSG_FIRSTHDR(&msghdr); + if (check_scm_creds_cmsgcred(cmsghdr) < 0) + break; + } + if (i > ipc_msg.msg_num) + rv = 0; +done: + free(cmsg_data); + if (sock_type == SOCK_STREAM && fd2 >= 0) + if (socket_close(fd2) < 0) + rv = -2; + return (rv); +} + +static int +t_cmsgcred(void) +{ + return (t_generic(t_cmsgcred_client, t_cmsgcred_server)); +} + +static int +t_sockcred_client(int type, int fd) +{ + struct msghdr msghdr; + struct iovec iov[1]; + int rv; + + if (sync_recv() < 0) + return (-2); + + rv = -2; + + msghdr_init_client(&msghdr, iov, NULL, 0, 0, 0); + + if (socket_connect(fd) < 0) + goto done; + + if (type == 2) + if (sync_recv() < 0) + goto done; + + if (message_sendn(fd, &msghdr) < 0) + goto done; + + rv = 0; +done: + return (rv); +} + +static int +t_sockcred_server(int type, int fd1) +{ + struct msghdr msghdr; + struct iovec iov[1]; + struct cmsghdr *cmsghdr; + void *cmsg_data; + size_t cmsg_size; + u_int i; + int fd2, rv, val; + + fd2 = -1; + rv = -2; + + cmsg_size = CMSG_SPACE(SOCKCREDSIZE(proc_cred.gid_num)); + cmsg_data = malloc(cmsg_size); + if (cmsg_data == NULL) { + logmsg("malloc"); + goto done; + } + + if (type == 1) { + dbgmsg("setting LOCAL_CREDS"); + val = 1; + if (setsockopt(fd1, 0, LOCAL_CREDS, &val, sizeof(val)) < 0) { + logmsg("setsockopt(LOCAL_CREDS)"); + goto done; + } + } + + if (sync_send() < 0) + goto done; + + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; + + if (type == 2) { + dbgmsg("setting LOCAL_CREDS"); + val = 1; + if (setsockopt(fd2, 0, LOCAL_CREDS, &val, sizeof(val)) < 0) { + logmsg("setsockopt(LOCAL_CREDS)"); + goto done; + } + if (sync_send() < 0) + goto done; + } + + rv = -1; + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); + + msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); + if (message_recv(fd2, &msghdr) < 0) { + rv = -2; + break; + } + + if (i > 1 && sock_type == SOCK_STREAM) { + if (check_msghdr(&msghdr, 0) < 0) + break; + } else { + if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) + break; + + cmsghdr = CMSG_FIRSTHDR(&msghdr); + if (check_scm_creds_sockcred(cmsghdr) < 0) + break; + } + } + if (i > ipc_msg.msg_num) + rv = 0; +done: + free(cmsg_data); + if (sock_type == SOCK_STREAM && fd2 >= 0) + if (socket_close(fd2) < 0) + rv = -2; + return (rv); +} + +static int +t_sockcred_1(void) +{ + u_int i; + int fd, rv, rv_client; + + switch (client_fork()) { + case 0: + for (i = 1; i <= 2; ++i) { + dbgmsg("client #%u", i); + fd = socket_create(); + if (fd < 0) + rv = -2; + else { + rv = t_sockcred_client(1, fd); + if (socket_close(fd) < 0) + rv = -2; + } + if (rv != 0) + break; + } + client_exit(rv); + break; + case 1: + fd = socket_create(); + if (fd < 0) + rv = -2; + else { + rv = t_sockcred_server(1, fd); + if (rv == 0) + rv = t_sockcred_server(3, fd); + rv_client = client_wait(); + if (rv == 0 || (rv == -2 && rv_client != 0)) + rv = rv_client; + if (socket_close(fd) < 0) + rv = -2; + } + break; + default: + rv = -2; + } + + return (rv); +} + +static int +t_sockcred_2_client(int fd) +{ + return (t_sockcred_client(2, fd)); +} + +static int +t_sockcred_2_server(int fd) +{ + return (t_sockcred_server(2, fd)); +} + +static int +t_sockcred_2(void) +{ + return (t_generic(t_sockcred_2_client, t_sockcred_2_server)); +} + +static int +t_cmsgcred_sockcred_server(int fd1) +{ + struct msghdr msghdr; + struct iovec iov[1]; + struct cmsghdr *cmsghdr; + void *cmsg_data, *cmsg1_data, *cmsg2_data; + size_t cmsg_size, cmsg1_size, cmsg2_size; + u_int i; + int fd2, rv, val; + + fd2 = -1; + rv = -2; + + cmsg1_size = CMSG_SPACE(SOCKCREDSIZE(proc_cred.gid_num)); + cmsg2_size = CMSG_SPACE(sizeof(struct cmsgcred)); + cmsg1_data = malloc(cmsg1_size); + cmsg2_data = malloc(cmsg2_size); + if (cmsg1_data == NULL || cmsg2_data == NULL) { + logmsg("malloc"); + goto done; + } + + dbgmsg("setting LOCAL_CREDS"); + val = 1; + if (setsockopt(fd1, 0, LOCAL_CREDS, &val, sizeof(val)) < 0) { + logmsg("setsockopt(LOCAL_CREDS)"); + goto done; + } + + if (sync_send() < 0) + goto done; + + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; + + cmsg_data = cmsg1_data; + cmsg_size = cmsg1_size; + rv = -1; + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); + + msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); + if (message_recv(fd2, &msghdr) < 0) { + rv = -2; + break; + } + + if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) + break; + + cmsghdr = CMSG_FIRSTHDR(&msghdr); + if (i == 1 || sock_type == SOCK_DGRAM) { + if (check_scm_creds_sockcred(cmsghdr) < 0) + break; + } else { + if (check_scm_creds_cmsgcred(cmsghdr) < 0) + break; + } + + cmsg_data = cmsg2_data; + cmsg_size = cmsg2_size; + } + if (i > ipc_msg.msg_num) + rv = 0; +done: + free(cmsg1_data); + free(cmsg2_data); + if (sock_type == SOCK_STREAM && fd2 >= 0) + if (socket_close(fd2) < 0) + rv = -2; + return (rv); +} + +static int +t_cmsgcred_sockcred(void) +{ + return (t_generic(t_cmsgcred_client, t_cmsgcred_sockcred_server)); +} + +static int +t_timeval_client(int fd) +{ + struct msghdr msghdr; + struct iovec iov[1]; + void *cmsg_data; + size_t cmsg_size; + int rv; + + if (sync_recv() < 0) + return (-2); + + rv = -2; + + cmsg_size = CMSG_SPACE(sizeof(struct timeval)); + cmsg_data = malloc(cmsg_size); + if (cmsg_data == NULL) { + logmsg("malloc"); + goto done; + } + msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size, + SCM_TIMESTAMP, sizeof(struct timeval)); + + if (socket_connect(fd) < 0) + goto done; + + if (message_sendn(fd, &msghdr) < 0) + goto done; + + rv = 0; +done: + free(cmsg_data); + return (rv); +} + +static int +t_timeval_server(int fd1) +{ + struct msghdr msghdr; + struct iovec iov[1]; + struct cmsghdr *cmsghdr; + void *cmsg_data; + size_t cmsg_size; + u_int i; + int fd2, rv; + + if (sync_send() < 0) + return (-2); + + fd2 = -1; + rv = -2; + + cmsg_size = CMSG_SPACE(sizeof(struct timeval)); + cmsg_data = malloc(cmsg_size); + if (cmsg_data == NULL) { + logmsg("malloc"); + goto done; + } + + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; + + rv = -1; + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); + + msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); + if (message_recv(fd2, &msghdr) < 0) { + rv = -2; + break; + } + + if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) + break; + + cmsghdr = CMSG_FIRSTHDR(&msghdr); + if (check_scm_timestamp(cmsghdr) < 0) + break; + } + if (i > ipc_msg.msg_num) + rv = 0; +done: + free(cmsg_data); + if (sock_type == SOCK_STREAM && fd2 >= 0) + if (socket_close(fd2) < 0) + rv = -2; + return (rv); +} + +static int +t_timeval(void) +{ + return (t_generic(t_timeval_client, t_timeval_server)); +} + +static int +t_bintime_client(int fd) +{ + struct msghdr msghdr; + struct iovec iov[1]; + void *cmsg_data; + size_t cmsg_size; + int rv; + + if (sync_recv() < 0) + return (-2); + + rv = -2; + + cmsg_size = CMSG_SPACE(sizeof(struct bintime)); + cmsg_data = malloc(cmsg_size); + if (cmsg_data == NULL) { + logmsg("malloc"); + goto done; + } + msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size, + SCM_BINTIME, sizeof(struct bintime)); + + if (socket_connect(fd) < 0) + goto done; + + if (message_sendn(fd, &msghdr) < 0) + goto done; + + rv = 0; +done: + free(cmsg_data); + return (rv); +} + +static int +t_bintime_server(int fd1) +{ + struct msghdr msghdr; + struct iovec iov[1]; + struct cmsghdr *cmsghdr; + void *cmsg_data; + size_t cmsg_size; + u_int i; + int fd2, rv; + + if (sync_send() < 0) + return (-2); + + fd2 = -1; + rv = -2; + + cmsg_size = CMSG_SPACE(sizeof(struct bintime)); + cmsg_data = malloc(cmsg_size); + if (cmsg_data == NULL) { + logmsg("malloc"); + goto done; + } + + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; + + rv = -1; + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); + + msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); + if (message_recv(fd2, &msghdr) < 0) { + rv = -2; + break; + } + + if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) + break; + + cmsghdr = CMSG_FIRSTHDR(&msghdr); + if (check_scm_bintime(cmsghdr) < 0) + break; + } + if (i > ipc_msg.msg_num) + rv = 0; +done: + free(cmsg_data); + if (sock_type == SOCK_STREAM && fd2 >= 0) + if (socket_close(fd2) < 0) + rv = -2; + return (rv); +} + +static int +t_bintime(void) +{ + return (t_generic(t_bintime_client, t_bintime_server)); +} + +static int +t_cmsg_len_client(int fd) +{ + struct msghdr msghdr; + struct iovec iov[1]; + struct cmsghdr *cmsghdr; + void *cmsg_data; + size_t size, cmsg_size; + socklen_t socklen; + int rv; + + if (sync_recv() < 0) + return (-2); + + rv = -2; + + cmsg_size = CMSG_SPACE(sizeof(struct cmsgcred)); + cmsg_data = malloc(cmsg_size); + if (cmsg_data == NULL) { + logmsg("malloc"); + goto done; + } + msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size, + SCM_CREDS, sizeof(struct cmsgcred)); + cmsghdr = CMSG_FIRSTHDR(&msghdr); + + if (socket_connect(fd) < 0) + goto done; + + size = msghdr.msg_iov != NULL ? msghdr.msg_iov->iov_len : 0; + rv = -1; + for (socklen = 0; socklen < CMSG_LEN(0); ++socklen) { + cmsghdr->cmsg_len = socklen; + dbgmsg("send: data size %zu", size); + dbgmsg("send: msghdr.msg_controllen %u", + (u_int)msghdr.msg_controllen); + dbgmsg("send: cmsghdr.cmsg_len %u", + (u_int)cmsghdr->cmsg_len); + if (sendmsg(fd, &msghdr, 0) < 0) + continue; + logmsgx("sent message with cmsghdr.cmsg_len %u < %u", + (u_int)cmsghdr->cmsg_len, (u_int)CMSG_LEN(0)); + break; + } + if (socklen == CMSG_LEN(0)) + rv = 0; + + if (sync_send() < 0) { + rv = -2; + goto done; + } +done: + free(cmsg_data); + return (rv); +} + +static int +t_cmsg_len_server(int fd1) +{ + int fd2, rv; + + if (sync_send() < 0) + return (-2); + + rv = -2; + + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; + + if (sync_recv() < 0) + goto done; + + rv = 0; +done: + if (sock_type == SOCK_STREAM && fd2 >= 0) + if (socket_close(fd2) < 0) + rv = -2; + return (rv); +} + +static int +t_cmsg_len(void) +{ + return (t_generic(t_cmsg_len_client, t_cmsg_len_server)); +} + +static int +t_peercred_client(int fd) +{ + struct xucred xucred; + socklen_t len; + + if (sync_recv() < 0) + return (-1); + + if (socket_connect(fd) < 0) + return (-1); + + len = sizeof(xucred); + if (getsockopt(fd, 0, LOCAL_PEERCRED, &xucred, &len) < 0) { + logmsg("getsockopt(LOCAL_PEERCRED)"); + return (-1); + } + + if (check_xucred(&xucred, len) < 0) + return (-1); + + return (0); +} + +static int +t_peercred_server(int fd1) +{ + struct xucred xucred; + socklen_t len; + int fd2, rv; + + if (sync_send() < 0) + return (-2); + + fd2 = socket_accept(fd1); + if (fd2 < 0) + return (-2); + + len = sizeof(xucred); + if (getsockopt(fd2, 0, LOCAL_PEERCRED, &xucred, &len) < 0) { + logmsg("getsockopt(LOCAL_PEERCRED)"); + rv = -2; + goto done; + } + + if (check_xucred(&xucred, len) < 0) { + rv = -1; + goto done; + } + + rv = 0; +done: + if (socket_close(fd2) < 0) + rv = -2; + return (rv); +} + +static int +t_peercred(void) +{ + return (t_generic(t_peercred_client, t_peercred_server)); +} diff --git a/tests/sys/socket/unix_cmsg_test.sh b/tests/sys/socket/unix_cmsg_test.sh new file mode 100644 index 0000000..c4a0b34 --- /dev/null +++ b/tests/sys/socket/unix_cmsg_test.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# $FreeBSD$ + +cmd=`echo $0 | sed -e 's,_test$,,'` + +IFS= +n=0 + +run() +{ + result=`${cmd} -t $2 $3 ${5%% *} 2>&1` + if [ $? -ne 0 ]; then + echo -n "not " + fi + echo "ok $1 - $4 ${5#* }" + echo ${result} | grep -E "SERVER|CLIENT" | while read line; do + echo "# ${line}" + done +} + +echo "1..47" + +for t1 in \ + "1 Sending, receiving cmsgcred" \ + "4 Sending cmsgcred, receiving sockcred" \ + "5 Sending, receiving timeval" \ + "6 Sending, receiving bintime" \ + "7 Check cmsghdr.cmsg_len" +do + for t2 in \ + "0 " \ + "1 (no data)" \ + "2 (no array)" \ + "3 (no data, array)" + do + n=$((n + 1)) + run ${n} stream "-z ${t2%% *}" STREAM "${t1} ${t2#* }" + done +done + +for t1 in \ + "2 Receiving sockcred (listening socket)" \ + "3 Receiving sockcred (accepted socket)" +do + for t2 in \ + "0 " \ + "1 (no data)" + do + n=$((n + 1)) + run ${n} stream "-z ${t2%% *}" STREAM "${t1} ${t2#* }" + done +done + +n=$((n + 1)) +run ${n} stream "-z 0" STREAM "8 Check LOCAL_PEERCRED socket option" + +for t1 in \ + "1 Sending, receiving cmsgcred" \ + "3 Sending cmsgcred, receiving sockcred" \ + "4 Sending, receiving timeval" \ + "5 Sending, receiving bintime" \ + "6 Check cmsghdr.cmsg_len" +do + for t2 in \ + "0 " \ + "1 (no data)" \ + "2 (no array)" \ + "3 (no data, array)" + do + n=$((n + 1)) + run ${n} dgram "-z ${t2%% *}" DGRAM "${t1} ${t2#* }" + done +done + +for t1 in \ + "2 Receiving sockcred" +do + for t2 in \ + "0 " \ + "1 (no data)" + do + n=$((n + 1)) + run ${n} dgram "-z ${t2%% *}" DGRAM "${t1} ${t2#* }" + done +done diff --git a/tests/sys/socket/unix_gc_test.c b/tests/sys/socket/unix_gc_test.c new file mode 100644 index 0000000..eb70dca --- /dev/null +++ b/tests/sys/socket/unix_gc_test.c @@ -0,0 +1,808 @@ +/*- + * Copyright (c) 2007 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * A few regression tests for UNIX domain sockets. Run from single-user mode + * as it checks the openfiles sysctl to look for leaks, and we don't want that + * changing due to other processes doing stuff. + */ + +#include <sys/types.h> +#include <sys/signal.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/un.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static int forcegc = 1; +static char dpath[PATH_MAX]; +static const char *test; + +static int +getsysctl(const char *name) +{ + size_t len; + int i; + + len = sizeof(i); + if (sysctlbyname(name, &i, &len, NULL, 0) < 0) + err(-1, "%s", name); + return (i); +} + +static int +getopenfiles(void) +{ + + return (getsysctl("kern.openfiles")); +} + +static int +getinflight(void) +{ + + return (getsysctl("net.local.inflight")); +} + +static int +getdeferred(void) +{ + + return (getsysctl("net.local.deferred")); +} + +static void +sendfd(int fd, int fdtosend) +{ + struct msghdr mh; + struct message { struct cmsghdr msg_hdr; int fd; } m; + ssize_t len; + int after_inflight, before_inflight; + + before_inflight = getinflight(); + + bzero(&mh, sizeof(mh)); + bzero(&m, sizeof(m)); + mh.msg_control = &m; + mh.msg_controllen = sizeof(m); + m.msg_hdr.cmsg_len = sizeof(m); + m.msg_hdr.cmsg_level = SOL_SOCKET; + m.msg_hdr.cmsg_type = SCM_RIGHTS; + m.fd = fdtosend; + len = sendmsg(fd, &mh, 0); + if (len < 0) + err(-1, "%s: sendmsg", test); + after_inflight = getinflight(); + if (after_inflight != before_inflight + 1) + errx(-1, "%s: sendfd: before %d after %d\n", test, + before_inflight, after_inflight); +} + +static void +close2(int fd1, int fd2) +{ + + close(fd1); + close(fd2); +} + +static void +close3(int fd1, int fd2, int fd3) +{ + + close2(fd1, fd2); + close(fd3); +} + +static void +close4(int fd1, int fd2, int fd3, int fd4) +{ + + close2(fd1, fd2); + close2(fd3, fd4); +} + +static void +close5(int fd1, int fd2, int fd3, int fd4, int fd5) +{ + + close3(fd1, fd2, fd3); + close2(fd4, fd5); +} + +static int +my_socket(int domain, int type, int proto) +{ + int sock; + + sock = socket(domain, type, proto); + if (sock < 0) + err(-1, "%s: socket", test); + return (sock); +} + +static void +my_bind(int sock, struct sockaddr *sa, socklen_t len) +{ + + if (bind(sock, sa, len) < 0) + err(-1, "%s: bind", test); +} + +static void +my_connect(int sock, struct sockaddr *sa, socklen_t len) +{ + + if (connect(sock, sa, len) < 0 && errno != EINPROGRESS) + err(-1, "%s: connect", test); +} + +static void +my_listen(int sock, int backlog) +{ + + if (listen(sock, backlog) < 0) + err(-1, "%s: listen", test); +} + +static void +my_socketpair(int *sv) +{ + + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) + err(-1, "%s: socketpair", test); +} + +static void +my_getsockname(int s, struct sockaddr *sa, socklen_t *salen) +{ + + if (getsockname(s, sa, salen) < 0) + err(-1, "%s: getsockname", test); +} + +static void +setnonblock(int s) +{ + + if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) + err(-1, "%s: fcntl(F_SETFL, O_NONBLOCK)", test); +} + +static void +alloc3fds(int *s, int *sv) +{ + + if ((*s = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + err(-1, "%s: socket", test); + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) + err(-1, "%s: socketpair", test); +} + +static void +alloc5fds(int *s, int *sva, int *svb) +{ + + if ((*s = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + err(-1, "%s: socket", test); + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sva) < 0) + err(-1, "%s: socketpair", test); + if (socketpair(PF_UNIX, SOCK_STREAM, 0, svb) < 0) + err(-1, "%s: socketpair", test); +} + +static void +save_sysctls(int *before_inflight, int *before_openfiles) +{ + + *before_inflight = getinflight(); + *before_openfiles = getopenfiles(); +} + +/* + * Try hard to make sure that the GC does in fact run before we test the + * condition of things. + */ +static void +trigger_gc(void) +{ + int s; + + if (forcegc) { + if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + err(-1, "trigger_gc: socket"); + close(s); + } + sleep(1); +} + +static void +test_sysctls(int before_inflight, int before_openfiles) +{ + int after_inflight, after_openfiles; + + trigger_gc(); + after_inflight = getinflight(); + if (after_inflight != before_inflight) + warnx("%s: before inflight: %d, after inflight: %d", + test, before_inflight, after_inflight); + + after_openfiles = getopenfiles(); + if (after_openfiles != before_openfiles) + warnx("%s: before: %d, after: %d", test, before_openfiles, + after_openfiles); +} + +static void +twosome_nothing(void) +{ + int inflight, openfiles; + int sv[2]; + + /* + * Create a pair, close in one order. + */ + test = "twosome_nothing1"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + my_socketpair(sv); + close2(sv[0], sv[1]); + test_sysctls(inflight, openfiles); + + /* + * Create a pair, close in the other order. + */ + test = "twosome_nothing2"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + my_socketpair(sv); + close2(sv[0], sv[1]); + test_sysctls(inflight, openfiles); +} + +/* + * Using a socket pair, send various endpoints over the pair and close in + * various orders. + */ +static void +twosome_drop_work(const char *testname, int sendvia, int tosend, int closefirst) +{ + int inflight, openfiles; + int sv[2]; + + printf("%s\n", testname); + test = testname; + save_sysctls(&inflight, &openfiles); + my_socketpair(sv); + sendfd(sv[sendvia], sv[tosend]); + if (closefirst == 0) + close2(sv[0], sv[1]); + else + close2(sv[1], sv[0]); + test_sysctls(inflight, openfiles); +} + +static void +twosome_drop(void) +{ + + /* + * In various combations, some wastefully symmetric, create socket + * pairs and send one or another endpoint over one or another + * endpoint, closing the endpoints in various orders. + */ + twosome_drop_work("twosome_drop1", 0, 0, 0); + twosome_drop_work("twosome_drop2", 0, 0, 1); + twosome_drop_work("twosome_drop3", 0, 1, 0); + twosome_drop_work("twosome_drop4", 0, 1, 1); + twosome_drop_work("twosome_drop5", 1, 0, 0); + twosome_drop_work("twosome_drop6", 1, 0, 1); + twosome_drop_work("twosome_drop7", 1, 1, 0); + twosome_drop_work("twosome_drop8", 1, 1, 1); +} + +static void +threesome_nothing(void) +{ + int inflight, openfiles; + int s, sv[2]; + + test = "threesome_nothing"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + alloc3fds(&s, sv); + close3(s, sv[0], sv[1]); + test_sysctls(inflight, openfiles); +} + +/* + * threesome_drop: create a pair and a spare, send the spare over the pair, and + * close in various orders and make sure all the fds went away. + */ +static void +threesome_drop(void) +{ + int inflight, openfiles; + int s, sv[2]; + + /* + * threesome_drop1: close sent send receive + */ + test = "threesome_drop1"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + alloc3fds(&s, sv); + sendfd(sv[0], s); + close3(s, sv[0], sv[1]); + test_sysctls(inflight, openfiles); + + /* + * threesome_drop2: close sent receive send + */ + test = "threesome_drop2"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + alloc3fds(&s, sv); + sendfd(sv[0], s); + close3(s, sv[1], sv[0]); + test_sysctls(inflight, openfiles); + + /* + * threesome_drop3: close receive sent send + */ + test = "threesome_drop3"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + alloc3fds(&s, sv); + sendfd(sv[0], s); + close3(sv[1], s, sv[0]); + test_sysctls(inflight, openfiles); + + /* + * threesome_drop4: close receive send sent + */ + test = "threesome_drop4"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + alloc3fds(&s, sv); + sendfd(sv[0], s); + close3(sv[1], sv[0], s); + test_sysctls(inflight, openfiles); + + /* + * threesome_drop5: close send receive sent + */ + test = "threesome_drop5"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + alloc3fds(&s, sv); + sendfd(sv[0], s); + close3(sv[0], sv[1], s); + test_sysctls(inflight, openfiles); + + /* + * threesome_drop6: close send sent receive + */ + test = "threesome_drop6"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + alloc3fds(&s, sv); + close3(sv[0], s, sv[1]); + test_sysctls(inflight, openfiles); +} + +/* + * Fivesome tests: create two socket pairs and a spare, send the spare over + * the first socket pair, then send the first socket pair over the second + * socket pair, and GC. Do various closes at various points to exercise + * various cases. + */ +static void +fivesome_nothing(void) +{ + int inflight, openfiles; + int spare, sva[2], svb[2]; + + test = "fivesome_nothing"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + alloc5fds(&spare, sva, svb); + close5(spare, sva[0], sva[1], svb[0], svb[1]); + test_sysctls(inflight, openfiles); +} + +static void +fivesome_drop_work(const char *testname, int close_spare_after_send, + int close_sva_after_send) +{ + int inflight, openfiles; + int spare, sva[2], svb[2]; + + printf("%s\n", testname); + test = testname; + save_sysctls(&inflight, &openfiles); + alloc5fds(&spare, sva, svb); + + /* + * Send spare over sva. + */ + sendfd(sva[0], spare); + if (close_spare_after_send) + close(spare); + + /* + * Send sva over svb. + */ + sendfd(svb[0], sva[0]); + sendfd(svb[0], sva[1]); + if (close_sva_after_send) + close2(sva[0], sva[1]); + + close2(svb[0], svb[1]); + + if (!close_sva_after_send) + close2(sva[0], sva[1]); + if (!close_spare_after_send) + close(spare); + + test_sysctls(inflight, openfiles); +} + +static void +fivesome_drop(void) +{ + + fivesome_drop_work("fivesome_drop1", 0, 0); + fivesome_drop_work("fivesome_drop2", 0, 1); + fivesome_drop_work("fivesome_drop3", 1, 0); + fivesome_drop_work("fivesome_drop4", 1, 1); +} + +/* + * Create a somewhat nasty dual-socket socket intended to upset the garbage + * collector if mark-and-sweep is wrong. + */ +static void +complex_cycles(void) +{ + int inflight, openfiles; + int spare, sva[2], svb[2]; + + test = "complex_cycles"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + alloc5fds(&spare, sva, svb); + sendfd(sva[0], svb[0]); + sendfd(sva[0], svb[1]); + sendfd(svb[0], sva[0]); + sendfd(svb[0], sva[1]); + sendfd(svb[0], spare); + sendfd(sva[0], spare); + close5(spare, sva[0], sva[1], svb[0], svb[1]); + test_sysctls(inflight, openfiles); +} + +/* + * Listen sockets can also be passed over UNIX domain sockets, so test + * various cases, including ones where listen sockets have waiting sockets + * hanging off them... + */ +static void +listen_nothing(void) +{ + struct sockaddr_un sun; + struct sockaddr_in sin; + int inflight, openfiles; + int s; + + test = "listen_nothing_unp"; + printf("%s\n", test); + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_LOCAL; + sun.sun_len = sizeof(sun); + snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dpath, test); + save_sysctls(&inflight, &openfiles); + s = my_socket(PF_LOCAL, SOCK_STREAM, 0); + my_bind(s, (struct sockaddr *)&sun, sizeof(sun)); + my_listen(s, -1); + close(s); + (void)unlink(sun.sun_path); + test_sysctls(inflight, openfiles); + + test = "listen_nothing_inet"; + printf("%s\n", test); + bzero(&sin, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_len = sizeof(sin); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(0); + save_sysctls(&inflight, &openfiles); + s = my_socket(PF_INET, SOCK_STREAM, 0); + my_bind(s, (struct sockaddr *)&sin, sizeof(sin)); + my_listen(s, -1); + close(s); + test_sysctls(inflight, openfiles); +} + +/* + * Send a listen UDP socket over a UNIX domain socket. + * + * Send a listen TCP socket over a UNIX domain socket. + * + * Do each twice, with closing of the listen socket vs. socketpair in + * different orders. + */ +static void +listen_drop(void) +{ + struct sockaddr_un sun; + struct sockaddr_in sin; + int inflight, openfiles; + int s, sv[2]; + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_LOCAL; + sun.sun_len = sizeof(sun); + + /* + * Close listen socket first. + */ + test = "listen_drop_unp1"; + printf("%s\n", test); + snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dpath, test); + save_sysctls(&inflight, &openfiles); + s = my_socket(PF_LOCAL, SOCK_STREAM, 0); + my_bind(s, (struct sockaddr *)&sun, sizeof(sun)); + my_listen(s, -1); + my_socketpair(sv); + sendfd(sv[0], s); + close3(s, sv[0], sv[1]); + test_sysctls(inflight, openfiles); + + /* + * Close socketpair first. + */ + test = "listen_drop_unp2"; + printf("%s\n", test); + snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dpath, test); + save_sysctls(&inflight, &openfiles); + s = my_socket(PF_LOCAL, SOCK_STREAM, 0); + my_bind(s, (struct sockaddr *)&sun, sizeof(sun)); + my_listen(s, -1); + my_socketpair(sv); + sendfd(sv[0], s); + close3(sv[0], sv[1], s); + test_sysctls(inflight, openfiles); + + sin.sin_family = AF_INET; + sin.sin_len = sizeof(sin); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(0); + + /* + * Close listen socket first. + */ + test = "listen_drop_inet1"; + printf("%s\n", test); + bzero(&sun, sizeof(sun)); + save_sysctls(&inflight, &openfiles); + s = my_socket(PF_INET, SOCK_STREAM, 0); + my_bind(s, (struct sockaddr *)&sin, sizeof(sin)); + my_listen(s, -1); + my_socketpair(sv); + sendfd(sv[0], s); + close3(s, sv[0], sv[1]); + test_sysctls(inflight, openfiles); + + /* + * Close socketpair first. + */ + test = "listen_drop_inet2"; + printf("%s\n", test); + bzero(&sun, sizeof(sun)); + save_sysctls(&inflight, &openfiles); + s = my_socket(PF_INET, SOCK_STREAM, 0); + my_bind(s, (struct sockaddr *)&sin, sizeof(sin)); + my_listen(s, -1); + my_socketpair(sv); + sendfd(sv[0], s); + close3(sv[0], sv[1], s); + test_sysctls(inflight, openfiles); +} + +/* + * Up things a notch with listen sockets: add connections that can be + * accepted to the listen queues. + */ +static void +listen_connect_nothing(void) +{ + struct sockaddr_in sin; + int slisten, sconnect, sv[2]; + int inflight, openfiles; + socklen_t len; + + test = "listen_connect_nothing"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + + slisten = my_socket(PF_INET, SOCK_STREAM, 0); + my_bind(slisten, (struct sockaddr *)&sin, sizeof(sin)); + my_listen(slisten, -1); + + my_socketpair(sv); + + len = sizeof(sin); + my_getsockname(slisten, (struct sockaddr *)&sin, &len); + + sconnect = my_socket(PF_INET, SOCK_STREAM, 0); + setnonblock(sconnect); + my_connect(sconnect, (struct sockaddr *)&sin, len); + + sleep(1); + + close4(slisten, sconnect, sv[0], sv[1]); + + test_sysctls(inflight, openfiles); +} + +static void +listen_connect_drop(void) +{ + struct sockaddr_in sin; + int slisten, sconnect, sv[2]; + int inflight, openfiles; + socklen_t len; + + test = "listen_connect_drop"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + + slisten = my_socket(PF_INET, SOCK_STREAM, 0); + my_bind(slisten, (struct sockaddr *)&sin, sizeof(sin)); + my_listen(slisten, -1); + + my_socketpair(sv); + + len = sizeof(sin); + my_getsockname(slisten, (struct sockaddr *)&sin, &len); + + sconnect = my_socket(PF_INET, SOCK_STREAM, 0); + setnonblock(sconnect); + my_connect(sconnect, (struct sockaddr *)&sin, len); + + sleep(1); + sendfd(sv[0], slisten); + close3(slisten, sv[0], sv[1]); + sleep(1); + close(sconnect); + + test_sysctls(inflight, openfiles); +} + +static void +recursion(void) +{ + int fd[2], ff[2]; + int inflight, openfiles, deferred, deferred1; + + test = "recursion"; + printf("%s\n", test); + save_sysctls(&inflight, &openfiles); + deferred = getdeferred(); + + my_socketpair(fd); + + for (;;) { + if (socketpair(PF_UNIX, SOCK_STREAM, 0, ff) == -1) { + if (errno == EMFILE || errno == ENFILE) + break; + err(-1, "socketpair"); + } + sendfd(ff[0], fd[0]); + sendfd(ff[0], fd[1]); + close2(fd[1], fd[0]); + fd[0] = ff[0]; + fd[1] = ff[1]; + } + close2(fd[0], fd[1]); + sleep(1); + test_sysctls(inflight, openfiles); + deferred1 = getdeferred(); + if (deferred != deferred1) + errx(-1, "recursion: deferred before %d after %d", deferred, + deferred1); +} + +#define RMDIR "rm -Rf " +int +main(void) +{ + char cmd[sizeof(RMDIR) + PATH_MAX]; + int serrno; + pid_t pid; + + strlcpy(dpath, "unpgc.XXXXXXXX", sizeof(dpath)); + if (mkdtemp(dpath) == NULL) + err(-1, "mkdtemp"); + + /* + * Set up a parent process to GC temporary storage when we're done. + */ + pid = fork(); + if (pid < 0) { + serrno = errno; + (void)rmdir(dpath); + errno = serrno; + err(-1, "fork"); + } + if (pid > 0) { + signal(SIGINT, SIG_IGN); + while (waitpid(pid, NULL, 0) != pid); + snprintf(cmd, sizeof(cmd), "%s %s", RMDIR, dpath); + (void)system(cmd); + exit(0); + } + + printf("Start: inflight %d open %d\n", getinflight(), + getopenfiles()); + + twosome_nothing(); + twosome_drop(); + + threesome_nothing(); + threesome_drop(); + + fivesome_nothing(); + fivesome_drop(); + + complex_cycles(); + + listen_nothing(); + listen_drop(); + + listen_connect_nothing(); + listen_connect_drop(); + + recursion(); + + printf("Finish: inflight %d open %d\n", getinflight(), + getopenfiles()); + return (0); +} diff --git a/tests/sys/socket/unix_passfd_test.c b/tests/sys/socket/unix_passfd_test.c new file mode 100644 index 0000000..f39597a --- /dev/null +++ b/tests/sys/socket/unix_passfd_test.c @@ -0,0 +1,395 @@ +/*- + * Copyright (c) 2005 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/un.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <atf-c.h> + +/* + * UNIX domain sockets allow file descriptors to be passed via "ancillary + * data", or control messages. This regression test is intended to exercise + * this facility, both performing some basic tests that it operates, and also + * causing some kernel edge cases to execute, such as garbage collection when + * there are cyclic file descriptor references. Right now we test only with + * stream sockets, but ideally we'd also test with datagram sockets. + */ + +static void +domainsocketpair(int *fdp) +{ + + ATF_REQUIRE_MSG(socketpair(PF_UNIX, SOCK_STREAM, 0, fdp) != -1, + "socketpair(PF_UNIX, SOCK_STREAM, ..) failed: %s, ", + strerror(errno)); +} + +static void +closesocketpair(int *fdp) +{ + + close(fdp[0]); + close(fdp[1]); +} + +static void +tempfile(int *fdp) +{ + char path[PATH_MAX]; + int fd; + + snprintf(path, PATH_MAX, "unix_passfd.XXXXXXXXXXXXXXX"); + fd = mkstemp(path); + ATF_REQUIRE_MSG(fd != -1, "mkstemp failed: %s", strerror(errno)); + (void)unlink(path); + *fdp = fd; +} + +static void +dofstat(int fd, struct stat *sb) +{ + + ATF_REQUIRE_MSG(fstat(fd, sb) != -1, "fstat failed: %s", + strerror(errno)); +} + +static void +samefile(struct stat *sb1, struct stat *sb2) +{ + + ATF_REQUIRE_EQ_MSG(sb1->st_dev, sb2->st_dev, + "different devices (%d != %d)", sb1->st_dev, sb2->st_dev); + ATF_REQUIRE_EQ_MSG(sb1->st_dev, sb2->st_dev, + "different inodes (%u != %u)", sb1->st_ino, sb2->st_ino); +} + +static void +sendfd_payload(int sockfd, int sendfd, + void *payload, size_t paylen) +{ + struct iovec iovec; + char message[CMSG_SPACE(sizeof(int))]; + struct cmsghdr *cmsghdr; + struct msghdr msghdr; + ssize_t len; + + bzero(&msghdr, sizeof(msghdr)); + bzero(&message, sizeof(message)); + + msghdr.msg_control = message; + msghdr.msg_controllen = sizeof(message); + + iovec.iov_base = payload; + iovec.iov_len = paylen; + + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + + cmsghdr = (struct cmsghdr *)message; + cmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); + cmsghdr->cmsg_level = SOL_SOCKET; + cmsghdr->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsghdr) = sendfd; + + len = sendmsg(sockfd, &msghdr, 0); + ATF_REQUIRE_MSG(len != -1, "sendmsg failed: %s", strerror(errno)); + ATF_REQUIRE_MSG((size_t)len == paylen, + "mismatch with amount of data sent via sendmsg (%zd != %zu)", + len, paylen); +} + +static void +sendfd(int sock_fd, int send_fd) +{ + char ch; + + return (sendfd_payload(sock_fd, send_fd, &ch, sizeof(ch))); +} + +static void +recvfd_payload(int sockfd, int *recvfd, void *buf, size_t buflen) +{ + struct cmsghdr *cmsghdr; + char message[CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX)) + sizeof(int)]; + struct msghdr msghdr; + struct iovec iovec; + ssize_t len; + + bzero(&msghdr, sizeof(msghdr)); + + msghdr.msg_control = message; + msghdr.msg_controllen = sizeof(message); + + iovec.iov_base = buf; + iovec.iov_len = buflen; + + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + + len = recvmsg(sockfd, &msghdr, 0); + ATF_REQUIRE_MSG(len != -1, "recvmsg failed: %s", strerror(errno)); + ATF_REQUIRE_MSG((size_t)len == buflen, + "mismatch with amount of data sent via recvmsg (%zd != %zu)", + len, buflen); + + cmsghdr = CMSG_FIRSTHDR(&msghdr); + ATF_REQUIRE_MSG(cmsghdr != NULL, "did not receive control message"); + *recvfd = -1; + for (; cmsghdr != NULL; cmsghdr = CMSG_NXTHDR(&msghdr, cmsghdr)) { + if (cmsghdr->cmsg_level == SOL_SOCKET && + cmsghdr->cmsg_type == SCM_RIGHTS && + cmsghdr->cmsg_len == CMSG_LEN(sizeof(int))) { + *recvfd = *(int *)CMSG_DATA(cmsghdr); + ATF_REQUIRE(*recvfd != -1); + } + } + ATF_REQUIRE_MSG(*recvfd != -1, + "recvmsg did not receive a single fd message"); +} + +static void +recvfd(int sock_fd, int *recv_fd) +{ + char ch; + + return (recvfd_payload(sock_fd, recv_fd, &ch, sizeof(ch))); +} + +/* + * First test: put a temporary file into a UNIX domain socket, then + * take it out and make sure it's the same file. First time around, + * don't close the reference after sending. + */ +ATF_TC_WITHOUT_HEAD(simple_send_fd); +ATF_TC_BODY(simple_send_fd, tc) +{ + struct stat getfd_1_stat, putfd_1_stat; + int fd[2], getfd_1, putfd_1; + + domainsocketpair(fd); + tempfile(&putfd_1); + dofstat(putfd_1, &putfd_1_stat); + sendfd(fd[0], putfd_1); + recvfd(fd[1], &getfd_1); + dofstat(getfd_1, &getfd_1_stat); + samefile(&putfd_1_stat, &getfd_1_stat); + close(putfd_1); + close(getfd_1); + closesocketpair(fd); +} + +/* + * Second test: same as first, only close the file reference after + * sending, so that the only reference is the descriptor in the UNIX + * domain socket buffer. + */ +ATF_TC_WITHOUT_HEAD(send_and_close); +ATF_TC_BODY(send_and_close, tc) +{ + struct stat getfd_1_stat, putfd_1_stat; + int fd[2], getfd_1, putfd_1; + + domainsocketpair(fd); + tempfile(&putfd_1); + dofstat(putfd_1, &putfd_1_stat); + sendfd(fd[0], putfd_1); + close(putfd_1); + recvfd(fd[1], &getfd_1); + dofstat(getfd_1, &getfd_1_stat); + samefile(&putfd_1_stat, &getfd_1_stat); + close(getfd_1); + closesocketpair(fd); + +} + +/* + * Third test: put a temporary file into a UNIX domain socket, then + * close both endpoints causing garbage collection to kick off. + */ +ATF_TC_WITHOUT_HEAD(send_and_cancel); +ATF_TC_BODY(send_and_cancel, tc) +{ + int fd[2], putfd_1; + + domainsocketpair(fd); + tempfile(&putfd_1); + sendfd(fd[0], putfd_1); + close(putfd_1); + closesocketpair(fd); +} + +/* + * Send two files. Then receive them. Make sure they are returned + * in the right order, and both get there. + */ +ATF_TC_WITHOUT_HEAD(two_files); +ATF_TC_BODY(two_files, tc) +{ + struct stat getfd_1_stat, getfd_2_stat, putfd_1_stat, putfd_2_stat; + int fd[2], getfd_1, getfd_2, putfd_1, putfd_2; + + domainsocketpair(fd); + tempfile(&putfd_1); + tempfile(&putfd_2); + dofstat(putfd_1, &putfd_1_stat); + dofstat(putfd_2, &putfd_2_stat); + sendfd(fd[0], putfd_1); + sendfd(fd[0], putfd_2); + close(putfd_1); + close(putfd_2); + recvfd(fd[1], &getfd_1); + recvfd(fd[1], &getfd_2); + dofstat(getfd_1, &getfd_1_stat); + dofstat(getfd_2, &getfd_2_stat); + samefile(&putfd_1_stat, &getfd_1_stat); + samefile(&putfd_2_stat, &getfd_2_stat); + close(getfd_1); + close(getfd_2); + closesocketpair(fd); +} + +/* + * Big bundling test. Send an endpoint of the UNIX domain socket + * over itself, closing the door behind it. + */ +ATF_TC_WITHOUT_HEAD(bundle); +ATF_TC_BODY(bundle, tc) +{ + int fd[2], getfd_1; + + domainsocketpair(fd); + + sendfd(fd[0], fd[0]); + close(fd[0]); + recvfd(fd[1], &getfd_1); + close(getfd_1); + close(fd[1]); +} + +/* + * Big bundling test part two: Send an endpoint of the UNIX domain + * socket over itself, close the door behind it, and never remove it + * from the other end. + */ +ATF_TC_WITHOUT_HEAD(bundle_cancel); +ATF_TC_BODY(bundle_cancel, tc) +{ + int fd[2]; + + domainsocketpair(fd); + sendfd(fd[0], fd[0]); + sendfd(fd[1], fd[0]); + closesocketpair(fd); +} + +/* + * Test for PR 151758: Send an character device over the UNIX + * domain socket and then close both sockets to orphan the + * device. + */ +ATF_TC_WITHOUT_HEAD(devfs_orphan); +ATF_TC_BODY(devfs_orphan, tc) +{ + int fd[2], putfd_1; + + domainsocketpair(fd); + putfd_1 = open(_PATH_DEVNULL, O_RDONLY); + ATF_REQUIRE_MSG(putfd_1 != -1, + "opening %s failed: %s", _PATH_DEVNULL, strerror(errno)); + sendfd(fd[0], putfd_1); + close(putfd_1); + closesocketpair(fd); +} + +#define LOCAL_STREAM_SENDSPACE "net.local.stream.sendspace" + +/* + * Test for PR 181741. Receiver sets LOCAL_CREDS, and kernel + * prepends a control message to the data. Sender sends large + * payload. Payload + SCM_RIGHTS + LOCAL_CREDS hit socket buffer + * limit, and receiver receives truncated data. + */ +ATF_TC_WITHOUT_HEAD(rights_with_LOCAL_CREDS_and_large_payload); +ATF_TC_BODY(rights_with_LOCAL_CREDS_and_large_payload, tc) +{ + void *buf; + int fd[2]; + size_t len; + u_long sendspace; + const int on = 1; + int getfd_1, putfd_1, rc; + + atf_tc_expect_fail("Bug 181741 has not been fixed yet"); + + len = sizeof(sendspace); + rc = sysctlbyname(LOCAL_STREAM_SENDSPACE, &sendspace, &len, NULL, 0); + ATF_REQUIRE_MSG(rc == 0, "sysctlbyname %s failed: %s", + LOCAL_STREAM_SENDSPACE, strerror(errno)); + + ATF_REQUIRE((buf = malloc(sendspace)) != NULL); + + domainsocketpair(fd); + rc = setsockopt(fd[1], 0, LOCAL_CREDS, &on, sizeof(on)); + ATF_REQUIRE_MSG(rc == 0, "setsockopt failed: %s", strerror(errno)); + + tempfile(&putfd_1); + + sendfd_payload(fd[0], putfd_1, buf, sendspace); + recvfd_payload(fd[1], &getfd_1, buf, sendspace); + + close(putfd_1); + close(getfd_1); + closesocketpair(fd); +} + +ATF_TP_ADD_TCS(tp) +{ + + ATF_TP_ADD_TC(tp, simple_send_fd); + ATF_TP_ADD_TC(tp, send_and_close); + ATF_TP_ADD_TC(tp, send_and_cancel); + ATF_TP_ADD_TC(tp, two_files); + ATF_TP_ADD_TC(tp, bundle); + ATF_TP_ADD_TC(tp, bundle_cancel); + ATF_TP_ADD_TC(tp, devfs_orphan); + ATF_TP_ADD_TC(tp, rights_with_LOCAL_CREDS_and_large_payload); + + return (atf_no_error()); +} diff --git a/tests/sys/socket/unix_sendto_race_test.c b/tests/sys/socket/unix_sendto_race_test.c new file mode 100644 index 0000000..9fd748d --- /dev/null +++ b/tests/sys/socket/unix_sendto_race_test.c @@ -0,0 +1,215 @@ +/*- + * Copyright (c) 2006 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Attempts to exercise UNIX domain socket races relating to the non-atomic + * connect-and-send properties of sendto(). As the result of such a race is + * a kernel panic, this test simply completes or doesn't. + * + * XXX: Despite implementing support for sendto() on stream sockets with + * implied connect, the appropriate flag isn't set in the FreeBSD kernel so + * it does not work. For now, don't call the stream test. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <err.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> + +#define ITERATIONS 1000000 + +static char socket_path[] = "tmp.XXXXXX"; + +static void +stream_server(int listenfd) +{ + int acceptfd; + + while (1) { + acceptfd = accept(listenfd, NULL, NULL); + if (acceptfd < 0) { + warn("stream_server: accept"); + continue; + } + sleep(1); + close(acceptfd); + } +} + +static void +stream_client(void) +{ + struct sockaddr_un sun; + ssize_t len; + char c = 0; + int fd, i; + + bzero(&sun, sizeof(sun)); + sun.sun_len = sizeof(sun); + sun.sun_family = AF_UNIX; + strcpy(sun.sun_path, socket_path); + for (i = 0; i < ITERATIONS; i++) { + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + warn("stream_client: socket"); + return; + } + len = sendto(fd, &c, sizeof(c), 0, (struct sockaddr *)&sun, + sizeof(sun)); + if (len < 0) + warn("stream_client: sendto"); + close(fd); + } +} + +static void +stream_test(void) +{ + struct sockaddr_un sun; + pid_t childpid; + int listenfd; + + listenfd = socket(PF_UNIX, SOCK_STREAM, 0); + if (listenfd < 0) + err(-1, "stream_test: socket"); + + bzero(&sun, sizeof(sun)); + sun.sun_len = sizeof(sun); + sun.sun_family = AF_UNIX; + strcpy(sun.sun_path, socket_path); + + if (bind(listenfd, (struct sockaddr *)&sun, sizeof(sun)) < 0) + err(-1, "stream_test: bind"); + + if (listen(listenfd, -1) < 0) + err(-1, "stream_test: listen"); + + childpid = fork(); + if (childpid < 0) + err(-1, "stream_test: fork"); + + if (childpid != 0) { + sleep(1); + stream_client(); + kill(childpid, SIGTERM); + sleep(1); + } else + stream_server(listenfd); + + (void)unlink(socket_path); +} + +static void +datagram_server(int serverfd) +{ + ssize_t len; + char c; + + while (1) { + len = recv(serverfd, &c, sizeof(c), 0); + if (len < 0) + warn("datagram_server: recv"); + } +} + +static void +datagram_client(void) +{ + struct sockaddr_un sun; + ssize_t len; + char c = 0; + int fd, i; + + bzero(&sun, sizeof(sun)); + sun.sun_len = sizeof(sun); + sun.sun_family = AF_UNIX; + strcpy(sun.sun_path, socket_path); + for (i = 0; i < ITERATIONS; i++) { + fd = socket(PF_UNIX, SOCK_DGRAM, 0); + if (fd < 0) { + warn("datagram_client: socket"); + return; + } + len = sendto(fd, &c, sizeof(c), 0, (struct sockaddr *)&sun, + sizeof(sun)); + if (len < 0) + warn("datagram_client: sendto"); + close(fd); + } +} + +static void +datagram_test(void) +{ + struct sockaddr_un sun; + pid_t childpid; + int serverfd; + + serverfd = socket(PF_UNIX, SOCK_DGRAM, 0); + if (serverfd < 0) + err(-1, "datagram_test: socket"); + + bzero(&sun, sizeof(sun)); + sun.sun_len = sizeof(sun); + sun.sun_family = AF_UNIX; + strcpy(sun.sun_path, socket_path); + + if (bind(serverfd, (struct sockaddr *)&sun, sizeof(sun)) < 0) + err(-1, "datagram_test: bind"); + + childpid = fork(); + if (childpid < 0) + err(-1, "datagram_test: fork"); + + if (childpid != 0) { + sleep(1); + datagram_client(); + kill(childpid, SIGTERM); + sleep(1); + } else + datagram_server(serverfd); + + (void)unlink(socket_path); +} + +int +main(void) +{ + + if (mkstemp(socket_path) == -1) + err(1, "mkstemp failed"); + (void)unlink(socket_path); + datagram_test(); + if (0) + stream_test(); + return (0); +} diff --git a/tests/sys/socket/unix_socket_test.c b/tests/sys/socket/unix_socket_test.c new file mode 100644 index 0000000..ca7225b --- /dev/null +++ b/tests/sys/socket/unix_socket_test.c @@ -0,0 +1,84 @@ +/*- + * Copyright (c) 2006 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Simple UNIX domain socket regression test: create and tear down various + * supported and unsupported socket types. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <err.h> +#include <errno.h> +#include <unistd.h> + +int +main(void) +{ + int sock, socks[2]; + + sock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sock < 0) + err(-1, "socket(PF_LOCAL, SOCK_STREAM, 0)"); + close(sock); + + sock = socket(PF_LOCAL, SOCK_DGRAM, 0); + if (sock < 0) + err(-1, "socket(PF_LOCAL, SOCK_DGRAM, 0)"); + close(sock); + + sock = socket(PF_LOCAL, SOCK_RAW, 0); + if (sock >= 0) { + close(sock); + errx(-1, "socket(PF_LOCAL, SOCK_RAW, 0) returned %d", sock); + } + if (errno != EPROTOTYPE) + err(-1, "socket(PF_LOCAL, SOCK_RAW, 0)"); + + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, socks) < 0) + err(-1, "socketpair(PF_LOCAL, SOCK_STREAM, 0, socks)"); + if (socks[0] < 0) + errx(-1, "socketpair(PF_LOCAL, SOCK_STREAM, 0, socks) [0] < 0"); + if (socks[1] < 0) + errx(-1, "socketpair(PF_LOCAL, SOCK_STREAM, 0, socks) [1] < 1"); + close(socks[0]); + close(socks[1]); + + if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, socks) < 0) + err(-1, "socketpair(PF_LOCAL, SOCK_DGRAM, 0, socks)"); + if (socks[0] < 0) + errx(-1, "socketpair(PF_LOCAL, SOCK_DGRAM, 0, socks) [0] < 0"); + if (socks[1] < 0) + errx(-1, "socketpair(PF_LOCAL, SOCK_DGRAM, 0, socks) [1] < 1"); + close(socks[0]); + close(socks[1]); + + return (0); +} diff --git a/tests/sys/socket/unix_sorflush_test.c b/tests/sys/socket/unix_sorflush_test.c new file mode 100644 index 0000000..e0deb00 --- /dev/null +++ b/tests/sys/socket/unix_sorflush_test.c @@ -0,0 +1,99 @@ +/*- + * Copyright (c) 2008 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Reproduce a race in which: + * + * - Process (a) is blocked in read on a socket waiting on data. + * - Process (b) is blocked in shutdown() on a socket waiting on (a). + * - Process (c) delivers a signal to (b) interrupting its wait. + * + * This race is premised on shutdown() not interrupting (a) properly, and the + * signal to (b) causing problems in the kernel. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/socket.h> + +#include <err.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +static void +receive_and_exit(int s) +{ + ssize_t ssize; + char ch; + + ssize = recv(s, &ch, sizeof(ch), 0); + if (ssize < 0) + err(-1, "receive_and_exit: recv"); + exit(0); +} + +static void +shutdown_and_exit(int s) +{ + + if (shutdown(s, SHUT_RD) < 0) + err(-1, "shutdown_and_exit: shutdown"); + exit(0); +} + +int +main(void) +{ + pid_t pida, pidb; + int sv[2]; + + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, sv) < 0) + err(-1, "socketpair"); + + pida = fork(); + if (pida < 0) + err(-1, "fork"); + if (pida == 0) + receive_and_exit(sv[1]); + sleep(1); + pidb = fork(); + if (pidb < 0) { + warn("fork"); + (void)kill(pida, SIGKILL); + exit(-1); + } + if (pidb == 0) + shutdown_and_exit(sv[1]); + sleep(1); + if (kill(pidb, SIGKILL) < 0) + err(-1, "kill"); + sleep(1); + printf("ok 1 - unix_sorflush\n"); + exit(0); +} diff --git a/tests/sys/socket/zerosend_test.c b/tests/sys/socket/zerosend_test.c new file mode 100644 index 0000000..7788760 --- /dev/null +++ b/tests/sys/socket/zerosend_test.c @@ -0,0 +1,342 @@ +/*- + * Copyright (c) 2007 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> + +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <atf-c.h> + +static void +try_0send(int fd) +{ + ssize_t len; + char ch; + + ch = 0; + len = send(fd, &ch, 0, 0); + ATF_REQUIRE_MSG(len != -1, "send failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(len == 0, "send returned %zd (not 0): %s", + len, strerror(errno)); +} + +static void +try_0write(int fd) +{ + ssize_t len; + char ch; + + ch = 0; + len = write(fd, &ch, 0); + ATF_REQUIRE_MSG(len != -1, "write failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(len == 0, "write returned: %zd (not 0): %s", + len, strerror(errno)); +} + +static void +setup_udp(int *fdp) +{ + struct sockaddr_in sin; + int port_base, sock1, sock2; + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + port_base = MAX((int)random() % 65535, 1025); + + sin.sin_port = htons(port_base); + sock1 = socket(PF_INET, SOCK_DGRAM, 0); + ATF_REQUIRE_MSG(sock1 != -1, "socket # 1 failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(bind(sock1, (struct sockaddr *)&sin, sizeof(sin)) == 0, + "bind # 1 failed: %s", strerror(errno)); + sin.sin_port = htons(port_base + 1); + ATF_REQUIRE_MSG(connect(sock1, (struct sockaddr *)&sin, sizeof(sin)) + == 0, "connect # 1 failed: %s", strerror(errno)); + + sock2 = socket(PF_INET, SOCK_DGRAM, 0); + ATF_REQUIRE_MSG(sock2 != -1, "socket # 2 failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(bind(sock2, (struct sockaddr *)&sin, sizeof(sin)) == 0, + "bind # 2 failed: %s", strerror(errno)); + sin.sin_port = htons(port_base); + ATF_REQUIRE_MSG(connect(sock2, (struct sockaddr *)&sin, sizeof(sin)) + == 0, "connect # 2 failed: %s", strerror(errno)); + + fdp[0] = sock1; + fdp[1] = sock2; + fdp[2] = -1; +} + +static void +setup_tcp(int *fdp) +{ + fd_set writefds, exceptfds; + struct sockaddr_in sin; + int port_base, ret, sock1, sock2, sock3; + struct timeval tv; + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + port_base = MAX((int)random() % 65535, 1025); + + /* + * First set up the listen socket. + */ + sin.sin_port = htons(port_base); + sock1 = socket(PF_INET, SOCK_STREAM, 0); + ATF_REQUIRE_MSG(sock1 != -1, "socket # 1 failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(bind(sock1, (struct sockaddr *)&sin, sizeof(sin)) == 0, + "bind # 1 failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(listen(sock1, -1) == 0, + "listen # 1 failed: %s", strerror(errno)); + + /* + * Now connect to it, non-blocking so that we don't deadlock against + * ourselves. + */ + sock2 = socket(PF_INET, SOCK_STREAM, 0); + ATF_REQUIRE_MSG(sock2 != -1, "socket # 2 failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(fcntl(sock2, F_SETFL, O_NONBLOCK) == 0, + "setting socket as nonblocking failed: %s", strerror(errno)); + ATF_REQUIRE_MSG( + (connect(sock2, (struct sockaddr *)&sin, sizeof(sin)) == 0 || + errno == EINPROGRESS), + "connect # 2 failed: %s", strerror(errno)); + + /* + * Now pick up the connection after sleeping a moment to make sure + * there's been time for some packets to go back and forth. + */ + ATF_REQUIRE_MSG(sleep(1) == 0, "sleep(1) <= 0"); + sock3 = accept(sock1, NULL, NULL); + ATF_REQUIRE_MSG(sock3 != -1, "accept failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(sleep(1) == 0, "sleep(1) <= 0"); + + FD_ZERO(&writefds); + FD_SET(sock2, &writefds); + FD_ZERO(&exceptfds); + FD_SET(sock2, &exceptfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = select(sock2 + 1, NULL, &writefds, &exceptfds, &tv); + ATF_REQUIRE_MSG(ret != -1, "select failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(!FD_ISSET(sock2, &exceptfds), + "select: exception occurred with sock2"); + ATF_REQUIRE_MSG(FD_ISSET(sock2, &writefds), + "not writable"); + + close(sock1); + fdp[0] = sock2; + fdp[1] = sock3; + fdp[2] = sock1; +} + +static void +setup_udsstream(int *fdp) +{ + + ATF_REQUIRE_MSG(socketpair(PF_LOCAL, SOCK_STREAM, 0, fdp) == 0, + "socketpair failed: %s", strerror(errno)); +} + +static void +setup_udsdgram(int *fdp) +{ + + ATF_REQUIRE_MSG(socketpair(PF_LOCAL, SOCK_DGRAM, 0, fdp) == 0, + "socketpair failed: %s", strerror(errno)); +} + +static void +setup_pipe(int *fdp) +{ + + ATF_REQUIRE_MSG(pipe(fdp) == 0, "pipe failed: %s", strerror(errno)); +} + +static void +setup_fifo(int *fdp) +{ + char path[] = "0send_fifo.XXXXXXX"; + int fd1, fd2; + + ATF_REQUIRE_MSG(mkstemp(path) != -1, + "mkstemp failed: %s", strerror(errno)); + unlink(path); + + ATF_REQUIRE_MSG(mkfifo(path, 0600) == 0, + "mkfifo(\"%s\", 0600) failed: %s", path, strerror(errno)); + + fd1 = open(path, O_RDONLY | O_NONBLOCK); + ATF_REQUIRE_MSG(fd1 != -1, "open(\"%s\", O_RDONLY)", path); + + fd2 = open(path, O_WRONLY | O_NONBLOCK); + ATF_REQUIRE_MSG(fd2 != -1, "open(\"%s\", O_WRONLY)", path); + + fdp[0] = fd2; + fdp[1] = fd1; + fdp[2] = -1; +} + +static int fd[3]; + +static void +close_fds(int *fdp) +{ + unsigned int i; + + for (i = 0; i < nitems(fdp); i++) + close(fdp[i]); +} + +ATF_TC_WITHOUT_HEAD(udp_zero_send); +ATF_TC_BODY(udp_zero_send, tc) +{ + + setup_udp(fd); + try_0send(fd[0]); + close_fds(fd); +} + +ATF_TC_WITHOUT_HEAD(udp_zero_write); +ATF_TC_BODY(udp_zero_write, tc) +{ + + setup_udp(fd); + try_0write(fd[0]); + close_fds(fd); +} + +ATF_TC_WITHOUT_HEAD(tcp_zero_send); +ATF_TC_BODY(tcp_zero_send, tc) +{ + + setup_tcp(fd); + try_0send(fd[0]); + close_fds(fd); +} + +ATF_TC_WITHOUT_HEAD(tcp_zero_write); +ATF_TC_BODY(tcp_zero_write, tc) +{ + + setup_tcp(fd); + try_0write(fd[0]); + close_fds(fd); +} + +ATF_TC_WITHOUT_HEAD(udsstream_zero_send); +ATF_TC_BODY(udsstream_zero_send, tc) +{ + + setup_udsstream(fd); + try_0send(fd[0]); + close_fds(fd); +} + +ATF_TC_WITHOUT_HEAD(udsstream_zero_write); +ATF_TC_BODY(udsstream_zero_write, tc) +{ + + setup_udsstream(fd); + try_0write(fd[0]); + close_fds(fd); +} + +ATF_TC_WITHOUT_HEAD(udsdgram_zero_send); +ATF_TC_BODY(udsdgram_zero_send, tc) +{ + + setup_udsdgram(fd); + try_0send(fd[0]); + close_fds(fd); +} + +ATF_TC_WITHOUT_HEAD(udsdgram_zero_write); +ATF_TC_BODY(udsdgram_zero_write, tc) +{ + + setup_udsdgram(fd); + try_0write(fd[0]); + close_fds(fd); +} + +ATF_TC_WITHOUT_HEAD(pipe_zero_write); +ATF_TC_BODY(pipe_zero_write, tc) +{ + + setup_pipe(fd); + try_0write(fd[0]); + close_fds(fd); +} + +ATF_TC_WITHOUT_HEAD(fifo_zero_write); +ATF_TC_BODY(fifo_zero_write, tc) +{ + + setup_fifo(fd); + try_0write(fd[0]); + close_fds(fd); +} + +ATF_TP_ADD_TCS(tp) +{ + + srandomdev(); + + ATF_TP_ADD_TC(tp, udp_zero_send); + ATF_TP_ADD_TC(tp, udp_zero_write); + ATF_TP_ADD_TC(tp, tcp_zero_send); + ATF_TP_ADD_TC(tp, tcp_zero_write); + ATF_TP_ADD_TC(tp, udsstream_zero_write); + ATF_TP_ADD_TC(tp, udsstream_zero_send); + ATF_TP_ADD_TC(tp, udsdgram_zero_write); + ATF_TP_ADD_TC(tp, udsdgram_zero_send); + ATF_TP_ADD_TC(tp, pipe_zero_write); + ATF_TP_ADD_TC(tp, fifo_zero_write); + + return (atf_no_error()); +} diff --git a/tests/sys/vfs/Makefile b/tests/sys/vfs/Makefile new file mode 100644 index 0000000..7cd908b --- /dev/null +++ b/tests/sys/vfs/Makefile @@ -0,0 +1,7 @@ +# $FreeBSD$ + +TESTSDIR= ${TESTSBASE}/sys/vfs + +TAP_TESTS_SH+= trailing_slash_test + +.include <bsd.test.mk> diff --git a/tests/sys/vfs/trailing_slash_test.sh b/tests/sys/vfs/trailing_slash_test.sh new file mode 100755 index 0000000..28c7f5f --- /dev/null +++ b/tests/sys/vfs/trailing_slash_test.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# +# $FreeBSD$ +# +# Tests vfs_lookup()'s handling of trailing slashes for symlinks that +# point to files. See kern/21768 for details. Fixed in r193028. +# + +testfile=$(mktemp tmp.XXXXXX) || exit +testlink="testlink-$$" + +tests=" +$testfile:$testlink:$testfile:0 +$testfile:$testlink:$testfile/:1 +$testfile:$testlink:$testlink:0 +$testfile:$testlink:$testlink/:1 +$testfile/:$testlink:$testlink:1 +$testfile/:$testlink:$testlink/:1 +" + +trap "rm $testfile $testlink" EXIT + +set $tests +echo "1..$#" +n=1 +for testspec ; do + ( + IFS=: + set $testspec + unset IFS + ln -fs "$1" "$2" || exit 1 + cat "$3" >/dev/null 2>&1 + ret=$? + if [ "$ret" -eq "$4" ] ; then + echo "ok $n" + else + echo "fail $n - expected $4, got $ret" + fi + ) + n=$((n+1)) +done |