Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ open PRs for overlapping work first and use `.github/PULL_REQUEST_TEMPLATE.md`.
- `src/main.rs`, `src/cli.rs`: GUI/CLI dispatch and snapshot output.
- `src/snapshot.rs`: Darwin socket enumeration, process metadata, formatting.
- `src/diff.rs`: endpoint identity and highlight lifecycle.
- `src/dns.rs`: background PTR/A resolution and cache.
- `src/dns.rs`: background PTR resolution, cache, and GUI lookup chain.
- `src/ptr.rs`: LAN mDNS and opt-in public-resolver PTR fallbacks (wire format).
- `src/app.rs`: egui table, controls, export, termination confirmation.
- `src/kill.rs`, `src/export.rs`: process signaling and exclusive CSV writes.
- `tests/`: diff regression cases and a live loopback listener test.
Expand Down
2 changes: 2 additions & 0 deletions Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<string>12.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>With Resolve names on, CyClaw-Net-Viewer asks LAN devices for their names over mDNS when your router does not know them.</string>
<key>NSHumanReadableCopyright</key>
<string>MIT. Not affiliated with Microsoft or Sysinternals.</string>
</dict>
Expand Down
48 changes: 46 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
macOS 12+ TCP/UDP endpoint viewer for watching process egress—especially while
developing [CyClaw](https://github.com/cgfixit/CyClaw).

<img src="https://github.com/cgfixit/CyClaw-Net-Viewer/blob/main/docs/image.jpg">
![CyClaw-Net-Viewer showing a CyClaw agent's live connections: a telemetry endpoint turning red as it is killed, a new curl check in green, a state change in yellow, and a new debug listener in blue](screenshots/overview-dark.png)

*Watching a CyClaw agent while flipping its telemetry kill switch. The agent's
connection to `telemetry.vendor.example` goes **red** (gone), a `curl` check
appears in **green**, a worker drops to `CLOSE_WAIT` in **yellow**, and a new
debug listener shows up in **blue**. Orange rows are steady traffic leaving the
Mac. Names come from reverse DNS, and the observed IP and port stay visible.
Synthetic data, documentation IP ranges only.*

| Process | PID | Proto | Dir | Local | Remote | State | Path |
| --- | ---: | --- | --- | --- | --- | --- | --- |
Expand All @@ -18,7 +25,14 @@ developing [CyClaw](https://github.com/cgfixit/CyClaw).

- Lists TCP and UDP sockets this Mac will admit through libproc, including IPv4 and IPv6. Columns: process, PID, protocol, direction, local and remote addresses, TCP state.
- Refreshes on a timer (default 1s) with the event colors below.
- Keeps the observed socket IP and port in every cell. Optional reverse DNS is an untrusted label only; it is off by default in the GUI. CLI `-n` stays numeric.
- Keeps the observed socket IP and port in every cell. Optional reverse DNS is an untrusted label only; it is off by default in the GUI. Long names are shortened before the IP is. CLI `-n` stays numeric.
- When your router has no reverse-DNS answer, Resolve names asks LAN devices
directly over mDNS. You can also opt in to Quad9 or Cloudflare for public
peers (**Options → Fallback DNS**, off by default). See
[Name resolution](#name-resolution).
- Filter (`/` or Cmd+F, Esc clears), sort any column, pause to inspect,
export visible rows to `~/Downloads` as a private CSV, and terminate the
owning process after a confirmation.
- Ships a double-click `.app` and a Tcpvcon-style CLI in the same `netboard` binary.

See [Direction](docs/DESIGN.md#direction). Refresh,
Expand All @@ -36,6 +50,33 @@ deletion, and address rules: [Design](docs/DESIGN.md).

Turn on **Off-box only** when watching CyClaw telemetry-kill. Event colors still win over orange.

## Screenshots

| | |
| --- | --- |
| ![Off-box only view listing just the connections that leave the Mac](screenshots/offbox-only.png) | **Off-box only** hides listeners, loopback, and wildcard sockets, leaving only traffic that leaves the Mac. That's what you watch for a telemetry kill. |
| ![Light theme filtered to cyclaw](screenshots/filter-light.png) | **Filter** matches process, PID, host, port, state, or path. The status bar counts what the filter hides. Light and dark follow macOS. |
| ![Terminate confirmation dialog](screenshots/terminate-confirm.png) | **Terminate** always asks first. It sends SIGTERM to the whole process, and only if the PID still belongs to the process you confirmed. |

All screenshots use synthetic endpoints with RFC 5737/3849 documentation addresses.

## Name resolution

**Resolve names** (`N`) is off by default. When on, each address goes through:

1. the macOS resolver (hosts file, router/ISP DNS, Bonjour);
2. for RFC 1918 or link-local IPv4 peers the router could not name, one
mDNS query on the local link. Bonjour/Avahi devices answer for their own
address. macOS 15+ may ask for Local Network permission the first time;
3. for public peers, only if you pick **Quad9** or **Cloudflare** under
**Options → Fallback DNS**, a direct PTR query to that resolver over UDP/53.
This sends those IPs to the provider in cleartext. Private, loopback, and
documentation addresses are never sent.

Answers must look like a plain hostname, or they are dropped. Misses are
retried after two minutes. Names are untrusted labels, not identities. The
CLI uses step 1 only, and `-n` skips DNS entirely.

## CLI

```text
Expand All @@ -55,6 +96,9 @@ netboard --cli [-a] [-c] [-n] [process|pid]

Download `CyClaw-Net-Viewer.zip` and `CyClaw-Net-Viewer.zip.sha256` from the
latest [GitHub Release](https://github.com/cgfixit/CyClaw-Net-Viewer/releases/latest).
Releases are built, verified, and attached by the manual
[Release workflow](.github/workflows/release.yml); see
[Cutting a release](CONTRIBUTING.md#cutting-a-release).
No Rust is required to run a downloaded app. Build from source (below) if
you prefer not to use a prebuilt binary.

Expand Down
9 changes: 8 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ check run requiring write access.
requests may complete. PTR answers are kept only when they are a plain
ASCII hostname (letters, digits, `-`, `_`, dots; at most 253 bytes;
non-numeric last label), so an answer cannot imitate an IP address or
carry spaces, controls, or bidi marks. Cached names are still untrusted
carry spaces, controls, or bidi marks. When the system resolver has no
answer, a LAN peer (RFC 1918 / link-local IPv4) gets one mDNS query on
the local link. **Options → Fallback DNS** is Off by default; choosing
Quad9 or Cloudflare sends globally routable peer IPs that the system
cannot name to that provider in cleartext over UDP/53. Private, loopback,
documentation, and other special-purpose addresses are never sent to it.
Fallback replies must match the query ID, source, and question name.
Misses are retried after two minutes. Cached names are still untrusted
labels, not verified peer identities. The GUI resolver keeps at most 4096 completed
addresses; evicted names may be looked up again.
- GUI CSV exports go to `~/Downloads` (or `~` when that folder is missing)
Expand Down
2 changes: 2 additions & 0 deletions docs/CONTROLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
| Right-click row | copy line, copy remote, terminate, reveal in Finder |
| File → Save CSV | write `cyclaw-net-viewer-YYYYMMDD-HHMMSS.csv` to `~/Downloads` (else `~`); the status bar shows the path |
| File → Terminate process | same as terminate; enabled only for ESTABLISHED TCP |
| Options → Fallback DNS | Off (default), Quad9, or Cloudflare for public peers the system resolver cannot name |
| `/` or Cmd+F | focus the filter; Esc clears it |

Single-key shortcuts are ignored while any control has keyboard focus
(typing in the filter, or a Tab-focused button), while the terminate
Expand Down
29 changes: 27 additions & 2 deletions docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,36 @@ A returning endpoint is new again. Pausing or a failed refresh does not age rows
## DNS workers and FFI

Eight workers receive from a bounded 256-request `crossbeam-channel` queue.
Duplicate pending/completed lookups stay suppressed, including negative answers;
a full queue leaves the address retryable on a later frame. Shutdown disconnects
Duplicate pending/completed lookups stay suppressed. A negative answer is
suppressed for two minutes, then retried, so a transient router or resolver
failure does not hide a name for the whole session; changing the fallback
setting retries misses at once. A full queue leaves the address retryable
on a later frame. Shutdown disconnects
the sender, drains accepted requests and joins workers without a cache lock.
System DNS calls cannot be cancelled, so shutdown can wait for resolver timeouts.

### GUI fallback chain (`src/ptr.rs`)

1. `getnameinfo` (hosts file, router/ISP DNS, mDNSResponder).
2. No answer and the peer is RFC 1918 or IPv4 link-local: one legacy unicast
mDNS PTR query (RFC 6762 section 6.7) to `224.0.0.251:5353` from an
ephemeral port, 800 ms window. Bonjour/Avahi hosts publish reverse records
for their own addresses even when the router has none. Only replies from
LAN-scoped sources are read; the packet never leaves the link.
3. No answer, the peer is globally routable unicast, and the user picked
Quad9 or Cloudflare in Options: a recursive PTR query over UDP/53 to that
address, 1.2 s window, one retry on timeout, none on NXDOMAIN. Replies
must come from the exact server address and port.

Loopback, multicast, documentation, CGNAT, benchmarking, reserved and IPv6
link-local/ULA addresses never go past step 1. Queries use a random ID and
OS-chosen port; replies must echo the ID, set QR, carry NOERROR without TC,
and answer the queried owner name (in-message CNAME chains for RFC 2317
delegation are followed up to 8 hops). Name decoding rejects forward or
looping compression pointers, labels containing dots or non-printable bytes,
and names over 255 bytes. Every answer, from any step, must then pass the
plain-hostname rule. The CLI stays on step 1 only.

CLI output resolves synchronously after filtering endpoints. Each unique address
is looked up at most once per CLI snapshot, with both answers and misses cached
across local and remote cells. Numeric mode and unspecified addresses skip DNS.
Expand Down
1 change: 1 addition & 0 deletions docs/KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
- `netstat2` UDP sockets have no remote address; connected UDP shows as `*:0` / Unknown. Local bindings do not establish packet direction.
- Unsigned ad-hoc `.app`. Notarization is out of scope.
- First snapshot can take a beat while libproc walks every PID.
- macOS 15+ may ask to allow CyClaw-Net-Viewer to find devices on your local network the first time Resolve names sends an mDNS query for a LAN peer. Allowing it lets LAN names resolve; denying it just leaves those peers numeric. The public DNS fallback (Options) needs outbound UDP/53; networks that block it leave names numeric.
Binary file removed docs/image.jpg
Binary file not shown.
Binary file added screenshots/filter-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/offbox-only.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/overview-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/terminate-confirm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading