aboutsummaryrefslogtreecommitdiff
path: root/tests/sys
diff options
context:
space:
mode:
Diffstat (limited to 'tests/sys')
-rw-r--r--tests/sys/fs/tarfs/tarfs_test.sh27
-rw-r--r--tests/sys/kern/unix_passfd_test.c29
-rw-r--r--tests/sys/kern/unix_seqpacket_test.c62
-rw-r--r--tests/sys/net/bpf/Makefile4
-rw-r--r--tests/sys/net/bpf/bpf.sh61
-rw-r--r--tests/sys/net/bpf/pcap-test.c268
-rw-r--r--tests/sys/netpfil/pf/divert-to.sh43
-rw-r--r--tests/sys/netpfil/pf/nat.sh60
-rw-r--r--tests/sys/sound/Makefile1
-rw-r--r--tests/sys/sound/polling.c205
10 files changed, 757 insertions, 3 deletions
diff --git a/tests/sys/fs/tarfs/tarfs_test.sh b/tests/sys/fs/tarfs/tarfs_test.sh
index d4de71271985..505bfc5325f0 100644
--- a/tests/sys/fs/tarfs/tarfs_test.sh
+++ b/tests/sys/fs/tarfs/tarfs_test.sh
@@ -396,6 +396,32 @@ tarfs_git_archive_cleanup() {
tarfs_cleanup
}
+atf_test_case tarfs_large cleanup
+tarfs_large_head() {
+ atf_set "descr" "Test support for large files"
+ atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
+ atf_set "timeout" "600"
+}
+tarfs_large_body() {
+ tarfs_setup
+ local tarball="${PWD}/tarfs_test.tar.zst"
+ local exp off
+ for exp in 31 32 33 34 35 36 ; do
+ for off in 1 0 ; do
+ local size=$(((1<<exp)-off))
+ atf_check truncate -s ${size} file
+ atf_check bsdtar -cf "${tarball}" --no-read-sparse --zstd file
+ atf_check mount -rt tarfs "${tarball}" "${mnt}"
+ atf_check -o inline:"${size}\n" stat -f%z "${mnt}"/file
+ atf_check umount "${mnt}"
+ done
+ done
+}
+tarfs_large_cleanup() {
+ tarfs_cleanup
+}
+
atf_init_test_cases() {
atf_add_test_case tarfs_basic
atf_add_test_case tarfs_basic_gnu
@@ -414,4 +440,5 @@ atf_init_test_cases() {
atf_add_test_case tarfs_long_names
atf_add_test_case tarfs_long_paths
atf_add_test_case tarfs_git_archive
+ atf_add_test_case tarfs_large
}
diff --git a/tests/sys/kern/unix_passfd_test.c b/tests/sys/kern/unix_passfd_test.c
index 7dc4541ad402..66bb406ea14e 100644
--- a/tests/sys/kern/unix_passfd_test.c
+++ b/tests/sys/kern/unix_passfd_test.c
@@ -1189,6 +1189,34 @@ ATF_TC_CLEANUP(cross_jail_dirfd, tc)
err(1, "jail_remove");
}
+ATF_TC_WITHOUT_HEAD(listening_socket);
+ATF_TC_BODY(listening_socket, tc)
+{
+ struct sockaddr_un sun;
+ int error, ls, s[2];
+
+ ls = socket(AF_UNIX, SOCK_STREAM, 0);
+ ATF_REQUIRE(ls != -1);
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_len = sizeof(sun);
+ sun.sun_family = AF_UNIX;
+ snprintf(sun.sun_path, sizeof(sun.sun_path), "listen.sock");
+ error = bind(ls, (struct sockaddr *)&sun, sizeof(sun));
+ ATF_REQUIRE_MSG(error == 0, "bind failed: %s", strerror(errno));
+ error = listen(ls, 0);
+
+ error = socketpair(AF_UNIX, SOCK_STREAM, 0, s);
+ ATF_REQUIRE_MSG(error == 0, "socketpair failed: %s", strerror(errno));
+
+ sendfd(s[0], ls);
+ sendfd(s[0], s[0]);
+ sendfd(s[0], s[1]);
+ close(ls);
+ close(s[0]);
+ close(s[1]);
+}
+
ATF_TP_ADD_TCS(tp)
{
@@ -1211,6 +1239,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, empty_rights_message);
ATF_TP_ADD_TC(tp, control_creates_records);
ATF_TP_ADD_TC(tp, cross_jail_dirfd);
+ ATF_TP_ADD_TC(tp, listening_socket);
return (atf_no_error());
}
diff --git a/tests/sys/kern/unix_seqpacket_test.c b/tests/sys/kern/unix_seqpacket_test.c
index b9a6be015241..27bd430430b4 100644
--- a/tests/sys/kern/unix_seqpacket_test.c
+++ b/tests/sys/kern/unix_seqpacket_test.c
@@ -1314,6 +1314,67 @@ ATF_TC_BODY(random_eor_and_waitall, tc)
free(params.records);
}
+/* See bug 290658. */
+#define PEEK_RACE_SIZE 10
+#define PEEK_RACE_TRIES 10000
+static void *
+peek_race_writer(void *args)
+{
+ struct timespec ts = {};
+ u_short seed[3];
+ char buf[PEEK_RACE_SIZE];
+ int fd = *(int *)args;
+
+ arc4random_buf(seed, sizeof(seed));
+ for (u_int i = 0; i < PEEK_RACE_TRIES; i++) {
+ ATF_REQUIRE_EQ(PEEK_RACE_SIZE,
+ send(fd, buf, sizeof(buf), MSG_EOR));
+ ts.tv_nsec = nrand48(seed) % 20;
+ (void)clock_nanosleep(CLOCK_MONOTONIC_FAST, 0, &ts, NULL);
+ }
+
+ return (NULL);
+}
+
+static void *
+peek_race_peeker(void *args)
+{
+ char buf[PEEK_RACE_SIZE * 10];
+ int fd = *(int *)args;
+
+ for (u_int i = 0; i < PEEK_RACE_TRIES; i++) {
+ ssize_t rcvd;
+
+ while ((rcvd = recv(fd, buf, sizeof(buf),
+ MSG_PEEK | MSG_DONTWAIT)) == -1)
+ ATF_REQUIRE(errno == EAGAIN);
+ ATF_REQUIRE(rcvd == PEEK_RACE_SIZE);
+
+ ATF_REQUIRE_EQ(PEEK_RACE_SIZE,
+ recv(fd, buf, sizeof(buf), 0));
+ }
+
+ return (NULL);
+}
+
+ATF_TC_WITHOUT_HEAD(peek_race);
+ATF_TC_BODY(peek_race, tc)
+{
+ pthread_t peeker, writer;
+ int sv[2];
+
+ do_socketpair(sv);
+
+ ATF_REQUIRE_EQ(0, pthread_create(&writer, NULL, peek_race_writer,
+ &sv[0]));
+ ATF_REQUIRE_EQ(0, pthread_create(&peeker, NULL, peek_race_peeker,
+ &sv[1]));
+ ATF_REQUIRE_EQ(0, pthread_join(writer, NULL));
+ ATF_REQUIRE_EQ(0, pthread_join(peeker, NULL));
+ close(sv[0]);
+ close(sv[1]);
+}
+
/*
* Main.
*/
@@ -1370,6 +1431,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, pipe_128k_8k);
ATF_TP_ADD_TC(tp, pipe_128k_128k);
ATF_TP_ADD_TC(tp, random_eor_and_waitall);
+ ATF_TP_ADD_TC(tp, peek_race);
return atf_no_error();
}
diff --git a/tests/sys/net/bpf/Makefile b/tests/sys/net/bpf/Makefile
index 9c8a25b15d16..641d1aaef676 100644
--- a/tests/sys/net/bpf/Makefile
+++ b/tests/sys/net/bpf/Makefile
@@ -7,8 +7,10 @@ BINDIR= ${TESTSDIR}
LIBADD+= nv
-PROGS= bpf_multi_read
+PROGS= bpf_multi_read pcap-test
LIBADD.bpf_multi_read+= pcap
+CFLAGS.pcap-test.c+= -Wno-cast-align
+LIBADD.pcap-test+= pcap
ATF_TESTS_SH= bpf
diff --git a/tests/sys/net/bpf/bpf.sh b/tests/sys/net/bpf/bpf.sh
index 2830c4862de9..f2d647b61de0 100644
--- a/tests/sys/net/bpf/bpf.sh
+++ b/tests/sys/net/bpf/bpf.sh
@@ -32,7 +32,6 @@ multi_read_head()
atf_set descr 'Test multiple readers on /dev/bpf'
atf_set require.user root
}
-
multi_read_body()
{
vnet_init
@@ -55,13 +54,71 @@ multi_read_body()
# Now let this run for 10 seconds
sleep 10
}
-
multi_read_cleanup()
{
vnet_cleanup
}
+atf_test_case "inject" "cleanup"
+inject_head()
+{
+ atf_set descr 'Catch packets, re-inject and check'
+ atf_set require.user root
+}
+inject_body()
+{
+ vnet_init
+
+ epair=$(vnet_mkepair)
+ ifconfig ${epair}a inet 192.0.2.1/24 up
+ vnet_mkjail alcatraz ${epair}b
+ jexec alcatraz ifconfig ${epair}b inet 192.0.2.2/24 up
+
+ in=$(pwd)/$(mktemp in.pcap.XXXXXXXXXX)
+ in2=$(pwd)/$(mktemp in2.pcap.XXXXXXXXXX)
+ out=$(pwd)/$(mktemp out.pcap.XXXXXXXXXX)
+
+ # write dump on jail side, with "in" direction
+ jexec alcatraz $(atf_get_srcdir)/pcap-test \
+ capture epair0b $in 3 in > out & pid=$!
+ while ! jexec alcatraz netstat -B | grep -q epair0b.*pcap-test; do
+ sleep 0.01;
+ done
+ atf_check -s exit:0 -o ignore ping -c 3 -i 0.1 192.0.2.2
+ atf_check -s exit:0 sh -c "wait $pid; exit $?"
+ atf_check -s exit:0 -o empty cat out
+
+ # inject dump on host side, recording on both sides
+ jexec alcatraz $(atf_get_srcdir)/pcap-test \
+ capture epair0b $in2 3 in > jout & jpid=$!
+ while ! jexec alcatraz netstat -B | grep -q epair0b.*pcap-test; do
+ sleep 0.01;
+ done
+ $(atf_get_srcdir)/pcap-test \
+ capture epair0a $out 3 out > hout & hpid=$!
+ while ! netstat -B | grep -q epair0a.*pcap-test; do
+ sleep 0.01;
+ done
+ atf_check -s exit:0 -o empty -e empty $(atf_get_srcdir)/pcap-test \
+ inject epair0a $in 3
+ atf_check -s exit:0 sh -c "wait $jpid; exit $?"
+ atf_check -s exit:0 -o empty cat jout
+ atf_check -s exit:0 sh -c "wait $hpid; exit $?"
+ atf_check -s exit:0 -o empty cat hout
+
+ # all 3 dumps should be equal
+ atf_check -s exit:0 -o empty -e empty $(atf_get_srcdir)/pcap-test \
+ compare $in $out
+ atf_check -s exit:0 -o empty -e empty $(atf_get_srcdir)/pcap-test \
+ compare $in $in2
+}
+inject_cleanup()
+{
+ vnet_cleanup
+}
+
atf_init_test_cases()
{
atf_add_test_case "multi_read"
+ atf_add_test_case "inject"
}
diff --git a/tests/sys/net/bpf/pcap-test.c b/tests/sys/net/bpf/pcap-test.c
new file mode 100644
index 000000000000..9d01548f7aae
--- /dev/null
+++ b/tests/sys/net/bpf/pcap-test.c
@@ -0,0 +1,268 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Gleb Smirnoff <glebius@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.
+ *
+ * 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/param.h>
+#include <sys/queue.h>
+#include <netinet/ip.h>
+#include <pcap/pcap.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <err.h>
+
+static int
+strtolerr(const char *s)
+{
+ int rv;
+
+ if ((rv = (int)strtol(s, NULL, 10)) < 1)
+ errx(1, "bad count %s", s);
+ return (rv);
+}
+
+static pcap_direction_t
+strtodir(const char *s)
+{
+ static const struct dirstr {
+ const char *str;
+ pcap_direction_t dir;
+ } dirs[] = {
+ { "in", PCAP_D_IN },
+ { "out", PCAP_D_OUT },
+ { "both", PCAP_D_INOUT },
+ { "inout", PCAP_D_INOUT },
+ };
+
+ for (u_int i = 0; i < nitems(dirs); i++)
+ if (strcasecmp(s, dirs[i].str) == 0)
+ return (dirs[i].dir);
+ errx(1, "bad directions %s", s);
+}
+
+static char errbuf[PCAP_ERRBUF_SIZE];
+
+static pcap_t *
+pcap_open(const char *name, pcap_direction_t dir)
+{
+ pcap_t *p;
+
+ if ((p = pcap_create(name, errbuf)) == NULL)
+ errx(1, "pcap_create: %s", errbuf);
+ if (pcap_set_timeout(p, 10) != 0)
+ errx(1, "pcap_set_timeout: %s", pcap_geterr(p));
+ if (pcap_activate(p) != 0)
+ errx(1, "pcap_activate: %s", errbuf);
+ if (pcap_setdirection(p, dir) != 0)
+ errx(1, "pcap_setdirection: %s", pcap_geterr(p));
+ return (p);
+}
+
+#if 0
+/*
+ * Deal with the FreeBSD writer only optimization hack in bpf(4).
+ * Needed only when net.bpf.optimize_writers=1.
+ */
+static pcap_t *
+pcap_rwopen(const char *name, pcap_direction_t dir)
+{
+ pcap_t *p;
+ struct bpf_program fp;
+
+ p = pcap_open(name, dir);
+ if (pcap_compile(p, &fp, "", 0, PCAP_NETMASK_UNKNOWN) != 0)
+ errx(1, "pcap_compile: %s", pcap_geterr(p));
+ if (pcap_setfilter(p, &fp) != 0)
+ errx(1, "pcap_setfilter: %s", pcap_geterr(p));
+ pcap_freecode(&fp);
+ return (p);
+}
+#endif
+
+static void
+list(int argc __unused, char *argv[] __unused)
+{
+ pcap_if_t *all, *p;
+
+ if (pcap_findalldevs(&all, errbuf) != 0)
+ errx(1, "pcap_findalldevs: %s", errbuf);
+ for (p = all; p != NULL; p = p->next)
+ printf("%s ", p->name);
+ printf("\n");
+ pcap_freealldevs(all);
+}
+
+/* args: tap file count direction */
+static void
+capture(int argc __unused, char *argv[])
+{
+ pcap_t *p;
+ pcap_dumper_t *d;
+ pcap_direction_t dir;
+ int cnt;
+
+ cnt = strtolerr(argv[2]);
+ dir = strtodir(argv[3]);
+ p = pcap_open(argv[0], dir);
+
+ if ((d = pcap_dump_open(p, argv[1])) == NULL)
+ errx(1, "pcap_dump_open: %s", pcap_geterr(p));
+
+ if (pcap_loop(p, cnt, pcap_dump, (u_char *)d) != 0)
+ errx(1, "pcap_loop: %s", pcap_geterr(p));
+ pcap_dump_close(d);
+}
+
+static void
+inject_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
+{
+ pcap_t *p = (pcap_t *)user;
+
+ if (h->caplen != h->len)
+ errx(1, "incomplete packet %u of %u", h->caplen, h->len);
+
+ if (pcap_inject(p, bytes, h->caplen) != (int)h->caplen)
+ errx(1, "pcap_inject: %s", errbuf);
+}
+
+/* args: tap file count */
+static void
+inject(int argc __unused, char *argv[])
+{
+ pcap_t *p, *d;
+ int cnt;
+
+ cnt = strtolerr(argv[2]);
+ p = pcap_open(argv[0], PCAP_D_INOUT);
+
+ if ((d = pcap_open_offline(argv[1], errbuf)) == NULL)
+ errx(1, "pcap_open_offline: %s", errbuf);
+ if (pcap_loop(d, cnt, inject_packet, (u_char *)p) != 0)
+ errx(1, "pcap_loop: %s", pcap_geterr(p));
+ pcap_close(p);
+ pcap_close(d);
+}
+
+struct packet {
+ STAILQ_ENTRY(packet) next;
+ const void *data;
+ u_int caplen;
+ u_int len;
+};
+STAILQ_HEAD(plist, packet);
+
+static void
+store_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
+{
+ struct plist *list = (struct plist *)user;
+ struct packet *p;
+
+ p = malloc(sizeof(*p));
+ p->data = bytes;
+ p->caplen = h->caplen;
+ p->len = h->len;
+ STAILQ_INSERT_TAIL(list, p, next);
+}
+
+/* args: file1 file2 */
+static void
+compare(int argc __unused, char *argv[])
+{
+ pcap_t *f1, *f2;
+ struct plist
+ list1 = STAILQ_HEAD_INITIALIZER(list1),
+ list2 = STAILQ_HEAD_INITIALIZER(list2);
+ struct packet *p1, *p2;
+ u_int cnt;
+
+ if ((f1 = pcap_open_offline(argv[0], errbuf)) == NULL)
+ errx(1, "pcap_open_offline: %s", errbuf);
+ if (pcap_loop(f1, 0, store_packet, (u_char *)&list1) != 0)
+ errx(1, "pcap_loop: %s", pcap_geterr(f1));
+ if ((f2 = pcap_open_offline(argv[1], errbuf)) == NULL)
+ errx(1, "pcap_open_offline: %s", errbuf);
+ if (pcap_loop(f2, 0, store_packet, (u_char *)&list2) != 0)
+ errx(1, "pcap_loop: %s", pcap_geterr(f2));
+
+ for (p1 = STAILQ_FIRST(&list1), p2 = STAILQ_FIRST(&list2), cnt = 1;
+ p1 != NULL && p2 != NULL;
+ p1 = STAILQ_NEXT(p1, next), p2 = STAILQ_NEXT(p2, next), cnt++) {
+ if (p1->len != p2->len)
+ errx(1, "packet #%u length %u != %u",
+ cnt, p1->len, p2->len);
+ if (p1->caplen != p2->caplen)
+ errx(1, "packet #%u capture length %u != %u",
+ cnt, p1->caplen, p2->caplen);
+ if (memcmp(p1->data, p2->data, p1->caplen) != 0)
+ errx(1, "packet #%u payload different", cnt);
+ }
+ if (p1 != NULL || p2 != NULL)
+ errx(1, "packet count different");
+
+ pcap_close(f1);
+ pcap_close(f2);
+}
+
+static const struct cmd {
+ const char *cmd;
+ void (*func)(int, char **);
+ u_int argc;
+} cmds[] = {
+ { .cmd = "list", .func = list, .argc = 0 },
+ { .cmd = "inject", .func = inject, .argc = 3 },
+ { .cmd = "capture", .func = capture,.argc = 4 },
+ { .cmd = "compare", .func = compare,.argc = 2 },
+};
+
+int
+main(int argc, char *argv[])
+{
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s ", argv[0]);
+ for (u_int i = 0; i < nitems(cmds); i++)
+ fprintf(stderr, "%s%s", cmds[i].cmd,
+ i != nitems(cmds) - 1 ? "|" : "\n");
+ exit(1);
+ }
+
+ for (u_int i = 0; i < nitems(cmds); i++)
+ if (strcasecmp(argv[1], cmds[i].cmd) == 0) {
+ argc -= 2;
+ argv += 2;
+ if (argc < (int)cmds[i].argc)
+ errx(1, "%s takes %u args",
+ cmds[i].cmd, cmds[i].argc);
+ cmds[i].func(argc, argv);
+ return (0);
+ }
+
+ warnx("Unknown command %s\n", argv[1]);
+ return (1);
+}
diff --git a/tests/sys/netpfil/pf/divert-to.sh b/tests/sys/netpfil/pf/divert-to.sh
index ae44cd5d51af..153136199311 100644
--- a/tests/sys/netpfil/pf/divert-to.sh
+++ b/tests/sys/netpfil/pf/divert-to.sh
@@ -372,6 +372,47 @@ in_dn_in_div_in_out_div_out_dn_out_cleanup()
pft_cleanup
}
+atf_test_case "pr260867" "cleanup"
+pr260867_head()
+{
+ atf_set descr 'Test for the loop reported in PR260867'
+ atf_set require.user root
+}
+
+pr260867_body()
+{
+ pft_init
+ divert_init
+
+ epair=$(vnet_mkepair)
+
+ ifconfig ${epair}a 192.0.2.1/24 up
+
+ vnet_mkjail alcatraz ${epair}b
+ jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore ping -c3 192.0.2.2
+
+ jexec alcatraz /usr/sbin/inetd -p ${PWD}/inetd-echo.pid $(atf_get_srcdir)/echo_inetd.conf
+ jexec alcatraz $(atf_get_srcdir)/../common/divapp 1001 divert-back &
+
+ jexec alcatraz pfctl -e
+ pft_set_rules alcatraz \
+ "pass in on ${epair}b proto tcp from any to port 7 divert-to 0.0.0.0 port 1001"
+
+ reply=$(echo "foo" | nc -N 192.0.2.2 7)
+ if ["${reply}" != "foo" ];
+ then
+ atf_fail "Did not receive echo reply"
+ fi
+}
+
+pr260867_cleanup()
+{
+ pft_cleanup
+}
+
atf_init_test_cases()
{
atf_add_test_case "in_div"
@@ -383,4 +424,6 @@ atf_init_test_cases()
atf_add_test_case "in_div_in_fwd_out_div_out"
atf_add_test_case "in_dn_in_div_in_out_div_out_dn_out"
+
+ atf_add_test_case "pr260867"
}
diff --git a/tests/sys/netpfil/pf/nat.sh b/tests/sys/netpfil/pf/nat.sh
index 0824671fa0f1..3d953b495953 100644
--- a/tests/sys/netpfil/pf/nat.sh
+++ b/tests/sys/netpfil/pf/nat.sh
@@ -260,6 +260,64 @@ endpoint_independent_compat_cleanup()
rm -f server2.out
}
+atf_test_case "endpoint_independent_exhaust" "cleanup"
+endpoint_independent_exhaust_head()
+{
+ atf_set descr 'Test that a client behind NAT gets the same external IP:port for different servers'
+ atf_set require.user root
+}
+
+endpoint_independent_exhaust_body()
+{
+ endpoint_independent_setup # Sets ${epair_…} variables
+
+ endpoint_independent_common \
+ "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a)" \
+ "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a) port 3000:3001 sticky-address endpoint-independent"
+
+ # Exhaust the available nat ports
+ for i in $(seq 1 10); do
+ echo "ping" | jexec client nc -u 198.51.100.32 1234 -w 0
+ echo "ping" | jexec client nc -u 198.51.100.22 1234 -w 0
+ done
+}
+
+endpoint_independent_exhaust_cleanup()
+{
+ pft_cleanup
+ rm -f server1.out
+ rm -f server2.out
+}
+
+atf_test_case "endpoint_independent_static_port" "cleanup"
+endpoint_independent_static_port_head()
+{
+ atf_set descr 'Test that a client behind NAT gets the same external IP:port for different servers, with static-port'
+ atf_set require.user root
+}
+
+endpoint_independent_static_port_body()
+{
+ endpoint_independent_setup # Sets ${epair_…} variables
+
+ endpoint_independent_common \
+ "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a)" \
+ "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a) static-port sticky-address endpoint-independent"
+
+ # Exhaust the available nat ports
+ for i in $(seq 1 10); do
+ echo "ping" | jexec client nc -u 198.51.100.32 1234 -w 0
+ echo "ping" | jexec client nc -u 198.51.100.22 1234 -w 0
+ done
+}
+
+endpoint_independent_static_port_cleanup()
+{
+ pft_cleanup
+ rm -f server1.out
+ rm -f server2.out
+}
+
atf_test_case "endpoint_independent_pass" "cleanup"
endpoint_independent_pass_head()
{
@@ -900,6 +958,8 @@ atf_init_test_cases()
atf_add_test_case "exhaust"
atf_add_test_case "nested_anchor"
atf_add_test_case "endpoint_independent_compat"
+ atf_add_test_case "endpoint_independent_exhaust"
+ atf_add_test_case "endpoint_independent_static_port"
atf_add_test_case "endpoint_independent_pass"
atf_add_test_case "nat6_nolinklocal"
atf_add_test_case "empty_table_source_hash"
diff --git a/tests/sys/sound/Makefile b/tests/sys/sound/Makefile
index 74a0765a0540..ab52a7aad386 100644
--- a/tests/sys/sound/Makefile
+++ b/tests/sys/sound/Makefile
@@ -3,6 +3,7 @@ PACKAGE= tests
TESTSDIR= ${TESTSBASE}/sys/sound
ATF_TESTS_C+= pcm_read_write
+ATF_TESTS_C+= polling
ATF_TESTS_C+= sndstat
CFLAGS+= -I${SRCTOP}/sys
diff --git a/tests/sys/sound/polling.c b/tests/sys/sound/polling.c
new file mode 100644
index 000000000000..b7a328d13d4c
--- /dev/null
+++ b/tests/sys/sound/polling.c
@@ -0,0 +1,205 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 The FreeBSD Foundation
+ *
+ * This software was developed by Christos Margiolis <christos@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * 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/event.h>
+#include <sys/soundcard.h>
+
+#include <atf-c.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <unistd.h>
+
+#define FMT_ERR(s) s ": %s", strerror(errno)
+
+static int
+oss_init(void)
+{
+ int fd, tmp, rc;
+
+ fd = open("/dev/dsp.dummy", O_RDWR);
+ ATF_REQUIRE_MSG(fd >= 0, FMT_ERR("open"));
+
+ tmp = 2;
+ rc = ioctl(fd, SNDCTL_DSP_CHANNELS, &tmp);
+ ATF_REQUIRE_EQ_MSG(rc, 0, FMT_ERR("ioctl"));
+
+ tmp = AFMT_S16_LE;
+ rc = ioctl(fd, SNDCTL_DSP_SETFMT, &tmp);
+ ATF_REQUIRE_EQ_MSG(rc, 0, FMT_ERR("ioctl"));
+
+ tmp = 48000;
+ rc = ioctl(fd, SNDCTL_DSP_SPEED, &tmp);
+ ATF_REQUIRE_EQ_MSG(rc, 0, FMT_ERR("ioctl"));
+
+ /*
+ * See http://manuals.opensound.com/developer/SNDCTL_DSP_SETTRIGGER.html
+ */
+ tmp = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
+ rc = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &tmp);
+ ATF_REQUIRE_EQ_MSG(rc, 0, FMT_ERR("ioctl"));
+
+ return (fd);
+}
+
+ATF_TC(poll_kqueue);
+ATF_TC_HEAD(poll_kqueue, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "kqueue(2) test");
+ atf_tc_set_md_var(tc, "require.kmods", "snd_dummy");
+}
+
+ATF_TC_BODY(poll_kqueue, tc)
+{
+ struct kevent ev;
+ int16_t buf[32];
+ int fd, kq;
+
+ fd = oss_init();
+
+ kq = kqueue();
+ ATF_REQUIRE_MSG(kq >= 0, FMT_ERR("kqueue"));
+ EV_SET(&ev, fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0);
+ ATF_REQUIRE_MSG(kevent(kq, &ev, 1, NULL, 0, NULL) == 0,
+ FMT_ERR("kevent"));
+
+ ATF_REQUIRE_MSG(kevent(kq, NULL, 0, &ev, 1, NULL) == 1,
+ FMT_ERR("kevent"));
+ ATF_REQUIRE_MSG((ev.flags & EV_ERROR) == 0, "EV_ERROR is set");
+ ATF_REQUIRE_MSG(ev.data != 0, "data is %" PRId64, ev.data);
+ ATF_REQUIRE_MSG(read(fd, buf, sizeof(buf)) > 0, FMT_ERR("read"));
+
+ EV_SET(&ev, fd, EVFILT_READ, EV_DELETE, 0, 0, 0);
+ close(kq);
+
+ kq = kqueue();
+ ATF_REQUIRE_MSG(kq >= 0, FMT_ERR("kqueue"));
+ EV_SET(&ev, fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, 0);
+ ATF_REQUIRE_MSG(kevent(kq, &ev, 1, NULL, 0, NULL) == 0,
+ FMT_ERR("kevent"));
+
+ ATF_REQUIRE_MSG(kevent(kq, NULL, 0, &ev, 1, NULL) == 1,
+ FMT_ERR("kevent"));
+ ATF_REQUIRE_MSG((ev.flags & EV_ERROR) == 0, "EV_ERROR is set");
+ ATF_REQUIRE_MSG(ev.data != 0, "data is %" PRId64, ev.data);
+ ATF_REQUIRE_MSG(write(fd, buf, sizeof(buf)) > 0, FMT_ERR("write"));
+
+ EV_SET(&ev, fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0);
+ close(kq);
+
+ close(fd);
+}
+
+ATF_TC(poll_poll);
+ATF_TC_HEAD(poll_poll, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "poll(2) test");
+ atf_tc_set_md_var(tc, "require.kmods", "snd_dummy");
+}
+
+ATF_TC_BODY(poll_poll, tc)
+{
+ struct pollfd pfd[2];
+ int16_t buf[32];
+ int fd;
+ bool rd = false;
+ bool wr = false;
+
+ fd = oss_init();
+
+ while (!rd || !wr) {
+ pfd[0].fd = fd;
+ pfd[0].events = POLLIN;
+ pfd[1].fd = fd;
+ pfd[1].events = POLLOUT;
+ ATF_REQUIRE_MSG(poll(pfd, sizeof(pfd) / sizeof(struct pollfd),
+ -1) > 0, FMT_ERR("poll"));
+
+ if (pfd[0].revents) {
+ ATF_REQUIRE_MSG(read(fd, buf, sizeof(buf)) > 0,
+ FMT_ERR("read"));
+ rd = true;
+ }
+ if (pfd[1].revents) {
+ ATF_REQUIRE_MSG(write(fd, buf, sizeof(buf)) > 0,
+ FMT_ERR("write"));
+ wr = true;
+ }
+ }
+ close(fd);
+}
+
+ATF_TC(poll_select);
+ATF_TC_HEAD(poll_select, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "select(2) test");
+ atf_tc_set_md_var(tc, "require.kmods", "snd_dummy");
+}
+
+ATF_TC_BODY(poll_select, tc)
+{
+ fd_set fds[2];
+ int16_t buf[32];
+ int fd;
+ bool rd = false;
+ bool wr = false;
+
+ fd = oss_init();
+
+ while (!rd || !wr) {
+ FD_ZERO(&fds[0]);
+ FD_ZERO(&fds[1]);
+ FD_SET(fd, &fds[0]);
+ FD_SET(fd, &fds[1]);
+ ATF_REQUIRE_MSG(select(fd + 2, &fds[0], &fds[1], NULL, NULL) > 0,
+ FMT_ERR("select"));
+ if (FD_ISSET(fd, &fds[0])) {
+ ATF_REQUIRE_MSG(read(fd, buf, sizeof(buf)) > 0,
+ FMT_ERR("read"));
+ rd = true;
+ }
+ if (FD_ISSET(fd, &fds[1])) {
+ ATF_REQUIRE_MSG(write(fd, buf, sizeof(buf)) > 0,
+ FMT_ERR("write"));
+ wr = true;
+ }
+ }
+ close(fd);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, poll_kqueue);
+ ATF_TP_ADD_TC(tp, poll_poll);
+ ATF_TP_ADD_TC(tp, poll_select);
+
+ return (atf_no_error());
+}