Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

voyager

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)

Features

  • 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 layoutsnormal, 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_t records to a pluggable renderer. See src/traceroute.c and src/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).

Building

Requires a C17 compiler and a Linux system (raw sockets are Linux/POSIX-specific).

make

Produces a voyager binary in the project root. Run the smoke test suite with make test, or view the man page locally with make man.

Installing

sudo make install                # installs to /usr/local by default
sudo make PREFIX=/usr install    # or any other prefix

This also installs voyager(1) to the man page tree.

Running without root

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/voyager

Usage

voyager [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).

Examples

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.com

Architecture

voyager/
├── 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.

How each protocol determines "destination reached"

  • ICMP: the target replies with ICMP_ECHOREPLY matching 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) or RST (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.

Roadmap (v0.1.0)

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_RECVHOPLIMIT control messages.
  • --continuous: a live, redrawing mtr-style view that continuously re-probes every hop and updates rolling statistics in place, including a dedicated live layout.
  • --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 --parallel flag, to reduce total run time on high-latency paths.
  • --resolve-once DNS 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.

License

See LICENSE.

About

a simple traceroute utility

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages