feat(pcap): add --timeout and clean Ctrl-C shutdown to ios pcap - #800
Open
danielpaulus wants to merge 2 commits into
Open
feat(pcap): add --timeout and clean Ctrl-C shutdown to ios pcap#800danielpaulus wants to merge 2 commits into
danielpaulus wants to merge 2 commits into
Conversation
The pcap capture loop ran forever with no way to stop it other than killing the process, which was the only way to end a capture and risked truncated output. Make the loop context-aware: 'ios pcap' now accepts --timeout=<duration> (e.g. 30s, 2m) and also stops cleanly on Ctrl-C/SIGTERM. On cancellation the blocked read is unblocked by closing the service connection and the pcap file is flushed and closed, leaving a valid capture file. Packets are still streamed to the file as they arrive. Adds device-free unit tests driving the capture loop with an in-memory fake pcapd connection (cancellation, deadline, and error paths; output validated with pcapgo) plus fixture tests for the pcapd frame parsing (fromBytes/getPacket). Fixes #487 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Owner
Author
|
/test-devices |
Contributor
Contributor
|
❌ Real-device tests failed — see run. |
…le-close The context watchdog in capture() closed the connection on cancellation while Start() also deferred its own close of the same connection. Because capture() returned as soon as the read unblocked, the watchdog's Close() could still be running when Start()'s deferred Close() fired, closing the same connection from two goroutines. It also classified any read error as a clean stop whenever ctx was done, so a genuine error coinciding with cancellation was nondeterministically suppressed. capture() now waits for the watchdog goroutine to finish before returning, so the watchdog's Close() completes before the caller's deferred close runs, and only reports a clean stop when the watchdog (not an unrelated failure) closed the connection. Adds a test proving capture waits for the watchdog's Close and closes the connection exactly once. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Owner
Author
|
Adversarial review (+ Codex cross-check) done. Verdict: solid PR — one design defect fixed, rest confirmed sound. Fixed (pushed to this branch):
Reviewed and dismissed:
Test status: |
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.
Problem
ios pcapcaptured forever: the read loop inios/pcap/pcap.gohad no cancellation mechanism, so the only way to end a capture was to kill the process. Issue #487 asks for collecting a pcap for a limited time.Design
pcap.Startnow takes acontext.Context. The capture loop is extracted intocapture(ctx, conn, w), which reads pcapd frames and streams pcap records to the writer until the context is done or the connection fails.nilreturn (ctx.Err() != nil), so the file is finalized (synced + closed) and remains a valid pcap.capturedepends only on a tinycaptureConninterface (Reader()/Close()) satisfied byios.DeviceConnectionInterface, and writes to anio.Writer— this is what makes the loop unit-testable without a device.ios pcap [--timeout=<duration>](Go duration syntax:30s,2m,1h). The command always installs asignal.NotifyContextfor SIGINT/SIGTERM, so Ctrl-C now also ends the capture cleanly instead of killing the process mid-write;--timeoutlayers acontext.WithTimeouton top.Packets are still written to the file incrementally as they arrive (streaming behavior unchanged), and a real connection error (context still active) is still surfaced as an error.
Implementation
ios/pcap/pcap.go: context-awareStart, newcaptureloop with a watchdog goroutine that closes the connection onctx.Done();writePacketgeneralized toio.Writerwith write errors checked; pcap global header factored intowritePcapHeader(reused by tests);f.Sync()on clean stop.cmd_device_debug.go:runPCAPCommandparses--timeout, builds the signal + timeout context, callspcap.Start(ctx, device).main.go: docopt usage and help text for--timeout=<duration>.ios/pcap/pcap_test.go: new device-free tests (see test plan).Options considered
SetReadDeadlinepolling: set short deadlines on the underlyingnet.Connand check the context between reads. Rejected:capturewould be tied to a concretenet.Conn(worse testability), and it burns wakeups while idle.time.AfterFunc(os.Exit): trivial, but exits mid-write and can truncate the pcap record — exactly what this PR is meant to avoid, and it leaves the library API uncancelable for embedders.Test plan
go build ./...and fullgo test ./...pass;go test -race ./ios/pcap/passes;gofmt -l/go vetclean on changed files.captureagainst an in-memory fake pcapd connection (io.Pipe) feeding canned wire frames (length-prefixed binary plist wrapping a struc-packedIOSPacketHeader+ payload):nil; output validated as a well-formed pcap withpcapgo(payloads, lengths, timestamps, clean EOF),fromBytes(plist → bytes),getPacket(header fields, fake-ethernet prefix whenFramePreLength == 0, iOS 15 beta4 extended header skip, pid/process-name filters)./test-devices.Fixes #487
🤖 Generated with Claude Code
https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk