aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/atf_python/sys/net/vnet.py2
-rw-r--r--tests/ci/Makefile73
-rw-r--r--tests/ci/tools/ci.conf8
-rwxr-xr-xtests/ci/tools/freebsdci34
-rw-r--r--tests/sys/cddl/zfs/tests/zfsd/zfsd_fault_001_pos.ksh4
-rw-r--r--tests/sys/fs/fusefs/Makefile5
-rw-r--r--tests/sys/fs/fusefs/destroy.cc2
-rw-r--r--tests/sys/fs/fusefs/fallocate.cc3
-rw-r--r--tests/sys/fs/fusefs/mockfs.cc3
-rw-r--r--tests/sys/fs/fusefs/mount.cc2
-rw-r--r--tests/sys/fs/fusefs/xattr.cc50
-rw-r--r--tests/sys/fs/tarfs/tarfs_test.sh18
-rw-r--r--tests/sys/kern/Makefile5
-rw-r--r--tests/sys/kern/jail_lookup_root.c133
-rw-r--r--tests/sys/kern/ptrace_test.c66
-rw-r--r--tests/sys/kern/socket_splice.c4
-rw-r--r--tests/sys/kern/tty/Makefile3
-rw-r--r--tests/sys/kern/tty/test_sti.c337
-rw-r--r--tests/sys/kern/unix_passfd_test.c46
-rw-r--r--tests/sys/kern/unix_seqpacket_test.c36
-rw-r--r--tests/sys/kern/unix_stream.c269
-rw-r--r--tests/sys/net/Makefile3
-rw-r--r--tests/sys/net/bpf/Makefile15
-rw-r--r--tests/sys/net/bpf/bpf.sh67
-rw-r--r--tests/sys/net/bpf/bpf_multi_read.c76
-rwxr-xr-xtests/sys/net/if_bridge_test.sh129
-rwxr-xr-xtests/sys/net/if_lagg_test.sh4
-rw-r--r--tests/sys/net/if_wg.sh15
-rwxr-xr-xtests/sys/netinet/arp.sh8
-rw-r--r--tests/sys/netinet/igmp.py50
-rw-r--r--tests/sys/netinet/ip_reass_test.c12
-rw-r--r--tests/sys/netinet/socket_afinet.c3
-rw-r--r--tests/sys/netinet/tcp_implied_connect.c1
-rw-r--r--tests/sys/netinet/udp_io.c1
-rw-r--r--tests/sys/netinet6/Makefile5
-rwxr-xr-xtests/sys/netinet6/addr6.sh45
-rw-r--r--tests/sys/netinet6/redirect.sh6
-rw-r--r--tests/sys/netlink/test_snl.c12
-rw-r--r--tests/sys/netlink/test_snl_generic.c4
-rw-r--r--tests/sys/netpfil/pf/Makefile5
-rw-r--r--tests/sys/netpfil/pf/anchor.sh41
-rw-r--r--tests/sys/netpfil/pf/frag6.py159
-rw-r--r--tests/sys/netpfil/pf/killstate.sh56
-rw-r--r--tests/sys/netpfil/pf/limits.sh53
-rw-r--r--tests/sys/netpfil/pf/map_e.sh90
-rw-r--r--tests/sys/netpfil/pf/nat.sh374
-rw-r--r--tests/sys/netpfil/pf/nat64.py15
-rw-r--r--tests/sys/netpfil/pf/nat66.py15
-rw-r--r--tests/sys/netpfil/pf/pflog.sh14
-rw-r--r--tests/sys/netpfil/pf/rdr.sh109
-rw-r--r--tests/sys/netpfil/pf/sctp.sh4
-rwxr-xr-xtests/sys/netpfil/pf/src_track.sh110
-rw-r--r--tests/sys/netpfil/pf/tcp.py158
-rw-r--r--tests/sys/netpfil/pf/utils.py41
-rw-r--r--tests/sys/sound/sndstat.c6
-rw-r--r--tests/sys/sys/Makefile1
-rw-r--r--tests/sys/sys/queue_test.c293
-rw-r--r--tests/sys/vm/soxstack/Makefile1
58 files changed, 2823 insertions, 281 deletions
diff --git a/tests/atf_python/sys/net/vnet.py b/tests/atf_python/sys/net/vnet.py
index 6da5f2625990..68c8ce4e0cba 100644
--- a/tests/atf_python/sys/net/vnet.py
+++ b/tests/atf_python/sys/net/vnet.py
@@ -134,7 +134,7 @@ class VnetInterface(object):
self.run_cmd(cmd)
def enable_ipv6(self):
- cmd = "/usr/sbin/ndp -i {} -disabled".format(self.name)
+ cmd = "/usr/sbin/ndp -i {} -- -disabled".format(self.name)
self.run_cmd(cmd)
def has_tentative(self) -> bool:
diff --git a/tests/ci/Makefile b/tests/ci/Makefile
index fed66e5cc317..e6464e8392e6 100644
--- a/tests/ci/Makefile
+++ b/tests/ci/Makefile
@@ -8,8 +8,9 @@
# Makefile for CI testing.
#
# User-driven targets:
-# ci: Run CI tests. Currently only smoke tests are supported.
-# ci-smokeit: Currently same as ci.
+# ci: Run CI tests
+# ci-smoke: Run smoke tests which is simply booting the image
+# ci-full: Run full tests
#
# Variables affecting the build process:
# TARGET/TARGET_ARCH: architecture of built release (default: same as build host)
@@ -26,6 +27,7 @@ PARALLEL_JOBS!=sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null
TOTAL_MEMORY!=expr ${_MEMORY} / 1073741824
KERNCONF?= GENERIC
LOCALBASE?= /usr/local
+EXTRA_MAKE_FLAGS?=
.if !defined(TARGET) || empty(TARGET)
TARGET= ${MACHINE}
@@ -68,11 +70,25 @@ SWAPSIZE?= 1g
VMFS?= ufs
FORMAT= raw
CIIMAGE= ci-${OSRELEASE}-${GITREV}-${KERNCONF}.${FORMAT}
+CIDISK?= ${.OBJDIR}/${CIIMAGE}
VMSIZE?= 6g
-CITYPE?=
+CITYPE?= full
+META_TAR!=mktemp /tmp/meta.XXXXXX
+META_DIR!=mktemp -d /tmp/meta.XXXXXX
+META_DIROUT!=mktemp -d /tmp/meta.XXXXXX
+DISC_CAM!=truncate -s 128m /tmp/disk-cam
+EXTRA_DISK_NUM?=5
+DISK_NUMBERS!=jot - 1 ${EXTRA_DISK_NUM}
+BHYVE_EXTRA_DISK_PARAM?=
+BHYVE_EXTRA_DISK_PARAM+=-s 4:0,ahci-hd,/tmp/disk-cam
+.for i in ${DISK_NUMBERS}
+NEW_DISK!=truncate -s 128m /tmp/disk${i}
+BHYVE_EXTRA_DISK_PARAM+=-s $$((${i} + 4)):0,virtio-blk,/tmp/disk${i}
+CLEANFILES+=/tmp/disk${i}
+.endfor
TEST_VM_NAME= ci-${OSRELEASE}-${GITREV}-${KERNCONF}
.if ${TOTAL_MEMORY} >= 16
-VM_MEM!=expr ${TOTAL_MEMORY} / 2
+VM_MEM=8
.elif ${TOTAL_MEMORY} >=4
VM_MEM=${TOTAL_MEMORY}
.else
@@ -108,8 +124,8 @@ VM_MEM_SIZE=${QEMU_MAX_MEM_SIZE}g
.else
VM_MEM_SIZE=${VM_MEM}g
.endif
-KLDVMMISLOADED!=kldload -q -n vmm 2>/dev/null && echo "1" || echo "0"
-.if ${KLDVMMISLOADED} == "0"
+VMGUEST!=sysctl -n kern.vm_guest 2>/dev/null || true
+.if ${VMGUEST} != "none"
USE_QEMU?=1
.endif
KLDFILEMONISLOADED!=kldload -q -n filemon 2>/dev/null && echo "1" || echo "0"
@@ -117,8 +133,8 @@ KLDFILEMONISLOADED!=kldload -q -n filemon 2>/dev/null && echo "1" || echo "0"
METAMODE?=-DWITH_META_MODE
.endif
-CLEANFILES= ${CIIMAGE} ci.img
-CLEANDIRS= ci-buildimage
+CLEANFILES+= ${.OBJDIR}/${CIIMAGE} ${.OBJDIR}/ci.img ${META_TAR}
+CLEANDIRS+= ${.OBJDIR}/ci-buildimage
portinstall: portinstall-pkg portinstall-qemu portinstall-expect portinstall-${TARGET_ARCH:tl} .PHONY
@@ -141,24 +157,27 @@ portinstall-expect: portinstall-pkg .PHONY
.endif
beforeclean: .PHONY
- chflags -R noschg .
+ chflags -R noschg ${.OBJDIR}/${.TARGET}
.include <bsd.obj.mk>
clean: beforeclean .PHONY
+cleandir: beforeclean .PHONY
+
ci-buildworld: .PHONY
@echo "Building world for ${TARGET_ARCH}"
${IMAKE} -j${PARALLEL_JOBS} -C ${WORLDDIR} ${METAMODE} \
${CROSS_TOOLCHAIN_PARAM} __MAKE_CONF=${MAKECONF} SRCCONF=${SRCCONF} \
- buildworld > ${.CURDIR}/_.${TARGET_ARCH}.${.TARGET} 2>&1 || \
+ ${EXTRA_MAKE_FLAGS} buildworld > ${.CURDIR}/_.${TARGET_ARCH}.${.TARGET} 2>&1 || \
(echo "${.TARGET} failed, check _.${TARGET_ARCH}.${.TARGET} for details" ; false)
ci-buildkernel: ci-buildworld-${TARGET_ARCH:tl} .PHONY
- @echo "Building kenrel for ${TARGET_ARCH"}"
+ @echo "Building kernel for ${TARGET_ARCH"}"
${IMAKE} -j${PARALLEL_JOBS} -C ${WORLDDIR} ${METAMODE} \
- ${CROSS_TOOLCHAIN_PARAM} __MAKE_CONF=${MAKECONF} \
- SRCCONF=${SRCCONF} buildkernel > ${.CURDIR}/_.${TARGET_ARCH}.${.TARGET} 2>&1 || \
+ ${CROSS_TOOLCHAIN_PARAM} __MAKE_CONF=${MAKECONF} SRCCONF=${SRCCONF}
+ ${EXTRA_MAKE_FLAGS} KERNCONF=${KERNCONF} \
+ buildkernel > ${.CURDIR}/_.${TARGET_ARCH}.${.TARGET} 2>&1 || \
(echo "${.TARGET} failed, check _.${TARGET_ARCH}.${.TARGET} for details" ; false)
ci-buildimage: ${QEMUTGT} ci-buildkernel-${TARGET_ARCH:tl} .PHONY
@@ -174,19 +193,32 @@ ci-buildimage: ${QEMUTGT} ci-buildkernel-${TARGET_ARCH:tl} .PHONY
(echo "${.TARGET} failed, check _.${TARGET_ARCH}.${.TARGET} for details" ; false)
touch ${.TARGET}
-ci-setsmokevar: .PHONY
+ci-set-smoke-var: .PHONY
CITYPE=smoke
+ci-set-full-var: .PHONY
+CITYPE=full
+
+ci-create-meta: .PHONY
+ truncate -s 512M ${META_TAR}
+ tar rvf ${META_TAR} -C ${META_DIR} .
+
+ci-extract-meta: .PHONY
+ tar xfv ${META_TAR} -C ${META_DIROUT}
+ @echo "Extracted kyua reports to ${META_DIROUT}"
+
ci-runtest: ci-buildimage-${TARGET_ARCH:tl} portinstall .PHONY
.if ${MACHINE} == "amd64" && ( ${TARGET_ARCH} == "amd64" || ${TARGET_ARCH} == "i386" ) && ( !defined(USE_QEMU) || empty(USE_QEMU) )
/usr/sbin/bhyvectl --vm=${TEST_VM_NAME} --destroy || true
- /usr/sbin/bhyveload -c stdio -m ${VM_MEM_SIZE} -d ${CIIMAGE} ${TEST_VM_NAME}
+ /usr/sbin/bhyveload -c stdio -m ${VM_MEM_SIZE} -d ${CIDISK} ${TEST_VM_NAME}
expect -c "set timeout ${TIMEOUT_EXPECT}; \
spawn /usr/bin/timeout -k 60 ${TIMEOUT_VM} /usr/sbin/bhyve \
-c ${PARALLEL_JOBS} -m ${VM_MEM_SIZE} -A -H -P \
-s 0:0,hostbridge \
-s 1:0,lpc \
- -s 2:0,virtio-blk,${CIIMAGE} \
+ -s 2:0,virtio-blk,${CIDISK} \
+ -s 3:0,virtio-blk,${META_TAR} \
+ ${BHYVE_EXTRA_DISK_PARAM} \
-l com1,stdio \
${TEST_VM_NAME}; \
expect { eof }"
@@ -199,7 +231,8 @@ ci-runtest: ci-buildimage-${TARGET_ARCH:tl} portinstall .PHONY
-nographic \
-no-reboot \
${QEMU_EXTRA_PARAM} \
- -drive if=none,file=${CIIMAGE},format=raw,id=hd0 \
+ -drive if=none,file=${CIDISK},format=raw,id=hd0 \
+ -drive if=none,file=${META_TAR},format=raw,id=hd1 \
${QEMU_DEVICES}
.endif
@@ -215,8 +248,10 @@ ci-checktarget: .PHONY
@echo "Error: ${TARGET_ARCH} is not supported on ${TYPE} ${REVISION} ${BRANCH}"
.endif
-ci-smokeit: ci-setsmokevar ci-checktarget .WAIT ci-runtest-${TARGET_ARCH:tl} .PHONY
+ci-smoke: ci-set-smoke-var ci-create-meta ci-checktarget .WAIT ci-runtest-${TARGET_ARCH:tl} .PHONY
+
+ci-full: ci-set-full-var ci-create-meta ci-checktarget .WAIT ci-runtest-${TARGET_ARCH:tl} ci-extract-meta .PHONY
-ci: ci-smokeit .PHONY
+ci: ci-${CITYPE:tl} .PHONY
.include "${RELEASEDIR}/Makefile.inc1"
diff --git a/tests/ci/tools/ci.conf b/tests/ci/tools/ci.conf
index 47001d6248c6..a9998a3e5373 100644
--- a/tests/ci/tools/ci.conf
+++ b/tests/ci/tools/ci.conf
@@ -11,7 +11,7 @@
export VM_RC_LIST="auditd freebsdci"
if [ "${CITYPE}" != "smoke" ]; then
-export VM_EXTRA_PACKAGES="coreutils devel/py-pytest gdb jq ksh93 net/py-dpkt net/scapy nist-kat nmap perl5 python python3 sudo tcptestsuite"
+export VM_EXTRA_PACKAGES="coreutils devel/py-pytest gdb jq ksh93 net/py-dpkt net/scapy nist-kat nmap perl5 python python3 sudo sysutils/porch tcptestsuite"
if [ "${TARGET}" = "amd64" ]; then
export VM_EXTRA_PACKAGES="${VM_EXTRA_PACKAGES} linux-c7-ltp"
@@ -41,6 +41,7 @@ cat << EOF >> ${DESTDIR}/etc/rc.conf
kld_list="" # Load modules needed by tests
kld_list="${kld_list} blake2" # sys/opencrypto
kld_list="${kld_list} cryptodev" # sys/opencrypto
+kld_list="${kld_list} dummymbuf" # sys/netpfil
kld_list="${kld_list} fusefs" # sys/fs/fusefs
kld_list="${kld_list} ipsec" # sys/netipsec
kld_list="${kld_list} mac_portacl" # sys/mac/portacl
@@ -64,6 +65,10 @@ if [ "${CITYPE}" = "smoke" ]; then
cat << EOF >> ${DESTDIR}/etc/rc.conf
freebsdci_type="smoke"
EOF
+elif [ "${CITYPE}" = "full" ]; then
+cat << EOF >> ${DESTDIR}/etc/rc.conf
+freebsdci_type="full"
+EOF
fi
cat << EOF >> ${DESTDIR}/etc/sysctl.conf
kern.cryptodevallowsoft=1
@@ -77,7 +82,6 @@ cat << EOF >> ${DESTDIR}/etc/fstab
fdesc /dev/fd fdescfs rw 0 0
EOF
mkdir -p ${DESTDIR}/usr/local/etc/rc.d
- echo $scriptdir
cp -p ${scriptdir}/../../tests/ci/tools/freebsdci ${DESTDIR}/usr/local/etc/rc.d/
touch ${DESTDIR}/firstboot
diff --git a/tests/ci/tools/freebsdci b/tests/ci/tools/freebsdci
index c77216c9fd4d..f0030fe00aba 100755
--- a/tests/ci/tools/freebsdci
+++ b/tests/ci/tools/freebsdci
@@ -26,6 +26,7 @@
. /etc/rc.subr
: ${freebsdci_enable:="NO"}
+: ${freebsdci_type:="full"}
name="freebsdci"
desc="Run FreeBSD CI"
@@ -33,10 +34,13 @@ rcvar=freebsdci_enable
start_cmd="firstboot_ci_run"
stop_cmd=":"
os_arch=$(uname -p)
+tardev=/dev/vtbd1
+metadir=/meta
+istar=$(file -s ${tardev} | grep "POSIX tar archive" | wc -l)
auto_shutdown()
{
- # XXX: Currently RISC-V kernels lack the ability to
+ # NOTE: Currently RISC-V kernels lack the ability to
# make qemu exit on shutdown. Reboot instead;
# it makes qemu exit too.
case "$os_arch" in
@@ -53,8 +57,6 @@ smoke_tests()
{
echo
echo "--------------------------------------------------------------"
- echo "BUILD sequence COMPLETED"
- echo "IMAGE sequence COMPLETED"
echo "BOOT sequence COMPLETED"
echo "INITIATING system SHUTDOWN"
echo "--------------------------------------------------------------"
@@ -62,15 +64,31 @@ smoke_tests()
full_tests()
{
- # Currently this is a placeholder.
- # This will be used to add the full tests scenario those are run in
- # the CI system
echo
echo "--------------------------------------------------------------"
- echo "BUILD sequence COMPLETED"
- echo "IMAGE sequence COMPLETED"
echo "BOOT sequence COMPLETED"
echo "TEST sequence STARTED"
+ if [ "${istar}" -eq 1 ]; then
+ rm -fr ${metadir}
+ mkdir -p ${metadir}
+ tar xvf ${tardev} -C ${metadir}
+ cd /usr/tests
+ set +e
+ kyua test
+ rc=$?
+ set -e
+ if [ ${rc} -ne 0 ] && [ ${rc} -ne 1 ]; then
+ exit ${rc}
+ fi
+ kyua report --verbose --results-filter passed,skipped,xfail,broken,failed --output test-report.txt
+ kyua report-junit --output=test-report.xml
+ mv test-report.* /${metadir}
+ tar cvf ${tardev} -C ${metadir} .
+ else
+ echo "ERROR: no device with POSIX tar archive format found."
+ # Don't shutdown because this is not run in unattended mode
+ exit 1
+ fi
echo "TEST sequence COMPLETED"
echo "INITIATING system SHUTDOWN"
echo "--------------------------------------------------------------"
diff --git a/tests/sys/cddl/zfs/tests/zfsd/zfsd_fault_001_pos.ksh b/tests/sys/cddl/zfs/tests/zfsd/zfsd_fault_001_pos.ksh
index 3456a328e7f9..df704e183fb0 100644
--- a/tests/sys/cddl/zfs/tests/zfsd/zfsd_fault_001_pos.ksh
+++ b/tests/sys/cddl/zfs/tests/zfsd/zfsd_fault_001_pos.ksh
@@ -78,6 +78,10 @@ for type in "raidz" "mirror"; do
$DD if=/dev/zero bs=128k count=1 >> \
/$TESTPOOL/$TESTFS/$TESTFILE 2> /dev/null
$FSYNC /$TESTPOOL/$TESTFS/$TESTFILE
+ # Due to a bug outside of zfsd, it may be necessary to reopen
+ # the pool before it will become DEGRADED.
+ # https://github.com/openzfs/zfs/issues/16245
+ $ZPOOL reopen $TESTPOOL
# Check to see if the pool is faulted yet
$ZPOOL status $TESTPOOL | grep -q 'state: DEGRADED'
if [ $? == 0 ]
diff --git a/tests/sys/fs/fusefs/Makefile b/tests/sys/fs/fusefs/Makefile
index d91199fd519e..4265f5b71dfa 100644
--- a/tests/sys/fs/fusefs/Makefile
+++ b/tests/sys/fs/fusefs/Makefile
@@ -55,7 +55,6 @@ GTESTS+= xattr
.for p in ${GTESTS}
SRCS.$p+= ${p}.cc
-SRCS.$p+= getmntopts.c
SRCS.$p+= mockfs.cc
SRCS.$p+= utils.cc
.endfor
@@ -68,7 +67,6 @@ TEST_METADATA.nfs+= required_user="root"
TEST_METADATA+= timeout=10
FUSEFS= ${SRCTOP}/sys/fs/fuse
-MOUNT= ${SRCTOP}/sbin/mount
# Suppress warnings that GCC generates for the libc++ and gtest headers.
CXXWARNFLAGS.gcc+= -Wno-placement-new -Wno-attributes
# Suppress Wcast-align for readdir.cc, because it is unavoidable when using
@@ -87,9 +85,6 @@ CXXWARNFLAGS+= -Wno-vla-cxx-extension
.endif
CXXFLAGS+= -I${SRCTOP}/tests
CXXFLAGS+= -I${FUSEFS}
-CXXFLAGS+= -I${MOUNT}
-.PATH: ${MOUNT}
-CXXSTD= c++14
LIBADD+= pthread
LIBADD+= gmock gtest
diff --git a/tests/sys/fs/fusefs/destroy.cc b/tests/sys/fs/fusefs/destroy.cc
index 16d50da19b9b..45acb1f99724 100644
--- a/tests/sys/fs/fusefs/destroy.cc
+++ b/tests/sys/fs/fusefs/destroy.cc
@@ -60,7 +60,7 @@ static void* open_th(void* arg) {
* Check for any memory leaks like this:
* 1) kldunload fusefs, if necessary
* 2) kldload fusefs
- * 3) ./destroy --gtest_filter=Destroy.unsent_operations
+ * 3) ./destroy --gtest_filter=Death.unsent_operations
* 4) kldunload fusefs
* 5) check /var/log/messages for anything like this:
Freed UMA keg (fuse_ticket) was not empty (31 items). Lost 2 pages of memory.
diff --git a/tests/sys/fs/fusefs/fallocate.cc b/tests/sys/fs/fusefs/fallocate.cc
index a05760207648..4e5b047b78b7 100644
--- a/tests/sys/fs/fusefs/fallocate.cc
+++ b/tests/sys/fs/fusefs/fallocate.cc
@@ -32,10 +32,9 @@ extern "C" {
#include <sys/time.h>
#include <fcntl.h>
+#include <mntopts.h> // for build_iovec
#include <signal.h>
#include <unistd.h>
-
-#include "mntopts.h" // for build_iovec
}
#include "mockfs.hh"
diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc
index 35ae6c207229..b1621d05703c 100644
--- a/tests/sys/fs/fusefs/mockfs.cc
+++ b/tests/sys/fs/fusefs/mockfs.cc
@@ -39,13 +39,12 @@ extern "C" {
#include <fcntl.h>
#include <libutil.h>
+#include <mntopts.h> // for build_iovec
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
-
-#include "mntopts.h" // for build_iovec
}
#include <cinttypes>
diff --git a/tests/sys/fs/fusefs/mount.cc b/tests/sys/fs/fusefs/mount.cc
index 7a8d2c1396f0..ece518b09f66 100644
--- a/tests/sys/fs/fusefs/mount.cc
+++ b/tests/sys/fs/fusefs/mount.cc
@@ -33,7 +33,7 @@ extern "C" {
#include <sys/mount.h>
#include <sys/uio.h>
-#include "mntopts.h" // for build_iovec
+#include <mntopts.h> // for build_iovec
}
#include "mockfs.hh"
diff --git a/tests/sys/fs/fusefs/xattr.cc b/tests/sys/fs/fusefs/xattr.cc
index b1cbb9ffa768..0ab203c96254 100644
--- a/tests/sys/fs/fusefs/xattr.cc
+++ b/tests/sys/fs/fusefs/xattr.cc
@@ -110,6 +110,8 @@ void expect_setxattr(uint64_t ino, const char *attr, const char *value,
const char *v = a + strlen(a) + 1;
return (in.header.opcode == FUSE_SETXATTR &&
in.header.nodeid == ino &&
+ in.body.setxattr.size == (strlen(value) + 1) &&
+ in.body.setxattr.setxattr_flags == 0 &&
0 == strcmp(attr, a) &&
0 == strcmp(value, v));
}, Eq(true)),
@@ -119,6 +121,33 @@ void expect_setxattr(uint64_t ino, const char *attr, const char *value,
};
+class Xattr_7_32:public FuseTest {
+public:
+virtual void SetUp()
+{
+ m_kernel_minor_version = 32;
+ FuseTest::SetUp();
+}
+
+void expect_setxattr_7_32(uint64_t ino, const char *attr, const char *value,
+ ProcessMockerT r)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ const char *a = (const char *)in.body.bytes +
+ FUSE_COMPAT_SETXATTR_IN_SIZE;
+ const char *v = a + strlen(a) + 1;
+ return (in.header.opcode == FUSE_SETXATTR &&
+ in.header.nodeid == ino &&
+ in.body.setxattr.size == (strlen(value) + 1) &&
+ 0 == strcmp(attr, a) &&
+ 0 == strcmp(value, v));
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(r));
+}
+};
+
class Getxattr: public Xattr {};
class Listxattr: public Xattr {};
@@ -153,6 +182,7 @@ void TearDown() {
class Removexattr: public Xattr {};
class Setxattr: public Xattr {};
+class Setxattr_7_32:public Xattr_7_32 {};
class RofsXattr: public Xattr {
public:
virtual void SetUp() {
@@ -569,7 +599,7 @@ TEST_F(Listxattr, size_only_race_smaller)
}));
expect_listxattr(ino, sizeof(attrs0),
ReturnImmediate([&](auto in __unused, auto& out) {
- strlcpy((char*)out.body.bytes, attrs1, sizeof(attrs1));
+ memcpy((char*)out.body.bytes, attrs1, sizeof(attrs1));
out.header.len = sizeof(fuse_out_header) +
sizeof(attrs1);
})
@@ -728,6 +758,7 @@ TEST_F(Removexattr, system)
<< strerror(errno);
}
+
/*
* If the filesystem returns ENOSYS, then it will be treated as a permanent
* failure and all future VOP_SETEXTATTR calls will fail with EOPNOTSUPP
@@ -815,6 +846,23 @@ TEST_F(Setxattr, system)
ASSERT_EQ(value_len, r) << strerror(errno);
}
+
+TEST_F(Setxattr_7_32, ok)
+{
+ uint64_t ino = 42;
+ const char value[] = "whatever";
+ ssize_t value_len = strlen(value) + 1;
+ int ns = EXTATTR_NAMESPACE_USER;
+ ssize_t r;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
+ expect_setxattr_7_32(ino, "user.foo", value, ReturnErrno(0));
+
+ r = extattr_set_file(FULLPATH, ns, "foo", (const void *)value,
+ value_len);
+ ASSERT_EQ(value_len, r) << strerror(errno);
+}
+
TEST_F(RofsXattr, deleteextattr_erofs)
{
uint64_t ino = 42;
diff --git a/tests/sys/fs/tarfs/tarfs_test.sh b/tests/sys/fs/tarfs/tarfs_test.sh
index f1322033fbad..20baadfea5c5 100644
--- a/tests/sys/fs/tarfs/tarfs_test.sh
+++ b/tests/sys/fs/tarfs/tarfs_test.sh
@@ -48,7 +48,6 @@ tarsum() {
}
tarfs_setup() {
- kldload -n tarfs || atf_skip "This test requires tarfs and could not load it"
mkdir "${mnt}"
}
@@ -60,6 +59,7 @@ atf_test_case tarfs_basic cleanup
tarfs_basic_head() {
atf_set "descr" "Basic function test"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_basic_body() {
tarfs_setup
@@ -87,6 +87,7 @@ atf_test_case tarfs_basic_gnu cleanup
tarfs_basic_gnu_head() {
atf_set "descr" "Basic function test using GNU tar"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
atf_set "require.progs" "gtar"
}
tarfs_basic_gnu_body() {
@@ -101,6 +102,7 @@ atf_test_case tarfs_notdir_device cleanup
tarfs_notdir_device_head() {
atf_set "descr" "Regression test for PR 269519 and 269561"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_notdir_device_body() {
tarfs_setup
@@ -121,6 +123,7 @@ atf_test_case tarfs_notdir_device_gnu cleanup
tarfs_notdir_device_gnu_head() {
atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
atf_set "require.progs" "gtar"
}
tarfs_notdir_device_gnu_body() {
@@ -135,6 +138,7 @@ atf_test_case tarfs_notdir_dot cleanup
tarfs_notdir_dot_head() {
atf_set "descr" "Regression test for PR 269519 and 269561"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_notdir_dot_body() {
tarfs_setup
@@ -155,6 +159,7 @@ atf_test_case tarfs_notdir_dot_gnu cleanup
tarfs_notdir_dot_gnu_head() {
atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
atf_set "require.progs" "gtar"
}
tarfs_notdir_dot_gnu_body() {
@@ -169,6 +174,7 @@ atf_test_case tarfs_notdir_dotdot cleanup
tarfs_notdir_dotdot_head() {
atf_set "descr" "Regression test for PR 269519 and 269561"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_notdir_dotdot_body() {
tarfs_setup
@@ -189,6 +195,7 @@ atf_test_case tarfs_notdir_dotdot_gnu cleanup
tarfs_notdir_dotdot_gnu_head() {
atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
atf_set "require.progs" "gtar"
}
tarfs_notdir_dotdot_gnu_body() {
@@ -203,6 +210,7 @@ atf_test_case tarfs_notdir_file cleanup
tarfs_notdir_file_head() {
atf_set "descr" "Regression test for PR 269519 and 269561"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_notdir_file_body() {
tarfs_setup
@@ -223,6 +231,7 @@ atf_test_case tarfs_notdir_file_gnu cleanup
tarfs_notdir_file_gnu_head() {
atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
atf_set "require.progs" "gtar"
}
tarfs_notdir_file_gnu_body() {
@@ -237,6 +246,7 @@ atf_test_case tarfs_emptylink cleanup
tarfs_emptylink_head() {
atf_set "descr" "Regression test for PR 277360: empty link target"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_emptylink_body() {
tarfs_setup
@@ -256,6 +266,7 @@ atf_test_case tarfs_linktodir cleanup
tarfs_linktodir_head() {
atf_set "descr" "Regression test for PR 277360: link to directory"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_linktodir_body() {
tarfs_setup
@@ -276,6 +287,7 @@ atf_test_case tarfs_linktononexistent cleanup
tarfs_linktononexistent_head() {
atf_set "descr" "Regression test for PR 277360: link to nonexistent target"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_linktononexistent_body() {
tarfs_setup
@@ -293,6 +305,7 @@ atf_test_case tarfs_checksum cleanup
tarfs_checksum_head() {
atf_set "descr" "Verify that the checksum covers header padding"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_checksum_body() {
tarfs_setup
@@ -313,6 +326,7 @@ atf_test_case tarfs_long_names cleanup
tarfs_long_names_head() {
atf_set "descr" "Verify that tarfs supports long file names"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_long_names_body() {
tarfs_setup
@@ -337,6 +351,7 @@ atf_test_case tarfs_long_paths cleanup
tarfs_long_paths_head() {
atf_set "descr" "Verify that tarfs supports long paths"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
}
tarfs_long_paths_body() {
tarfs_setup
@@ -361,6 +376,7 @@ atf_test_case tarfs_git_archive cleanup
tarfs_git_archive_head() {
atf_set "descr" "Verify that tarfs supports archives created by git"
atf_set "require.user" "root"
+ atf_set "require.kmods" "tarfs"
atf_set "require.progs" "git"
}
tarfs_git_archive_body() {
diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile
index 900c9a5b3bbe..94a75048a31a 100644
--- a/tests/sys/kern/Makefile
+++ b/tests/sys/kern/Makefile
@@ -15,7 +15,10 @@ ATF_TESTS_C+= kcov
.endif
ATF_TESTS_C+= kern_copyin
ATF_TESTS_C+= kern_descrip_test
+# One test modifies the maxfiles limit, which can cause spurious test failures.
+TEST_METADATA.kern_descrip_test+= is_exclusive="true"
ATF_TESTS_C+= fdgrowtable_test
+ATF_TESTS_C+= jail_lookup_root
ATF_TESTS_C+= kill_zombie
.if ${MK_OPENSSL} != "no"
ATF_TESTS_C+= ktls_test
@@ -75,6 +78,7 @@ PROGS+= coredump_phnum_helper
PROGS+= pdeathsig_helper
PROGS+= sendfile_helper
+LIBADD.jail_lookup_root+= jail util
CFLAGS.sys_getrandom+= -I${SRCTOP}/sys/contrib/zstd/lib
LIBADD.sys_getrandom+= zstd
LIBADD.sys_getrandom+= c
@@ -92,6 +96,7 @@ LIBADD.sendfile_helper+= pthread
LIBADD.fdgrowtable_test+= util pthread kvm procstat
LIBADD.sigwait+= rt
LIBADD.ktrace_test+= sysdecode
+LIBADD.unix_stream+= pthread
NETBSD_ATF_TESTS_C+= lockf_test
NETBSD_ATF_TESTS_C+= mqueue_test
diff --git a/tests/sys/kern/jail_lookup_root.c b/tests/sys/kern/jail_lookup_root.c
new file mode 100644
index 000000000000..34e89f4aea2b
--- /dev/null
+++ b/tests/sys/kern/jail_lookup_root.c
@@ -0,0 +1,133 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Mark Johnston <markj@FreeBSD.org>
+ */
+
+#include <sys/param.h>
+#include <sys/jail.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <jail.h>
+#include <mntopts.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <atf-c.h>
+
+static void
+mkdir_checked(const char *dir, mode_t mode)
+{
+ int error;
+
+ error = mkdir(dir, mode);
+ ATF_REQUIRE_MSG(error == 0 || errno == EEXIST,
+ "mkdir %s: %s", dir, strerror(errno));
+}
+
+static void __unused
+mount_nullfs(const char *dir, const char *target)
+{
+ struct iovec *iov;
+ char errmsg[1024];
+ int error, iovlen;
+
+ iov = NULL;
+ iovlen = 0;
+
+ build_iovec(&iov, &iovlen, __DECONST(char *, "fstype"),
+ __DECONST(char *, "nullfs"), (size_t)-1);
+ build_iovec(&iov, &iovlen, __DECONST(char *, "fspath"),
+ __DECONST(char *, target), (size_t)-1);
+ build_iovec(&iov, &iovlen, __DECONST(char *, "from"),
+ __DECONST(char *, dir), (size_t)-1);
+ build_iovec(&iov, &iovlen, __DECONST(char *, "errmsg"),
+ errmsg, sizeof(errmsg));
+
+ errmsg[0] = '\0';
+ error = nmount(iov, iovlen, 0);
+ ATF_REQUIRE_MSG(error == 0, "nmount: %s",
+ errmsg[0] != '\0' ? errmsg : strerror(errno));
+
+ free_iovec(&iov, &iovlen);
+}
+
+ATF_TC_WITH_CLEANUP(jail_root);
+ATF_TC_HEAD(jail_root, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(jail_root, tc)
+{
+ int error, fd, jid;
+
+ mkdir_checked("./root", 0755);
+ mkdir_checked("./root/a", 0755);
+ mkdir_checked("./root/b", 0755);
+ mkdir_checked("./root/a/c", 0755);
+
+ jid = jail_setv(JAIL_CREATE | JAIL_ATTACH,
+ "name", "nullfs_jail_root_test",
+ "allow.mount", "true",
+ "allow.mount.nullfs", "true",
+ "enforce_statfs", "1",
+ "path", "./root",
+ "persist", NULL,
+ NULL);
+ ATF_REQUIRE_MSG(jid >= 0, "jail_setv: %s", jail_errmsg);
+
+ mount_nullfs("/a", "/b");
+
+ error = chdir("/b/c");
+ ATF_REQUIRE(error == 0);
+
+ error = rename("/a/c", "/c");
+ ATF_REQUIRE(error == 0);
+
+ /* Descending to the jail root should be ok. */
+ error = chdir("..");
+ ATF_REQUIRE(error == 0);
+
+ /* Going beyond the root will trigger an error. */
+ error = chdir("..");
+ ATF_REQUIRE_ERRNO(ENOENT, error != 0);
+ fd = open("..", O_RDONLY | O_DIRECTORY);
+ ATF_REQUIRE_ERRNO(ENOENT, fd < 0);
+}
+ATF_TC_CLEANUP(jail_root, tc)
+{
+ struct statfs fs;
+ fsid_t fsid;
+ int error, jid;
+
+ error = statfs("./root/b", &fs);
+ if (error != 0)
+ err(1, "statfs ./b");
+ fsid = fs.f_fsid;
+ error = statfs("./root", &fs);
+ if (error != 0)
+ err(1, "statfs ./root");
+ if (fsid.val[0] != fs.f_fsid.val[0] ||
+ fsid.val[1] != fs.f_fsid.val[1]) {
+ error = unmount("./root/b", 0);
+ if (error != 0)
+ err(1, "unmount ./root/b");
+ }
+
+ jid = jail_getid("nullfs_jail_root_test");
+ if (jid >= 0) {
+ error = jail_remove(jid);
+ if (error != 0)
+ err(1, "jail_remove");
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, jail_root);
+ return (atf_no_error());
+}
diff --git a/tests/sys/kern/ptrace_test.c b/tests/sys/kern/ptrace_test.c
index db681293f043..5dc235eb9200 100644
--- a/tests/sys/kern/ptrace_test.c
+++ b/tests/sys/kern/ptrace_test.c
@@ -28,6 +28,7 @@
#include <sys/elf.h>
#include <sys/event.h>
#include <sys/file.h>
+#include <sys/mman.h>
#include <sys/time.h>
#include <sys/procctl.h>
#include <sys/procdesc.h>
@@ -4378,7 +4379,10 @@ ATF_TC_BODY(ptrace__PT_SC_REMOTE_getpid, tc)
exit(0);
}
- attach_child(fpid);
+ wpid = waitpid(fpid, &status, 0);
+ REQUIRE_EQ(wpid, fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ REQUIRE_EQ(WSTOPSIG(status), SIGSTOP);
pscr.pscr_syscall = SYS_getpid;
pscr.pscr_nargs = 0;
@@ -4461,6 +4465,65 @@ ATF_TC_BODY(ptrace__reap_kill_stopped, tc)
REQUIRE_EQ(-1, prk.rk_fpid);
}
+struct child_res {
+ struct timespec sleep_time;
+ int nanosleep_res;
+ int nanosleep_errno;
+};
+
+static const long nsec = 1000000000L;
+static const struct timespec ten_sec = {
+ .tv_sec = 10,
+ .tv_nsec = 0,
+};
+static const struct timespec twelve_sec = {
+ .tv_sec = 12,
+ .tv_nsec = 0,
+};
+
+ATF_TC_WITHOUT_HEAD(ptrace__PT_ATTACH_no_EINTR);
+ATF_TC_BODY(ptrace__PT_ATTACH_no_EINTR, tc)
+{
+ struct child_res *shm;
+ struct timespec rqt, now, wake;
+ pid_t debuggee;
+ int status;
+
+ shm = mmap(NULL, sizeof(*shm), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANON, -1, 0);
+ ATF_REQUIRE(shm != MAP_FAILED);
+
+ ATF_REQUIRE((debuggee = fork()) != -1);
+ if (debuggee == 0) {
+ rqt.tv_sec = 10;
+ rqt.tv_nsec = 0;
+ clock_gettime(CLOCK_MONOTONIC_PRECISE, &now);
+ errno = 0;
+ shm->nanosleep_res = nanosleep(&rqt, NULL);
+ shm->nanosleep_errno = errno;
+ clock_gettime(CLOCK_MONOTONIC_PRECISE, &wake);
+ timespecsub(&wake, &now, &shm->sleep_time);
+ _exit(0);
+ }
+
+ /* Give the debuggee some time to go to sleep. */
+ sleep(2);
+ REQUIRE_EQ(ptrace(PT_ATTACH, debuggee, 0, 0), 0);
+ REQUIRE_EQ(waitpid(debuggee, &status, 0), debuggee);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ REQUIRE_EQ(WSTOPSIG(status), SIGSTOP);
+
+ REQUIRE_EQ(ptrace(PT_DETACH, debuggee, 0, 0), 0);
+ REQUIRE_EQ(waitpid(debuggee, &status, 0), debuggee);
+ ATF_REQUIRE(WIFEXITED(status));
+ REQUIRE_EQ(WEXITSTATUS(status), 0);
+
+ ATF_REQUIRE(shm->nanosleep_res == 0);
+ ATF_REQUIRE(shm->nanosleep_errno == 0);
+ ATF_REQUIRE(timespeccmp(&shm->sleep_time, &ten_sec, >=));
+ ATF_REQUIRE(timespeccmp(&shm->sleep_time, &twelve_sec, <=));
+}
+
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, ptrace__parent_wait_after_trace_me);
@@ -4529,6 +4592,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, ptrace__procdesc_reparent_wait_child);
ATF_TP_ADD_TC(tp, ptrace__PT_SC_REMOTE_getpid);
ATF_TP_ADD_TC(tp, ptrace__reap_kill_stopped);
+ ATF_TP_ADD_TC(tp, ptrace__PT_ATTACH_no_EINTR);
return (atf_no_error());
}
diff --git a/tests/sys/kern/socket_splice.c b/tests/sys/kern/socket_splice.c
index 3a85ae91ecc7..dfd4cb4f5957 100644
--- a/tests/sys/kern/socket_splice.c
+++ b/tests/sys/kern/socket_splice.c
@@ -84,7 +84,7 @@ tcp_socketpair(int out[2], int domain)
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_len = sizeof(sin);
- sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
sin.sin_port = htons(0);
sinp = (struct sockaddr *)&sin;
} else {
@@ -92,7 +92,7 @@ tcp_socketpair(int out[2], int domain)
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_len = sizeof(sin6);
- sin6.sin6_addr = in6addr_any;
+ sin6.sin6_addr = in6addr_loopback;
sin6.sin6_port = htons(0);
sinp = (struct sockaddr *)&sin6;
}
diff --git a/tests/sys/kern/tty/Makefile b/tests/sys/kern/tty/Makefile
index c362793a8b64..8628ab79875f 100644
--- a/tests/sys/kern/tty/Makefile
+++ b/tests/sys/kern/tty/Makefile
@@ -5,8 +5,11 @@ PLAIN_TESTS_PORCH+= test_canon
PLAIN_TESTS_PORCH+= test_canon_fullbuf
PLAIN_TESTS_PORCH+= test_ncanon
PLAIN_TESTS_PORCH+= test_recanon
+ATF_TESTS_C+= test_sti
PROGS+= fionread
PROGS+= readsz
+LIBADD.test_sti= util
+
.include <bsd.test.mk>
diff --git a/tests/sys/kern/tty/test_sti.c b/tests/sys/kern/tty/test_sti.c
new file mode 100644
index 000000000000..f792001b4e3f
--- /dev/null
+++ b/tests/sys/kern/tty/test_sti.c
@@ -0,0 +1,337 @@
+/*-
+ * Copyright (c) 2025 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <termios.h>
+
+#include <atf-c.h>
+#include <libutil.h>
+
+enum stierr {
+ STIERR_CONFIG_FETCH,
+ STIERR_CONFIG,
+ STIERR_INJECT,
+ STIERR_READFAIL,
+ STIERR_BADTEXT,
+ STIERR_DATAFOUND,
+ STIERR_ROTTY,
+ STIERR_WOTTY,
+ STIERR_WOOK,
+ STIERR_BADERR,
+
+ STIERR_MAXERR
+};
+
+static const struct stierr_map {
+ enum stierr stierr;
+ const char *msg;
+} stierr_map[] = {
+ { STIERR_CONFIG_FETCH, "Failed to fetch ctty configuration" },
+ { STIERR_CONFIG, "Failed to configure ctty in the child" },
+ { STIERR_INJECT, "Failed to inject characters via TIOCSTI" },
+ { STIERR_READFAIL, "Failed to read(2) from stdin" },
+ { STIERR_BADTEXT, "read(2) data did not match injected data" },
+ { STIERR_DATAFOUND, "read(2) data when we did not expected to" },
+ { STIERR_ROTTY, "Failed to open tty r/o" },
+ { STIERR_WOTTY, "Failed to open tty w/o" },
+ { STIERR_WOOK, "TIOCSTI on w/o tty succeeded" },
+ { STIERR_BADERR, "Received wrong error from failed TIOCSTI" },
+};
+_Static_assert(nitems(stierr_map) == STIERR_MAXERR,
+ "Failed to describe all errors");
+
+/*
+ * Inject each character of the input string into the TTY. The caller can
+ * assume that errno is preserved on return.
+ */
+static ssize_t
+inject(int fileno, const char *str)
+{
+ size_t nb = 0;
+
+ for (const char *walker = str; *walker != '\0'; walker++) {
+ if (ioctl(fileno, TIOCSTI, walker) != 0)
+ return (-1);
+ nb++;
+ }
+
+ return (nb);
+}
+
+/*
+ * Forks off a new process, stashes the parent's handle for the pty in *termfd
+ * and returns the pid. 0 for the child, >0 for the parent, as usual.
+ *
+ * Most tests fork so that we can do them while unprivileged, which we can only
+ * do if we're operating on our ctty (and we don't want to touch the tty of
+ * whatever may be running the tests).
+ */
+static int
+init_pty(int *termfd, bool canon)
+{
+ int pid;
+
+ pid = forkpty(termfd, NULL, NULL, NULL);
+ ATF_REQUIRE(pid != -1);
+
+ if (pid == 0) {
+ struct termios term;
+
+ /*
+ * Child reconfigures tty to disable echo and put it into raw
+ * mode if requested.
+ */
+ if (tcgetattr(STDIN_FILENO, &term) == -1)
+ _exit(STIERR_CONFIG_FETCH);
+ term.c_lflag &= ~ECHO;
+ if (!canon)
+ term.c_lflag &= ~ICANON;
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &term) == -1)
+ _exit(STIERR_CONFIG);
+ }
+
+ return (pid);
+}
+
+static void
+finalize_child(pid_t pid, int signo)
+{
+ int status, wpid;
+
+ while ((wpid = waitpid(pid, &status, 0)) != pid) {
+ if (wpid != -1)
+ continue;
+ ATF_REQUIRE_EQ_MSG(EINTR, errno,
+ "waitpid: %s", strerror(errno));
+ }
+
+ /*
+ * Some tests will signal the child for whatever reason, and we're
+ * expecting it to terminate it. For those cases, it's OK to just see
+ * that termination. For all other cases, we expect a graceful exit
+ * with an exit status that reflects a cause that we have an error
+ * mapped for.
+ */
+ if (signo >= 0) {
+ ATF_REQUIRE(WIFSIGNALED(status));
+ ATF_REQUIRE_EQ(signo, WTERMSIG(status));
+ } else {
+ ATF_REQUIRE(WIFEXITED(status));
+ if (WEXITSTATUS(status) != 0) {
+ int err = WEXITSTATUS(status);
+
+ for (size_t i = 0; i < nitems(stierr_map); i++) {
+ const struct stierr_map *map = &stierr_map[i];
+
+ if ((int)map->stierr == err) {
+ atf_tc_fail("%s", map->msg);
+ __assert_unreachable();
+ }
+ }
+ }
+ }
+}
+
+ATF_TC(basic);
+ATF_TC_HEAD(basic, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test for basic functionality of TIOCSTI");
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(basic, tc)
+{
+ int pid, term;
+
+ /*
+ * We don't canonicalize on this test because we can assume that the
+ * injected data will be available after TIOCSTI returns. This is all
+ * within a single thread for the basic test, so we simplify our lives
+ * slightly in raw mode.
+ */
+ pid = init_pty(&term, false);
+ if (pid == 0) {
+ static const char sending[] = "Text";
+ char readbuf[32];
+ ssize_t injected, readsz;
+
+ injected = inject(STDIN_FILENO, sending);
+ if (injected != sizeof(sending) - 1)
+ _exit(STIERR_INJECT);
+
+ readsz = read(STDIN_FILENO, readbuf, sizeof(readbuf));
+
+ if (readsz < 0 || readsz != injected)
+ _exit(STIERR_READFAIL);
+ if (memcmp(readbuf, sending, readsz) != 0)
+ _exit(STIERR_BADTEXT);
+
+ _exit(0);
+ }
+
+ finalize_child(pid, -1);
+}
+
+ATF_TC(root);
+ATF_TC_HEAD(root, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test that root can inject into another TTY");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(root, tc)
+{
+ static const char sending[] = "Text\r";
+ ssize_t injected;
+ int pid, term;
+
+ /*
+ * We leave canonicalization enabled for this one so that the read(2)
+ * below hangs until we have all of the data available, rather than
+ * having to signal OOB that it's safe to read.
+ */
+ pid = init_pty(&term, true);
+ if (pid == 0) {
+ char readbuf[32];
+ ssize_t readsz;
+
+ readsz = read(STDIN_FILENO, readbuf, sizeof(readbuf));
+ if (readsz < 0 || readsz != sizeof(sending) - 1)
+ _exit(STIERR_READFAIL);
+
+ /*
+ * Here we ignore the trailing \r, because it won't have
+ * surfaced in our read(2).
+ */
+ if (memcmp(readbuf, sending, readsz - 1) != 0)
+ _exit(STIERR_BADTEXT);
+
+ _exit(0);
+ }
+
+ injected = inject(term, sending);
+ ATF_REQUIRE_EQ_MSG(sizeof(sending) - 1, injected,
+ "Injected %zu characters, expected %zu", injected,
+ sizeof(sending) - 1);
+
+ finalize_child(pid, -1);
+}
+
+ATF_TC(unprivileged_fail_noctty);
+ATF_TC_HEAD(unprivileged_fail_noctty, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test that unprivileged cannot inject into non-controlling TTY");
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(unprivileged_fail_noctty, tc)
+{
+ const char sending[] = "Text";
+ ssize_t injected;
+ int pid, serrno, term;
+
+ pid = init_pty(&term, false);
+ if (pid == 0) {
+ char readbuf[32];
+ ssize_t readsz;
+
+ /*
+ * This should hang until we get terminated by the parent.
+ */
+ readsz = read(STDIN_FILENO, readbuf, sizeof(readbuf));
+ if (readsz > 0)
+ _exit(STIERR_DATAFOUND);
+
+ _exit(0);
+ }
+
+ /* Should fail. */
+ injected = inject(term, sending);
+ serrno = errno;
+
+ /* Done with the child, just kill it now to avoid problems later. */
+ kill(pid, SIGINT);
+ finalize_child(pid, SIGINT);
+
+ ATF_REQUIRE_EQ_MSG(-1, (ssize_t)injected,
+ "TIOCSTI into non-ctty succeeded");
+ ATF_REQUIRE_EQ(EACCES, serrno);
+}
+
+ATF_TC(unprivileged_fail_noread);
+ATF_TC_HEAD(unprivileged_fail_noread, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test that unprivileged cannot inject into TTY not opened for read");
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(unprivileged_fail_noread, tc)
+{
+ int pid, term;
+
+ /*
+ * Canonicalization actually doesn't matter for this one, we'll trust
+ * that the failure means we didn't inject anything.
+ */
+ pid = init_pty(&term, true);
+ if (pid == 0) {
+ static const char sending[] = "Text";
+ ssize_t injected;
+ int rotty, wotty;
+
+ /*
+ * We open the tty both r/o and w/o to ensure we got the device
+ * name right; one of these will pass, one of these will fail.
+ */
+ wotty = openat(STDIN_FILENO, "", O_EMPTY_PATH | O_WRONLY);
+ if (wotty == -1)
+ _exit(STIERR_WOTTY);
+ rotty = openat(STDIN_FILENO, "", O_EMPTY_PATH | O_RDONLY);
+ if (rotty == -1)
+ _exit(STIERR_ROTTY);
+
+ /*
+ * This injection is expected to fail with EPERM, because it may
+ * be our controlling tty but it is not open for reading.
+ */
+ injected = inject(wotty, sending);
+ if (injected != -1)
+ _exit(STIERR_WOOK);
+ if (errno != EPERM)
+ _exit(STIERR_BADERR);
+
+ /*
+ * Demonstrate that it does succeed on the other fd we opened,
+ * which is r/o.
+ */
+ injected = inject(rotty, sending);
+ if (injected != sizeof(sending) - 1)
+ _exit(STIERR_INJECT);
+
+ _exit(0);
+ }
+
+ finalize_child(pid, -1);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, basic);
+ ATF_TP_ADD_TC(tp, root);
+ ATF_TP_ADD_TC(tp, unprivileged_fail_noctty);
+ ATF_TP_ADD_TC(tp, unprivileged_fail_noread);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/kern/unix_passfd_test.c b/tests/sys/kern/unix_passfd_test.c
index 74095859d899..8c6a976a0e12 100644
--- a/tests/sys/kern/unix_passfd_test.c
+++ b/tests/sys/kern/unix_passfd_test.c
@@ -544,6 +544,51 @@ ATF_TC_BODY(send_overflow, tc)
closesocketpair(fd);
}
+/*
+ * Make sure that we do not receive descriptors with MSG_PEEK.
+ */
+ATF_TC_WITHOUT_HEAD(peek);
+ATF_TC_BODY(peek, tc)
+{
+ int fd[2], getfd, putfd, nfds;
+
+ domainsocketpair(fd);
+ tempfile(&putfd);
+ nfds = getnfds();
+ sendfd(fd[0], putfd);
+ ATF_REQUIRE(getnfds() == nfds);
+
+ /* First make MSG_PEEK recvmsg(2)... */
+ char cbuf[CMSG_SPACE(sizeof(int))];
+ char buf[1];
+ struct iovec iov = {
+ .iov_base = buf,
+ .iov_len = sizeof(buf)
+ };
+ struct msghdr msghdr = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = cbuf,
+ .msg_controllen = sizeof(cbuf),
+ };
+ ATF_REQUIRE(1 == recvmsg(fd[1], &msghdr, MSG_PEEK));
+ for (struct cmsghdr *cmsghdr = CMSG_FIRSTHDR(&msghdr);
+ cmsghdr != NULL; cmsghdr = CMSG_NXTHDR(&msghdr, cmsghdr)) {
+ /* Usually this is some garbage. */
+ printf("level %d type %d len %u\n",
+ cmsghdr->cmsg_level, cmsghdr->cmsg_type, cmsghdr->cmsg_len);
+ }
+
+ /* ... and make sure we did not receive any descriptors! */
+ ATF_REQUIRE(getnfds() == nfds);
+
+ /* Now really receive a descriptor. */
+ recvfd(fd[1], &getfd, 0);
+ ATF_REQUIRE(getnfds() == nfds + 1);
+ close(putfd);
+ close(getfd);
+ closesocketpair(fd);
+}
/*
* Send two files. Then receive them. Make sure they are returned in the
@@ -997,6 +1042,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, send_and_shutdown);
ATF_TP_ADD_TC(tp, send_a_lot);
ATF_TP_ADD_TC(tp, send_overflow);
+ ATF_TP_ADD_TC(tp, peek);
ATF_TP_ADD_TC(tp, two_files);
ATF_TP_ADD_TC(tp, bundle);
ATF_TP_ADD_TC(tp, bundle_cancel);
diff --git a/tests/sys/kern/unix_seqpacket_test.c b/tests/sys/kern/unix_seqpacket_test.c
index d142e228b036..b9a6be015241 100644
--- a/tests/sys/kern/unix_seqpacket_test.c
+++ b/tests/sys/kern/unix_seqpacket_test.c
@@ -894,6 +894,38 @@ ATF_TC_BODY(shutdown_send_sigpipe, tc)
close(s2);
}
+/*
+ * https://syzkaller.appspot.com/bug?id=ac94349a29f2efc40e9274239e4ca9b2c473a4e7
+ */
+ATF_TC_WITHOUT_HEAD(shutdown_o_async);
+ATF_TC_BODY(shutdown_o_async, tc)
+{
+ int sv[2];
+
+ do_socketpair(sv);
+
+ ATF_CHECK_EQ(0, fcntl(sv[0], F_SETFL, O_ASYNC));
+ ATF_CHECK_EQ(0, shutdown(sv[0], SHUT_WR));
+ close(sv[0]);
+ close(sv[1]);
+}
+
+/*
+ * If peer had done SHUT_WR on their side, our recv(2) shouldn't block.
+ */
+ATF_TC_WITHOUT_HEAD(shutdown_recv);
+ATF_TC_BODY(shutdown_recv, tc)
+{
+ char buf[10];
+ int sv[2];
+
+ do_socketpair(sv);
+ ATF_CHECK_EQ(0, shutdown(sv[0], SHUT_WR));
+ ATF_CHECK_EQ(0, recv(sv[1], buf, sizeof(buf), 0));
+ close(sv[0]);
+ close(sv[1]);
+}
+
/* nonblocking send(2) and recv(2) a single short record */
ATF_TC_WITHOUT_HEAD(send_recv_nonblocking);
ATF_TC_BODY(send_recv_nonblocking, tc)
@@ -1197,8 +1229,6 @@ ATF_TC_BODY(random_eor_and_waitall, tc)
size_t off;
int fd[2], eor;
- atf_tc_skip("https://bugs.freebsd.org/279354");
-
arc4random_buf(params.seed, sizeof(params.seed));
printf("Using seed:");
for (u_int i = 0; i < (u_int)sizeof(params.seed)/sizeof(u_short); i++)
@@ -1312,6 +1342,8 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, implied_connect);
ATF_TP_ADD_TC(tp, shutdown_send);
ATF_TP_ADD_TC(tp, shutdown_send_sigpipe);
+ ATF_TP_ADD_TC(tp, shutdown_o_async);
+ ATF_TP_ADD_TC(tp, shutdown_recv);
ATF_TP_ADD_TC(tp, eagain_8k_8k);
ATF_TP_ADD_TC(tp, eagain_8k_128k);
ATF_TP_ADD_TC(tp, eagain_128k_8k);
diff --git a/tests/sys/kern/unix_stream.c b/tests/sys/kern/unix_stream.c
index d93bbeff4e41..f8ba288308bd 100644
--- a/tests/sys/kern/unix_stream.c
+++ b/tests/sys/kern/unix_stream.c
@@ -28,6 +28,7 @@
#include <sys/cdefs.h>
#include <sys/socket.h>
#include <sys/event.h>
+#include <sys/select.h>
#include <sys/sysctl.h>
#include <sys/un.h>
#include <errno.h>
@@ -35,6 +36,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
+#include <pthread.h>
+#include <pthread_np.h>
#include <atf-c.h>
@@ -100,47 +103,102 @@ ATF_TC_BODY(send_0, tc)
}
static void
-check_writable(int fd, int expect)
+check_readable_select(int fd, int expect, bool timeout)
+{
+ fd_set rdfds;
+ int nfds;
+
+ FD_ZERO(&rdfds);
+ FD_SET(fd, &rdfds);
+ nfds = select(fd + 1, &rdfds, NULL, NULL, timeout ?
+ &(struct timeval){.tv_usec = 1000} : NULL);
+ ATF_REQUIRE_MSG(nfds == expect,
+ "select() returns %d errno %d", nfds, errno);
+}
+
+static void
+check_writable_select(int fd, int expect, bool timeout)
{
fd_set wrfds;
- struct pollfd pfd[1];
- struct kevent kev;
- int nfds, kq;
+ int nfds;
FD_ZERO(&wrfds);
FD_SET(fd, &wrfds);
- nfds = select(fd + 1, NULL, &wrfds, NULL,
- &(struct timeval){.tv_usec = 1000});
+ nfds = select(fd + 1, NULL, &wrfds, NULL, timeout ?
+ &(struct timeval){.tv_usec = 1000} : NULL);
ATF_REQUIRE_MSG(nfds == expect,
"select() returns %d errno %d", nfds, errno);
+}
+
+static void
+check_readable_poll(int fd, int expect, bool timeout)
+{
+ struct pollfd pfd[1];
+ int nfds;
+
+ pfd[0] = (struct pollfd){
+ .fd = fd,
+ .events = POLLIN | POLLRDNORM,
+ };
+ nfds = poll(pfd, 1, timeout ? 1 : INFTIM);
+ ATF_REQUIRE_MSG(nfds == expect,
+ "poll() returns %d errno %d", nfds, errno);
+}
+
+static void
+check_writable_poll(int fd, int expect, bool timeout)
+{
+ struct pollfd pfd[1];
+ int nfds;
pfd[0] = (struct pollfd){
.fd = fd,
.events = POLLOUT | POLLWRNORM,
};
- nfds = poll(pfd, 1, 1);
+ nfds = poll(pfd, 1, timeout ? 1 : INFTIM);
ATF_REQUIRE_MSG(nfds == expect,
"poll() returns %d errno %d", nfds, errno);
+}
+
+static void
+check_writable_kevent(int fd, int expect, bool timeout)
+{
+ struct kevent kev;
+ int nfds, kq;
ATF_REQUIRE(kq = kqueue());
EV_SET(&kev, fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL);
- ATF_REQUIRE(kevent(kq, &kev, 1, NULL, 0, NULL) == 0);
- nfds = kevent(kq, NULL, 0, &kev, 1,
- &(struct timespec){.tv_nsec = 1000000});
+ nfds = kevent(kq, &kev, 1, NULL, 0, NULL);
+ ATF_REQUIRE_MSG(nfds == 0,
+ "kevent() returns %d errno %d", nfds, errno);
+ nfds = kevent(kq, NULL, 0, &kev, 1, timeout ?
+ &(struct timespec){.tv_nsec = 1000000} : NULL);
ATF_REQUIRE_MSG(nfds == expect,
- "kevent() returns %d errno %d", nfds, errno);
+ "kevent() returns %d errno %d", nfds, errno);
close(kq);
}
-/*
- * Make sure that a full socket is not reported as writable by event APIs.
- */
-ATF_TC_WITHOUT_HEAD(full_not_writable);
-ATF_TC_BODY(full_not_writable, tc)
+typedef void check_writable_func_t(int, int, bool);
+struct check_writable_ctx {
+ check_writable_func_t *method;
+ int fd;
+};
+
+static void *
+check_writable_blocking_thread(void *arg)
+{
+ struct check_writable_ctx *ctx = arg;
+
+ ctx->method(ctx->fd, 1, false);
+
+ return (NULL);
+}
+
+static void
+full_socketpair(int *sv)
{
void *buf;
u_long sendspace;
- int sv[2];
sendspace = getsendspace();
ATF_REQUIRE((buf = malloc(sendspace)) != NULL);
@@ -149,15 +207,175 @@ ATF_TC_BODY(full_not_writable, tc)
do {} while (send(sv[0], buf, sendspace, 0) == (ssize_t)sendspace);
ATF_REQUIRE(errno == EAGAIN);
ATF_REQUIRE(fcntl(sv[0], F_SETFL, 0) != -1);
+ free(buf);
+}
- check_writable(sv[0], 0);
+static void
+full_writability_check(int *sv, check_writable_func_t method)
+{
+ struct check_writable_ctx ctx = {
+ .method = method,
+ .fd = sv[0],
+ };
+ pthread_t thr;
+ void *buf;
+ u_long space;
+
+ space = getsendspace() / 2;
+ ATF_REQUIRE((buf = malloc(space)) != NULL);
+
+ /* First check with timeout, expecting 0 fds returned. */
+ method(sv[0], 0, true);
- /* Read some data and re-check. */
- ATF_REQUIRE(read(sv[1], buf, sendspace / 2) == (ssize_t)sendspace / 2);
+ /* Launch blocking thread. */
+ ATF_REQUIRE(pthread_create(&thr, NULL, check_writable_blocking_thread,
+ &ctx) == 0);
- check_writable(sv[0], 1);
+ /* Sleep a bit to make sure that thread is put to sleep. */
+ usleep(10000);
+ ATF_REQUIRE(pthread_peekjoin_np(thr, NULL) == EBUSY);
+ /* Read some data and re-check, the fd is expected to be returned. */
+ ATF_REQUIRE(read(sv[1], buf, space) == (ssize_t)space);
+
+ method(sv[0], 1, true);
+
+ /* Now check that thread was successfully woken up and exited. */
+ ATF_REQUIRE(pthread_join(thr, NULL) == 0);
+
+ close(sv[0]);
+ close(sv[1]);
free(buf);
+}
+
+/*
+ * Make sure that a full socket is not reported as writable by event APIs.
+ */
+ATF_TC_WITHOUT_HEAD(full_writability_select);
+ATF_TC_BODY(full_writability_select, tc)
+{
+ int sv[2];
+
+ full_socketpair(sv);
+ full_writability_check(sv, check_writable_select);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(full_writability_poll);
+ATF_TC_BODY(full_writability_poll, tc)
+{
+ int sv[2];
+
+ full_socketpair(sv);
+ full_writability_check(sv, check_writable_poll);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(full_writability_kevent);
+ATF_TC_BODY(full_writability_kevent, tc)
+{
+ int sv[2];
+
+ full_socketpair(sv);
+ full_writability_check(sv, check_writable_kevent);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(connected_writability);
+ATF_TC_BODY(connected_writability, tc)
+{
+ int sv[2];
+
+ do_socketpair(sv);
+ check_writable_select(sv[0], 1, true);
+ check_writable_poll(sv[0], 1, true);
+ check_writable_kevent(sv[0], 1, true);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(unconnected_writability);
+ATF_TC_BODY(unconnected_writability, tc)
+{
+ int s;
+
+ ATF_REQUIRE((s = socket(PF_LOCAL, SOCK_STREAM, 0)) > 0);
+ check_writable_select(s, 0, true);
+ check_writable_poll(s, 0, true);
+ check_writable_kevent(s, 0, true);
+ close(s);
+}
+
+ATF_TC_WITHOUT_HEAD(peerclosed_writability);
+ATF_TC_BODY(peerclosed_writability, tc)
+{
+ struct kevent kev;
+ int sv[2], kq;
+
+ do_socketpair(sv);
+ close(sv[1]);
+
+ check_writable_select(sv[0], 1, false);
+ check_writable_poll(sv[0], 1, false);
+
+ ATF_REQUIRE(kq = kqueue());
+ EV_SET(&kev, sv[0], EVFILT_WRITE, EV_ADD, 0, 0, NULL);
+ ATF_REQUIRE(kevent(kq, &kev, 1, &kev, 1, NULL) == 1);
+ ATF_REQUIRE(kev.ident == (uintptr_t)sv[0] &&
+ kev.filter == EVFILT_WRITE &&
+ kev.flags == EV_EOF);
+
+ close(sv[0]);
+}
+
+ATF_TC_WITHOUT_HEAD(peershutdown_writability);
+ATF_TC_BODY(peershutdown_writability, tc)
+{
+ int sv[2];
+
+ do_socketpair(sv);
+ shutdown(sv[1], SHUT_RD);
+
+ check_writable_select(sv[0], 1, false);
+ check_writable_poll(sv[0], 1, false);
+ /*
+ * XXXGL: historically unix(4) sockets were not reporting peer's
+ * shutdown(SHUT_RD) as our EV_EOF. The kevent(2) manual page says
+ * "filter will set EV_EOF when the reader disconnects", which is hard
+ * to interpret unambigously. For now leave the historic behavior,
+ * but we may want to change that in uipc_usrreq.c:uipc_filt_sowrite(),
+ * and then this test will look like the peerclosed_writability test.
+ */
+ check_writable_kevent(sv[0], 1, false);
+
+ close(sv[0]);
+ close(sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(peershutdown_readability);
+ATF_TC_BODY(peershutdown_readability, tc)
+{
+ ssize_t readsz;
+ int sv[2];
+ char c;
+
+ do_socketpair(sv);
+ shutdown(sv[1], SHUT_WR);
+
+ /*
+ * The other side should flag as readable in select(2) to allow it to
+ * read(2) and observe EOF. Ensure that both poll(2) and select(2)
+ * are consistent here.
+ */
+ check_readable_select(sv[0], 1, false);
+ check_readable_poll(sv[0], 1, false);
+
+ readsz = read(sv[0], &c, sizeof(c));
+ ATF_REQUIRE_INTEQ(0, readsz);
+
close(sv[0]);
close(sv[1]);
}
@@ -166,7 +384,14 @@ ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, getpeereid);
ATF_TP_ADD_TC(tp, send_0);
- ATF_TP_ADD_TC(tp, full_not_writable);
+ ATF_TP_ADD_TC(tp, connected_writability);
+ ATF_TP_ADD_TC(tp, unconnected_writability);
+ ATF_TP_ADD_TC(tp, full_writability_select);
+ ATF_TP_ADD_TC(tp, full_writability_poll);
+ ATF_TP_ADD_TC(tp, full_writability_kevent);
+ ATF_TP_ADD_TC(tp, peerclosed_writability);
+ ATF_TP_ADD_TC(tp, peershutdown_writability);
+ ATF_TP_ADD_TC(tp, peershutdown_readability);
return atf_no_error();
}
diff --git a/tests/sys/net/Makefile b/tests/sys/net/Makefile
index 95ab86156a0a..bc8f9c5e9c80 100644
--- a/tests/sys/net/Makefile
+++ b/tests/sys/net/Makefile
@@ -7,6 +7,8 @@ ATF_TESTS_C+= if_epair
ATF_TESTS_SH+= if_epair_test
ATF_TESTS_SH+= if_bridge_test
TEST_METADATA.if_bridge_test+= required_programs="python"
+TEST_METADATA.if_bridge_test+= execenv="jail"
+TEST_METADATA.if_bridge_test+= execenv_jail_params="vnet allow.raw_sockets"
ATF_TESTS_SH+= if_clone_test
ATF_TESTS_SH+= if_gif
ATF_TESTS_SH+= if_lagg_test
@@ -15,6 +17,7 @@ ATF_TESTS_SH+= if_tun_test
ATF_TESTS_SH+= if_vlan
ATF_TESTS_SH+= if_wg
+TESTS_SUBDIRS+= bpf
TESTS_SUBDIRS+= if_ovpn
TESTS_SUBDIRS+= routing
diff --git a/tests/sys/net/bpf/Makefile b/tests/sys/net/bpf/Makefile
new file mode 100644
index 000000000000..9c8a25b15d16
--- /dev/null
+++ b/tests/sys/net/bpf/Makefile
@@ -0,0 +1,15 @@
+.include <src.opts.mk>
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/net/bpf
+BINDIR= ${TESTSDIR}
+
+LIBADD+= nv
+
+PROGS= bpf_multi_read
+LIBADD.bpf_multi_read+= pcap
+
+ATF_TESTS_SH= bpf
+
+.include <bsd.test.mk>
diff --git a/tests/sys/net/bpf/bpf.sh b/tests/sys/net/bpf/bpf.sh
new file mode 100644
index 000000000000..2830c4862de9
--- /dev/null
+++ b/tests/sys/net/bpf/bpf.sh
@@ -0,0 +1,67 @@
+##
+# 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.
+
+. $(atf_get_srcdir)/../../common/vnet.subr
+
+atf_test_case "multi_read" "cleanup"
+multi_read_head()
+{
+ atf_set descr 'Test multiple readers on /dev/bpf'
+ atf_set require.user root
+}
+
+multi_read_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
+
+ atf_check -s exit:0 -o ignore \
+ ping -c 1 192.0.2.2
+
+ # Start a multi-thread (or multi-process) read on bpf
+ $(atf_get_srcdir)/bpf_multi_read ${epair}a &
+
+ # Generate traffic
+ ping -f 192.0.2.2 >/dev/null 2>&1 &
+
+ # Now let this run for 10 seconds
+ sleep 10
+}
+
+multi_read_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "multi_read"
+}
diff --git a/tests/sys/net/bpf/bpf_multi_read.c b/tests/sys/net/bpf/bpf_multi_read.c
new file mode 100644
index 000000000000..3a8edd76d623
--- /dev/null
+++ b/tests/sys/net/bpf/bpf_multi_read.c
@@ -0,0 +1,76 @@
+/*-
+ * 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.
+ *
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <pcap.h>
+#include <unistd.h>
+
+static void
+callback(u_char *arg __unused, const struct pcap_pkthdr *hdr __unused,
+ const unsigned char *bytes __unused)
+{
+}
+
+int
+main(int argc, const char **argv)
+{
+ pcap_t *pcap;
+ const char *interface;
+ char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
+ int ret;
+
+ if (argc != 2)
+ err(1, "Usage: %s <interface>\n", argv[0]);
+
+ interface = argv[1];
+
+ pcap = pcap_create(interface, errbuf);
+ if (! pcap)
+ perror("Failed to pcap interface");
+
+ ret = pcap_set_snaplen(pcap, 86);
+ if (ret != 0)
+ perror("Failed to set snaplen");
+
+ ret = pcap_set_timeout(pcap, 100);
+ if (ret != 0)
+ perror("Failed to set timeout");
+
+ ret = pcap_activate(pcap);
+ if (ret != 0)
+ perror("Failed to activate");
+
+ /* So we have two readers on one /dev/bpf fd */
+ fork();
+
+ printf("Interface open\n");
+ pcap_loop(pcap, 0, callback, NULL);
+
+ return (0);
+}
diff --git a/tests/sys/net/if_bridge_test.sh b/tests/sys/net/if_bridge_test.sh
index 46ebb17edbdc..2c6b039048e3 100755
--- a/tests/sys/net/if_bridge_test.sh
+++ b/tests/sys/net/if_bridge_test.sh
@@ -703,6 +703,132 @@ many_bridge_members_cleanup()
vnet_cleanup
}
+atf_test_case "member_ifaddrs_enabled" "cleanup"
+member_ifaddrs_enabled_head()
+{
+ atf_set descr 'bridge with member_ifaddrs=1'
+ atf_set require.user root
+}
+
+member_ifaddrs_enabled_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ ep=$(vnet_mkepair)
+ ifconfig ${ep}a inet 192.0.2.1/24 up
+
+ vnet_mkjail one ${ep}b
+ jexec one sysctl net.link.bridge.member_ifaddrs=1
+ jexec one ifconfig ${ep}b inet 192.0.2.2/24 up
+ jexec one ifconfig bridge0 create addm ${ep}b
+
+ atf_check -s exit:0 -o ignore ping -c3 -t1 192.0.2.2
+}
+
+member_ifaddrs_enabled_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_test_case "member_ifaddrs_disabled" "cleanup"
+member_ifaddrs_disabled_head()
+{
+ atf_set descr 'bridge with member_ifaddrs=0'
+ atf_set require.user root
+}
+
+member_ifaddrs_disabled_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ vnet_mkjail one
+ jexec one sysctl net.link.bridge.member_ifaddrs=0
+
+ bridge=$(jexec one ifconfig bridge create)
+
+ # adding an interface with an IPv4 address
+ ep=$(jexec one ifconfig epair create)
+ jexec one ifconfig ${ep} 192.0.2.1/32
+ atf_check -s exit:1 -e ignore jexec one ifconfig ${bridge} addm ${ep}
+
+ # adding an interface with an IPv6 address
+ ep=$(jexec one ifconfig epair create)
+ jexec one ifconfig ${ep} inet6 2001:db8::1/128
+ atf_check -s exit:1 -e ignore jexec one ifconfig ${bridge} addm ${ep}
+
+ # adding an interface with an IPv6 link-local address
+ ep=$(jexec one ifconfig epair create)
+ jexec one ifconfig ${ep} inet6 -ifdisabled auto_linklocal up
+ atf_check -s exit:1 -e ignore jexec one ifconfig ${bridge} addm ${ep}
+
+ # adding an IPv4 address to a member
+ ep=$(jexec one ifconfig epair create)
+ jexec one ifconfig ${bridge} addm ${ep}
+ atf_check -s exit:1 -e ignore jexec one ifconfig ${ep} inet 192.0.2.2/32
+
+ # adding an IPv6 address to a member
+ ep=$(jexec one ifconfig epair create)
+ jexec one ifconfig ${bridge} addm ${ep}
+ atf_check -s exit:1 -e ignore jexec one ifconfig ${ep} inet6 2001:db8::1/128
+}
+
+member_ifaddrs_disabled_cleanup()
+{
+ vnet_cleanup
+}
+
+#
+# Test kern/287150: when member_ifaddrs=0, and a physical interface which is in
+# a bridge also has a vlan(4) on it, tagged packets are not correctly passed to
+# vlan(4).
+atf_test_case "member_ifaddrs_vlan" "cleanup"
+member_ifaddrs_vlan_head()
+{
+ atf_set descr 'kern/287150: vlan and bridge on the same interface'
+ atf_set require.user root
+}
+
+member_ifaddrs_vlan_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+ eptwo=$(vnet_mkepair)
+
+ # The first jail has an epair with an IP address on vlan 20.
+ vnet_mkjail one ${epone}a
+ atf_check -s exit:0 jexec one ifconfig ${epone}a up
+ atf_check -s exit:0 jexec one \
+ ifconfig ${epone}a.20 create inet 192.0.2.1/24 up
+
+ # The second jail has an epair with an IP address on vlan 20,
+ # which is also in a bridge.
+ vnet_mkjail two ${epone}b
+
+ jexec two ifconfig
+ atf_check -s exit:0 -o save:bridge jexec two ifconfig bridge create
+ bridge=$(cat bridge)
+ atf_check -s exit:0 jexec two ifconfig ${bridge} addm ${epone}b up
+
+ atf_check -s exit:0 -o ignore jexec two \
+ sysctl net.link.bridge.member_ifaddrs=0
+ atf_check -s exit:0 jexec two ifconfig ${epone}b up
+ atf_check -s exit:0 jexec two \
+ ifconfig ${epone}b.20 create inet 192.0.2.2/24 up
+
+ # Make sure the two jails can communicate over the vlan.
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+}
+
+member_ifaddrs_vlan_cleanup()
+{
+ vnet_cleanup
+}
+
atf_init_test_cases()
{
atf_add_test_case "bridge_transmit_ipv4_unicast"
@@ -718,4 +844,7 @@ atf_init_test_cases()
atf_add_test_case "mtu"
atf_add_test_case "vlan"
atf_add_test_case "many_bridge_members"
+ atf_add_test_case "member_ifaddrs_enabled"
+ atf_add_test_case "member_ifaddrs_disabled"
+ atf_add_test_case "member_ifaddrs_vlan"
}
diff --git a/tests/sys/net/if_lagg_test.sh b/tests/sys/net/if_lagg_test.sh
index 4bdb59c8aab9..e2b998599991 100755
--- a/tests/sys/net/if_lagg_test.sh
+++ b/tests/sys/net/if_lagg_test.sh
@@ -190,10 +190,6 @@ lacp_linkstate_destroy_stress_head()
}
lacp_linkstate_destroy_stress_body()
{
- if [ "$(atf_config_get ci false)" = "true" ]; then
- atf_skip "https://bugs.freebsd.org/244168"
- fi
-
local TAP0 TAP1 LAGG MAC SRCDIR
# Configure the lagg interface to use an RFC5737 nonrouteable addresses
diff --git a/tests/sys/net/if_wg.sh b/tests/sys/net/if_wg.sh
index e5df6afface1..6d2f56dc8f2e 100644
--- a/tests/sys/net/if_wg.sh
+++ b/tests/sys/net/if_wg.sh
@@ -34,6 +34,7 @@ wg_basic_head()
{
atf_set descr 'Create a wg(4) tunnel over an epair and pass traffic between jails'
atf_set require.user root
+ atf_set require.kmods if_wg
}
wg_basic_body()
@@ -41,8 +42,6 @@ wg_basic_body()
local epair pri1 pri2 pub1 pub2 wg1 wg2
local endpoint1 endpoint2 tunnel1 tunnel2
- kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
-
pri1=$(wg genkey)
pri2=$(wg genkey)
@@ -175,6 +174,7 @@ wg_basic_netmap_head()
{
atf_set descr 'Create a wg(4) tunnel over an epair and pass traffic between jails with netmap'
atf_set require.user root
+ atf_set require.kmods if_wg netmap
}
wg_basic_netmap_body()
@@ -183,9 +183,6 @@ wg_basic_netmap_body()
local endpoint1 endpoint2 tunnel1 tunnel2 tunnel3 tunnel4
local pid status
- kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
- kldload -n netmap || atf_skip "This test requires netmap and could not load it"
-
pri1=$(wg genkey)
pri2=$(wg genkey)
@@ -268,6 +265,7 @@ wg_key_peerdev_shared_head()
{
atf_set descr 'Create a wg(4) interface with a shared pubkey between device and a peer'
atf_set require.user root
+ atf_set require.kmods if_wg
}
wg_key_peerdev_shared_body()
@@ -275,8 +273,6 @@ wg_key_peerdev_shared_body()
local epair pri1 pub1 wg1
local endpoint1 tunnel1
- kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
-
pri1=$(wg genkey)
endpoint1=192.168.2.1
@@ -316,8 +312,6 @@ wg_key_peerdev_makeshared_body()
local epair pri1 pub1 pri2 wg1 wg2
local endpoint1 tunnel1
- kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
-
pri1=$(wg genkey)
pri2=$(wg genkey)
@@ -361,6 +355,7 @@ wg_vnet_parent_routing_head()
{
atf_set descr 'Create a wg(4) tunnel without epairs and pass traffic between jails'
atf_set require.user root
+ atf_set require.kmods if_wg
}
wg_vnet_parent_routing_body()
@@ -368,8 +363,6 @@ wg_vnet_parent_routing_body()
local pri1 pri2 pub1 pub2 wg1 wg2
local tunnel1 tunnel2
- kldload -n if_wg
-
pri1=$(wg genkey)
pri2=$(wg genkey)
diff --git a/tests/sys/netinet/arp.sh b/tests/sys/netinet/arp.sh
index c7744d5de938..df5dbc50ffa1 100755
--- a/tests/sys/netinet/arp.sh
+++ b/tests/sys/netinet/arp.sh
@@ -188,7 +188,9 @@ static_body() {
ipa=198.51.100.1
ipb=198.51.100.2
+ ipb_re=$(echo ${ipb} | sed 's/\./\\./g')
max_age=$(sysctl -n net.link.ether.inet.max_age)
+ max_age="(${max_age}|$((${max_age} - 1)))"
atf_check ifconfig -j ${jname}a ${epair0}a inet ${ipa}/24
eth="$(ifconfig -j ${jname}b ${epair0}b |
@@ -197,8 +199,8 @@ static_body() {
# Expected outputs
permanent=\
"? (${ipb}) at 00:00:00:00:00:00 on ${epair0}a permanent [ethernet]\n"
- temporary=\
-"? (${ipb}) at ${eth} on ${epair0}a expires in ${max_age} seconds [ethernet]\n"
+ temporary_re=\
+"\? \(${ipb_re}\) at ${eth} on ${epair0}a expires in ${max_age} seconds \[ethernet\]"
deleted=\
"${ipb} (${ipb}) deleted\n"
@@ -217,7 +219,7 @@ static_body() {
# then check -S
atf_check -o "inline:${deleted}" jexec ${jname}a arp -nd ${ipb}
atf_check -o ignore jexec ${jname}b ping -c1 ${ipa}
- atf_check -o "inline:${temporary}" jexec ${jname}a arp -n ${ipb}
+ atf_check -o "match:${temporary_re}" jexec ${jname}a arp -n ${ipb}
# Note: this doesn't fail, tracked all the way down to FreeBSD 8
# atf_check -s not-exit:0 jexec ${jname}a arp -s ${ipb} 0:0:0:0:0:0
atf_check -o "inline:${deleted}" \
diff --git a/tests/sys/netinet/igmp.py b/tests/sys/netinet/igmp.py
index 5d3b38cac38f..feb9b8b571d5 100644
--- a/tests/sys/netinet/igmp.py
+++ b/tests/sys/netinet/igmp.py
@@ -62,6 +62,25 @@ def check_igmpv3(args, pkt):
return True
+def check_igmpv2(args, pkt):
+ pkt.show()
+
+ igmp = pkt.getlayer(sc.igmp.IGMP)
+ if igmp is None:
+ return False
+
+ if igmp.gaddr != args["group"]:
+ return False
+
+ if args["type"] == "join":
+ if igmp.type != 0x16:
+ return False
+ if args["type"] == "leave":
+ if igmp.type != 0x17:
+ return False
+
+ return True
+
class TestIGMP(VnetTestTemplate):
REQUIRED_MODULES = []
TOPOLOGY = {
@@ -82,7 +101,7 @@ class TestIGMP(VnetTestTemplate):
@pytest.mark.require_progs(["scapy"])
def test_igmp3_join_leave(self):
- "Test that we send the expected join/leave IGMPv2 messages"
+ "Test that we send the expected join/leave IGMPv3 messages"
if1 = self.vnet.iface_alias_map["if1"]
@@ -107,3 +126,32 @@ class TestIGMP(VnetTestTemplate):
s.close()
sniffer.join()
assert(sniffer.correctPackets > 0)
+
+ @pytest.mark.require_progs(["scapy"])
+ def test_igmp2_join_leave(self):
+ "Test that we send the expected join/leave IGMPv2 messages"
+ ToolsHelper.print_output("/sbin/sysctl net.inet.igmp.default_version=2")
+
+ if1 = self.vnet.iface_alias_map["if1"]
+
+ # Start a background sniff
+ from sniffer import Sniffer
+ expected_pkt = { "type": "join", "group": "230.0.0.1" }
+ sniffer = Sniffer(expected_pkt, check_igmpv2, if1.name, timeout=10)
+
+ # Now join a multicast group, and see if we're getting the igmp packet we expect
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+ mreq = struct.pack("4sl", socket.inet_aton('230.0.0.1'), socket.INADDR_ANY)
+ s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+
+ # Wait for the sniffer to see the join packet
+ sniffer.join()
+ assert(sniffer.correctPackets > 0)
+
+ # Now leave, check for the packet
+ expected_pkt = { "type": "leave", "group": "230.0.0.1" }
+ sniffer = Sniffer(expected_pkt, check_igmpv2, if1.name)
+
+ s.close()
+ sniffer.join()
+ assert(sniffer.correctPackets > 0)
diff --git a/tests/sys/netinet/ip_reass_test.c b/tests/sys/netinet/ip_reass_test.c
index a65bfa34e1d4..538815bd7a2c 100644
--- a/tests/sys/netinet/ip_reass_test.c
+++ b/tests/sys/netinet/ip_reass_test.c
@@ -60,12 +60,16 @@ update_cksum(struct ip *ip)
{
size_t i;
uint32_t cksum;
- uint16_t *cksump;
+ uint8_t *cksump;
+ uint16_t tmp;
ip->ip_sum = 0;
- cksump = (uint16_t *)ip;
- for (cksum = 0, i = 0; i < sizeof(*ip) / sizeof(*cksump); cksump++, i++)
- cksum += ntohs(*cksump);
+ cksump = (char *)ip;
+ for (cksum = 0, i = 0; i < sizeof(*ip) / sizeof(uint16_t); i++) {
+ tmp = *cksump++;
+ tmp = tmp << 8 | *cksump++;
+ cksum += ntohs(tmp);
+ }
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum = ~(cksum + (cksum >> 16));
ip->ip_sum = htons((uint16_t)cksum);
diff --git a/tests/sys/netinet/socket_afinet.c b/tests/sys/netinet/socket_afinet.c
index 6fc98d982602..9c718fc5a901 100644
--- a/tests/sys/netinet/socket_afinet.c
+++ b/tests/sys/netinet/socket_afinet.c
@@ -550,7 +550,8 @@ bind_connected_port_test(const atf_tc_t *tc, int domain)
error = getsockname(sd[0], sinp, &(socklen_t){ sinp->sa_len });
ATF_REQUIRE_MSG(error == 0, "getsockname failed: %s", strerror(errno));
-
+ if (domain == PF_INET)
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
error = connect(sd[1], sinp, sinp->sa_len);
ATF_REQUIRE_MSG(error == 0, "connect failed: %s", strerror(errno));
tmp = accept(sd[0], NULL, NULL);
diff --git a/tests/sys/netinet/tcp_implied_connect.c b/tests/sys/netinet/tcp_implied_connect.c
index 6e8cb0606a0a..d03d6be4fb92 100644
--- a/tests/sys/netinet/tcp_implied_connect.c
+++ b/tests/sys/netinet/tcp_implied_connect.c
@@ -51,6 +51,7 @@ ATF_TC_BODY(tcp_implied_connect, tc)
ATF_REQUIRE(bind(s, (struct sockaddr *)&sin, sizeof(sin)) == 0);
len = sizeof(sin);
ATF_REQUIRE(getsockname(s, (struct sockaddr *)&sin, &len) == 0);
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ATF_REQUIRE(listen(s, -1) == 0);
#if 0
/*
diff --git a/tests/sys/netinet/udp_io.c b/tests/sys/netinet/udp_io.c
index 27cd02735ed4..04f9bf56ed02 100644
--- a/tests/sys/netinet/udp_io.c
+++ b/tests/sys/netinet/udp_io.c
@@ -52,6 +52,7 @@ udp_socketpair(int *s)
ATF_REQUIRE((c = socket(PF_INET, SOCK_DGRAM, 0)) > 0);
ATF_REQUIRE(bind(b, (struct sockaddr *)&sin, sizeof(sin)) == 0);
ATF_REQUIRE(getsockname(b, (struct sockaddr *)&sin, &slen) == 0);
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ATF_REQUIRE(connect(c, (struct sockaddr *)&sin, sizeof(sin)) == 0);
s[0] = b;
diff --git a/tests/sys/netinet6/Makefile b/tests/sys/netinet6/Makefile
index 753571fbf7a1..26f1a18a8d32 100644
--- a/tests/sys/netinet6/Makefile
+++ b/tests/sys/netinet6/Makefile
@@ -14,7 +14,8 @@ ATF_TESTS_SH= exthdr \
lpm6 \
fibs6 \
ndp \
- proxy_ndp
+ proxy_ndp \
+ addr6
TEST_METADATA.divert+= execenv="jail" \
execenv_jail_params="vnet allow.raw_sockets"
@@ -33,6 +34,8 @@ TEST_METADATA.redirect+= execenv="jail" \
execenv_jail_params="vnet allow.raw_sockets"
TEST_METADATA.scapyi386+= execenv="jail" \
execenv_jail_params="vnet allow.raw_sockets"
+TEST_METADATA.addr6+= execenv="jail" \
+ execenv_jail_params="vnet allow.raw_sockets"
${PACKAGE}FILES+= exthdr.py \
mld.py \
diff --git a/tests/sys/netinet6/addr6.sh b/tests/sys/netinet6/addr6.sh
new file mode 100755
index 000000000000..38e4bb152240
--- /dev/null
+++ b/tests/sys/netinet6/addr6.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env atf-sh
+#-
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2025 Lexi Winter.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+. $(atf_get_srcdir)/../common/vnet.subr
+
+atf_test_case "addr6_invalid_addr" "cleanup"
+addr6_invalid_addr_head()
+{
+ atf_set descr "adding an invalid IPv6 address returns an error"
+ atf_set require.user root
+}
+
+addr6_invalid_addr_body()
+{
+ vnet_init
+
+ ep=$(vnet_mkepair)
+ atf_check -s exit:0 ifconfig ${ep}a inet6 2001:db8::1/128
+ atf_check -s exit:1 -e ignore ifconfig ${ep}a inet6 2001:db8::1/127 alias
+}
+
+addr6_invalid_addr_cleanup()
+{
+ vnet_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "addr6_invalid_addr"
+}
diff --git a/tests/sys/netinet6/redirect.sh b/tests/sys/netinet6/redirect.sh
index aa0731d89101..40874f8c9b6d 100644
--- a/tests/sys/netinet6/redirect.sh
+++ b/tests/sys/netinet6/redirect.sh
@@ -39,10 +39,6 @@ valid_redirect_head() {
valid_redirect_body() {
- if [ "$(atf_config_get ci false)" = "true" ]; then
- atf_skip "https://bugs.freebsd.org/247729"
- fi
-
ids=65533
id=`printf "%x" ${ids}`
if [ $$ -gt 65535 ]; then
@@ -89,7 +85,7 @@ valid_redirect_body() {
while [ `ifconfig ${epair}a inet6 | grep -c tentative` != "0" ]; do
sleep 0.1
done
- while [ `jexec ${jname}b ifconfig ${epair}b inet6 | grep -c tentative` != "0" ]; do
+ while [ `jexec ${jname} ifconfig ${epair}b inet6 | grep -c tentative` != "0" ]; do
sleep 0.1
done
diff --git a/tests/sys/netlink/test_snl.c b/tests/sys/netlink/test_snl.c
index bd607efa82fc..040414a96e2c 100644
--- a/tests/sys/netlink/test_snl.c
+++ b/tests/sys/netlink/test_snl.c
@@ -13,6 +13,18 @@
#include <atf-c.h>
+static const struct snl_hdr_parser *snl_all_core_parsers[] = {
+ &snl_errmsg_parser, &snl_donemsg_parser,
+ &_nla_bit_parser, &_nla_bitset_parser,
+};
+
+static const struct snl_hdr_parser *snl_all_route_parsers[] = {
+ &_metrics_mp_nh_parser, &_mpath_nh_parser, &_metrics_parser, &snl_rtm_route_parser,
+ &_link_fbsd_parser, &snl_rtm_link_parser, &snl_rtm_link_parser_simple,
+ &_neigh_fbsd_parser, &snl_rtm_neigh_parser,
+ &_addr_fbsd_parser, &snl_rtm_addr_parser, &_nh_fbsd_parser, &snl_nhmsg_parser,
+};
+
static void
require_netlink(void)
{
diff --git a/tests/sys/netlink/test_snl_generic.c b/tests/sys/netlink/test_snl_generic.c
index 839127fe5232..c63b1380f2ad 100644
--- a/tests/sys/netlink/test_snl_generic.c
+++ b/tests/sys/netlink/test_snl_generic.c
@@ -11,6 +11,10 @@
#include <atf-c.h>
+static const struct snl_hdr_parser *snl_all_genl_parsers[] = {
+ &_genl_ctrl_getfam_parser, &_genl_ctrl_mc_parser,
+};
+
static void
require_netlink(void)
{
diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile
index e3110d0e5df7..d70631a9de53 100644
--- a/tests/sys/netpfil/pf/Makefile
+++ b/tests/sys/netpfil/pf/Makefile
@@ -21,7 +21,6 @@ ATF_TESTS_SH+= altq \
loginterface \
killstate \
macro \
- map_e \
match \
max_states \
mbuf \
@@ -60,6 +59,7 @@ ATF_TESTS_PYTEST+= nat64.py
ATF_TESTS_PYTEST+= nat66.py
ATF_TESTS_PYTEST+= return.py
ATF_TESTS_PYTEST+= sctp.py
+ATF_TESTS_PYTEST+= tcp.py
# Allow tests to run in parallel in their own jails
TEST_METADATA+= execenv="jail"
@@ -81,7 +81,8 @@ ${PACKAGE}FILES+= \
pft_ether.py \
pft_read_ipfix.py \
rdr-srcport.py \
- utils.subr
+ utils.subr \
+ utils.py
${PACKAGE}FILESMODE_bsnmpd.conf= 0555
${PACKAGE}FILESMODE_CVE-2019-5597.py= 0555
diff --git a/tests/sys/netpfil/pf/anchor.sh b/tests/sys/netpfil/pf/anchor.sh
index 463cd4d475e3..fbac10240d8d 100644
--- a/tests/sys/netpfil/pf/anchor.sh
+++ b/tests/sys/netpfil/pf/anchor.sh
@@ -361,6 +361,46 @@ nat_cleanup()
pft_cleanup
}
+atf_test_case "include" "cleanup"
+include_head()
+{
+ atf_set descr 'Test including inside anchors'
+ atf_set require.user root
+}
+
+include_body()
+{
+ pft_init
+
+ wd=`pwd`
+
+ epair=$(vnet_mkepair)
+ vnet_mkjail alcatraz ${epair}a
+
+ ifconfig ${epair}b 192.0.2.2/24 up
+ jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up
+
+ # Sanity check
+ atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
+
+ echo "pass" > ${wd}/extra.conf
+ jexec alcatraz pfctl -e
+ pft_set_rules alcatraz \
+ "block" \
+ "anchor \"foo\" {\n\
+ include \"${wd}/extra.conf\"\n\
+ }"
+
+ jexec alcatraz pfctl -sr
+
+ atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1
+}
+
+include_cleanup()
+{
+ pft_cleanup
+}
+
atf_init_test_cases()
{
atf_add_test_case "pr183198"
@@ -372,4 +412,5 @@ atf_init_test_cases()
atf_add_test_case "quick_nested"
atf_add_test_case "counter"
atf_add_test_case "nat"
+ atf_add_test_case "include"
}
diff --git a/tests/sys/netpfil/pf/frag6.py b/tests/sys/netpfil/pf/frag6.py
index 108b53874d0b..0ed980f96fdd 100644
--- a/tests/sys/netpfil/pf/frag6.py
+++ b/tests/sys/netpfil/pf/frag6.py
@@ -1,24 +1,11 @@
import pytest
import logging
-import threading
-import time
import random
logging.getLogger("scapy").setLevel(logging.CRITICAL)
+from utils import DelayedSend
from atf_python.sys.net.tools import ToolsHelper
from atf_python.sys.net.vnet import VnetTestTemplate
-class DelayedSend(threading.Thread):
- def __init__(self, packet):
- threading.Thread.__init__(self)
- self._packet = packet
-
- self.start()
-
- def run(self):
- import scapy.all as sp
- time.sleep(1)
- sp.send(self._packet)
-
class TestFrag6(VnetTestTemplate):
REQUIRED_MODULES = ["pf", "dummymbuf"]
TOPOLOGY = {
@@ -97,6 +84,96 @@ class TestFrag6(VnetTestTemplate):
sp.send(pkts, inter = 0.1)
+class TestFrag6HopHyHop(VnetTestTemplate):
+ REQUIRED_MODULES = ["pf"]
+ TOPOLOGY = {
+ "vnet1": {"ifaces": ["if1", "if2"]},
+ "vnet2": {"ifaces": ["if1", "if2"]},
+ "if1": {"prefixes6": [("2001:db8::1/64", "2001:db8::2/64")]},
+ "if2": {"prefixes6": [("2001:db8:666::1/64", "2001:db8:1::2/64")]},
+ }
+
+ def vnet2_handler(self, vnet):
+ ifname = vnet.iface_alias_map["if1"].name
+ ToolsHelper.print_output("/sbin/sysctl net.inet6.ip6.forwarding=1")
+ ToolsHelper.print_output("/usr/sbin/ndp -s 2001:db8:1::1 00:01:02:03:04:05")
+ ToolsHelper.print_output("/sbin/pfctl -e")
+ ToolsHelper.print_output("/sbin/pfctl -x loud")
+ ToolsHelper.pf_rules([
+ "scrub fragment reassemble min-ttl 10",
+ "pass",
+ ])
+
+ @pytest.mark.require_user("root")
+ @pytest.mark.require_progs(["scapy"])
+ def test_hop_by_hop(self):
+ "Verify that we reject non-first hop-by-hop headers"
+ if1 = self.vnet.iface_alias_map["if1"].name
+ if2 = self.vnet.iface_alias_map["if2"].name
+ ToolsHelper.print_output("/sbin/route add -6 default 2001:db8::2")
+ ToolsHelper.print_output("/sbin/ping6 -c 1 2001:db8:1::2")
+
+ # Import in the correct vnet, so at to not confuse Scapy
+ import scapy.all as sp
+
+ # A hop-by-hop header is accepted if it's the first header
+ pkt = sp.IPv6(src="2001:db8::1", dst="2001:db8:1::1") \
+ / sp.IPv6ExtHdrHopByHop() \
+ / sp.ICMPv6EchoRequest(data=sp.raw(bytes.fromhex('f0') * 30))
+ pkt.show()
+
+ # Delay the send so the sniffer is running when we transmit.
+ s = DelayedSend(pkt)
+
+ replies = sp.sniff(iface=if2, timeout=3)
+ found = False
+ for p in replies:
+ p.show()
+ ip6 = p.getlayer(sp.IPv6)
+ hbh = p.getlayer(sp.IPv6ExtHdrHopByHop)
+ icmp6 = p.getlayer(sp.ICMPv6EchoRequest)
+
+ if not ip6 or not icmp6:
+ continue
+ assert ip6.src == "2001:db8::1"
+ assert ip6.dst == "2001:db8:1::1"
+ assert hbh
+ assert icmp6
+ found = True
+ assert found
+
+ # A hop-by-hop header causes the packet to be dropped if it's not the
+ # first extension header
+ pkt = sp.IPv6(src="2001:db8::1", dst="2001:db8:1::1") \
+ / sp.IPv6ExtHdrFragment(offset=0, m=0) \
+ / sp.IPv6ExtHdrHopByHop() \
+ / sp.ICMPv6EchoRequest(data=sp.raw(bytes.fromhex('f0') * 30))
+ pkt2 = sp.IPv6(src="2001:db8::1", dst="2001:db8:1::1") \
+ / sp.ICMPv6EchoRequest(data=sp.raw(bytes.fromhex('f0') * 30))
+
+ # Delay the send so the sniffer is running when we transmit.
+ ToolsHelper.print_output("/sbin/ping6 -c 1 2001:db8:1::2")
+
+ s = DelayedSend([ pkt2, pkt ])
+ replies = sp.sniff(iface=if2, timeout=10)
+ found = False
+ for p in replies:
+ # Expect to find the packet without the hop-by-hop header, not the
+ # one with
+ p.show()
+ ip6 = p.getlayer(sp.IPv6)
+ hbh = p.getlayer(sp.IPv6ExtHdrHopByHop)
+ icmp6 = p.getlayer(sp.ICMPv6EchoRequest)
+
+ if not ip6 or not icmp6:
+ continue
+ assert ip6.src == "2001:db8::1"
+ assert ip6.dst == "2001:db8:1::1"
+ assert not hbh
+ assert icmp6
+ found = True
+ assert found
+
class TestFrag6_Overlap(VnetTestTemplate):
REQUIRED_MODULES = ["pf"]
TOPOLOGY = {
@@ -141,3 +218,57 @@ class TestFrag6_Overlap(VnetTestTemplate):
for p in packets:
p.show()
assert not p.getlayer(sp.ICMPv6EchoReply)
+
+class TestFrag6_RouteTo(VnetTestTemplate):
+ REQUIRED_MODULES = ["pf"]
+ TOPOLOGY = {
+ "vnet1": {"ifaces": ["if1"]},
+ "vnet2": {"ifaces": ["if1", "if2"]},
+ "vnet3": {"ifaces": ["if2"]},
+ "if1": {"prefixes6": [("2001:db8::1/64", "2001:db8::2/64")]},
+ "if2": {"prefixes6": [("2001:db8:1::1/64", "2001:db8:1::2/64")]},
+ }
+
+ def vnet2_handler(self, vnet):
+ if2name = vnet.iface_alias_map["if2"].name
+ ToolsHelper.print_output("/sbin/pfctl -e")
+ ToolsHelper.print_output("/sbin/pfctl -x loud")
+ ToolsHelper.pf_rules([
+ "scrub fragment reassemble",
+ "pass in route-to (%s 2001:db8:1::2) from 2001:db8::1 to 2001:db8:666::1" % if2name,
+ ])
+
+ ToolsHelper.print_output("/sbin/ifconfig %s mtu 1300" % if2name)
+ ToolsHelper.print_output("/sbin/sysctl net.inet6.ip6.forwarding=1")
+
+ def vnet3_handler(self, vnet):
+ pass
+
+ def test_too_big(self):
+ ToolsHelper.print_output("/sbin/route add -6 default 2001:db8::2")
+
+ # Import in the correct vnet, so at to not confuse Scapy
+ import scapy.all as sp
+
+ pkt = sp.IPv6(dst="2001:db8:666::1") \
+ / sp.ICMPv6EchoRequest(data=sp.raw(bytes.fromhex('f0') * 3000))
+ frags = sp.fragment6(pkt, 1320)
+
+ reply = sp.sr1(frags, timeout=3)
+ if reply:
+ reply.show()
+
+ assert reply
+
+ ip6 = reply.getlayer(sp.IPv6)
+ icmp6 = reply.getlayer(sp.ICMPv6PacketTooBig)
+ err_ip6 = reply.getlayer(sp.IPerror6)
+
+ assert ip6
+ assert ip6.src == "2001:db8::2"
+ assert ip6.dst == "2001:db8::1"
+ assert icmp6
+ assert icmp6.mtu == 1300
+ assert err_ip6
+ assert err_ip6.src == "2001:db8::1"
+ assert err_ip6.dst == "2001:db8:666::1"
diff --git a/tests/sys/netpfil/pf/killstate.sh b/tests/sys/netpfil/pf/killstate.sh
index 5d8e040d3cbb..59db93276d58 100644
--- a/tests/sys/netpfil/pf/killstate.sh
+++ b/tests/sys/netpfil/pf/killstate.sh
@@ -574,6 +574,61 @@ id_cleanup()
pft_cleanup
}
+atf_test_case "key" "cleanup"
+key_head()
+{
+ atf_set descr 'Test killing states by their key'
+ atf_set require.user root
+}
+
+key_body()
+{
+ pft_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
+ jexec alcatraz pfctl -e
+
+ pft_set_rules alcatraz \
+ "block all" \
+ "pass in proto tcp" \
+ "pass in proto icmp"
+
+ # Sanity check & establish state
+ atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
+ --sendif ${epair}a \
+ --to 192.0.2.2 \
+ --replyif ${epair}a
+
+ # Get the state key
+ key=$(jexec alcatraz pfctl -ss -vvv | awk '/icmp/ { print($2 " " $3 " " $4 " " $5); }')
+ bad_key=$(echo ${key} | sed 's/icmp/tcp/')
+
+ # Kill the wrong key
+ atf_check -s exit:0 -e "match:killed 0 states" \
+ jexec alcatraz pfctl -k key -k "${bad_key}"
+ if ! find_state;
+ then
+ atf_fail "Killing a different ID removed the state."
+ fi
+
+ # Kill the correct key
+ atf_check -s exit:0 -e "match:killed 1 states" \
+ jexec alcatraz pfctl -k key -k "${key}"
+ if find_state;
+ then
+ atf_fail "Killing the state did not remove it."
+ fi
+}
+
+key_cleanup()
+{
+ pft_cleanup
+}
+
atf_test_case "nat" "cleanup"
nat_head()
{
@@ -653,5 +708,6 @@ atf_init_test_cases()
atf_add_test_case "match"
atf_add_test_case "interface"
atf_add_test_case "id"
+ atf_add_test_case "key"
atf_add_test_case "nat"
}
diff --git a/tests/sys/netpfil/pf/limits.sh b/tests/sys/netpfil/pf/limits.sh
index 474684bef660..69f0b6af2ccf 100644
--- a/tests/sys/netpfil/pf/limits.sh
+++ b/tests/sys/netpfil/pf/limits.sh
@@ -60,7 +60,60 @@ basic_cleanup()
pft_cleanup
}
+atf_test_case "zero" "cleanup"
+zero_head()
+{
+ atf_set descr 'Test changing a limit from zero on an in-use zone'
+ atf_set require.user root
+}
+
+zero_body()
+{
+ pft_init
+
+ epair=$(vnet_mkepair)
+ ifconfig ${epair}b 192.0.2.2/24 up
+
+ vnet_mkjail alcatraz ${epair}a
+ jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up
+
+ atf_check -s exit:0 -o ignore \
+ ping -c 3 192.0.2.1
+
+ jexec alcatraz pfctl -e
+ # Set no limit
+ pft_set_rules noflush alcatraz \
+ "set limit states 0" \
+ "pass"
+
+ # Check that we really report no limit
+ atf_check -s exit:0 -o 'match:states hard limit 0' \
+ jexec alcatraz pfctl -sa
+
+ # Create a state
+ atf_check -s exit:0 -o ignore \
+ ping -c 3 192.0.2.1
+
+ # Limit states
+ pft_set_rules noflush alcatraz \
+ "set limit states 1000" \
+ "pass"
+
+ # And create a new state
+ atf_check -s exit:0 -o ignore \
+ ping -c 3 192.0.2.1
+
+ atf_check -s exit:0 -o 'match:states hard limit 1000' \
+ jexec alcatraz pfctl -sa
+}
+
+zero_cleanup()
+{
+ pft_cleanup
+}
+
atf_init_test_cases()
{
atf_add_test_case "basic"
+ atf_add_test_case "zero"
}
diff --git a/tests/sys/netpfil/pf/map_e.sh b/tests/sys/netpfil/pf/map_e.sh
deleted file mode 100644
index 59f9e7f7e14c..000000000000
--- a/tests/sys/netpfil/pf/map_e.sh
+++ /dev/null
@@ -1,90 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright (c) 2021 KUROSAWA Takahiro <takahiro.kurosawa@gmail.com>
-#
-# 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.
-
-. $(atf_get_srcdir)/utils.subr
-
-atf_test_case "map_e" "cleanup"
-map_e_head()
-{
- atf_set descr 'map-e-portset test'
- atf_set require.user root
-}
-
-map_e_body()
-{
- NC_TRY_COUNT=12
-
- pft_init
-
- epair_map_e=$(vnet_mkepair)
- epair_echo=$(vnet_mkepair)
-
- vnet_mkjail map_e ${epair_map_e}b ${epair_echo}a
- vnet_mkjail echo ${epair_echo}b
-
- ifconfig ${epair_map_e}a 192.0.2.2/24 up
- route add -net 198.51.100.0/24 192.0.2.1
-
- jexec map_e ifconfig ${epair_map_e}b 192.0.2.1/24 up
- jexec map_e ifconfig ${epair_echo}a 198.51.100.1/24 up
- jexec map_e sysctl net.inet.ip.forwarding=1
-
- jexec echo ifconfig ${epair_echo}b 198.51.100.2/24 up
- jexec echo /usr/sbin/inetd -p ${PWD}/inetd-echo.pid $(atf_get_srcdir)/echo_inetd.conf
-
- # Enable pf!
- jexec map_e pfctl -e
- pft_set_rules map_e \
- "nat pass on ${epair_echo}a inet from 192.0.2.0/24 to any -> (${epair_echo}a) map-e-portset 2/12/0x342"
-
- # Only allow specified ports.
- jexec echo pfctl -e
- pft_set_rules echo "block return all" \
- "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \
- "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \
- "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \
- "set skip on lo"
-
- i=0
- while [ ${i} -lt ${NC_TRY_COUNT} ]
- do
- echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7
- if [ $? -ne 0 ]; then
- atf_fail "nc failed (${i})"
- fi
- i=$((${i}+1))
- done
-}
-
-map_e_cleanup()
-{
- pft_cleanup
-}
-
-atf_init_test_cases()
-{
- atf_add_test_case "map_e"
-}
diff --git a/tests/sys/netpfil/pf/nat.sh b/tests/sys/netpfil/pf/nat.sh
index f7026feb5078..f1fdf6405d97 100644
--- a/tests/sys/netpfil/pf/nat.sh
+++ b/tests/sys/netpfil/pf/nat.sh
@@ -2,6 +2,8 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2018 Kristof Provost <kp@FreeBSD.org>
+# Copyright (c) 2025 Kajetan Staszkiewicz <ks@FreeBSD.org>
+# Copyright (c) 2021 KUROSAWA Takahiro <takahiro.kurosawa@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -112,14 +114,7 @@ nested_anchor_body()
}
-atf_test_case "endpoint_independent" "cleanup"
-endpoint_independent_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_body()
+endpoint_independent_setup()
{
pft_init
filter="udp and dst port 1234" # only capture udp pings
@@ -153,13 +148,15 @@ endpoint_independent_body()
jexec server1 ifconfig ${epair_server1}a 198.51.100.32/24 up
jexec server2 ifconfig ${epair_server2}a 198.51.100.22/24 up
+}
+endpoint_independent_common()
+{
# Enable pf!
jexec nat pfctl -e
# validate non-endpoint independent nat rule behaviour
- pft_set_rules nat \
- "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a)"
+ pft_set_rules nat "${1}"
jexec server1 tcpdump -i ${epair_server1}a -w ${PWD}/server1.pcap \
--immediate-mode $filter &
@@ -198,8 +195,7 @@ endpoint_independent_body()
fi
# validate endpoint independent nat rule behaviour
- pft_set_rules nat \
- "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a) endpoint-independent"
+ pft_set_rules nat "${2}"
jexec server1 tcpdump -i ${epair_server1}a -w ${PWD}/server1.pcap \
--immediate-mode $filter &
@@ -238,7 +234,47 @@ endpoint_independent_body()
fi
}
-endpoint_independent_cleanup()
+atf_test_case "endpoint_independent_compat" "cleanup"
+endpoint_independent_compat_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_compat_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) endpoint-independent"
+}
+
+endpoint_independent_compat_cleanup()
+{
+ pft_cleanup
+ rm -f server1.out
+ rm -f server2.out
+}
+
+atf_test_case "endpoint_independent_pass" "cleanup"
+endpoint_independent_pass_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_pass_body()
+{
+ endpoint_independent_setup # Sets ${epair_…} variables
+
+ endpoint_independent_common \
+ "pass out on ${epair_nat}a inet from ! (${epair_nat}a) to any nat-to (${epair_nat}a) keep state" \
+ "pass out on ${epair_nat}a inet from ! (${epair_nat}a) to any nat-to (${epair_nat}a) endpoint-independent keep state"
+
+}
+
+endpoint_independent_pass_cleanup()
{
pft_cleanup
rm -f server1.out
@@ -438,14 +474,324 @@ no_addrs_random_cleanup()
pft_cleanup
}
+nat_pass_head()
+{
+ atf_set descr 'IPv4 NAT on pass rule'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+nat_pass_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}
+
+ pft_set_rules router \
+ "block" \
+ "pass in on ${epair_tester}b inet proto tcp keep state" \
+ "pass out on ${epair_server}a inet proto tcp nat-to ${epair_server}a 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_cleanup()
+{
+ pft_cleanup
+}
+
+nat_match_head()
+{
+ atf_set descr 'IPv4 NAT on match rule'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+nat_match_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}
+
+ # NAT is applied during ruleset evaluation:
+ # rules after "match" match on NAT-ed address
+ pft_set_rules router \
+ "block" \
+ "pass in on ${epair_tester}b inet proto tcp keep state" \
+ "match out on ${epair_server}a inet proto tcp nat-to ${epair_server}a" \
+ "pass out on ${epair_server}a inet proto tcp from ${epair_server}a 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_match_cleanup()
+{
+ pft_cleanup
+}
+
+map_e_common()
+{
+ NC_TRY_COUNT=12
+
+ pft_init
+
+ epair_map_e=$(vnet_mkepair)
+ epair_echo=$(vnet_mkepair)
+
+ vnet_mkjail map_e ${epair_map_e}b ${epair_echo}a
+ vnet_mkjail echo ${epair_echo}b
+
+ ifconfig ${epair_map_e}a 192.0.2.2/24 up
+ route add -net 198.51.100.0/24 192.0.2.1
+
+ jexec map_e ifconfig ${epair_map_e}b 192.0.2.1/24 up
+ jexec map_e ifconfig ${epair_echo}a 198.51.100.1/24 up
+ jexec map_e sysctl net.inet.ip.forwarding=1
+
+ jexec echo ifconfig ${epair_echo}b 198.51.100.2/24 up
+ jexec echo /usr/sbin/inetd -p ${PWD}/inetd-echo.pid $(atf_get_srcdir)/echo_inetd.conf
+
+ # Enable pf!
+ jexec map_e pfctl -e
+}
+
+atf_test_case "map_e_compat" "cleanup"
+map_e_compat_head()
+{
+ atf_set descr 'map-e-portset test'
+ atf_set require.user root
+}
+
+map_e_compat_body()
+{
+ map_e_common
+
+ pft_set_rules map_e \
+ "nat pass on ${epair_echo}a inet from 192.0.2.0/24 to any -> (${epair_echo}a) map-e-portset 2/12/0x342"
+
+ # Only allow specified ports.
+ jexec echo pfctl -e
+ pft_set_rules echo "block return all" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \
+ "set skip on lo"
+
+ i=0
+ while [ ${i} -lt ${NC_TRY_COUNT} ]
+ do
+ echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7
+ if [ $? -ne 0 ]; then
+ atf_fail "nc failed (${i})"
+ fi
+ i=$((${i}+1))
+ done
+}
+
+map_e_compat_cleanup()
+{
+ pft_cleanup
+}
+
+
+atf_test_case "map_e_pass" "cleanup"
+map_e_pass_head()
+{
+ atf_set descr 'map-e-portset test'
+ atf_set require.user root
+}
+
+map_e_pass_body()
+{
+ map_e_common
+
+ pft_set_rules map_e \
+ "pass out on ${epair_echo}a inet from 192.0.2.0/24 to any nat-to (${epair_echo}a) map-e-portset 2/12/0x342 keep state"
+
+ jexec map_e pfctl -qvvsr
+
+ # Only allow specified ports.
+ jexec echo pfctl -e
+ pft_set_rules echo "block return all" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \
+ "set skip on lo"
+
+ i=0
+ while [ ${i} -lt ${NC_TRY_COUNT} ]
+ do
+ echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7
+ if [ $? -ne 0 ]; then
+ atf_fail "nc failed (${i})"
+ fi
+ i=$((${i}+1))
+ done
+}
+
+map_e_pass_cleanup()
+{
+ pft_cleanup
+}
+
+binat_compat_head()
+{
+ atf_set descr 'IPv4 BINAT with nat ruleset'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+binat_compat_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}
+
+ pft_set_rules router \
+ "set state-policy if-bound" \
+ "set ruleset-optimization none" \
+ "binat on ${epair_server}a inet proto tcp from ${net_tester_host_tester} to any tag sometag -> ${epair_server}a" \
+ "block" \
+ "pass in on ${epair_tester}b inet proto tcp !tagged sometag keep state" \
+ "pass out on ${epair_server}a inet proto tcp tagged sometag keep state" \
+ "pass in on ${epair_server}a inet proto tcp tagged sometag keep state" \
+ "pass out on ${epair_tester}b inet proto tcp tagged sometag keep state"
+
+ # Test the outbound NAT part of BINAT.
+ ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
+
+ states=$(mktemp) || exit 1
+ jexec router pfctl -qvss | normalize_pfctl_s > $states
+
+ for state_regexp in \
+ "${epair_tester}b tcp ${net_server_host_server}:9 <- ${net_tester_host_tester}:4201 .* 3:2 pkts,.* rule 1" \
+ "${epair_server}a tcp ${net_server_host_router}:4201 \(${net_tester_host_tester}:4201\) -> ${net_server_host_server}:9 .* 3:2 pkts,.* rule 2" \
+ ; do
+ grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
+ done
+
+ # Test the inbound RDR part of BINAT.
+ # The "tester" becomes "server" and vice versa.
+ inetd_conf=$(mktemp)
+ echo "discard stream tcp nowait root internal" > $inetd_conf
+ inetd -p ${PWD}/inetd_tester.pid $inetd_conf
+
+ atf_check -s exit:0 \
+ jexec server ${common_dir}/pft_ping.py \
+ --ping-type=tcp3way --send-sport=4202 \
+ --sendif ${epair_server}b \
+ --to ${net_server_host_router} \
+ --replyif ${epair_server}b
+
+ states=$(mktemp) || exit 1
+ jexec router pfctl -qvss | normalize_pfctl_s > $states
+
+ for state_regexp in \
+ "${epair_server}a tcp ${net_tester_host_tester}:9 \(${net_server_host_router}:9\) <- ${net_server_host_server}:4202 .* 3:2 pkts,.* rule 3" \
+ "${epair_tester}b tcp ${net_server_host_server}:4202 -> ${net_tester_host_tester}:9 .* 3:2 pkts,.* rule 4" \
+ ; do
+ grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
+ done
+}
+
+binat_compat_cleanup()
+{
+ pft_cleanup
+ kill $(cat ${PWD}/inetd_tester.pid)
+}
+
+binat_match_head()
+{
+ atf_set descr 'IPv4 BINAT with nat ruleset'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+binat_match_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}
+
+ # The "binat-to" rule expands to 2 rules so the ""pass" rules start at 3!
+ pft_set_rules router \
+ "set state-policy if-bound" \
+ "set ruleset-optimization none" \
+ "block" \
+ "match on ${epair_server}a inet proto tcp from ${net_tester_host_tester} to any tag sometag binat-to ${epair_server}a" \
+ "pass in on ${epair_tester}b inet proto tcp !tagged sometag keep state" \
+ "pass out on ${epair_server}a inet proto tcp tagged sometag keep state" \
+ "pass in on ${epair_server}a inet proto tcp tagged sometag keep state" \
+ "pass out on ${epair_tester}b inet proto tcp tagged sometag keep state"
+
+ # Test the outbound NAT part of BINAT.
+ ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
+
+ states=$(mktemp) || exit 1
+ jexec router pfctl -qvss | normalize_pfctl_s > $states
+
+ for state_regexp in \
+ "${epair_tester}b tcp ${net_server_host_server}:9 <- ${net_tester_host_tester}:4201 .* 3:2 pkts,.* rule 3" \
+ "${epair_server}a tcp ${net_server_host_router}:4201 \(${net_tester_host_tester}:4201\) -> ${net_server_host_server}:9 .* 3:2 pkts,.* rule 4" \
+ ; do
+ grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
+ done
+
+ # Test the inbound RDR part of BINAT.
+ # The "tester" becomes "server" and vice versa.
+ inetd_conf=$(mktemp)
+ echo "discard stream tcp nowait root internal" > $inetd_conf
+ inetd -p ${PWD}/inetd_tester.pid $inetd_conf
+
+ atf_check -s exit:0 \
+ jexec server ${common_dir}/pft_ping.py \
+ --ping-type=tcp3way --send-sport=4202 \
+ --sendif ${epair_server}b \
+ --to ${net_server_host_router} \
+ --replyif ${epair_server}b
+
+ states=$(mktemp) || exit 1
+ jexec router pfctl -qvss | normalize_pfctl_s > $states
+
+ for state_regexp in \
+ "${epair_server}a tcp ${net_tester_host_tester}:9 \(${net_server_host_router}:9\) <- ${net_server_host_server}:4202 .* 3:2 pkts,.* rule 5" \
+ "${epair_tester}b tcp ${net_server_host_server}:4202 -> ${net_tester_host_tester}:9 .* 3:2 pkts,.* rule 6" \
+ ; do
+ grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
+ done
+}
+
+binat_match_cleanup()
+{
+ pft_cleanup
+ kill $(cat ${PWD}/inetd_tester.pid)
+}
+
atf_init_test_cases()
{
atf_add_test_case "exhaust"
atf_add_test_case "nested_anchor"
- atf_add_test_case "endpoint_independent"
+ atf_add_test_case "endpoint_independent_compat"
+ atf_add_test_case "endpoint_independent_pass"
atf_add_test_case "nat6_nolinklocal"
atf_add_test_case "empty_table_source_hash"
atf_add_test_case "no_addrs_source_hash"
atf_add_test_case "empty_table_random"
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_match"
+ atf_add_test_case "binat_compat"
+ atf_add_test_case "binat_match"
}
diff --git a/tests/sys/netpfil/pf/nat64.py b/tests/sys/netpfil/pf/nat64.py
index e64b7bbd573b..32fd8f4245a1 100644
--- a/tests/sys/netpfil/pf/nat64.py
+++ b/tests/sys/netpfil/pf/nat64.py
@@ -28,23 +28,10 @@ import pytest
import selectors
import socket
import sys
-import threading
-import time
+from utils import DelayedSend
from atf_python.sys.net.tools import ToolsHelper
from atf_python.sys.net.vnet import VnetTestTemplate
-class DelayedSend(threading.Thread):
- def __init__(self, packet):
- threading.Thread.__init__(self)
- self._packet = packet
-
- self.start()
-
- def run(self):
- import scapy.all as sp
- time.sleep(1)
- sp.send(self._packet)
-
class TestNAT64(VnetTestTemplate):
REQUIRED_MODULES = [ "pf" ]
TOPOLOGY = {
diff --git a/tests/sys/netpfil/pf/nat66.py b/tests/sys/netpfil/pf/nat66.py
index f93512b5b99c..16b4ef3dd02b 100644
--- a/tests/sys/netpfil/pf/nat66.py
+++ b/tests/sys/netpfil/pf/nat66.py
@@ -29,23 +29,10 @@ import ipaddress
import pytest
import re
import socket
-import threading
-import time
+from utils import DelayedSend
from atf_python.sys.net.tools import ToolsHelper
from atf_python.sys.net.vnet import VnetTestTemplate
-class DelayedSend(threading.Thread):
- def __init__(self, packet):
- threading.Thread.__init__(self)
- self._packet = packet
-
- self.start()
-
- def run(self):
- import scapy.all as sp
- time.sleep(1)
- sp.send(self._packet)
-
class TestNAT66(VnetTestTemplate):
REQUIRED_MODULES = [ "pf" ]
TOPOLOGY = {
diff --git a/tests/sys/netpfil/pf/pflog.sh b/tests/sys/netpfil/pf/pflog.sh
index fdd9af6316d0..a34ec893a75c 100644
--- a/tests/sys/netpfil/pf/pflog.sh
+++ b/tests/sys/netpfil/pf/pflog.sh
@@ -238,7 +238,7 @@ state_max_body()
cat pflog.txt
# Second ping is blocked due to the state limit.
- atf_check -o match:".*rule 0/0\(match\): block in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \
+ atf_check -o match:".*rule 0/12\(state-limit\): block in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \
cat pflog.txt
# At most three lines should be written: one for the first ping, and
@@ -246,6 +246,18 @@ state_max_body()
# then a drop because of the state limit. Ideally only the drop would
# be logged; if this is fixed, the count will be 2 instead of 3.
atf_check -o match:3 grep -c . pflog.txt
+
+ # If the rule doesn't specify logging, we shouldn't log drops
+ # due to state limits.
+ pft_set_rules alcatraz "pass inet keep state (max 1)"
+
+ atf_check -s exit:0 -o ignore \
+ ping -c 1 192.0.2.1
+
+ atf_check -s exit:2 -o ignore \
+ ping -c 1 192.0.2.1
+
+ atf_check -o match:3 grep -c . pflog.txt
}
state_max_cleanup()
diff --git a/tests/sys/netpfil/pf/rdr.sh b/tests/sys/netpfil/pf/rdr.sh
index a7a8c77c0515..4c08b4973891 100644
--- a/tests/sys/netpfil/pf/rdr.sh
+++ b/tests/sys/netpfil/pf/rdr.sh
@@ -27,14 +27,6 @@
. $(atf_get_srcdir)/utils.subr
-atf_test_case "tcp_v6" "cleanup"
-tcp_v6_head()
-{
- atf_set descr 'TCP rdr with IPv6'
- atf_set require.user root
- atf_set require.progs python3
-}
-
#
# Test that rdr works for TCP with IPv6.
#
@@ -47,7 +39,7 @@ tcp_v6_head()
#
# Test for incorrect checksums after the rewrite by looking at a packet capture (see bug 210860)
#
-tcp_v6_body()
+tcp_v6_setup()
{
pft_init
@@ -83,9 +75,11 @@ tcp_v6_body()
jexec ${j}c route add -inet6 2001:db8:a::0/64 2001:db8:b::1
jexec ${j}b pfctl -e
+}
- pft_set_rules ${j}b \
- "rdr on ${epair_one}a proto tcp from any to any port 80 -> 2001:db8:b::2 port 8000"
+tcp_v6_common()
+{
+ pft_set_rules ${j}b "${1}"
# Check that a can reach c over the router
atf_check -s exit:0 -o ignore \
@@ -116,19 +110,44 @@ tcp_v6_body()
atf_check_equal " 0" "$count"
}
-tcp_v6_cleanup()
+atf_test_case "tcp_v6_compat" "cleanup"
+tcp_v6_compat_head()
{
- pft_cleanup
+ atf_set descr 'TCP rdr with IPv6 with NAT rules'
+ atf_set require.user root
+ atf_set require.progs python3
}
+tcp_v6_compat_body()
+{
+ tcp_v6_setup # Sets ${epair_…} variables
+ tcp_v6_common \
+ "rdr on ${epair_one}a proto tcp from any to any port 80 -> 2001:db8:b::2 port 8000"
+}
-atf_test_case "srcport" "cleanup"
-srcport_head()
+tcp_v6_compat_cleanup()
{
- atf_set descr 'TCP rdr srcport modulation'
+ pft_cleanup
+}
+
+atf_test_case "tcp_v6_pass" "cleanup"
+tcp_v6_pass_head()
+{
+ atf_set descr 'TCP rdr with IPv6 with pass/match rules'
atf_set require.user root
atf_set require.progs python3
- atf_set timeout 9999
+}
+
+tcp_v6_pass_body()
+{
+ tcp_v6_setup # Sets ${epair_…} variables
+ tcp_v6_common \
+ "rdr on ${epair_one}a proto tcp from any to any port 80 -> 2001:db8:b::2 port 8000"
+}
+
+tcp_v6_pass_cleanup()
+{
+ pft_cleanup
}
#
@@ -145,7 +164,7 @@ srcport_head()
# In this case, the rdr rule should also rewrite the source port (again) to
# resolve the state conflict.
#
-srcport_body()
+srcport_setup()
{
pft_init
@@ -188,14 +207,17 @@ srcport_body()
jexec ${j}c sysctl net.inet.ip.forwarding=1
jexec ${j}b pfctl -e
jexec ${j}c pfctl -e
+}
+srcport_common()
+{
pft_set_rules ${j}b \
"set debug misc" \
- "nat on ${epair2}a inet from 198.51.100.0/24 to any -> ${epair2}a static-port"
+ "${1}"
pft_set_rules ${j}c \
"set debug misc" \
- "rdr on ${epair2}b proto tcp from any to ${epair2}b port 7777 -> 203.0.113.50 port 8888"
+ "${2}"
jexec ${j}a route add default 198.51.100.1
jexec ${j}c route add 198.51.100.0/24 198.51.101.2
@@ -215,13 +237,54 @@ srcport_body()
atf_check -o match:"[0-9]+" -o not-inline:"1234" cat port3
}
-srcport_cleanup()
+atf_test_case "srcport_compat" "cleanup"
+srcport_compat_head()
+{
+ atf_set descr 'TCP rdr srcport modulation with NAT rules'
+ atf_set require.user root
+ atf_set require.progs python3
+ atf_set timeout 9999
+}
+
+srcport_compat_body()
+{
+ srcport_setup # Sets ${epair_…} variables
+ srcport_common \
+ "nat on ${epair2}a inet from 198.51.100.0/24 to any -> ${epair2}a static-port" \
+ "rdr on ${epair2}b proto tcp from any to ${epair2}b port 7777 -> 203.0.113.50 port 8888"
+}
+
+srcport_compat_cleanup()
+{
+ pft_cleanup
+}
+
+atf_test_case "srcport_pass" "cleanup"
+srcport_pass_head()
+{
+ atf_set descr 'TCP rdr srcport modulation with pass/match rules'
+ atf_set require.user root
+ atf_set require.progs python3
+ atf_set timeout 9999
+}
+
+srcport_pass_body()
+{
+ srcport_setup # Sets ${epair_…} variables
+ srcport_common \
+ "pass out on ${epair2}a inet from 198.51.100.0/24 to any nat-to ${epair2}a static-port" \
+ "pass in on ${epair2}b proto tcp from any to ${epair2}b port 7777 rdr-to 203.0.113.50 port 8888"
+}
+
+srcport_pass_cleanup()
{
pft_cleanup
}
atf_init_test_cases()
{
- atf_add_test_case "tcp_v6"
- atf_add_test_case "srcport"
+ atf_add_test_case "tcp_v6_compat"
+ atf_add_test_case "tcp_v6_pass"
+ atf_add_test_case "srcport_compat"
+ atf_add_test_case "srcport_pass"
}
diff --git a/tests/sys/netpfil/pf/sctp.sh b/tests/sys/netpfil/pf/sctp.sh
index 563103827fac..14cd49dfb1f5 100644
--- a/tests/sys/netpfil/pf/sctp.sh
+++ b/tests/sys/netpfil/pf/sctp.sh
@@ -818,10 +818,10 @@ related_icmp_body()
# Generate traffic that will be fragmented by rtr2, and will provoke an
# ICMP unreachable - need to frag (mtu 1300) message
- dd if=/dev/random bs=1600 count=1 | nc --sctp -N -w 3 203.0.113.2 1234
+ dd if=/dev/random bs=10000 count=1 | nc --sctp -N -w 3 203.0.113.2 1234
# We'd expect to see an ICMP message
- atf_check -s exit:0 -o match:".*destination unreachable: 1" \
+ atf_check -s exit:0 -o match:".*destination unreachable: [1-9]" \
netstat -s -p icmp
}
diff --git a/tests/sys/netpfil/pf/src_track.sh b/tests/sys/netpfil/pf/src_track.sh
index 3668898682ff..c24f88062c4d 100755
--- a/tests/sys/netpfil/pf/src_track.sh
+++ b/tests/sys/netpfil/pf/src_track.sh
@@ -307,14 +307,14 @@ max_src_states_global_cleanup()
pft_cleanup
}
-route_to_head()
+sn_types_compat_head()
{
- atf_set descr 'Max states per source per rule with route-to'
+ atf_set descr 'Combination of source node types with compat NAT rules'
atf_set require.user root
atf_set require.progs python3 scapy
}
-route_to_body()
+sn_types_compat_body()
{
setup_router_dummy_ipv6
@@ -398,11 +398,110 @@ route_to_body()
! grep -q 'filter rule 3' $nodes || atf_fail "Source node found for rule 3"
}
-route_to_cleanup()
+sn_types_compat_cleanup()
{
pft_cleanup
}
+sn_types_pass_head()
+{
+ atf_set descr 'Combination of source node types with pass NAT rules'
+ atf_set require.user root
+ atf_set require.progs python3 scapy
+}
+
+sn_types_pass_body()
+{
+ setup_router_dummy_ipv6
+
+ # Clients will connect from another network behind the router.
+ # This allows for using multiple source addresses.
+ jexec router route add -6 2001:db8:44::0/64 2001:db8:42::2
+
+ # Additional gateways for route-to.
+ rtgw=${net_server_host_server%::*}::2:1
+ jexec router ndp -s ${rtgw} 00:01:02:03:04:05
+
+ # This test will check for proper source node creation for:
+ # max-src-states -> PF_SN_LIMIT
+ # sticky-address -> PF_SN_NAT
+ # route-to -> PF_SN_ROUTE
+ # The test expands to all 8 combinations of those source nodes being
+ # present or not.
+
+ pft_set_rules router \
+ "table <rtgws> { ${rtgw} }" \
+ "table <rdrgws> { 2001:db8:45::1 }" \
+ "block" \
+ "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
+ "match in on ${epair_tester}b inet6 proto tcp from 2001:db8:44::10/124 to 2001:db8:45::1 rdr-to <rdrgws> port 4242 sticky-address label rule_3" \
+ "pass in quick on ${epair_tester}b route-to ( ${epair_server}a <rtgws>) inet6 proto tcp from port 4211 keep state label rule_4" \
+ "pass in quick on ${epair_tester}b route-to ( ${epair_server}a <rtgws>) sticky-address inet6 proto tcp from port 4212 keep state label rule_5" \
+ "pass in quick on ${epair_tester}b route-to ( ${epair_server}a <rtgws>) inet6 proto tcp from port 4213 keep state (max-src-states 3 source-track rule) label rule_6" \
+ "pass in quick on ${epair_tester}b route-to ( ${epair_server}a <rtgws>) sticky-address inet6 proto tcp from port 4214 keep state (max-src-states 3 source-track rule) label rule_7" \
+ "pass out quick on ${epair_server}a keep state"
+
+ # We don't check if state limits are properly enforced, this is tested
+ # by other tests in this file.
+ # Source address will not match the NAT rule
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4211 --fromaddr 2001:db8:44::01 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4212 --fromaddr 2001:db8:44::02 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4213 --fromaddr 2001:db8:44::03 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4214 --fromaddr 2001:db8:44::04 --to 2001:db8:45::1
+ # Source address will match the NAT rule
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4211 --fromaddr 2001:db8:44::11 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4212 --fromaddr 2001:db8:44::12 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4213 --fromaddr 2001:db8:44::13 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4214 --fromaddr 2001:db8:44::14 --to 2001:db8:45::1
+
+ states=$(mktemp) || exit 1
+ jexec router pfctl -qvss | normalize_pfctl_s > $states
+ nodes=$(mktemp) || exit 1
+ jexec router pfctl -qvvsS | normalize_pfctl_s > $nodes
+
+ echo " === states ==="
+ cat $states
+ echo " === nodes ==="
+ cat $nodes
+ echo " === end === "
+
+ # Order of states in output is not guaranteed, find each one separately.
+ for state_regexp in \
+ 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::1\[4211\] .* 1:0 pkts, 76:0 bytes, rule 4$' \
+ 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::2\[4212\] .* 1:0 pkts, 76:0 bytes, rule 5, route sticky-address$' \
+ 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::3\[4213\] .* 1:0 pkts, 76:0 bytes, rule 6, limit source-track$' \
+ 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::4\[4214\] .* 1:0 pkts, 76:0 bytes, rule 7, limit source-track, route sticky-address$' \
+ 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::11\[4211\] .* 1:0 pkts, 76:0 bytes, rule 4, NAT/RDR sticky-address' \
+ 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::12\[4212\] .* 1:0 pkts, 76:0 bytes, rule 5, NAT/RDR sticky-address, route sticky-address' \
+ 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::13\[4213\] .* 1:0 pkts, 76:0 bytes, rule 6, limit source-track, NAT/RDR sticky-address' \
+ 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::14\[4214\] .* 1:0 pkts, 76:0 bytes, rule 7, limit source-track, NAT/RDR sticky-address, route sticky-address' \
+ ; do
+ grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
+ done
+
+ # Order of source nodes in output is not guaranteed, find each one separately.
+ for node_regexp in \
+ '2001:db8:44::2 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 5, route sticky-address' \
+ '2001:db8:44::3 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 6, limit source-track' \
+ '2001:db8:44::4 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s ) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, route sticky-address' \
+ '2001:db8:44::4 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, limit source-track' \
+ '2001:db8:44::11 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \
+ '2001:db8:44::12 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \
+ '2001:db8:44::12 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 5, route sticky-address' \
+ '2001:db8:44::13 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \
+ '2001:db8:44::13 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 6, limit source-track' \
+ '2001:db8:44::14 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \
+ '2001:db8:44::14 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s ) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, route sticky-address' \
+ '2001:db8:44::14 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, limit source-track' \
+ ; do
+ grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'"
+ done
+}
+
+sn_types_pass_cleanup()
+{
+ pft_cleanup
+}
atf_init_test_cases()
{
@@ -411,5 +510,6 @@ atf_init_test_cases()
atf_add_test_case "max_src_conn_rule"
atf_add_test_case "max_src_states_rule"
atf_add_test_case "max_src_states_global"
- atf_add_test_case "route_to"
+ atf_add_test_case "sn_types_compat"
+ atf_add_test_case "sn_types_pass"
}
diff --git a/tests/sys/netpfil/pf/tcp.py b/tests/sys/netpfil/pf/tcp.py
new file mode 100644
index 000000000000..53e0658f419c
--- /dev/null
+++ b/tests/sys/netpfil/pf/tcp.py
@@ -0,0 +1,158 @@
+#
+# 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.
+
+import sys
+import pytest
+import random
+import socket
+import selectors
+from utils import DelayedSend
+from atf_python.sys.net.tools import ToolsHelper
+from atf_python.sys.net.vnet import VnetTestTemplate
+
+class TCPClient:
+ def __init__(self, src, dst, sport, dport, sp):
+ self.src = src
+ self.dst = dst
+ self.sport = sport
+ self.dport = dport
+ self.sp = sp
+ self.seq = random.randrange(1, (2**32)-1)
+ self.ack = 0
+
+ def syn(self):
+ syn = self.sp.IP(src=self.src, dst=self.dst) \
+ / self.sp.TCP(sport=self.sport, dport=self.dport, flags="S", seq=self.seq)
+ return syn
+
+ def connect(self):
+ syn = self.syn()
+ r = self.sp.sr1(syn, timeout=5)
+
+ assert r
+ t = r.getlayer(self.sp.TCP)
+ assert t
+ assert t.sport == self.dport
+ assert t.dport == self.sport
+ assert t.flags == "SA"
+
+ self.seq += 1
+ self.ack = t.seq + 1
+ ack = self.sp.IP(src=self.src, dst=self.dst) \
+ / self.sp.TCP(sport=self.sport, dport=self.dport, flags="A", ack=self.ack, seq=self.seq)
+ self.sp.send(ack)
+
+ def send(self, data):
+ length = len(data)
+ pkt = self.sp.IP(src=self.src, dst=self.dst) \
+ / self.sp.TCP(sport=self.sport, dport=self.dport, ack=self.ack, seq=self.seq, flags="") \
+ / self.sp.Raw(data)
+ self.seq += length
+ pkt.show()
+ self.sp.send(pkt)
+
+class TestTcp(VnetTestTemplate):
+ REQUIRED_MODULES = [ "pf" ]
+ TOPOLOGY = {
+ "vnet1": {"ifaces": ["if1"]},
+ "vnet2": {"ifaces": ["if1"]},
+ "if1": {"prefixes4": [("192.0.2.1/24", "192.0.2.2/24")]},
+ }
+
+ def vnet2_handler(self, vnet):
+ ToolsHelper.print_output("/usr/sbin/arp -s 192.0.2.3 00:01:02:03:04:05")
+ ToolsHelper.print_output("/sbin/pfctl -e")
+ ToolsHelper.pf_rules([
+ "pass"
+ ])
+ ToolsHelper.print_output("/sbin/pfctl -x loud")
+
+ # Start TCP listener
+ sel = selectors.DefaultSelector()
+ t = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ t.bind(("0.0.0.0", 1234))
+ t.listen(100)
+ t.setblocking(False)
+ sel.register(t, selectors.EVENT_READ, data=None)
+
+ while True:
+ events = sel.select(timeout=2)
+ for key, mask in events:
+ sock = key.fileobj
+ if key.data is None:
+ conn, addr = sock.accept()
+ print(f"Accepted connection from {addr}")
+ events = selectors.EVENT_READ | selectors.EVENT_WRITE
+ sel.register(conn, events, data="TCP")
+ else:
+ if mask & selectors.EVENT_READ:
+ recv_data = sock.recv(1024)
+ print(f"Received TCP {recv_data}")
+ ToolsHelper.print_output("/sbin/pfctl -ss -vv")
+ sock.send(recv_data)
+
+ @pytest.mark.require_user("root")
+ @pytest.mark.require_progs(["scapy"])
+ def test_challenge_ack(self):
+ vnet = self.vnet_map["vnet1"]
+ ifname = vnet.iface_alias_map["if1"].name
+
+ # Import in the correct vnet, so at to not confuse Scapy
+ import scapy.all as sp
+
+ a = TCPClient("192.0.2.3", "192.0.2.2", 1234, 1234, sp)
+ a.connect()
+ a.send(b"foo")
+
+ b = TCPClient("192.0.2.3", "192.0.2.2", 1234, 1234, sp)
+ syn = b.syn()
+ syn.show()
+ s = DelayedSend(syn)
+ packets = sp.sniff(iface=ifname, timeout=3)
+ found = False
+ for p in packets:
+ ip = p.getlayer(sp.IP)
+ if not ip:
+ continue
+ tcp = p.getlayer(sp.TCP)
+ if not tcp:
+ continue
+
+ if ip.src != "192.0.2.2":
+ continue
+
+ p.show()
+
+ assert ip.dst == "192.0.2.3"
+ assert tcp.sport == 1234
+ assert tcp.dport == 1234
+ assert tcp.flags == "A"
+
+ # We only expect one
+ assert not found
+ found = True
+
+ assert found
diff --git a/tests/sys/netpfil/pf/utils.py b/tests/sys/netpfil/pf/utils.py
new file mode 100644
index 000000000000..3cd8278f7cf7
--- /dev/null
+++ b/tests/sys/netpfil/pf/utils.py
@@ -0,0 +1,41 @@
+#
+# 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.
+#
+import threading
+import time
+
+class DelayedSend(threading.Thread):
+ def __init__(self, packet):
+ threading.Thread.__init__(self)
+ self._packet = packet
+
+ self.start()
+
+ def run(self):
+ import scapy.all as sp
+ time.sleep(1)
+ sp.send(self._packet)
+
diff --git a/tests/sys/sound/sndstat.c b/tests/sys/sound/sndstat.c
index 553c745ec950..ed292b570429 100644
--- a/tests/sys/sound/sndstat.c
+++ b/tests/sys/sound/sndstat.c
@@ -148,10 +148,10 @@ ATF_TC_BODY(sndstat_nv, tc)
NV(number, UNIT);
NV(string, STATUS);
NV(bool, BITPERFECT);
- NV(number, PVCHAN);
+ NV(bool, PVCHAN);
NV(number, PVCHANRATE);
NV(number, PVCHANFORMAT);
- NV(number, RVCHAN);
+ NV(bool, RVCHAN);
NV(number, PVCHANRATE);
NV(number, PVCHANFORMAT);
#undef NV
@@ -184,12 +184,14 @@ ATF_TC_BODY(sndstat_nv, tc)
NV(number, LEFTVOL);
NV(number, RIGHTVOL);
NV(number, HWBUF_FORMAT);
+ NV(number, HWBUF_RATE);
NV(number, HWBUF_SIZE);
NV(number, HWBUF_BLKSZ);
NV(number, HWBUF_BLKCNT);
NV(number, HWBUF_FREE);
NV(number, HWBUF_READY);
NV(number, SWBUF_FORMAT);
+ NV(number, SWBUF_RATE);
NV(number, SWBUF_SIZE);
NV(number, SWBUF_BLKSZ);
NV(number, SWBUF_BLKCNT);
diff --git a/tests/sys/sys/Makefile b/tests/sys/sys/Makefile
index 40060911856f..a1b4e3234e1c 100644
--- a/tests/sys/sys/Makefile
+++ b/tests/sys/sys/Makefile
@@ -7,6 +7,7 @@ ATF_TESTS_C= arb_test \
bitstring_test \
buf_ring_test \
qmath_test \
+ queue_test \
rb_test \
splay_test \
time_test
diff --git a/tests/sys/sys/queue_test.c b/tests/sys/sys/queue_test.c
new file mode 100644
index 000000000000..7f8738751b85
--- /dev/null
+++ b/tests/sys/sys/queue_test.c
@@ -0,0 +1,293 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 The FreeBSD Foundation
+ *
+ * This software was developed by Olivier Certner <olce@FreeBSD.org> at
+ * Kumacom SARL under sponsorship from the FreeBSD Foundation.
+ */
+
+#include <sys/types.h>
+#define QUEUE_MACRO_DEBUG_ASSERTIONS
+#include <sys/queue.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <atf-c.h>
+
+/*
+ * General utilities.
+ */
+#define DIAG(fmt, ...) do { \
+ fprintf(stderr, "%s(): " fmt "\n", __func__, ##__VA_ARGS__); \
+} while (0)
+
+/*
+ * Common definitions and utilities.
+ *
+ * 'type' should be tailq, stailq, list or slist. 'TYPE' is 'type' in
+ * uppercase.
+ */
+
+#define QUEUE_TESTS_COMMON(type, TYPE) \
+/* \
+ * Definitions and utilities. \
+ */ \
+ \
+struct type ## _id_elem { \
+ TYPE ## _ENTRY(type ## _id_elem) ie_entry; \
+ u_int ie_id; \
+}; \
+ \
+TYPE ## _HEAD(type ## _ids, type ## _id_elem); \
+ \
+static void \
+type ## _check(const struct type ## _ids *const type, \
+ const u_int nb, const u_int id_shift); \
+ \
+/* \
+ * Creates a tailq/list with 'nb' elements with contiguous IDs \
+ * in ascending order starting at 'id_shift'. \
+ */ \
+static struct type ## _ids * \
+type ## _create(const u_int nb, const u_int id_shift) \
+{ \
+ struct type ## _ids *const type = \
+ malloc(sizeof(*type)); \
+ \
+ ATF_REQUIRE_MSG(type != NULL, \
+ "Cannot malloc " #type " head"); \
+ \
+ TYPE ## _INIT(type); \
+ for (u_int i = 0; i < nb; ++i) { \
+ struct type ## _id_elem *const e = \
+ malloc(sizeof(*e)); \
+ \
+ ATF_REQUIRE_MSG(e != NULL, \
+ "Cannot malloc " #type " element %u", i); \
+ e->ie_id = nb - 1 - i + id_shift; \
+ TYPE ## _INSERT_HEAD(type, e, ie_entry); \
+ } \
+ \
+ DIAG("Created " #type " %p with %u elements", \
+ type, nb); \
+ type ## _check(type, nb, id_shift); \
+ return (type); \
+} \
+ \
+/* Performs no check. */ \
+static void \
+type ## _destroy(struct type ## _ids *const type) \
+{ \
+ struct type ## _id_elem *e, *tmp_e; \
+ \
+ DIAG("Destroying " #type" %p", type); \
+ TYPE ## _FOREACH_SAFE(e, type, ie_entry, \
+ tmp_e) { \
+ free(e); \
+ } \
+ free(type); \
+} \
+ \
+ \
+/* Checks that some tailq/list is as produced by *_create(). */ \
+static void \
+type ## _check(const struct type ## _ids *const type, \
+ const u_int nb, const u_int id_shift) \
+{ \
+ struct type ## _id_elem *e; \
+ u_int i = 0; \
+ \
+ TYPE ## _FOREACH(e, type, ie_entry) { \
+ ATF_REQUIRE_MSG(i + 1 <= nb, \
+ #type " %p has more than %u elements", \
+ type, nb); \
+ ATF_REQUIRE_MSG(e->ie_id == i + id_shift, \
+ #type " %p element %p: Found ID %u, " \
+ "expected %u", \
+ type, e, e->ie_id, i + id_shift); \
+ ++i; \
+ } \
+ ATF_REQUIRE_MSG(i == nb, \
+ #type " %p has only %u elements, expected %u", \
+ type, i, nb); \
+} \
+ \
+/* Returns NULL if not enough elements. */ \
+static struct type ## _id_elem * \
+type ## _nth(const struct type ## _ids *const type, \
+ const u_int idx) \
+{ \
+ struct type ## _id_elem *e; \
+ u_int i = 0; \
+ \
+ TYPE ## _FOREACH(e, type, ie_entry) { \
+ if (i == idx) { \
+ DIAG(#type " %p has element %p " \
+ "(ID %u) at index %u", \
+ type, e, e->ie_id, idx); \
+ return (e); \
+ } \
+ ++i; \
+ } \
+ DIAG(#type " %p: Only %u elements, no index %u", \
+ type, i, idx); \
+ return (NULL); \
+} \
+ \
+/* \
+ * Tests. \
+ */ \
+ \
+ATF_TC(type ## _split_after_and_concat); \
+ATF_TC_HEAD(type ## _split_after_and_concat, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test " #TYPE "_SPLIT_AFTER() followed by " \
+ #TYPE "_CONCAT()"); \
+} \
+ATF_TC_BODY(type ## _split_after_and_concat, tc) \
+{ \
+ struct type ## _ids *const type = \
+ type ## _create(100, 0); \
+ struct type ## _ids rest; \
+ struct type ## _id_elem *e; \
+ \
+ e = type ## _nth(type, 49); \
+ TYPE ## _SPLIT_AFTER(type, e, &rest, ie_entry); \
+ type ## _check(type, 50, 0); \
+ type ## _check(&rest, 50, 50); \
+ QUEUE_TESTS_ ## TYPE ## _CONCAT(type, &rest); \
+ ATF_REQUIRE_MSG(TYPE ## _EMPTY(&rest), \
+ "'rest' not empty after concat"); \
+ type ## _check(type, 100, 0); \
+ type ## _destroy(type); \
+}
+
+#define QUEUE_TESTS_CHECK_REVERSED(type, TYPE) \
+/* \
+ * Checks that some tailq/list is reversed. \
+ */ \
+static void \
+type ## _check_reversed(const struct type ## _ids *const type, \
+ const u_int nb, const u_int id_shift) \
+{ \
+ struct type ## _id_elem *e; \
+ u_int i = 0; \
+ \
+ TYPE ## _FOREACH(e, type, ie_entry) { \
+ const u_int expected_id = nb - 1 - i + id_shift; \
+ \
+ ATF_REQUIRE_MSG(i < nb, \
+ #type " %p has more than %u elements", \
+ type, nb); \
+ ATF_REQUIRE_MSG(e->ie_id == expected_id, \
+ #type " %p element %p, idx %u: Found ID %u, " \
+ "expected %u", \
+ type, e, i, e->ie_id, expected_id); \
+ ++i; \
+ } \
+ ATF_REQUIRE_MSG(i == nb, \
+ #type " %p has only %u elements, expected %u", \
+ type, i, nb); \
+}
+
+/*
+ * Paper over the *_CONCAT() signature differences.
+ */
+
+#define QUEUE_TESTS_TAILQ_CONCAT(first, second) \
+ TAILQ_CONCAT(first, second, ie_entry)
+
+#define QUEUE_TESTS_LIST_CONCAT(first, second) \
+ LIST_CONCAT(first, second, list_id_elem, ie_entry)
+
+#define QUEUE_TESTS_STAILQ_CONCAT(first, second) \
+ STAILQ_CONCAT(first, second)
+
+#define QUEUE_TESTS_SLIST_CONCAT(first, second) \
+ SLIST_CONCAT(first, second, slist_id_elem, ie_entry)
+
+/*
+ * ATF test registration.
+ */
+
+#define QUEUE_TESTS_REGISTRATION(tp, type) \
+ ATF_TP_ADD_TC(tp, type ## _split_after_and_concat)
+
+/*
+ * Macros defining print functions.
+ *
+ * They are currently not used in the tests above, but are useful for debugging.
+ */
+
+#define QUEUE_TESTS_TQ_PRINT(type, hfp) \
+ static void \
+ type ## _print(const struct type ## _ids *const type) \
+ { \
+ printf(#type " %p: " __STRING(hfp ## _first) \
+ " = %p, " __STRING(hfp ## _last) " = %p\n", \
+ type, type->hfp ## _first, type->hfp ## _last); \
+ }
+
+#define QUEUE_TESTS_L_PRINT(type, hfp) \
+ static void \
+ type ## _print(const struct type ## _ids *const type) \
+ { \
+ printf(#type " %p: " __STRING(hfp ## _first) " = %p\n", \
+ type, type->hfp ## _first); \
+ }
+
+
+/*
+ * Meat.
+ */
+
+/* Common tests. */
+QUEUE_TESTS_COMMON(tailq, TAILQ);
+QUEUE_TESTS_COMMON(list, LIST);
+QUEUE_TESTS_COMMON(stailq, STAILQ);
+QUEUE_TESTS_COMMON(slist, SLIST);
+
+/* STAILQ_REVERSE(). */
+QUEUE_TESTS_CHECK_REVERSED(stailq, STAILQ);
+ATF_TC(stailq_reverse);
+ATF_TC_HEAD(stailq_reverse, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test STAILQ_REVERSE");
+}
+ATF_TC_BODY(stailq_reverse, tc)
+{
+ const u_int size = 100;
+ struct stailq_ids *const stailq = stailq_create(size, 0);
+ struct stailq_ids *const empty_stailq = stailq_create(0, 0);
+ const struct stailq_id_elem *last;
+
+ stailq_check(stailq, size, 0);
+ STAILQ_REVERSE(stailq, stailq_id_elem, ie_entry);
+ stailq_check_reversed(stailq, size, 0);
+ last = STAILQ_LAST(stailq, stailq_id_elem, ie_entry);
+ ATF_REQUIRE_MSG(last->ie_id == 0,
+ "Last element of stailq %p has id %u, expected 0",
+ stailq, last->ie_id);
+ stailq_destroy(stailq);
+
+ STAILQ_REVERSE(empty_stailq, stailq_id_elem, ie_entry);
+ stailq_check(empty_stailq, 0, 0);
+ stailq_destroy(empty_stailq);
+}
+
+/*
+ * Main.
+ */
+ATF_TP_ADD_TCS(tp)
+{
+ QUEUE_TESTS_REGISTRATION(tp, tailq);
+ QUEUE_TESTS_REGISTRATION(tp, list);
+ QUEUE_TESTS_REGISTRATION(tp, stailq);
+ QUEUE_TESTS_REGISTRATION(tp, slist);
+ ATF_TP_ADD_TC(tp, stailq_reverse);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/vm/soxstack/Makefile b/tests/sys/vm/soxstack/Makefile
index bd159c2fde75..f9f3bd55b50a 100644
--- a/tests/sys/vm/soxstack/Makefile
+++ b/tests/sys/vm/soxstack/Makefile
@@ -1,3 +1,4 @@
+PACKAGE= tests
SHLIB= soxstack
SHLIB_NAME= libsoxstack.so
SHLIB_MAJOR= 1