Skip to content

feat(system): sockets() — which process holds that port (S2) - #89

Merged
TimelordUK merged 1 commit into
mainfrom
feat/s2-sockets
Sep 17, 2026
Merged

TimelordUK merged 1 commit into
mainfrom
feat/s2-sockets

Conversation

@TimelordUK

Copy link
Copy Markdown
Owner

Summary

Adds sockets(), the second system table (S2 in docs/SYSTEM_TABLES.md): every TCP and UDP socket on the machine with its owning pid, so it joins to processes().

WITH s AS (SELECT * FROM sockets()),
     p AS (SELECT * FROM processes())
SELECT s.*, p.name
FROM s INNER JOIN p ON s.pid = p.pid
WHERE p.user IS NOT NULL
ORDER BY started DESC
LIMIT 10;
  • Fast. netstat -a is slow because it looks up a hostname for every address. sockets() never does: it reads the OS socket tables directly through netstat2 (GetExtendedTcpTable/GetExtendedUdpTable on Windows, netlink sock_diag + /proc/*/fd on Linux). About 6 ms for 204 sockets on Windows, 3 ms on Linux.
  • Same columns everywhere: protocol, family, local_address, local_port, remote_address, remote_port, state, pid. One row per (socket, pid). A socket with no visible owner still gets a row, with a NULL pid.
  • NULL instead of misleading values:
    • UDP and listening TCP sockets have no remote end, so the remote columns are NULL rather than 0.0.0.0 / 0.
    • Pid 0 is NULL. Windows uses 0 for sockets whose process has exited, and processes() lists pid 0 as Idle, so the join would otherwise blame the Idle process.
  • TCP states use the standard RFC 793 names; Windows' DELETE_TCB maps to CLOSED.
  • Behind the existing system-tables feature. Includes examples/system_sockets.sql (7 queries) and the S2 write-up.

Test plan

  • cargo test --release on Windows 11: all pass. New tests open their own listener, connection and UDP socket and check each row; pid handling has its own unit test.
  • Ubuntu 22.04 under WSL2: all 4 system tests pass, and it builds without libclang installed.
  • Linux results checked against ss -tulpn as root and as a normal user.
  • examples/system_sockets.sql passes via tests/integration/test_examples.py.
  • cargo fmt --check, clippy clean for system.rs.
  • --no-default-features --features redis-cache compiles. Plain --no-default-features already fails on main because of a RedisCache import in src/main_handlers.rs, unrelated to this PR.
  • macOS not tested.

🤖 Generated with Claude Code

SELECT * FROM sockets() WHERE state = 'LISTEN', joined to processes() on
pid through CTEs. An ordinary TableGenerator beside processes(), reading
the OS's own socket tables through netstat2: GetExtendedTcpTable and
GetExtendedUdpTable on Windows, netlink sock_diag plus /proc/*/fd on
Linux, libproc on macOS.

Nothing is resolved to a hostname. Reverse DNS is what makes `netstat -a`
take seconds on Windows; the table itself comes back in ~6 ms over 204
sockets here, and ~3 ms on Linux.

Eight columns, identical on every platform: protocol, family,
local_address, local_port, remote_address, remote_port, state, pid.
One row per (socket, pid); a socket with no visible owner still gets one
row with a NULL pid. State uses the RFC 793 names, with Windows'
DELETE_TCB mapped to CLOSED.

Two absences come out as NULL rather than plausible values:
- UDP and listening TCP sockets have no far end, which the OS reports as
  0.0.0.0:0. Those are NULL remote columns.
- Windows gives pid 0 to sockets whose process has exited (every
  TIME_WAIT row here), and processes() lists pid 0 as Idle. Passing it
  through would have made the join blame the idle process.

Tested on Windows 11 and on Ubuntu under WSL2, where the build needs no
libclang (netstat2 only runs bindgen on macOS). As root on Linux the
join agrees with `ss -tulpn`; sockets owned by other WSL distributions
sharing the network namespace stay NULL there, as they do for ss.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@TimelordUK
TimelordUK merged commit 5bb8c79 into main Sep 17, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant