aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/traceroute
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/traceroute')
-rw-r--r--usr.sbin/traceroute/Makefile3
-rw-r--r--usr.sbin/traceroute/tests/Makefile7
-rwxr-xr-xusr.sbin/traceroute/tests/traceroute_test.sh874
-rw-r--r--usr.sbin/traceroute/traceroute.865
4 files changed, 944 insertions, 5 deletions
diff --git a/usr.sbin/traceroute/Makefile b/usr.sbin/traceroute/Makefile
index 62d82a47d953..c52bd52abb1d 100644
--- a/usr.sbin/traceroute/Makefile
+++ b/usr.sbin/traceroute/Makefile
@@ -7,6 +7,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
+}
diff --git a/usr.sbin/traceroute/traceroute.8 b/usr.sbin/traceroute/traceroute.8
index 203b743fb408..f36d473f2727 100644
--- a/usr.sbin/traceroute/traceroute.8
+++ b/usr.sbin/traceroute/traceroute.8
@@ -1,3 +1,6 @@
+.\"
+.\" SPDX-License-Identifier: BSD-4.3TAHOE
+.\"
.\" Copyright (c) 1989, 1995, 1996, 1997, 1999, 2000
.\" The Regents of the University of California. All rights reserved.
.\"
@@ -15,7 +18,7 @@
.\"
.\" $Id: traceroute.8,v 1.19 2000/09/21 08:44:19 leres Exp $
.\"
-.Dd November 17, 2023
+.Dd May 14, 2025
.Dt TRACEROUTE 8
.Os
.Sh NAME
@@ -136,10 +139,62 @@ to terminate the route tracing).
If something is listening on a port in the default range, this option can be
used to pick an unused port range.
.It Fl P Ar proto
-Send packets of specified IP protocol.
-The currently supported protocols
-are: UDP, UDP-Lite, TCP, SCTP, GRE and ICMP.
-Other protocols may also be specified (either by name or by number), though
+Use packets of specified IP protocol when sending probes.
+The
+.Ar proto
+argument may be one of the following:
+.Bl -tag -width Ar udplite
+.It Ar udp
+Use
+.Xr udp 4
+packets.
+This is the default.
+.It Ar icmp
+Use
+.Xr icmp 4
+.Dq echo request
+packets.
+.It Ar udplite
+Use
+.Xr udplite 4
+packets.
+.It Ar tcp
+Use
+.Xr tcp 4
+.Dq SYN
+packets.
+This will cause a successful traceroute to end with no response (i.e., a
+.Dq *
+response) since
+.Nm
+does not know how to detect the RST or SYN+ACK response from the
+destination host.
+.It Ar sctp
+Use
+.Xr sctp 4
+packets.
+The
+.Ar packetlen
+argument must be a multiple of 4.
+SCTP probes will be constructed as SCTP
+.Dq INIT
+chunks, unless the packet length is too small, in which case the probes
+will be SCTP
+.Dq SHUTDOWN-ACK
+chunks followed by zero or one
+.Dq PAD
+chunks.
+.It Ar gre
+Use
+.Xr gre 4
+packets.
+The GRE packets will be constructed as if they contain a PPTP
+(Point-to-Point Tunneling Protocol) payload.
+.El
+.Pp
+Other protocols may also be specified, either by number or by name (see
+.Xr protocols 5 ) ,
+though
.Nm
does not implement any special knowledge of their packet formats.
This option is useful for determining which router along a path may be blocking