seehandshake 1.0.2

Visualize TLS handshakes in your terminal, in real time.
Documentation
# Process attribution

For every observed TCP flow, SeeHandshake tries to identify the local
process that owns the socket — the same information `ss -tp` or `lsof -i`
would show. When it succeeds, the connection panel shows the process
name, PID, UID, and (for CLI programs) the full command line.

This is *not* end-to-end user-action tracing. See "Honest limits" below.

## How the lookup works (Linux)

1. On the first packet of a new flow, the tracker asks its
   [`OriginResolver`]../src/origin/mod.rs to resolve the flow.
2. On Linux the resolver
   ([`LinuxProcResolver`]../src/origin/linux.rs) reads
   `/proc/net/tcp` and `/proc/net/tcp6`. Only rows in state `01`
   (ESTABLISHED) are kept. That gives a map keyed on
   `(local, remote)` sockets, with an inode and UID per row.
3. The resolver looks up the flow in both orderings —
   `(seg.src, seg.dst)` and `(seg.dst, seg.src)`. The kernel only lists
   sockets whose local side is on this host, so only one ordering
   matches.
4. If a row matches, the resolver walks `/proc/<pid>/fd/` for every
   readable process, looking for a `socket:[inode]` symlink that points
   at the row's inode. When it finds one, it reads
   `/proc/<pid>/comm`, `/proc/<pid>/cmdline`, and the UID from
   `/proc/<pid>/status`.
5. Results are cached on the connection. Subsequent packets on the same
   flow skip the lookup entirely. The `/proc` snapshot itself is cached
   for 500 ms so a burst of flows opened in the same second (loading a
   web page) does one pass over `/proc`, not one per flow.

The result is stashed on `HandshakeInfo::origin` and rendered in the
metadata panel and in the detail-view record header.

## What you'll see

- **`Local(ProcessOrigin)`** — the socket is owned by a process you can
  read. UI shows `firefox (pid 12345, uid 1000)`, and for CLI programs
  the full `cmdline` line underneath (e.g. `curl https://example.com`).
- **`OtherUser { uid }`** — the row exists in `/proc/net/tcp` but no
  readable `/proc/<pid>/fd` symlink was found. Common for daemons
  running under service accounts (`systemd-resolve`, `avahi`). UI shows
  `other user (uid 193)`. Running SeeHandshake as root would upgrade
  most of these to `Local(...)`, but the tool intentionally does not
  ask for that; being honest about the limit beats silently escalating.
- **`Unknown`** — the flow was observed but no matching socket exists.
  The connection may have already closed, or the flow was routed
  through this box without a local socket (uncommon).
- **`Unsupported`** — running on a platform without an attribution
  implementation (currently anything other than Linux). The UI omits
  the row entirely.

## Honest limits

- **CLI cmdlines *are* the user action.** `curl https://example.com` is
  exactly what the user typed. `wget`, `git fetch`, `apt update`, etc.
  all show the invocation.
- **Browser processes are not.** A row that says `firefox` tells you
  Firefox opened the socket; it cannot distinguish a click, a link
  preview, a favicon fetch, a background sync, a WebPush poll, an ad
  tracker, or a certificate revocation lookup. That distinction only
  exists inside the browser. If you need it, use the browser's own
  DevTools network panel.
- **Multiplexed protocols share one process.** Two "different" HTTP
  destinations reached via the same HTTP/3 or HTTP/2 client will share
  a process; the SNI is the only per-flow discriminator.
- **Race with process exit.** If `/proc/net/tcp` still lists a socket
  whose owning process has exited between the packet and the lookup,
  you'll see `Unknown` or a stale row. This is rare in practice but
  cannot be avoided from userland.

## Turning it off

There is no runtime flag. To disable attribution, run on a platform
without a resolver, or edit
[`src/origin/mod.rs::default_resolver`](../src/origin/mod.rs) to return
[`NullOriginResolver`](../src/origin/other.rs). The rest of the UI
degrades gracefully — the Origin row is simply omitted.