diff options
Diffstat (limited to 'tests')
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 |