diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/sys/file/Makefile | 2 | ||||
| -rw-r--r-- | tests/sys/file/closefrom_test.c | 490 | ||||
| -rw-r--r-- | tests/sys/fs/fusefs/bad_server.cc | 8 | ||||
| -rw-r--r-- | tests/sys/fs/fusefs/bmap.cc | 87 | ||||
| -rw-r--r-- | tests/sys/fs/fusefs/mockfs.cc | 9 | ||||
| -rw-r--r-- | tests/sys/fs/fusefs/mockfs.hh | 7 | ||||
| -rw-r--r-- | tests/sys/fs/fusefs/notify.cc | 56 | ||||
| -rw-r--r-- | tests/sys/netinet/so_reuseport_lb_test.c | 148 | ||||
| -rw-r--r-- | tests/sys/netpfil/pf/Makefile | 2 | ||||
| -rw-r--r-- | tests/sys/netpfil/pf/anchor.sh | 46 | ||||
| -rw-r--r-- | tests/sys/netpfil/pf/ioctl/validation.c | 58 | ||||
| -rw-r--r-- | tests/sys/netpfil/pf/nat.sh | 47 | ||||
| -rw-r--r-- | tests/sys/netpfil/pf/proxy.sh | 61 | ||||
| -rw-r--r-- | tests/sys/netpfil/pf/sctp.sh | 18 | ||||
| -rwxr-xr-x | tests/sys/netpfil/pf/src_track.sh | 74 | ||||
| -rw-r--r-- | tests/sys/netpfil/pf/table.sh | 62 | ||||
| -rw-r--r-- | tests/sys/netpfil/pf/tftpd_inetd.conf | 28 | ||||
| -rw-r--r-- | tests/sys/netpfil/pf/tftpd_proxy_inetd.conf | 27 | 
18 files changed, 953 insertions, 277 deletions
| diff --git a/tests/sys/file/Makefile b/tests/sys/file/Makefile index beb4452359b7..c1fcef68d08e 100644 --- a/tests/sys/file/Makefile +++ b/tests/sys/file/Makefile @@ -3,7 +3,7 @@ TESTSDIR=	${TESTSBASE}/sys/file  BINDIR=		${TESTSDIR}  ATF_TESTS_C+=	path_test -TAP_TESTS_C+=	closefrom_test +ATF_TESTS_C+=	closefrom_test  TAP_TESTS_C+=	dup_test  ATF_TESTS_C+=	fcntlflags_test  TAP_TESTS_SH+=	flock_test diff --git a/tests/sys/file/closefrom_test.c b/tests/sys/file/closefrom_test.c index 7dccf858c772..a51e1630e24d 100644 --- a/tests/sys/file/closefrom_test.c +++ b/tests/sys/file/closefrom_test.c @@ -25,13 +25,13 @@   * SUCH DAMAGE.   */ -#include <sys/cdefs.h>  /*   * Regression tests for the closefrom(2) system call.   */  #include <sys/param.h>  #include <sys/mman.h> +#include <sys/stat.h>  #include <sys/user.h>  #include <sys/wait.h>  #include <errno.h> @@ -44,67 +44,57 @@  #include <string.h>  #include <unistd.h> -struct shared_info { -	int	failed; -	char	tag[64]; -	char	message[0]; -}; +#include <atf-c.h> -static int test = 1; +static char *shared_page; -static void -ok(const char *descr) +/* + * A variant of ATF_REQUIRE that is suitable for use in child + * processes.  Since these tests close stderr, errors are reported to + * a shared page of memory checked by the parent process. + */ +#define	CHILD_REQUIRE(exp) do {				\ +	if (!(exp))					\ +		child_fail_require(__FILE__, __LINE__,	\ +		    #exp " not met");			\ +} while (0) + +static __dead2 __printflike(3, 4) void +child_fail_require(const char *file, int line, const char *fmt, ...)  { +	FILE *fp; +	va_list ap; -	printf("ok %d - %s\n", test, descr); -	test++; -} +	fp = fmemopen(shared_page, PAGE_SIZE - 1, "w"); +	if (fp == NULL) +		exit(1); -static void -fail(const char *descr, const char *fmt, ...) -{ -	va_list ap; +	fprintf(fp, "%s:%d: ", file, line); +	va_start(ap, fmt); +	vfprintf(fp, fmt, ap); +	va_end(ap); +	fclose(fp); -	printf("not ok %d - %s", test, descr); -	test++; -	if (fmt) { -		va_start(ap, fmt); -		printf(" # "); -		vprintf(fmt, ap); -		va_end(ap); -	} -	printf("\n"); -	exit(1); +	exit(0);  } -#define	fail_err(descr)		fail((descr), "%s", strerror(errno)) - -static void -cok(struct shared_info *info, const char *descr) +static pid_t +child_fork(void)  { - -	info->failed = 0; -	strlcpy(info->tag, descr, sizeof(info->tag)); -	exit(0); +	shared_page = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | +	    MAP_SHARED, -1, 0); +	ATF_REQUIRE_MSG(shared_page != MAP_FAILED, "mmap: %s", strerror(errno)); +	return (atf_utils_fork());  }  static void -cfail(struct shared_info *info, const char *descr, const char *fmt, ...) +child_wait(pid_t pid)  { -	va_list ap; - -	info->failed = 1; -	strlcpy(info->tag, descr, sizeof(info->tag)); -	if (fmt) { -		va_start(ap, fmt); -		vsprintf(info->message, fmt, ap); -		va_end(ap); -	} -	exit(0); +	atf_utils_wait(pid, 0, "", ""); +	if (shared_page[0] != '\0') +		atf_tc_fail("%s", shared_page);  } -#define	cfail_err(info, descr)	cfail((info), (descr), "%s", strerror(errno)) -  /*   * Use kinfo_getfile() to fetch the list of file descriptors and figure out   * the highest open file descriptor. @@ -116,9 +106,8 @@ highest_fd(void)  	int cnt, i, highest;  	kif = kinfo_getfile(getpid(), &cnt); -	if (kif == NULL) -		fail_err("kinfo_getfile"); -	highest = INT_MIN; +	ATF_REQUIRE_MSG(kif != NULL, "kinfo_getfile: %s", strerror(errno)); +	highest = -1;  	for (i = 0; i < cnt; i++)  		if (kif[i].kf_fd > highest)  			highest = kif[i].kf_fd; @@ -132,262 +121,253 @@ devnull(void)  	int fd;  	fd = open(_PATH_DEVNULL, O_RDONLY); -	if (fd < 0) -		fail_err("open(\" "_PATH_DEVNULL" \")"); +	ATF_REQUIRE_MSG(fd != -1, "open(\" "_PATH_DEVNULL" \"): %s", +	    strerror(errno));  	return (fd);  } -int -main(void) +ATF_TC_WITHOUT_HEAD(closefrom_simple); +ATF_TC_BODY(closefrom_simple, tc)  { -	struct shared_info *info; -	pid_t pid; -	int fd, flags, i, start; - -	printf("1..22\n"); +	int fd, start;  	/* We'd better start up with fd's 0, 1, and 2 open. */ -	start = devnull(); -	if (start == -1) -		fail("open", "bad descriptor %d", start); -	ok("open"); +	start = highest_fd(); +	ATF_REQUIRE(start >= 2); + +	fd = devnull(); +	ATF_REQUIRE(fd > start);  	/* Make sure highest_fd() works. */ -	fd = highest_fd(); -	if (start != fd) -		fail("highest_fd", "bad descriptor %d != %d", start, fd); -	ok("highest_fd"); - -	/* Try to use closefrom() for just closing fd 3. */ -	closefrom(start + 1); -	fd = highest_fd(); -	if (fd != start) -		fail("closefrom", "highest fd %d", fd); -	ok("closefrom"); +	ATF_REQUIRE_INTEQ(fd, highest_fd()); + +	/* Try to use closefrom() to close just the new fd. */ +	closefrom(fd); +	ATF_REQUIRE_INTEQ(start, highest_fd()); +} + +ATF_TC_WITHOUT_HEAD(closefrom_with_holes); +ATF_TC_BODY(closefrom_with_holes, tc) +{ +	int i, start; +	 +	start = highest_fd();  	/* Eat up 16 descriptors. */  	for (i = 0; i < 16; i++)  		(void)devnull(); -	fd = highest_fd(); -	if (fd != start + 16) -		fail("open 16", "highest fd %d", fd); -	ok("open 16"); + +	ATF_REQUIRE_INTEQ(start + 16, highest_fd());  	/* Close half of them. */ -	closefrom(11); -	fd = highest_fd(); -	if (fd != 10) -		fail("closefrom", "highest fd %d", fd); -	ok("closefrom"); - -	/* Explicitly close descriptors 6 and 8 to create holes. */ -	if (close(6) < 0 || close(8) < 0) -		fail_err("close2 "); -	ok("close 2"); - -	/* Verify that close on 6 and 8 fails with EBADF. */ -	if (close(6) == 0) -		fail("close(6)", "did not fail"); -	if (errno != EBADF) -		fail_err("close(6)"); -	ok("close(6)"); -	if (close(8) == 0) -		fail("close(8)", "did not fail"); -	if (errno != EBADF) -		fail_err("close(8)"); -	ok("close(8)"); - -	/* Close from 4 on. */ -	closefrom(4); -	fd = highest_fd(); -	if (fd != 3) -		fail("closefrom", "highest fd %d", fd); -	ok("closefrom"); - -	/* Allocate a small SHM region for IPC with our child. */ -	info = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_ANON | -	    MAP_SHARED, -1, 0); -	if (info == MAP_FAILED) -		fail_err("mmap"); -	ok("mmap"); - -	/* Fork a child process to test closefrom(0). */ -	pid = fork(); -	if (pid < 0) -		fail_err("fork"); +	closefrom(start + 9); +	ATF_REQUIRE_INTEQ(start + 8, highest_fd()); + +	/* Explicitly close two descriptors to create holes. */ +	ATF_REQUIRE_MSG(close(start + 3) == 0, "close(start + 3): %s", +	    strerror(errno)); +	ATF_REQUIRE_MSG(close(start + 5) == 0, "close(start + 5): %s", +	    strerror(errno)); + +	/* Verify that close on the closed descriptors fails with EBADF. */ +	ATF_REQUIRE_ERRNO(EBADF, close(start + 3) == -1); +	ATF_REQUIRE_ERRNO(EBADF, close(start + 5) == -1); + +	/* Close most remaining descriptors. */ +	closefrom(start + 2); +	ATF_REQUIRE_INTEQ(start + 1, highest_fd()); +} + +ATF_TC_WITHOUT_HEAD(closefrom_zero); +ATF_TC_BODY(closefrom_zero, tc) +{ +	pid_t pid; +	int fd; + +	/* Ensure standard descriptors are open. */ +	ATF_REQUIRE(highest_fd() >= 2); + +	pid = child_fork();  	if (pid == 0) {  		/* Child. */  		closefrom(0);  		fd = highest_fd(); -		if (fd >= 0) -			cfail(info, "closefrom(0)", "highest fd %d", fd); -		cok(info, "closefrom(0)"); +		CHILD_REQUIRE(fd == -1); +		exit(0);  	} -	if (wait(NULL) < 0) -		fail_err("wait"); -	if (info->failed) -		fail(info->tag, "%s", info->message); -	ok(info->tag); - -	/* Fork a child process to test closefrom(-1). */ -	pid = fork(); -	if (pid < 0) -		fail_err("fork"); + +	child_wait(pid); +} + +ATF_TC_WITHOUT_HEAD(closefrom_negative_one); +ATF_TC_BODY(closefrom_negative_one, tc) +{ +	pid_t pid; +	int fd; + +	/* Ensure standard descriptors are open. */ +	ATF_REQUIRE(highest_fd() >= 2); + +	pid = child_fork();  	if (pid == 0) {  		/* Child. */  		closefrom(-1);  		fd = highest_fd(); -		if (fd >= 0) -			cfail(info, "closefrom(-1)", "highest fd %d", fd); -		cok(info, "closefrom(-1)"); +		CHILD_REQUIRE(fd == -1); +		exit(0);  	} -	if (wait(NULL) < 0) -		fail_err("wait"); -	if (info->failed) -		fail(info->tag, "%s", info->message); -	ok(info->tag); - -	/* Dup stdout to 6. */ -	if (dup2(1, 6) < 0) -		fail_err("dup2"); -	fd = highest_fd(); -	if (fd != 6) -		fail("dup2", "highest fd %d", fd); -	ok("dup2"); + +	child_wait(pid); +} + +ATF_TC_WITHOUT_HEAD(closefrom_in_holes); +ATF_TC_BODY(closefrom_in_holes, tc) +{ +	int start; + +	start = highest_fd(); +	ATF_REQUIRE(start >= 2); + +	/* Dup stdout to a higher fd. */ +	ATF_REQUIRE_INTEQ(start + 4, dup2(1, start + 4)); +	ATF_REQUIRE_INTEQ(start + 4, highest_fd());  	/* Do a closefrom() starting in a hole. */ -	closefrom(4); -	fd = highest_fd(); -	if (fd != 3) -		fail("closefrom", "highest fd %d", fd); -	ok("closefrom"); +	closefrom(start + 2); +	ATF_REQUIRE_INTEQ(start, highest_fd());  	/* Do a closefrom() beyond our highest open fd. */ -	closefrom(32); -	fd = highest_fd(); -	if (fd != 3) -		fail("closefrom", "highest fd %d", fd); -	ok("closefrom"); +	closefrom(start + 32); +	ATF_REQUIRE_INTEQ(start, highest_fd()); +} + +ATF_TC_WITHOUT_HEAD(closerange_basic); +ATF_TC_BODY(closerange_basic, tc) +{ +	struct stat sb; +	int i, start; -	/* Chew up another 8 fd */ +	start = highest_fd(); + +	/* Open 8 file descriptors */  	for (i = 0; i < 8; i++)  		(void)devnull(); -	fd = highest_fd(); -	start = fd - 7; +	ATF_REQUIRE_INTEQ(start + 8, highest_fd());  	/* close_range() a hole in the middle */ -	close_range(start + 3, start + 5, 0); -	for (i = start + 3; i < start + 6; ++i) { -		if (close(i) == 0 || errno != EBADF) { -			--i; -			break; -		} -	} -	if (i != start + 6) -		fail("close_range", "failed to close at %d in %d - %d", i + 1, -		    start + 3, start + 6); -	ok("close_range"); +	ATF_REQUIRE_INTEQ(0, close_range(start + 3, start + 5, 0)); +	for (i = start + 3; i < start + 6; ++i) +		ATF_REQUIRE_ERRNO(EBADF, fstat(i, &sb) == -1);  	/* close_range from the middle of the hole */ -	close_range(start + 4, start + 6, 0); -	if ((i = highest_fd()) != fd) -		fail("close_range", "highest fd %d", i); -	ok("close_range"); +	ATF_REQUIRE_INTEQ(0, close_range(start + 4, start + 6, 0)); +	ATF_REQUIRE_INTEQ(start + 8, highest_fd());  	/* close_range to the end; effectively closefrom(2) */ -	close_range(start + 3, ~0L, 0); -	if ((i = highest_fd()) != start + 2) -		fail("close_range", "highest fd %d", i); -	ok("close_range"); +	ATF_REQUIRE_INTEQ(0, close_range(start + 3, ~0L, 0)); +	ATF_REQUIRE_INTEQ(start + 2, highest_fd());  	/* Now close the rest */ -	close_range(start, start + 4, 0); -	fd = highest_fd(); -	if (fd != 3) -		fail("close_range", "highest fd %d", fd); -	ok("close_range"); - -	/* Fork a child process to test closefrom(0) twice. */ -	pid = fork(); -	if (pid < 0) -		fail_err("fork"); +	ATF_REQUIRE_INTEQ(0, close_range(start + 1, start + 4, 0)); +	ATF_REQUIRE_INTEQ(start, highest_fd()); +} + +ATF_TC_WITHOUT_HEAD(closefrom_zero_twice); +ATF_TC_BODY(closefrom_zero_twice, tc) +{ +	pid_t pid; +	int fd; + +	/* Ensure standard descriptors are open. */ +	ATF_REQUIRE(highest_fd() >= 2); + +	pid = child_fork();  	if (pid == 0) {  		/* Child. */  		closefrom(0); +		fd = highest_fd(); +		CHILD_REQUIRE(fd == -1);  		closefrom(0); -		cok(info, "closefrom(0)"); +		fd = highest_fd(); +		CHILD_REQUIRE(fd == -1); +		exit(0);  	} -	if (wait(NULL) < 0) -		fail_err("wait"); -	if (info->failed) -		fail(info->tag, "%s", info->message); -	ok(info->tag); -	/* test CLOSE_RANGE_CLOEXEC */ +	child_wait(pid); +} + +static void +require_fd_flag(int fd, const char *descr, const char *descr2, int flag, +    bool set) +{ +	int flags; + +	flags = fcntl(fd, F_GETFD); +	ATF_REQUIRE_MSG(flags >= 0, "fcntl(.., F_GETFD): %s", strerror(errno)); + +	if (set) { +		ATF_REQUIRE_MSG((flags & flag) == flag, +		    "%s did not set %s on fd %d", descr, descr2, fd); +	} else { +		ATF_REQUIRE_MSG((flags & flag) == 0, +		    "%s set %s when it should not have on fd %d", descr, descr2, +		    fd); +	} +} + +ATF_TC_WITHOUT_HEAD(closerange_CLOEXEC); +ATF_TC_BODY(closerange_CLOEXEC, tc) +{ +	int i, start; + +	start = highest_fd(); +	ATF_REQUIRE(start >= 2); +  	for (i = 0; i < 8; i++)  		(void)devnull(); -	fd = highest_fd(); -	start = fd - 8; -	if (close_range(start + 1, start + 4, CLOSE_RANGE_CLOEXEC) < 0) -		fail_err("close_range(..., CLOSE_RANGE_CLOEXEC)"); -	flags = fcntl(start, F_GETFD); -	if (flags < 0) -		fail_err("fcntl(.., F_GETFD)"); -	if ((flags & FD_CLOEXEC) != 0) -		fail("close_range", "CLOSE_RANGE_CLOEXEC set close-on-exec " -		    "when it should not have on fd %d", start); -	for (i = start + 1; i <= start + 4; i++) { -		flags = fcntl(i, F_GETFD); -		if (flags < 0) -			fail_err("fcntl(.., F_GETFD)"); -		if ((flags & FD_CLOEXEC) == 0) -			fail("close_range", "CLOSE_RANGE_CLOEXEC did not set " -			    "close-on-exec on fd %d", i); -	} -	for (; i < start + 8; i++) { -		flags = fcntl(i, F_GETFD); -		if (flags < 0) -			fail_err("fcntl(.., F_GETFD)"); -		if ((flags & FD_CLOEXEC) != 0) -			fail("close_range", "CLOSE_RANGE_CLOEXEC set close-on-exec " -			    "when it should not have on fd %d", i); +	ATF_REQUIRE_INTEQ(start + 8, highest_fd()); + +	ATF_REQUIRE_INTEQ(0, close_range(start + 2, start + 5, +	    CLOSE_RANGE_CLOEXEC)); +	for (i = 1; i < 9; i++) { +		require_fd_flag(start + i, "CLOSE_RANGE_CLOEXEC", +		    "close-on-exec", FD_CLOEXEC, i >= 2 && i <= 5);  	} -	if (close_range(start, start + 8, 0) < 0) -		fail_err("close_range"); -	ok("close_range(..., CLOSE_RANGE_CLOEXEC)"); +	ATF_REQUIRE_INTEQ(0, close_range(start + 1, start + 8, 0)); +} + +ATF_TC_WITHOUT_HEAD(closerange_CLOFORK); +ATF_TC_BODY(closerange_CLOFORK, tc) +{ +	int i, start; + +	start = highest_fd(); +	ATF_REQUIRE(start >= 2); -	/* test CLOSE_RANGE_CLOFORK */  	for (i = 0; i < 8; i++)  		(void)devnull(); -	fd = highest_fd(); -	start = fd - 8; -	if (close_range(start + 1, start + 4, CLOSE_RANGE_CLOFORK) < 0) -		fail_err("close_range(..., CLOSE_RANGE_CLOFORK)"); -	flags = fcntl(start, F_GETFD); -	if (flags < 0) -		fail_err("fcntl(.., F_GETFD)"); -	if ((flags & FD_CLOFORK) != 0) -		fail("close_range", "CLOSE_RANGE_CLOFORK set close-on-exec " -		    "when it should not have on fd %d", start); -	for (i = start + 1; i <= start + 4; i++) { -		flags = fcntl(i, F_GETFD); -		if (flags < 0) -			fail_err("fcntl(.., F_GETFD)"); -		if ((flags & FD_CLOFORK) == 0) -			fail("close_range", "CLOSE_RANGE_CLOFORK did not set " -			    "close-on-exec on fd %d", i); -	} -	for (; i < start + 8; i++) { -		flags = fcntl(i, F_GETFD); -		if (flags < 0) -			fail_err("fcntl(.., F_GETFD)"); -		if ((flags & FD_CLOFORK) != 0) -			fail("close_range", "CLOSE_RANGE_CLOFORK set close-on-exec " -			    "when it should not have on fd %d", i); +	ATF_REQUIRE_INTEQ(start + 8, highest_fd()); + +	ATF_REQUIRE_INTEQ(0, close_range(start + 2, start + 5, +	    CLOSE_RANGE_CLOFORK)); +	for (i = 1; i < 9; i++) { +		require_fd_flag(start + i, "CLOSE_RANGE_CLOFORK", +		    "close-on-fork", FD_CLOFORK, i >= 2 && i <= 5);  	} -	if (close_range(start, start + 8, 0) < 0) -		fail_err("close_range"); -	ok("close_range(..., CLOSE_RANGE_CLOFORK)"); +	ATF_REQUIRE_INTEQ(0, close_range(start + 1, start + 8, 0)); +} -	return (0); +ATF_TP_ADD_TCS(tp) +{ +	ATF_TP_ADD_TC(tp, closefrom_simple); +	ATF_TP_ADD_TC(tp, closefrom_with_holes); +	ATF_TP_ADD_TC(tp, closefrom_zero); +	ATF_TP_ADD_TC(tp, closefrom_negative_one); +	ATF_TP_ADD_TC(tp, closefrom_in_holes); +	ATF_TP_ADD_TC(tp, closerange_basic); +	ATF_TP_ADD_TC(tp, closefrom_zero_twice); +	ATF_TP_ADD_TC(tp, closerange_CLOEXEC); +	ATF_TP_ADD_TC(tp, closerange_CLOFORK); + +	return (atf_no_error());  } diff --git a/tests/sys/fs/fusefs/bad_server.cc b/tests/sys/fs/fusefs/bad_server.cc index c3d195735446..825523cac2bb 100644 --- a/tests/sys/fs/fusefs/bad_server.cc +++ b/tests/sys/fs/fusefs/bad_server.cc @@ -64,12 +64,12 @@ TEST_F(BadServer, ShortWrite)  	out.header.error = 0;  	out.header.unique = 0;			// Asynchronous notification  	out.expected_errno = EINVAL; -	m_mock->write_response(out);  	/* -	 * Tell the event loop to quit.  The kernel has already disconnected us +	 * Tell the event loop to quit.  The kernel will disconnect us  	 * because of the short write.  	 */ -	m_mock->m_quit = true; +	m_mock->m_expect_unmount = true; +	m_mock->write_response(out);  }  /* @@ -98,7 +98,7 @@ TEST_F(BadServer, ErrorWithPayload)  		out.push_back(std::move(out1));  		// The kernel may disconnect us for bad behavior, so don't try -		// to read any more. +		// to read or write any more.  		m_mock->m_quit = true;  	})); diff --git a/tests/sys/fs/fusefs/bmap.cc b/tests/sys/fs/fusefs/bmap.cc index 30612079657d..e61dadb6d79e 100644 --- a/tests/sys/fs/fusefs/bmap.cc +++ b/tests/sys/fs/fusefs/bmap.cc @@ -178,6 +178,93 @@ TEST_F(Bmap, default_)  }  /* + * The server returns an error for some reason for FUSE_BMAP.  fusefs should + * faithfully report that error up to the caller. + */ +TEST_F(Bmap, einval) +{ +	struct fiobmap2_arg arg; +	const off_t filesize = 1 << 30; +	int64_t lbn = 100; +	const ino_t ino = 42; +	int fd; + +	expect_lookup(RELPATH, 42, filesize); +	expect_open(ino, 0, 1); +	EXPECT_CALL(*m_mock, process( +		ResultOf([=](auto in) { +			return (in.header.opcode == FUSE_BMAP && +				in.header.nodeid == ino); +		}, Eq(true)), +		_) +	).WillOnce(Invoke(ReturnErrno(EINVAL))); + +	fd = open(FULLPATH, O_RDWR); +	ASSERT_LE(0, fd) << strerror(errno); + +	arg.bn = lbn; +	arg.runp = -1; +	arg.runb = -1; +	ASSERT_EQ(-1, ioctl(fd, FIOBMAP2, &arg)); +	EXPECT_EQ(EINVAL, errno); + +	leak(fd); +} + +/* + * Even if the server returns EINVAL during VOP_BMAP, we should still be able + * to successfully read a block.  This is a regression test for + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=264196 .  The bug did not + * lie in fusefs, but this is a convenient place for a regression test. + */ +TEST_F(Bmap, spurious_einval) +{ +	const off_t filesize = 4ull << 30; +	const ino_t ino = 42; +	int fd, r; +	char buf[1]; + +	expect_lookup(RELPATH, 42, filesize); +	expect_open(ino, 0, 1); +	EXPECT_CALL(*m_mock, process( +		ResultOf([=](auto in) { +			return (in.header.opcode == FUSE_BMAP && +				in.header.nodeid == ino); +		}, Eq(true)), +		_) +	).WillRepeatedly(Invoke(ReturnErrno(EINVAL))); +	EXPECT_CALL(*m_mock, process( +	ResultOf([=](auto in) { +		return (in.header.opcode == FUSE_READ && +			in.header.nodeid == ino && +			in.body.read.offset == 0 && +			in.body.read.size == (uint64_t)m_maxbcachebuf); +		}, Eq(true)), +		_) +	).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { +		size_t osize = in.body.read.size; + +		assert(osize < sizeof(out.body.bytes)); +		out.header.len = sizeof(struct fuse_out_header) + osize; +		bzero(out.body.bytes, osize); +	}))); + +	fd = open(FULLPATH, O_RDWR); +	ASSERT_LE(0, fd) << strerror(errno); + +	/* +	 * Read the same block multiple times.  On a system affected by PR +	 * 264196 , the second read will fail. +	 */ +	r = read(fd, buf, sizeof(buf)); +	EXPECT_EQ(r, 1) << strerror(errno); +	r = read(fd, buf, sizeof(buf)); +	EXPECT_EQ(r, 1) << strerror(errno); +	r = read(fd, buf, sizeof(buf)); +	EXPECT_EQ(r, 1) << strerror(errno); +} + +/*   * VOP_BMAP should not query the server for the file's size, even if its cached   * attributes have expired.   * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=256937 diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc index e8081dea9604..b6a32d9b60af 100644 --- a/tests/sys/fs/fusefs/mockfs.cc +++ b/tests/sys/fs/fusefs/mockfs.cc @@ -433,7 +433,8 @@ MockFS::MockFS(int max_read, int max_readahead, bool allow_other,  	  m_child_pid(-1),  	  m_maxwrite(MIN(max_write, max_max_write)),  	  m_nready(-1), -	  m_quit(false) +	  m_quit(false), +	  m_expect_unmount(false)  {  	struct sigaction sa;  	struct iovec *iov = NULL; @@ -827,10 +828,12 @@ void MockFS::loop() {  	}  } -int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen) +int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen, +		int expected_errno)  {  	std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out); +	out->expected_errno = expected_errno;  	out->header.unique = 0;	/* 0 means asynchronous notification */  	out->header.error = FUSE_NOTIFY_INVAL_ENTRY;  	out->body.inval_entry.parent = parent; @@ -977,7 +980,7 @@ void MockFS::read_request(mockfs_buf_in &in, ssize_t &res) {  	}  	res = read(m_fuse_fd, &in, sizeof(in)); -	if (res < 0 && !m_quit) { +	if (res < 0 && errno != EBADF && !m_quit && !m_expect_unmount) {  		m_quit = true;  		FAIL() << "read: " << strerror(errno);  	} diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh index ba6f7fded9d0..f98a5337c9d1 100644 --- a/tests/sys/fs/fusefs/mockfs.hh +++ b/tests/sys/fs/fusefs/mockfs.hh @@ -360,6 +360,9 @@ class MockFS {  	/* Tell the daemon to shut down ASAP */  	bool m_quit; +	/* Tell the daemon that the server might forcibly unmount us */ +	bool m_expect_unmount; +  	/* Create a new mockfs and mount it to a tempdir */  	MockFS(int max_read, int max_readahead, bool allow_other,  		bool default_permissions, bool push_symlinks_in, bool ro, @@ -390,8 +393,10 @@ class MockFS {  	 * @param	parent	Parent directory's inode number  	 * @param	name	name of dirent to invalidate  	 * @param	namelen	size of name, including the NUL +	 * @param	expected_errno The error that write() should return  	 */ -	int notify_inval_entry(ino_t parent, const char *name, size_t namelen); +	int notify_inval_entry(ino_t parent, const char *name, size_t namelen, +			int expected_errno = 0);  	/*  	 * Send an asynchronous notification to invalidate an inode's cached diff --git a/tests/sys/fs/fusefs/notify.cc b/tests/sys/fs/fusefs/notify.cc index 1e22bde13db7..d370a1e6e706 100644 --- a/tests/sys/fs/fusefs/notify.cc +++ b/tests/sys/fs/fusefs/notify.cc @@ -385,6 +385,27 @@ TEST_F(Notify, inval_inode_with_clean_cache)  	leak(fd);  } +/* + * Attempting to invalidate an entry or inode after unmounting should fail, but + * nothing bad should happen. + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290519 + */ +TEST_F(Notify, notify_after_unmount) +{ +	const static char *name = "foo"; +	struct inval_entry_args iea; + +	expect_destroy(0); + +	m_mock->unmount(); + +	iea.mock = m_mock; +	iea.parent = FUSE_ROOT_ID; +	iea.name = name; +	iea.namelen = strlen(name); +	iea.mock->notify_inval_entry(iea.parent, iea.name, iea.namelen, ENODEV); +} +  /* FUSE_NOTIFY_STORE with a file that's not in the entry cache */  /* disabled because FUSE_NOTIFY_STORE is not yet implemented */  TEST_F(Notify, DISABLED_store_nonexistent) @@ -544,3 +565,38 @@ TEST_F(NotifyWriteback, inval_inode_attrs_only)  	leak(fd);  } + +/* + * Attempting asynchronous invalidation of an Entry before mounting the file + * system should fail, but nothing bad should happen. + * + * Note that invalidating an inode before mount goes through the same path, and + * is not separately tested. + * + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290519 + */ +TEST(PreMount, inval_entry_before_mount) +{ +	const static char name[] = "foo"; +	size_t namelen = strlen(name); +	struct mockfs_buf_out *out; +	int r; +	int fuse_fd; + +	fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR); +	ASSERT_GE(fuse_fd, 0) << strerror(errno); + +	out = new mockfs_buf_out; +	out->header.unique = 0;	/* 0 means asynchronous notification */ +	out->header.error = FUSE_NOTIFY_INVAL_ENTRY; +	out->body.inval_entry.parent = FUSE_ROOT_ID; +	out->body.inval_entry.namelen = namelen; +	strlcpy((char*)&out->body.bytes + sizeof(out->body.inval_entry), +		name, sizeof(out->body.bytes) - sizeof(out->body.inval_entry)); +	out->header.len = sizeof(out->header) + sizeof(out->body.inval_entry) + +		namelen; +	r = write(fuse_fd, out, out->header.len); +	EXPECT_EQ(-1, r); +	EXPECT_EQ(ENODEV, errno); +	delete out; +} diff --git a/tests/sys/netinet/so_reuseport_lb_test.c b/tests/sys/netinet/so_reuseport_lb_test.c index 0479bd070ca6..393a626af5a4 100644 --- a/tests/sys/netinet/so_reuseport_lb_test.c +++ b/tests/sys/netinet/so_reuseport_lb_test.c @@ -29,6 +29,8 @@  #include <sys/param.h>  #include <sys/event.h> +#include <sys/filio.h> +#include <sys/ioccom.h>  #include <sys/socket.h>  #include <netinet/in.h> @@ -556,6 +558,150 @@ ATF_TC_BODY(connect_bound, tc)  	close(s);  } +/* + * The kernel erroneously permits calling connect() on a UDP socket with + * SO_REUSEPORT_LB set.  Verify that packets sent to the bound address are + * dropped unless they come from the connected address. + */ +ATF_TC_WITHOUT_HEAD(connect_udp); +ATF_TC_BODY(connect_udp, tc) +{ +	struct sockaddr_in sin = { +		.sin_family = AF_INET, +		.sin_len = sizeof(sin), +		.sin_addr = { htonl(INADDR_LOOPBACK) }, +	}; +	ssize_t n; +	int error, len, s1, s2, s3; +	char ch; + +	s1 = socket(PF_INET, SOCK_DGRAM, 0); +	ATF_REQUIRE(s1 >= 0); +	s2 = socket(PF_INET, SOCK_DGRAM, 0); +	ATF_REQUIRE(s2 >= 0); +	s3 = socket(PF_INET, SOCK_DGRAM, 0); +	ATF_REQUIRE(s3 >= 0); + +	error = setsockopt(s1, SOL_SOCKET, SO_REUSEPORT_LB, (int[]){1}, +	    sizeof(int)); +	ATF_REQUIRE_MSG(error == 0, +	    "setsockopt(SO_REUSEPORT_LB) failed: %s", strerror(errno)); +	error = bind(s1, (struct sockaddr *)&sin, sizeof(sin)); +	ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno)); + +	error = bind(s2, (struct sockaddr *)&sin, sizeof(sin)); +	ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno)); + +	error = bind(s3, (struct sockaddr *)&sin, sizeof(sin)); +	ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno)); + +	/* Connect to an address not owned by s2. */ +	error = getsockname(s3, (struct sockaddr *)&sin, +	    (socklen_t[]){sizeof(sin)}); +	ATF_REQUIRE(error == 0); +	error = connect(s1, (struct sockaddr *)&sin, sizeof(sin)); +	ATF_REQUIRE_MSG(error == 0, "connect() failed: %s", strerror(errno)); + +	/* Try to send a packet to s1 from s2. */ +	error = getsockname(s1, (struct sockaddr *)&sin, +	    (socklen_t[]){sizeof(sin)}); +	ATF_REQUIRE(error == 0); + +	ch = 42; +	n = sendto(s2, &ch, sizeof(ch), 0, (struct sockaddr *)&sin, +	    sizeof(sin)); +	ATF_REQUIRE(n == 1); + +	/* Give the packet some time to arrive. */ +	usleep(100000); + +	/* s1 is connected to s3 and shouldn't receive from s2. */ +	error = ioctl(s1, FIONREAD, &len); +	ATF_REQUIRE(error == 0); +	ATF_REQUIRE_MSG(len == 0, "unexpected data available"); + +	/* ... but s3 can of course send to s1. */ +	n = sendto(s3, &ch, sizeof(ch), 0, (struct sockaddr *)&sin, +	    sizeof(sin)); +	ATF_REQUIRE(n == 1); +	usleep(100000); +	error = ioctl(s1, FIONREAD, &len); +	ATF_REQUIRE(error == 0); +	ATF_REQUIRE_MSG(len == 1, "expected data available"); +} + +/* + * The kernel erroneously permits calling connect() on a UDP socket with + * SO_REUSEPORT_LB set.  Verify that packets sent to the bound address are + * dropped unless they come from the connected address. + */ +ATF_TC_WITHOUT_HEAD(connect_udp6); +ATF_TC_BODY(connect_udp6, tc) +{ +	struct sockaddr_in6 sin6 = { +		.sin6_family = AF_INET6, +		.sin6_len = sizeof(sin6), +		.sin6_addr = IN6ADDR_LOOPBACK_INIT, +	}; +	ssize_t n; +	int error, len, s1, s2, s3; +	char ch; + +	s1 = socket(PF_INET6, SOCK_DGRAM, 0); +	ATF_REQUIRE(s1 >= 0); +	s2 = socket(PF_INET6, SOCK_DGRAM, 0); +	ATF_REQUIRE(s2 >= 0); +	s3 = socket(PF_INET6, SOCK_DGRAM, 0); +	ATF_REQUIRE(s3 >= 0); + +	error = setsockopt(s1, SOL_SOCKET, SO_REUSEPORT_LB, (int[]){1}, +	    sizeof(int)); +	ATF_REQUIRE_MSG(error == 0, +	    "setsockopt(SO_REUSEPORT_LB) failed: %s", strerror(errno)); +	error = bind(s1, (struct sockaddr *)&sin6, sizeof(sin6)); +	ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno)); + +	error = bind(s2, (struct sockaddr *)&sin6, sizeof(sin6)); +	ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno)); + +	error = bind(s3, (struct sockaddr *)&sin6, sizeof(sin6)); +	ATF_REQUIRE_MSG(error == 0, "bind() failed: %s", strerror(errno)); + +	/* Connect to an address not owned by s2. */ +	error = getsockname(s3, (struct sockaddr *)&sin6, +	    (socklen_t[]){sizeof(sin6)}); +	ATF_REQUIRE(error == 0); +	error = connect(s1, (struct sockaddr *)&sin6, sizeof(sin6)); +	ATF_REQUIRE_MSG(error == 0, "connect() failed: %s", strerror(errno)); + +	/* Try to send a packet to s1 from s2. */ +	error = getsockname(s1, (struct sockaddr *)&sin6, +	    (socklen_t[]){sizeof(sin6)}); +	ATF_REQUIRE(error == 0); + +	ch = 42; +	n = sendto(s2, &ch, sizeof(ch), 0, (struct sockaddr *)&sin6, +	    sizeof(sin6)); +	ATF_REQUIRE(n == 1); + +	/* Give the packet some time to arrive. */ +	usleep(100000); + +	/* s1 is connected to s3 and shouldn't receive from s2. */ +	error = ioctl(s1, FIONREAD, &len); +	ATF_REQUIRE(error == 0); +	ATF_REQUIRE_MSG(len == 0, "unexpected data available"); + +	/* ... but s3 can of course send to s1. */ +	n = sendto(s3, &ch, sizeof(ch), 0, (struct sockaddr *)&sin6, +	    sizeof(sin6)); +	ATF_REQUIRE(n == 1); +	usleep(100000); +	error = ioctl(s1, FIONREAD, &len); +	ATF_REQUIRE(error == 0); +	ATF_REQUIRE_MSG(len == 1, "expected data available"); +} +  ATF_TP_ADD_TCS(tp)  {  	ATF_TP_ADD_TC(tp, basic_ipv4); @@ -566,6 +712,8 @@ ATF_TP_ADD_TCS(tp)  	ATF_TP_ADD_TC(tp, bind_without_listen);  	ATF_TP_ADD_TC(tp, connect_not_bound);  	ATF_TP_ADD_TC(tp, connect_bound); +	ATF_TP_ADD_TC(tp, connect_udp); +	ATF_TP_ADD_TC(tp, connect_udp6);  	return (atf_no_error());  } diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile index b363e0b17c76..9416f6abbdf1 100644 --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -90,6 +90,8 @@ ${PACKAGE}FILES+=	\  			pft_ether.py \  			pft_read_ipfix.py \  			rdr-srcport.py \ +			tftpd_inetd.conf \ +			tftpd_proxy_inetd.conf \  			utils.subr \  			utils.py diff --git a/tests/sys/netpfil/pf/anchor.sh b/tests/sys/netpfil/pf/anchor.sh index 64ca84b34c3d..f321c742788e 100644 --- a/tests/sys/netpfil/pf/anchor.sh +++ b/tests/sys/netpfil/pf/anchor.sh @@ -123,6 +123,51 @@ nested_anchor_cleanup()  	pft_cleanup  } +atf_test_case "deeply_nested" "cleanup" +deeply_nested_head() +{ +	atf_set descr 'Test setting and retrieving deeply nested anchors' +	atf_set require.user root +} + +deeply_nested_body() +{ +	pft_init + +	epair=$(vnet_mkepair) +	vnet_mkjail alcatraz ${epair}a + +	pft_set_rules alcatraz \ +		"anchor \"foo\" { \n\ +			anchor \"bar\" { \n\ +				anchor \"foobar\" { \n\ +					pass on ${epair}a \n\ +				} \n\ +				anchor \"quux\" { \n\ +					pass on ${epair}a \n\ +				} \n\ +			} \n\ +			anchor \"baz\" { \n\ +				pass on ${epair}a \n\ +			} \n\ +			anchor \"qux\" { \n\ +				pass on ${epair}a \n\ +			} \n\ +		}" + +	atf_check -s exit:0 -o \ +	    inline:"  foo\n  foo/bar\n  foo/bar/foobar\n  foo/bar/quux\n  foo/baz\n  foo/qux\n" \ +	    jexec alcatraz pfctl -sA + +	atf_check -s exit:0 -o inline:"  foo/bar/foobar\n  foo/bar/quux\n" \ +	    jexec alcatraz pfctl -a foo/bar -sA +} + +deeply_nested_cleanup() +{ +	pft_cleanup +} +  atf_test_case "wildcard" "cleanup"  wildcard_head()  { @@ -498,6 +543,7 @@ atf_init_test_cases()  	atf_add_test_case "pr183198"  	atf_add_test_case "pr279225"  	atf_add_test_case "nested_anchor" +	atf_add_test_case "deeply_nested"  	atf_add_test_case "wildcard"  	atf_add_test_case "nested_label"  	atf_add_test_case "quick" diff --git a/tests/sys/netpfil/pf/ioctl/validation.c b/tests/sys/netpfil/pf/ioctl/validation.c index 3e03163cc752..bb060e22f3a0 100644 --- a/tests/sys/netpfil/pf/ioctl/validation.c +++ b/tests/sys/netpfil/pf/ioctl/validation.c @@ -194,6 +194,38 @@ ATF_TC_CLEANUP(gettables, tc)  	COMMON_CLEANUP();  } +ATF_TC_WITH_CLEANUP(clrtables); +ATF_TC_HEAD(clrtables, tc) +{ +	atf_tc_set_md_var(tc, "require.user", "root"); +	atf_tc_set_md_var(tc, "require.kmods", "pf"); +} + +ATF_TC_BODY(clrtables, tc) +{ +	struct pfioc_table io; +	struct pfr_table tbl; +	int flags; + +	COMMON_HEAD(); + +	flags = 0; + +	memset(&io, '/', sizeof(io)); +	io.pfrio_flags = flags; +	io.pfrio_buffer = &tbl; +	io.pfrio_esize = 0; +	io.pfrio_size = 1; + +	if (ioctl(dev, DIOCRCLRTABLES, &io) == 0) +		atf_tc_fail("Request with unterminated anchor name succeeded"); +} + +ATF_TC_CLEANUP(clrtables, tc) +{ +	COMMON_CLEANUP(); +} +  ATF_TC_WITH_CLEANUP(gettstats);  ATF_TC_HEAD(gettstats, tc)  { @@ -949,11 +981,36 @@ ATF_TC_CLEANUP(natlook, tc)  	COMMON_CLEANUP();  } +ATF_TC_WITH_CLEANUP(addstate); +ATF_TC_HEAD(addstate, tc) +{ +	atf_tc_set_md_var(tc, "require.user", "root"); +	atf_tc_set_md_var(tc, "require.kmods", "pfsync"); +} + +ATF_TC_BODY(addstate, tc) +{ +	struct pfioc_state st; + +	COMMON_HEAD(); + +	memset(&st, 'a', sizeof(st)); +	st.state.timeout = PFTM_TCP_FIRST_PACKET; + +	ATF_CHECK_ERRNO(EINVAL, ioctl(dev, DIOCADDSTATE, &st) == -1); +} + +ATF_TC_CLEANUP(addstate, tc) +{ +	COMMON_CLEANUP(); +} +  ATF_TP_ADD_TCS(tp)  {  	ATF_TP_ADD_TC(tp, addtables);  	ATF_TP_ADD_TC(tp, deltables);  	ATF_TP_ADD_TC(tp, gettables); +	ATF_TP_ADD_TC(tp, clrtables);  	ATF_TP_ADD_TC(tp, getastats);  	ATF_TP_ADD_TC(tp, gettstats);  	ATF_TP_ADD_TC(tp, clrtstats); @@ -974,6 +1031,7 @@ ATF_TP_ADD_TCS(tp)  	ATF_TP_ADD_TC(tp, rpool_mtx);  	ATF_TP_ADD_TC(tp, rpool_mtx2);  	ATF_TP_ADD_TC(tp, natlook); +	ATF_TP_ADD_TC(tp, addstate);  	return (atf_no_error());  } diff --git a/tests/sys/netpfil/pf/nat.sh b/tests/sys/netpfil/pf/nat.sh index 1ef87cee3598..0824671fa0f1 100644 --- a/tests/sys/netpfil/pf/nat.sh +++ b/tests/sys/netpfil/pf/nat.sh @@ -477,15 +477,49 @@ no_addrs_random_cleanup()  	pft_cleanup  } -atf_test_case "nat_pass" "cleanup" -nat_pass_head() +atf_test_case "nat_pass_in" "cleanup" +nat_pass_in_head()  { -	atf_set descr 'IPv4 NAT on pass rule' +	atf_set descr 'IPv4 NAT on inbound pass rule'  	atf_set require.user root  	atf_set require.progs scapy  } -nat_pass_body() +nat_pass_in_body() +{ +	setup_router_server_ipv4 +	# Delete the route back to make sure that the traffic has been NAT-ed +	jexec server route del -net ${net_tester} ${net_server_host_router} +	# Provide routing back to the NAT address +	jexec server route add 203.0.113.0/24 ${net_server_host_router} +	jexec router route add 203.0.113.0/24 -iface ${epair_tester}b + +	pft_set_rules router \ +		"block" \ +		"pass in  on ${epair_tester}b inet proto tcp nat-to 203.0.113.0 keep state" \ +		"pass out on ${epair_server}a inet proto tcp keep state" + +	ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201 + +	jexec router pfctl -qvvsr +	jexec router pfctl -qvvss +	jexec router ifconfig +	jexec router netstat -rn +} + +nat_pass_in_cleanup() +{ +	pft_cleanup +} + +nat_pass_out_head() +{ +	atf_set descr 'IPv4 NAT on outbound pass rule' +	atf_set require.user root +	atf_set require.progs scapy +} + +nat_pass_out_body()  {  	setup_router_server_ipv4  	# Delete the route back to make sure that the traffic has been NAT-ed @@ -504,7 +538,7 @@ nat_pass_body()  	jexec router netstat -rn  } -nat_pass_cleanup() +nat_pass_out_cleanup()  {  	pft_cleanup  } @@ -874,7 +908,8 @@ atf_init_test_cases()  	atf_add_test_case "no_addrs_random"  	atf_add_test_case "map_e_compat"  	atf_add_test_case "map_e_pass" -	atf_add_test_case "nat_pass" +	atf_add_test_case "nat_pass_in" +	atf_add_test_case "nat_pass_out"  	atf_add_test_case "nat_match"  	atf_add_test_case "binat_compat"  	atf_add_test_case "binat_match" diff --git a/tests/sys/netpfil/pf/proxy.sh b/tests/sys/netpfil/pf/proxy.sh index 78ce25930c04..a07bb259b544 100644 --- a/tests/sys/netpfil/pf/proxy.sh +++ b/tests/sys/netpfil/pf/proxy.sh @@ -88,7 +88,68 @@ ftp_cleanup()  	pft_cleanup  } +atf_test_case "tftp" "cleanup" +tftp_head() +{ +	atf_set descr 'Test tftp-proxy' +	atf_set require.user root +} + +tftp_body() +{ +	pft_init + +	epair_client=$(vnet_mkepair) +	epair_link=$(vnet_mkepair) + +	ifconfig ${epair_client}a 192.0.2.2/24 up +	route add -net 198.51.100.0/24 192.0.2.1 + +	vnet_mkjail fwd ${epair_client}b ${epair_link}a +	jexec fwd ifconfig lo0 127.0.0.1/8 up +	jexec fwd ifconfig ${epair_client}b 192.0.2.1/24 up +	jexec fwd ifconfig ${epair_link}a 198.51.100.1/24 up +	jexec fwd ifconfig lo0 127.0.0.1/8 up +	jexec fwd sysctl net.inet.ip.forwarding=1 + +	vnet_mkjail srv ${epair_link}b +	jexec srv ifconfig ${epair_link}b 198.51.100.2/24 up +	jexec srv route add default 198.51.100.1 + +	# Start tftp server in srv +	jexec srv /usr/sbin/inetd -p ${PWD}/inetd-srv.pid \ +	    $(atf_get_srcdir)/tftpd_inetd.conf + +	jexec fwd /usr/sbin/inetd -p ${PWD}/inetd-fwd.pid \ +	    $(atf_get_srcdir)/tftpd_proxy_inetd.conf + +	jexec fwd pfctl -e +	pft_set_rules fwd \ +		"nat on ${epair_link}a inet from 192.0.2.0/24 to any -> (${epair_link}a)" \ +		"nat-anchor \"tftp-proxy/*\"" \ +		"rdr-anchor \"tftp-proxy/*\"" \ +		"rdr pass on ${epair_client}b proto udp from 192.0.2.0/24 to any port 69 -> 127.0.0.1 port 69" \ +		"anchor \"tftp-proxy/*\"" \ +		"pass out proto udp from 127.0.0.1 to any port 69" + +	# Create a dummy file to download +	echo 'foo' > /tmp/remote.txt +	echo 'get remote.txt local.txt' | tftp 198.51.100.2 + +	# Compare the downloaded file to the original +	if ! diff -q local.txt /tmp/remote.txt; +	then +		atf_fail 'Failed to retrieve file' +	fi +} + +tftp_cleanup() +{ +	pft_cleanup +} +  atf_init_test_cases()  {  	atf_add_test_case "ftp" +	atf_add_test_case "tftp"  } diff --git a/tests/sys/netpfil/pf/sctp.sh b/tests/sys/netpfil/pf/sctp.sh index 78055f5a9dd2..47bf40181b1b 100644 --- a/tests/sys/netpfil/pf/sctp.sh +++ b/tests/sys/netpfil/pf/sctp.sh @@ -29,9 +29,6 @@  sctp_init()  {  	pft_init -	if ! kldstat -q -m sctp; then -		atf_skip "This test requires SCTP" -	fi  }  atf_test_case "basic_v4" "cleanup" @@ -39,6 +36,7 @@ basic_v4_head()  {  	atf_set descr 'Basic SCTP connection over IPv4 passthrough'  	atf_set require.user root +	atf_set require.kmods sctp  }  basic_v4_body() @@ -112,6 +110,7 @@ basic_v6_head()  {  	atf_set descr 'Basic SCTP connection over IPv6'  	atf_set require.user root +	atf_set require.kmods sctp  }  basic_v6_body() @@ -186,6 +185,7 @@ reuse_head()  {  	atf_set descr 'Test handling dumb clients that reuse source ports'  	atf_set require.user root +	atf_set require.kmods sctp  }  reuse_body() @@ -244,6 +244,7 @@ abort_v4_head()  {  	atf_set descr 'Test sending ABORT messages'  	atf_set require.user root +	atf_set require.kmods sctp  }  abort_v4_body() @@ -302,6 +303,7 @@ abort_v6_head()  {  	atf_set descr 'Test sending ABORT messages over IPv6'  	atf_set require.user root +	atf_set require.kmods sctp  }  abort_v6_body() @@ -360,6 +362,7 @@ nat_v4_head()  {  	atf_set descr 'Test NAT-ing SCTP over IPv4'  	atf_set require.user root +	atf_set require.kmods sctp  }  nat_v4_body() @@ -412,6 +415,7 @@ nat_v6_head()  {  	atf_set descr 'Test NAT-ing SCTP over IPv6'  	atf_set require.user root +	atf_set require.kmods sctp  }  nat_v6_body() @@ -464,6 +468,7 @@ rdr_v4_head()  {  	atf_set descr 'Test rdr SCTP over IPv4'  	atf_set require.user root +	atf_set require.kmods sctp  }  rdr_v4_body() @@ -531,6 +536,7 @@ pfsync_head()  {  	atf_set descr 'Test pfsync-ing SCTP connections'  	atf_set require.user root +	atf_set require.kmods carp sctp  }  pfsync_body() @@ -563,10 +569,6 @@ pfsync_body()  	sctp_init  	pfsynct_init  	vnet_init_bridge -	if ! kldstat -q -m carp -	then -		atf_skip "This test requires carp" -	fi  	j="sctp:pfsync" @@ -722,6 +724,7 @@ timeout_head()  {  	atf_set descr 'Test setting and retrieving timeout values'  	atf_set require.user root +	atf_set require.kmods sctp  }  timeout_body() @@ -753,6 +756,7 @@ related_icmp_head()  {  	atf_set descr 'Verify that ICMP messages related to an SCTP connection are allowed'  	atf_set require.user root +	atf_set require.kmods sctp  }  related_icmp_body() diff --git a/tests/sys/netpfil/pf/src_track.sh b/tests/sys/netpfil/pf/src_track.sh index d86b4ce6c466..1b09030f6174 100755 --- a/tests/sys/netpfil/pf/src_track.sh +++ b/tests/sys/netpfil/pf/src_track.sh @@ -588,6 +588,79 @@ mixed_af_cleanup()  	pft_cleanup  } +atf_test_case "check_valid" "cleanup" +check_valid_head() +{ +	atf_set descr 'Test if source node is invalidated on change in redirection pool' +	atf_set require.user root +	atf_set require.progs python3 scapy +} + +check_valid_body() +{ +	setup_router_server_nat64 + +	# Clients will connect from another network behind the router. +	# This allows for using multiple source addresses. +	jexec router route add -6 ${net_clients_6}::/${net_clients_6_mask} ${net_tester_6_host_tester} + +	jexec server1 ifconfig ${epair_server1}b inet6 ${net_server1_6}::42:1/128 alias +	jexec server1 ifconfig ${epair_server1}b inet6 ${net_server1_6}::42:2/128 alias + +	jexec router pfctl -e +	pft_set_rules router \ +		"set debug loud " \ +		"set state-policy if-bound" \ +		"table <targets> { ${net_server1_6}::42:1 }" \ +		"pass in on ${epair_tester}b \ +			route-to { (${epair_server1}a <targets>) } \ +			sticky-address \ +			proto tcp \ +			keep state" + +	atf_check -s exit:0 ${common_dir}/pft_ping.py \ +		--sendif ${epair_tester}a --replyif ${epair_tester}a \ +		--fromaddr ${net_clients_6}::1 --to ${host_server_6} \ +		--ping-type=tcp3way --send-sport=4201 + +	# A source node is created using the original redirection target +	nodes=$(mktemp) || exit 1 +	jexec router pfctl -qvvsS | normalize_pfctl_s > $nodes +	node_regexp='2001:db8:44::1 -> 2001:db8:4201::42:1 .* states 1,.* route sticky-address' +	grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" + +	# Change contents of the redirection table +	echo ${net_server1_6}::42:2 | jexec router pfctl -Tr -t targets -f - + +	atf_check -s exit:0 ${common_dir}/pft_ping.py \ +		--sendif ${epair_tester}a --replyif ${epair_tester}a \ +		--fromaddr ${net_clients_6}::1 --to ${host_server_6} \ +		--ping-type=tcp3way --send-sport=4202 + +	# The original source node was deleted, a new one was created. +	# It has 1 states. +	jexec router pfctl -qvvsS | normalize_pfctl_s > $nodes +	node_regexp='2001:db8:44::1 -> 2001:db8:4201::42:2 .* states 1,.* route sticky-address' +	grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" + +	atf_check -s exit:0 ${common_dir}/pft_ping.py \ +		--sendif ${epair_tester}a --replyif ${epair_tester}a \ +		--fromaddr ${net_clients_6}::1 --to ${host_server_6} \ +		--ping-type=tcp3way --send-sport=4203 + +	# Without redirection table change the source node is reused. +	# It has 2 states. +	jexec router pfctl -qvvsS | normalize_pfctl_s > $nodes +	node_regexp='2001:db8:44::1 -> 2001:db8:4201::42:2 .* states 2,.* route sticky-address' +	grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" +} + +check_valid_cleanup() +{ +	pft_cleanup +} + +  atf_init_test_cases()  {  	atf_add_test_case "source_track" @@ -598,4 +671,5 @@ atf_init_test_cases()  	atf_add_test_case "sn_types_compat"  	atf_add_test_case "sn_types_pass"  	atf_add_test_case "mixed_af" +	atf_add_test_case "check_valid"  } diff --git a/tests/sys/netpfil/pf/table.sh b/tests/sys/netpfil/pf/table.sh index 69fe12fc9804..6761ce652beb 100644 --- a/tests/sys/netpfil/pf/table.sh +++ b/tests/sys/netpfil/pf/table.sh @@ -747,6 +747,67 @@ in_anchor_cleanup()  	pft_cleanup  } +atf_test_case "replace" "cleanup" +replace_head() +{ +	atf_set descr 'Test table replace command' +	atf_set require.user root +} + +replace_body() +{ +	pft_init +	pwd=$(pwd) + +	epair_send=$(vnet_mkepair) +	ifconfig ${epair_send}a 192.0.2.1/24 up + +	vnet_mkjail alcatraz ${epair_send}b +	jexec alcatraz ifconfig ${epair_send}b 192.0.2.2/24 up +	jexec alcatraz pfctl -e + +	pft_set_rules alcatraz \ +	    "table <foo> counters { 192.0.2.1 }" \ +	    "block all" \ +	    "pass in from <foo> to any" \ +	    "pass out from any to <foo>" \ +	    "set skip on lo" + +	atf_check -s exit:0 -o ignore ping -c 3 192.0.2.2 + +	# Replace the address +	atf_check -s exit:0 -e "match:1 addresses added." -e "match:1 addresses deleted." \ +	    jexec alcatraz pfctl -t foo -T replace 192.0.2.3 +	atf_check -s exit:0 -o "match:192.0.2.3" \ +	    jexec alcatraz pfctl -t foo -T show +	atf_check -s exit:2 -o ignore ping -c 3 192.0.2.2 + +	# Negated address +	atf_check -s exit:0 -e "match:1 addresses changed." \ +	    jexec alcatraz pfctl -t foo -T replace "!192.0.2.3" + +	# Now add 500 addresses +	for i in `seq 1 2`; do +		for j in `seq 1 250`; do +			echo "1.${i}.${j}.1" >> ${pwd}/foo.lst +		done +	done +	atf_check -s exit:0 -e "match:500 addresses added." -e "match:1 addresses deleted." \ +	    jexec alcatraz pfctl -t foo -T replace -f ${pwd}/foo.lst + +	atf_check -s exit:0 -o "not-match:192.0.2.3" \ +	    jexec alcatraz pfctl -t foo -T show + +	# Loading the same list produces no changes. +	atf_check -s exit:0 -e "match:no changes." \ +	    jexec alcatraz pfctl -t foo -T replace -f ${pwd}/foo.lst +} + +replace_cleanup() +{ +	pft_cleanup +} +  atf_init_test_cases()  {  	atf_add_test_case "v4_counters" @@ -765,4 +826,5 @@ atf_init_test_cases()  	atf_add_test_case "large"  	atf_add_test_case "show_recursive"  	atf_add_test_case "in_anchor" +	atf_add_test_case "replace"  } diff --git a/tests/sys/netpfil/pf/tftpd_inetd.conf b/tests/sys/netpfil/pf/tftpd_inetd.conf new file mode 100644 index 000000000000..3554d0a7fb08 --- /dev/null +++ b/tests/sys/netpfil/pf/tftpd_inetd.conf @@ -0,0 +1,28 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2025 Rubicon Communications, LLC (Netgate) +# +# 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. + +tftp	dgram	udp	wait	root	/usr/libexec/tftpd	tftpd -l -S -s /tmp +tftp	dgram	udp6	wait	root	/usr/libexec/tftpd	tftpd -l -S -s /tmp diff --git a/tests/sys/netpfil/pf/tftpd_proxy_inetd.conf b/tests/sys/netpfil/pf/tftpd_proxy_inetd.conf new file mode 100644 index 000000000000..aa5f000f3bba --- /dev/null +++ b/tests/sys/netpfil/pf/tftpd_proxy_inetd.conf @@ -0,0 +1,27 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2025 Rubicon Communications, LLC (Netgate) +# +# 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. + +tftp	dgram	udp	wait	root	/usr/libexec/tftp-proxy tftp-proxy | 
