git: dd60e309af24 - stable/14 - traceroute: add tests

From: Lexi Winter <ivy_at_FreeBSD.org>
Date: Wed, 21 May 2025 06:44:51 UTC
The branch stable/14 has been updated by ivy:

URL: https://cgit.FreeBSD.org/src/commit/?id=dd60e309af24409f0f756b13814926ea7570ce17

commit dd60e309af24409f0f756b13814926ea7570ce17
Author:     Lexi Winter <ivy@FreeBSD.org>
AuthorDate: 2025-05-05 21:47:58 +0000
Commit:     Lexi Winter <ivy@FreeBSD.org>
CommitDate: 2025-05-21 06:29:13 +0000

    traceroute: add tests
    
    add some basic tests for traceroute.  this covers most of the flags we
    can easily test; in some cases we use tcpdump to ensure the correct
    packets are actually being sent.
    
    to run the tests, we create three jails: one for the source host, one
    for the destination host, and one to route packets betweem them.  this
    ensures we're actually testing traceroute across a routed network and
    not just sending probe packets to a directly connected host.
    
    no tests for traceroute6 are in this commit since the traceroute6 merge
    into traceroute is in progress elsewhere.
    
    Reviewed by:    des, adrian
    Approved by:    des (mentor)
    Differential Revision:  https://reviews.freebsd.org/D49838
    
    (cherry picked from commit ff8200f9b8d464d6b4cbdeaaacdc2b78d339111b)
    
    traceroute tests: fix the test when SCTP is supported
    
    The SCTP test assumes a default system configuration where SCTP is not
    supported, so the probe packet returns an ICMP error which is displayed
    as a !P response.  If SCTP is supported, then something else is returned
    instead (depending on exactly what probe we sent, but not an ICMP error)
    and the test fails.
    
    Since we already check the correct probes are sent using tcpdump, remove
    the match for the second hop entirely.
    
    Reported by:    Jenkins
    Reviewed by:    des
    Approved by:    des (mentor)
    Differential Revision:  https://reviews.freebsd.org/D50336
    
    (cherry picked from commit 4dbe268d2e7ad4f1172a174a3c55f0fdff3aaa99)
---
 etc/mtree/BSD.tests.dist                     |   2 +
 usr.sbin/traceroute/Makefile                 |   3 +
 usr.sbin/traceroute/tests/Makefile           |   7 +
 usr.sbin/traceroute/tests/traceroute_test.sh | 874 +++++++++++++++++++++++++++
 4 files changed, 886 insertions(+)

diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
index 2159b24978c0..a909ea3714d3 100644
--- a/etc/mtree/BSD.tests.dist
+++ b/etc/mtree/BSD.tests.dist
@@ -1217,6 +1217,8 @@
         ..
         syslogd
         ..
+        traceroute
+        ..
     ..
 ..
 
diff --git a/usr.sbin/traceroute/Makefile b/usr.sbin/traceroute/Makefile
index 45a80174f5ab..b47e0e2bd55f 100644
--- a/usr.sbin/traceroute/Makefile
+++ b/usr.sbin/traceroute/Makefile
@@ -8,6 +8,9 @@ SRCS=	as.c traceroute.c ifaddrlist.c findsaddr-udp.c
 BINOWN=	root
 BINMODE=4555
 
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
 .if !defined(TRACEROUTE_NO_IPSEC)
 CFLAGS+= -DIPSEC
 .endif
diff --git a/usr.sbin/traceroute/tests/Makefile b/usr.sbin/traceroute/tests/Makefile
new file mode 100644
index 000000000000..7c3d6f777582
--- /dev/null
+++ b/usr.sbin/traceroute/tests/Makefile
@@ -0,0 +1,7 @@
+ATF_TESTS_SH+=	traceroute_test
+
+# Allow tests to run in parallel in their own jails
+TEST_METADATA+=	execenv="jail"
+TEST_METADATA+=	execenv_jail_params="vnet allow.raw_sockets"
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/traceroute/tests/traceroute_test.sh b/usr.sbin/traceroute/tests/traceroute_test.sh
new file mode 100755
index 000000000000..268e0bd58b5b
--- /dev/null
+++ b/usr.sbin/traceroute/tests/traceroute_test.sh
@@ -0,0 +1,874 @@
+# 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.
+
+# We are missing tests for the following flags:
+#
+# -a (turn on ASN lookups)
+# -A (specify ASN lookup server)
+# -d (enable SO_DEBUG)
+# -D (print the diff between our packet and the quote in the ICMP error)
+# -E (detect ECN bleaching)
+# -n (or rather, we enable -n by default and don't test without it)
+# -S (print per-hop packet loss)
+# -v (verbose output)
+# -w (how long to wait for an error response)
+# -x (toggle IP checksums)
+# -z (how long to wait between each probe)
+
+. $(atf_get_srcdir)/../../sys/common/vnet.subr
+
+# These are the default flags we use for most test cases:
+# - only send a single probe packet to reduce the risk of kernel ICMP
+#   rate-limiting breaking the test.
+# - only trace up to 5 hops and only wait 1 second for a response so the test
+#   fails quicker if something goes wrong.
+# - disable DNS resolution as we don't usually care about this.
+TR_FLAGS="-w 1 -q 1 -m 5 -n"
+
+# The prefix our test networks are in.
+TEST_PREFIX="192.0.2.0/24"
+
+# The IPv4 addresses of the first link net between trsrc and trrtr.
+LINK_TRSRC_TRSRC="192.0.2.5"
+LINK_TRSRC_TRRTR="192.0.2.6"
+LINK_TRSRC_PREFIXLEN="30"
+
+# The IPv4 addresses of the second link net between trsrc and trrtr.
+LINK_TRSRC2_TRSRC="192.0.2.13"
+LINK_TRSRC2_TRRTR="192.0.2.14"
+LINK_TRSRC2_PREFIXLEN="30"
+
+# The IPv4 addresses of the link net between trdst and trrtr.
+LINK_TRDST_TRDST="192.0.2.9"
+LINK_TRDST_TRRTR="192.0.2.10"
+LINK_TRDST_PREFIXLEN="30"
+
+# This is an address inside $TEST_PREFIX which is not routed anywhere.
+UNREACHABLE_ADDR="192.0.2.255"
+
+setup_network()
+{
+	# Create 3 jails: one to be the source host, one to be the router,
+	# and one to be the destination host.
+
+	vnet_init
+
+	# src jail
+	epsrc=$(vnet_mkepair)
+	epsrc2=$(vnet_mkepair)
+	vnet_mkjail trsrc ${epsrc}a ${epsrc2}a
+
+	# dst jail
+	epdst=$(vnet_mkepair)
+	vnet_mkjail trdst ${epdst}a
+
+	# router jail
+	vnet_mkjail trrtr ${epsrc}b ${epsrc2}b ${epdst}b
+
+	# Configure IPv4 addresses and routes on each jail.
+
+	# trsrc
+	jexec trsrc ifconfig ${epsrc}a inet \
+	    ${LINK_TRSRC_TRSRC}/${LINK_TRSRC_PREFIXLEN}
+	jexec trrtr ifconfig ${epsrc}b inet \
+	    ${LINK_TRSRC_TRRTR}/${LINK_TRSRC_PREFIXLEN}
+	jexec trsrc route add -inet ${TEST_PREFIX} ${LINK_TRSRC_TRRTR}
+
+	# trsrc2
+	jexec trsrc ifconfig ${epsrc2}a inet \
+	    ${LINK_TRSRC2_TRSRC}/${LINK_TRSRC2_PREFIXLEN}
+	jexec trrtr ifconfig ${epsrc2}b inet \
+	    ${LINK_TRSRC2_TRRTR}/${LINK_TRSRC2_PREFIXLEN}
+
+	# trdst
+	jexec trdst ifconfig ${epdst}a inet \
+	    ${LINK_TRDST_TRDST}/${LINK_TRDST_PREFIXLEN}
+	jexec trrtr ifconfig ${epdst}b inet \
+	    ${LINK_TRDST_TRRTR}/${LINK_TRDST_PREFIXLEN}
+	jexec trdst route add -inet ${TEST_PREFIX} ${LINK_TRDST_TRRTR}
+
+	# The router jail (only) needs IP forwarding enabled.
+	jexec trrtr sysctl net.inet.ip.forwarding=1
+}
+
+##
+#
+# start_tcpdump, stop_tcpdump: used to capture packets during the test so we
+# can verify we actually sent the expected packets.
+
+start_tcpdump()
+{
+	# Run tcpdump on trrtr, either on the given interface or on
+	# ${epsrc}b, which is trsrc's default route interface.
+
+	interface="$1"
+	if [ -z "$interface" ]; then
+		interface="${epsrc}b"
+	fi
+
+	rm -f "${PWD}/traceroute.pcap"
+
+	jexec trrtr daemon -p "${PWD}/tcpdump.pid" \
+	    tcpdump --immediate-mode -w "${PWD}/traceroute.pcap" -nv \
+	    -i $interface
+
+	# Give tcpdump time to start
+	sleep 1
+}
+
+stop_tcpdump()
+{
+	# Sleep to give tcpdump a chance to finish flushing
+	jexec trrtr kill -USR2 $(cat "${PWD}/tcpdump.pid")
+	sleep 1
+	jexec trrtr kill $(cat "${PWD}/tcpdump.pid")
+
+	# Format the packet capture and merge continued lines (starting with
+	# whitespace) into a single line; this makes it easier to match in
+	# atf_check.  Append a blank line since the N command exits on EOF.
+	(tcpdump -nv -r "${PWD}/traceroute.pcap"; echo) | \
+	    sed -E -e :a -e N -e 's/\n +/ /' -e ta -e P -e D \
+	    > tcpdump.output
+}
+
+##
+# test: ipv4_basic
+#
+
+atf_test_case "ipv4_basic" "cleanup"
+ipv4_basic_head()
+{
+	atf_set descr "Basic IPv4 traceroute across a router"
+	atf_set require.user root
+}
+
+ipv4_basic_body()
+{
+	setup_network
+
+	# Use a more detailed set of regexp here than the rest of the tests to
+	# make sure the basic output format is correct.
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST} \\(${LINK_TRDST_TRDST}\\), 5 hops max, 40 byte packets$" \
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}  [0-9.]+ ms$"	\
+	    -o match:"^ 2  ${LINK_TRDST_TRDST}  [0-9.]+ ms$"	\
+	    -o not-match:"^ 3"					\
+	    jexec trsrc traceroute $TR_FLAGS ${LINK_TRDST_TRDST}
+}
+
+ipv4_basic_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_icmp
+#
+
+atf_test_case "ipv4_icmp" "cleanup"
+ipv4_icmp_head()
+{
+	atf_set descr "Basic IPv4 ICMP traceroute across a router"
+	atf_set require.user root
+}
+
+ipv4_icmp_body()
+{
+	setup_network
+
+	# -I and -Picmp should mean the same thing, so test both.
+
+	for icmp_flag in -Picmp -I; do
+		start_tcpdump
+
+		atf_check -s exit:0					\
+		    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+		    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+		    -o match:"^ 2  ${LINK_TRDST_TRDST}"			\
+		    -o not-match:"^ 3"					\
+		    jexec trsrc traceroute $TR_FLAGS $icmp_flag		\
+		    ${LINK_TRDST_TRDST}
+
+		stop_tcpdump
+
+		atf_check -s exit:0 -e ignore 				\
+		    -o match:"IP \\(tos 0x0, ttl 1, .*, proto ICMP.*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}: ICMP echo request" \
+		    -o match:"IP \\(tos 0x0, ttl 2, .*, proto ICMP.*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}: ICMP echo request" \
+		    cat tcpdump.output
+	done
+}
+
+ipv4_icmp_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_udp
+#
+
+atf_test_case "ipv4_udp" "cleanup"
+ipv4_udp_head()
+{
+	atf_set descr "IPv4 UDP traceroute"
+	atf_set require.user root
+}
+
+ipv4_udp_body()
+{
+	setup_network
+
+	start_tcpdump
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o match:"^ 2  ${LINK_TRDST_TRDST}"			\
+	    -o not-match:"^ 3"					\
+	    jexec trsrc traceroute $TR_FLAGS -Pudp ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: UDP" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: UDP" \
+	    cat tcpdump.output
+
+	# Test with -e, the destination port should not increment.
+
+	start_tcpdump
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o match:"^ 2  ${LINK_TRDST_TRDST}"			\
+	    -o not-match:"^ 3"					\
+	    jexec trsrc traceroute $TR_FLAGS -Pudp -e -p 40000 ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: UDP" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: UDP" \
+	    cat tcpdump.output
+}
+
+ipv4_udp_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_sctp
+#
+
+atf_test_case "ipv4_sctp" "cleanup"
+ipv4_sctp_head()
+{
+	atf_set descr "IPv4 SCTP traceroute"
+	atf_set require.user root
+}
+
+ipv4_sctp_body()
+{
+	setup_network
+
+	# For the default packet size, we should sent a SHUTDOWN ACK packet.
+
+	start_tcpdump
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    jexec trsrc traceroute $TR_FLAGS -Psctp ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: sctp \(1\) \[SHUTDOWN ACK\]" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: sctp \(1\) \[SHUTDOWN ACK\]" \
+	    cat tcpdump.output
+
+	# For a larger packet size we should send INIT packets.
+
+	start_tcpdump
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    jexec trsrc traceroute $TR_FLAGS -Psctp ${LINK_TRDST_TRDST} 128
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: sctp \(1\) \[INIT\]" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: sctp \(1\) \[INIT\]" \
+	    cat tcpdump.output
+
+	# Test with -e, the destination port should not increment.
+
+	start_tcpdump
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    jexec trsrc traceroute $TR_FLAGS -Psctp -e -p 40000 ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: sctp \(1\) \[SHUTDOWN ACK\]" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto SCTP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: sctp \(1\) \[SHUTDOWN ACK\]" \
+	    cat tcpdump.output
+}
+
+ipv4_sctp_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_tcp
+#
+
+atf_test_case "ipv4_tcp" "cleanup"
+ipv4_tcp_head()
+{
+	atf_set descr "IPv4 TCP traceroute"
+	atf_set require.user root
+}
+
+ipv4_tcp_body()
+{
+	setup_network
+
+	start_tcpdump
+
+	# We expect the second hop to be a failure since traceroute doesn't
+	# know how to capture the RST packet.
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o match:"^ 2  \\*"					\
+	    jexec trsrc traceroute $TR_FLAGS -Ptcp ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto TCP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: Flags \[S\]" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto TCP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: Flags \[S\]" \
+	    cat tcpdump.output
+
+	# Test with -e, the destination port should not increment.
+	start_tcpdump
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o match:"^ 2  \\*"					\
+	    jexec trsrc traceroute $TR_FLAGS -Ptcp -e -p 40000 ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto TCP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: Flags \[S\]" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto TCP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40000: Flags \[S\]" \
+	    cat tcpdump.output
+}
+
+ipv4_tcp_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_srcaddr
+#
+
+atf_test_case "ipv4_srcaddr" "cleanup"
+ipv4_srcaddr_head()
+{
+	atf_set descr "IPv4 traceroute with explicit source address"
+	atf_set require.user root
+}
+
+ipv4_srcaddr_body()
+{
+	setup_network
+
+	start_tcpdump
+
+	atf_check -s exit:0				\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST} \\($LINK_TRDST_TRDST\\) from ${LINK_TRSRC2_TRSRC}" \
+	    -o match:"^ 1  ${LINK_TRSRC2_TRRTR}"	\
+	    -o match:"^ 2  ${LINK_TRDST_TRDST}"		\
+	    -o not-match:"^ 3"				\
+	    jexec trsrc traceroute $TR_FLAGS		\
+	        -s ${LINK_TRSRC2_TRSRC} ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP.*\\).* ${LINK_TRSRC2_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: UDP" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP.*\\).* ${LINK_TRSRC2_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: UDP" \
+	    cat tcpdump.output
+}
+
+ipv4_srcaddr_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_srcinterface
+#
+
+atf_test_case "ipv4_srcinterface" "cleanup"
+ipv4_srcinterface_head()
+{
+	atf_set descr "IPv4 traceroute with explicit source interface"
+	atf_set require.user root
+}
+
+ipv4_srcinterface_body()
+{
+	setup_network
+
+	start_tcpdump
+
+	# Unlike -s, traceroute doesn't print 'from ...' when using -i.
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC2_TRRTR}"		\
+	    -o match:"^ 2  ${LINK_TRDST_TRDST}"			\
+	    -o not-match:"^ 3"					\
+	    jexec trsrc traceroute $TR_FLAGS			\
+	        -i ${epsrc2}a ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP.*\\).* ${LINK_TRSRC2_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: UDP" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP.*\\).* ${LINK_TRSRC2_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: UDP" \
+	    cat tcpdump.output
+}
+
+ipv4_srcinterface_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_maxhops
+#
+
+atf_test_case "ipv4_maxhops" "cleanup"
+ipv4_maxhops_head()
+{
+	atf_set descr "IPv4 traceroute with -m"
+	atf_set require.user root
+}
+
+ipv4_maxhops_body()
+{
+	setup_network
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o not-match:"^ 2"					\
+	    jexec trsrc traceroute -w1 -q1 -m1 ${LINK_TRDST_TRDST}
+}
+
+ipv4_maxhops_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_unreachable
+#
+
+atf_test_case "ipv4_unreachable" "cleanup"
+ipv4_unreachable_head()
+{
+	atf_set descr "IPv4 traceroute to an unreachable destination"
+	atf_set require.user root
+}
+
+ipv4_unreachable_body()
+{
+	setup_network
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${UNREACHABLE_ADDR}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o match:"^ 2  ${LINK_TRSRC_TRRTR}  [0-9.]+ ms !H"	\
+	    -o not-match:"^ 3"					\
+	    jexec trsrc traceroute $TR_FLAGS $UNREACHABLE_ADDR
+}
+
+ipv4_unreachable_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_hugepacket
+#
+
+atf_test_case "ipv4_hugepacket" "cleanup"
+ipv4_hugepacket_head()
+{
+	atf_set descr "IPv4 traceroute with a huge packet"
+	atf_set require.user root
+}
+
+ipv4_hugepacket_body()
+{
+	setup_network
+
+	# We expect this to fail since we specified -F (don't fragment) and the
+	# 2000-byte packet is too large to fit through our tiny epair.  Make
+	# sure traceroute reports the error.
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST} \\(${LINK_TRDST_TRDST}\\), 5 hops max, 2000 byte packets$" \
+	    -o match:"^ 1 traceroute: wrote ${LINK_TRDST_TRDST} 2000 chars, ret=-1" \
+	    -e match:"^traceroute: sendto: Message too long"	\
+	    jexec trsrc traceroute -F $TR_FLAGS ${LINK_TRDST_TRDST} 2000
+}
+
+ipv4_hugepacket_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_firsthop
+#
+
+atf_test_case "ipv4_firsthop" "cleanup"
+ipv4_firsthop_head()
+{
+	atf_set descr "IPv4 traceroute with one hop skipped"
+	atf_set require.user root
+}
+
+ipv4_firsthop_body()
+{
+	setup_network
+
+	# -f 2 means we skip the first hop.  For backward compatibility, -M is
+	# the same as -f, so test that too.
+
+	for flag in -f2 -M2; do
+		start_tcpdump
+
+		atf_check -s exit:0					\
+		    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+		    -o not-match:"^ 1"					\
+		    -o match:"^ 2  ${LINK_TRDST_TRDST}"			\
+		    -o not-match:"^ 3"					\
+		    jexec trsrc traceroute $flag $TR_FLAGS ${LINK_TRDST_TRDST}
+
+		stop_tcpdump
+		atf_check -s exit:0 -e ignore 				\
+		    -o not-match:"^..:..:..\....... IP \\(tos 0x0, ttl 1, .*, proto UDP.*\\)" \
+		    -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: UDP" \
+		    cat tcpdump.output
+	done
+}
+
+ipv4_firsthop_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_nprobes
+#
+
+atf_test_case "ipv4_nprobes" "cleanup"
+ipv4_nprobes_head()
+{
+	atf_set descr "IPv4 traceroute with varying number of probes"
+	atf_set require.user root
+}
+
+ipv4_nprobes_body()
+{
+	setup_network
+
+	# By default we should send 3 probes.
+	atf_check -s exit:0 -e ignore				\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR} \(${LINK_TRSRC_TRRTR}\)(  [0-9.]+ ms){3}$" \
+	    jexec trsrc traceroute -w1 -m1 ${LINK_TRDST_TRDST}
+
+	# Also test 1 and 2 (below the default) and 5 (above the default)
+	for nprobes in 1 2 5; do
+		atf_check -s exit:0 -e ignore				\
+		    -o match:"^ 1  ${LINK_TRSRC_TRRTR} \(${LINK_TRSRC_TRRTR}\)(  [0-9.]+ ms){$nprobes}$" \
+		    jexec trsrc traceroute -q$nprobes -w1 -m1 ${LINK_TRDST_TRDST}
+	    done
+}
+
+ipv4_nprobes_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_baseport
+#
+
+atf_test_case "ipv4_baseport" "cleanup"
+ipv4_baseport_head()
+{
+	atf_set descr "IPv4 traceroute with non-default base port"
+	atf_set require.user root
+}
+
+ipv4_baseport_body()
+{
+	setup_network
+
+	start_tcpdump
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o match:"^ 2  ${LINK_TRDST_TRDST}"			\
+	    -o not-match:"^ 3"					\
+	    jexec trsrc traceroute $TR_FLAGS -p 40000		\
+	    ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40001: UDP" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP.*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.40002: UDP" \
+	    cat tcpdump.output
+}
+
+ipv4_baseport_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_gre
+#
+
+atf_test_case "ipv4_gre" "cleanup"
+ipv4_gre_head()
+{
+	atf_set descr "IPv4 GRE traceroute"
+	atf_set require.user root
+}
+
+ipv4_gre_body()
+{
+	setup_network
+
+	start_tcpdump
+
+	# We expect the second hop to be a failure since the remote host will
+	# ignore the GRE packet.
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o match:"^ 2  \\*"					\
+	    jexec trsrc traceroute $TR_FLAGS -Pgre ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto GRE .*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}: GREv1" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto GRE .*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}: GREv1" \
+	    cat tcpdump.output
+}
+
+ipv4_gre_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_udplite
+#
+
+atf_test_case "ipv4_udplite" "cleanup"
+ipv4_udplite_head()
+{
+	atf_set descr "IPv4 UDP-Lite traceroute"
+	atf_set require.user root
+}
+
+ipv4_udplite_body()
+{
+	setup_network
+
+	start_tcpdump
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o match:"^ 2  ${LINK_TRDST_TRDST}"			\
+	    -o not-match:"^ 3"					\
+	    jexec trsrc traceroute $TR_FLAGS -Pudplite ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto unknown \(136\), .*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}:  ip-proto-136" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto unknown \(136\), .*\\).* ${LINK_TRSRC_TRSRC} > ${LINK_TRDST_TRDST}:  ip-proto-136" \
+	    cat tcpdump.output
+}
+
+ipv4_udplite_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_iptos
+#
+
+atf_test_case "ipv4_iptos" "cleanup"
+ipv4_iptos_head()
+{
+	atf_set descr "IPv4 traceroute with explicit ToS"
+	atf_set require.user root
+}
+
+ipv4_iptos_body()
+{
+	setup_network
+
+	start_tcpdump
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o match:"^ 2  ${LINK_TRDST_TRDST}"			\
+	    -o not-match:"^ 3"					\
+	    jexec trsrc traceroute $TR_FLAGS -t 4 ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x4, ttl 1, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33435: UDP" \
+	    -o match:"IP \\(tos 0x4, ttl 2, .*, proto UDP .*\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRDST_TRDST}.33436: UDP" \
+	    cat tcpdump.output
+}
+
+ipv4_iptos_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_srcroute
+#
+
+atf_test_case "ipv4_srcroute" "cleanup"
+ipv4_srcroute_head()
+{
+	atf_set descr "IPv4 traceroute with explicit source routing"
+	atf_set require.user root
+}
+
+ipv4_srcroute_body()
+{
+	setup_network
+	jexec trsrc sysctl net.inet.ip.sourceroute=1
+	jexec trsrc sysctl net.inet.ip.accept_sourceroute=1
+	jexec trrtr sysctl net.inet.ip.sourceroute=1
+
+	start_tcpdump
+
+	# As we don't enable source routing on trdst, we should get an ICMP
+	# source routing failed error (!S).
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}"			\
+	    -o match:"^ 2  ${LINK_TRDST_TRDST}  [0-9.]+ ms !S"	\
+	    -o not-match:"^ 3"					\
+	    jexec trsrc traceroute $TR_FLAGS			\
+	        -g ${LINK_TRSRC_TRRTR} ${LINK_TRDST_TRDST}
+
+	stop_tcpdump
+	atf_check -s exit:0 -e ignore 				\
+	    -o match:"IP \\(tos 0x0, ttl 1, .*, proto UDP .*, options \\(NOP,LSRR ${LINK_TRDST_TRDST}\\)\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRSRC_TRRTR}.33435: UDP" \
+	    -o match:"IP \\(tos 0x0, ttl 2, .*, proto UDP .*, options \\(NOP,LSRR ${LINK_TRDST_TRDST}\\)\\).* ${LINK_TRSRC_TRSRC}.[0-9]+ > ${LINK_TRSRC_TRRTR}.33436: UDP" \
+	    cat tcpdump.output
+}
+
+ipv4_srcroute_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test: ipv4_dontroute
+#
+
+atf_test_case "ipv4_dontroute" "cleanup"
+ipv4_dontroute_head()
+{
+	atf_set descr "IPv4 traceroute with -r"
+	atf_set require.user root
+}
+
+ipv4_dontroute_body()
+{
+	setup_network
+
+	# This one should work as trrtr is directly connected.
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRSRC_TRRTR}"	\
+	    -o match:"^ 1  ${LINK_TRSRC_TRRTR}  [0-9.]+ ms$"	\
+	    -o not-match:"^ 2"					\
+	    jexec trsrc traceroute -r $TR_FLAGS ${LINK_TRSRC_TRRTR}
+
+	# This one should fail.
+
+	atf_check -s exit:0					\
+	    -e match:"^traceroute to ${LINK_TRDST_TRDST}"	\
+	    -o match:"^ 1 traceroute: wrote ${LINK_TRDST_TRDST} 40 chars, ret=-1" \
+	    jexec trsrc traceroute -r $TR_FLAGS ${LINK_TRDST_TRDST}
+}
+
+ipv4_dontroute_cleanup()
+{
+	vnet_cleanup
+}
+
+##
+# test case declarations
+
+atf_init_test_cases()
+{
+	atf_add_test_case ipv4_basic
+	atf_add_test_case ipv4_udp
+	atf_add_test_case ipv4_icmp
+	atf_add_test_case ipv4_tcp
+	atf_add_test_case ipv4_sctp
+	atf_add_test_case ipv4_gre
+	atf_add_test_case ipv4_udplite
+	atf_add_test_case ipv4_srcaddr
+	atf_add_test_case ipv4_srcinterface
+	atf_add_test_case ipv4_maxhops
+	atf_add_test_case ipv4_unreachable
+	atf_add_test_case ipv4_hugepacket
+	atf_add_test_case ipv4_firsthop
+	atf_add_test_case ipv4_nprobes
+	atf_add_test_case ipv4_baseport
+	atf_add_test_case ipv4_iptos
+	atf_add_test_case ipv4_srcroute
+	atf_add_test_case ipv4_dontroute
+}