A modular Linux traceroute utility, written in C17 against libc and the Linux kernel networking APIs only — no external traceroute libraries.
Designed by mattsva with help of AI.
voyager traceroute to 8.8.8.8, 30 hops max, 60 byte packets
1 192.0.2.1 1.203 ms 0.981 ms 1.104 ms
2 21.4.1.111 2.311 ms 2.098 ms 2.456 ms
3 8.8.8.8 9.812 ms 9.204 ms 9.671 ms
Destination reached at hop 3 (32.1 ms total)
- Three probing strategies: ICMP Echo (
-I, default), UDP (-U, classic Van Jacobson style), and TCP SYN (-T, useful for tracing through stateless firewalls that block ICMP/UDP but allow TCP to a known port). - Configurable everything: max hops, first hop, probes per hop, timeout, payload size, destination port.
- Five built-in output layouts —
normal,compact,table,json,csv— plus a fully custom, user-defined layout engine driven by simple{token}template files. - Reverse DNS via
getnameinfo(), toggleable with-n/--dns. - Per-hop statistics: packet loss %, min/avg/max RTT.
- Clean separation of concerns: the probing engine never prints
anything — it emits structured
hop_result_trecords to a pluggable renderer. Seesrc/traceroute.candsrc/layouts/. - Experimental IPv6 support (ICMP mode) using Hop Limit.
- Extension points for source address / interface binding, continuous (mtr-style) mode, ASN, and GeoIP annotation (see ROADMAP).
Requires a C17 compiler and a Linux system (raw sockets are Linux/POSIX-specific).
makeProduces a voyager binary in the project root. Run the smoke test
suite with make test, or view the man page locally with make man.
sudo make install # installs to /usr/local by default
sudo make PREFIX=/usr install # or any other prefixThis also installs voyager(1) to the man page tree.
voyager opens raw IP sockets and therefore needs root privileges or
the CAP_NET_RAW capability. Instead of running every invocation with
sudo, you can grant the capability once:
sudo setcap cap_net_raw+ep /usr/local/bin/voyagervoyager [OPTIONS] <HOST>
General:
-h, --help Show help and exit
-v, --version Show version and exit
Target:
--host HOST Target hostname or IP (or bare positional arg)
Hop control:
-m, --max-hops NUM Maximum hops to probe (default: 30)
-f, --first-hop NUM First TTL to probe (default: 1)
Probe control:
-q, --queries NUM Probes per hop (default: 3, max 16)
-w, --wait SECONDS Per-probe timeout (default: 1.0)
-s, --size BYTES Probe payload size (default: 60)
Protocol:
-I, --icmp ICMP Echo probes (default)
-U, --udp UDP probes
-T, --tcp TCP SYN probes
-p, --port PORT UDP base / TCP destination port
Output:
-n, --numeric Disable reverse DNS
--dns Force-enable reverse DNS
--layout NAME normal|compact|table|json|csv
--layout-file FILE Custom layout template
--json / --csv Shortcuts for --layout json/csv
--quiet Only print the final summary
Debug:
-V, --verbose Extra per-hop diagnostic detail
--debug-packets Dump probe/response matching to stderr
Advanced:
--ipv6 IPv6 (Hop Limit), ICMP mode only in v0.0.1
--source ADDR Bind probes to a local source address
--interface IFACE Bind probes to a network interface
--continuous mtr-style live mode (reserved, see ROADMAP)
--asn / --geoip Hop annotation (reserved, see ROADMAP)
Full details, including the custom layout template format, are in
man/voyager.1 (man ./man/voyager.1 or make man).
voyager example.com
voyager -U -q 5 -m 20 example.com
voyager -T -p 443 --json example.com
voyager --layout table -n 8.8.8.8
voyager --layout-file layouts/table.layout example.comvoyager/
├── src/
│ ├── main.c # CLI entry point; wires args → engine → layout
│ ├── args.c/h # getopt_long-based CLI parser, config_t model
│ ├── traceroute.c/h # probing engine (ICMP/UDP/TCP, IPv4 + IPv6-ICMP)
│ ├── packet.c/h # checksum + monotonic timing helpers
│ ├── icmp.c/h # ICMP echo request build + response parsing
│ ├── udp.c/h # UDP probe helpers
│ ├── tcp.c/h # raw TCP SYN construction + parsing
│ ├── dns.c/h # forward/reverse DNS via getaddrinfo/getnameinfo
│ ├── stats.c/h # min/avg/max RTT + packet loss computation
│ ├── version.h # name/version/author metadata
│ └── layouts/
│ ├── layout.c/h # layout interface, selection, template engine
│ ├── normal.c # classic traceroute(8)-style output
│ ├── compact.c # one terse line per hop
│ ├── table.c # aligned column table
│ ├── json.c # structured JSON document
│ └── csv.c # CSV rows
├── layouts/ # example *.layout template files for --layout-file
├── man/voyager.1 # man page
└── Makefile
Design principle: the traceroute engine (traceroute.c) is the
only part of the program that touches sockets. It never calls
printf(). Every completed hop is packaged into a hop_result_t and
handed to a hop_callback_t, which main.c wires to the selected
layout_t's render_hop(). This means adding a new output format
never requires touching probing logic, and vice versa.
- ICMP: the target replies with
ICMP_ECHOREPLYmatching our request's id/sequence. - UDP: the target replies with
ICMP_DEST_UNREACH(Port Unreachable) referencing our original probe's destination port, embedded in the ICMP error payload. - TCP: the target replies directly with a
SYN-ACK(port open) orRST(port closed) — either confirms it was reached.
In all three modes, intermediate routers reply with
ICMP_TIME_EXCEEDED, which voyager correlates back to a specific
probe by inspecting the original packet's IP/transport headers
embedded in the ICMP error.
The following are present in v0.0.1 as working CLI flags and defined interfaces, but with placeholder or partial backends — planned for a complete implementation in v0.1.0:
- Full IPv6 parity: UDP and TCP probing over IPv6 (ICMP-over-IPv6
already works), plus reply Hop Limit capture via ancillary
IPV6_RECVHOPLIMITcontrol messages. --continuous: a live, redrawing mtr-style view that continuously re-probes every hop and updates rolling statistics in place, including a dedicatedlivelayout.--asn: hop annotation with origin AS number and name, via a pluggable lookup backend (e.g. Team Cymru WHOIS/DNS, or a local MRT RIB).--geoip: hop annotation with approximate city/country, via a pluggable backend (e.g. a local MaxMind-format database).- Path MTU discovery mode and ECN/DSCP marking options for probes.
- Parallel probing (send all queries for a hop concurrently rather
than sequentially) with a
--parallelflag, to reduce total run time on high-latency paths. --resolve-onceDNS caching so repeated hops (common in asymmetric or looping paths) don't trigger duplicate reverse lookups.- Packaged distribution: a signed release tarball, a
.deb/.rpm, and a reproducible build recipe.
See LICENSE.