- Zig 94.8%
- Nix 5.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
test / test (push) Successful in 12m1s
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WMedP6sZjZGg8FvX8TZ6oX |
||
| .forgejo/workflows | ||
| LICENSES | ||
| src | ||
| tools | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| build.zig.zon.nix | ||
| CONTRIBUTING.md | ||
| flake.lock | ||
| flake.nix | ||
| package.nix | ||
| README.md | ||
| REUSE.toml | ||
ziptop
htop for your network: a real-time IP traffic monitor for the terminal,
written in Zig 0.17. It is a port of franlol's iptop, which is
written in TypeScript for Bun, and it draws the same screen from the same
sources.
- Throughput charts in braille — download rising and upload hanging below it, two samples to a cell and four dots of height per row, with the live rate, the peak and average of the window, and the total since boot.
- Traffic by process — every process with an open socket, by rate or by total, with an inspector that charts one process and lists its connections.
- Traffic by remote host and by service — who the traffic is with, resolved by name and placed by country, and which services carry it (https against QUIC, ssh, DNS and the rest), each with a center-pivot gauge of download against upload.
- A world map — the countries the traffic goes to, tinted by how much,
with a dot for each host on a braille map of the world.
ziptop --mapmakes it the whole screen. - Every socket — each TCP and UDP connection and listener, busiest first.
- Latency — round trips to the gateway, to the Internet (1.1.1.1) and to the DNS server, with their jitter charted and the packet loss counted.
- No root and no packet capture — it reads what the operating system's own tools already report, every half second.
Fourteen color themes come with it, switched with t and remembered.
Running it
$ nix run git+https://git.jcollie.dev/jeff/ziptop.git
or build it (see Building) and run ziptop. ziptop --map
starts in the fullscreen world map, ziptop --help lists the options and
ziptop --version prints the version.
Keys
| Key | Does |
|---|---|
j k ↓ ↑ |
Move the selection through the processes |
⏎ |
Inspect the selected process |
esc |
Leave the inspector, or quit |
s |
Sort the processes by rate or by total, and show that in the bars |
m |
Show or hide the world map |
t |
Open the theme picker, where j and k preview, ⏎ keeps and esc goes back |
[ ] |
Sample less or more often: every 2 s, 1 s or 0.5 s |
| mouse wheel | Scroll the list under the pointer |
q, ctrl-c |
Quit |
The map is left out when the terminal is fewer than 32 rows tall.
The settings file
The chosen theme is kept in $XDG_CONFIG_HOME/ziptop/config, or
~/.config/ziptop/config when XDG_CONFIG_HOME is unset. It is plain
key = value lines with # comments, so it can be edited by hand:
theme = Gruvbox Dark
The themes are Tokyo Night (the default), Catppuccin Mocha, Gruvbox Dark, Nord, Dracula, Rosé Pine, Solarized Dark, One Dark, Everforest, Kanagawa, Monokai, Ayu Dark, Catppuccin Latte and Rosé Pine Dawn.
What it reads
It runs on Linux and macOS, and on each it reads the tools already there:
| Linux | macOS | |
|---|---|---|
| Interface counters | /proc/net/dev |
netstat -ibn |
| Traffic by process and connection | ss -tinp |
nettop -x -J bytes_in,bytes_out -L 1 |
| Sockets | ss -tunap |
lsof -nP -i -FpcnPT |
| Gateway and local address | ip route, ip addr |
route -n get default, ipconfig getifaddr |
| DNS server | /etc/resolv.conf |
scutil --dns |
| Latency | ping |
ping |
On Linux that is iproute2 and iputils. Without root, a socket belonging to another user is listed without its process, and its traffic is not counted against any process. Linux keeps byte counters for TCP sockets only, so UDP traffic — QUIC most of all — shows in the throughput charts and in the services panel's connection count, but not in the rates of a process or a host.
Some network drivers refresh their counters only every second or two. A rate is therefore worked out over the time since an interface's counters last moved, and held for up to two and a half seconds while they do not, rather than reading as zero on every sample that falls between two refreshes.
What it asks of the Internet
Three things leave the machine:
- Reverse DNS lookups of the remote addresses, through the resolvers in
/etc/resolv.conf, so that hosts are shown by name. - Geolocation of every public remote address, from ip-api.com. Its free tier is plain HTTP only, so the addresses being looked up cross the network unencrypted. Addresses are sent a hundred at a time to its batch endpoint, no more than its limit of fifteen requests a minute allows, and private, loopback and link-local addresses are never sent.
- The public address of the network, from api.ipify.org over HTTPS, once at startup.
Building
The development shell has the Zig compiler and everything else:
$ nix develop
$ zig build # zig-out/bin/ziptop
$ zig build run # build and run it
$ zig build run -- --map
$ zig build test
nix build builds the package, running the tests on the way, and
nix flake check does the same.
The world map
src/worldmap_data.zig is a land bitmap of 180 by 72 dots — longitude −180°
to 180° and latitude 84° to −60° — with each land dot tagged with the country
it falls in. It is generated rather than written, from country polygons in
GeoJSON and a table of ISO 3166 codes:
$ curl -LO https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json
$ curl -Lo iso3166.json https://raw.githubusercontent.com/lukes/ISO-3166-Countries-with-Regional-Codes/master/all/all.json
$ zig build worldmap -- countries.geo.json iso3166.json > src/worldmap_data.zig
The tool reads the polygons with zig-geojson and asks it whether the center of each dot is inside one, holes and all. The tool is the only thing that uses zig-geojson; nothing of it is compiled into ziptop.
How it is put together
src/collectors/parses each tool's output into counters, with no I/O of its own, so every parser is tested against captured output.traffic.zigis the engine that turns two samples of counters into rates, smooths them so that rows do not trade places on every sample, and lets processes and hosts that fall quiet decay rather than vanish.src/sampler.zigruns the collectors on their own tasks and keeps the newest sample of each kind, each in an arena of its own, in a store the interface draws from.src/lookups.ziganswers the reverse lookups and the geolocation in the background, from caches: the first sample that mentions an address only asks, and a later one finds the answer.src/ui.zigdraws the screen with libvaxis and handles the keys and the mouse.src/braille.zig,src/format.zig,src/tint.zigandsrc/theme.zigare the charts, the numbers, the country colors and the palettes it draws with.
Repository
The repository lives on a Forgejo instance and is mirrored to Tangled:
$ git clone https://git.jcollie.dev/jeff/ziptop.git # Forgejo
$ git clone https://tangled.org/jcollie.dev/ziptop # Tangled
Both carry the same history on the same main branch.
Issues and contributions
Report issues and submit pull requests on the Tangled mirror, https://tangled.org/jcollie.dev/ziptop. CONTRIBUTING.md says what a change needs to pass.
References cited
- IPTOP franlol, "iptop: htop for your network",
https://github.com/franlol/iptop. The program this is a port of, at commit
31e4291: its panels, its collectors, its themes and its world map. - [RFC1035] Mockapetris, P., "Domain names - implementation and
specification", STD 13, RFC 1035, November 1987,
https://www.rfc-editor.org/info/rfc1035. §3.5, the
in-addr.arpanames under which an IPv4 address's PTR record is found. - [RFC3596] Thomson, S., Huitema, C., Ksinant, V., and M. Souissi, "DNS
Extensions to Support IP Version 6", STD 88, RFC 3596, October 2003,
https://www.rfc-editor.org/info/rfc3596. §2.5, the
ip6.arpanames for IPv6 addresses. - [RFC1918] Rekhter, Y., Moskowitz, B., Karrenberg, D., de Groot, G. J., and E. Lear, "Address Allocation for Private Internets", BCP 5, RFC 1918, February 1996, https://www.rfc-editor.org/info/rfc1918. The IPv4 ranges that are never sent to be geolocated.
- [RFC4193] Hinden, R. and B. Haberman, "Unique Local IPv6 Unicast Addresses", RFC 4193, October 2005, https://www.rfc-editor.org/info/rfc4193. The IPv6 counterpart.
- IP-API ip-api.com, "Batch JSON API",
https://ip-api.com/docs/api:batch. The geolocation endpoint, its limit of
a hundred addresses a request and fifteen requests a minute, and the
X-RlandX-Ttlheaders that say how much of that is left. - IPIFY "ipify: A Simple Public IP Address API", https://www.ipify.org/. Where the public address comes from.
- [SS] "ss(8) — another utility to investigate sockets", Linux manual page, https://man7.org/linux/man-pages/man8/ss.8.html. The socket listing and the TCP information, byte counters included, that the Linux collectors read.
- [WORLD-GEO-JSON] Sundström, J., "world.geo.json", https://github.com/johan/world.geo.json. The country polygons the world map is rasterized from.
- [ISO3166] Duncalfe, L., "ISO-3166 Countries with Regional Codes", https://github.com/lukes/ISO-3166-Countries-with-Regional-Codes. The table that turns the polygons' alpha-3 codes into the alpha-2 codes ip-api.com answers with.
- [BRAILLE] The Unicode Consortium, "Braille Patterns, Range: 2800–28FF", https://www.unicode.org/charts/PDF/U2800.pdf. The dot numbering every chart and the map are drawn with.
- [XDG-BASEDIR] "XDG Base Directory Specification", https://specifications.freedesktop.org/basedir-spec/latest/. Where the settings file is kept.
- LIBVAXIS Culverhouse, T., "libvaxis", https://github.com/rockorager/libvaxis. Draws the terminal and parses its input.
License
MIT. The port keeps the copyright of the program it is ported from, which is
© 2026 Francesc Muñoz and also MIT licensed; see LICENSES/MIT.txt and the
header of each file.