Repository navigation
perf(platform): read macOS sockets through proc_info instead of lsof - #88
Merged
Merged
Conversation
The macOS socket check started lsof once per socket class and 200-pid batch, plus one unrestricted lsof pass to resolve a listener's clients, and parsed its text output. That text caused two bugs this month (the socket address in field d, not D; accepted connections shown only by their bound path) and timed out on loaded machines. The check now calls the proc_info syscall that libproc and lsof use: PROC_PIDLISTFDS lists a process's descriptors and PROC_PIDFDSOCKETINFO returns struct socket_fdinfo, decoded at the SDK's offsets (identical for arm64 and x86_64). soi_pcb is a Unix socket's own address and unsi_conn_pcb its peer, so an accepted connection now names its client directly. Records feed the existing classifier unchanged. - The all-process scan of resolved checks takes about 7 ms instead of 126 ms for lsof. - A process that exited is skipped; another user's process (EPERM with a different owner) is expected and makes its peers system services; any other unreadable process of this user fails the check closed. - A socket record of unexpected size means the kernel layout changed: the check logs socket_layout_fallback once and uses lsof.
Review fixes for the proc_info socket check: - A tree pid that cannot be read (for example a setuid child) now marks only that pid connected instead of failing the check for every tree. - ENOTSOCK and EOPNOTSUPP, like EBADF, mean a descriptor changed between listing and reading, so it is skipped. - ENOMEM from PROC_PIDFDSOCKETINFO means the kernel's struct outgrew the buffer, so it also triggers the lsof fallback. - The all-process scan takes processes and owners from one kern.proc.all snapshot: other users' processes are never asked, which removes the per-pid owner lookup, its race, and pid-list truncation. - Each process is read once per scan. The scan has its own deadline (5 s per 200 tree pids, plus 10 s for all processes); running out fails a resolved check closed. - PROC_PIDLISTFDS is sized first and buffers are reused across pids. - physicalFootprint shares the proc_info call, and compile-time checks keep darwinPlatform a SocketProber and PeerResolver.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
On macOS, Driga's socket check now reads sockets through the kernel's
proc_infosystem call, the interfacelsofitself uses, instead of startinglsofand parsing its text output. It decodes each socket record at the SDK offsets and feeds the result to the existing classifier unchanged. If the kernel's record layout ever changes, Driga logssocket_layout_fallbackand goes back tolsof.Why
lsofcaused both macOS socket bugs fixed in #87:d, notD.lsofshows a connection a server accepted only by its bound path, so finding a listener's clients needed a scan of every process.lsofalso timed out on loaded machines. The kernel record names an accepted connection's client directly, throughunsi_conn_pcb. The all-process scan now takes about 7 ms instead of 126 ms.Rejected alternatives:
puregobindings to libproc.dylib.footprint_darwin.goalready makes the rawSYS_PROC_INFOcall, and a raw call keeps release builds free of cgo.lsofentirely. I kept it as the fallback for one release, since the layout has only been checked on macOS 27 arm64 at runtime.Risk and rollout
This can affect when Driga kills a process.
lsoftext to kernel records, and a test checks that both agree on real sockets.kern.proc.allsnapshot.lsof.Contract changes
None.
ExternalPeersandResolvedPeerskeep their signatures and semantics. One new log event,socket_layout_fallback.Verified
Run on macOS 27.0.1, arm64.
go test ./...andgo test -race ./internal/platform/: all pass exceptTestPermissionHookBuiltBinary(TempDir cleanup), which also fails onmain.GOOS=linuxandGOOS=windowsbuilds and vet;just _fmt-check tidy-check vet generate-check docs-check.libprocmatching reallsoffor this process.driga orphans kill --yesrefused and named the broker itself, whichlsofcouldn't see. With the client gone, it killed the tree. No fallback was logged./code-review high: all 10 findings fixed in the second commit.