diff options
| author | srdusr <[email protected]> | 2024-09-19 23:57:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-09-19 23:57:00 +0200 |
| commit | aae93b4575e10d223c6cdd8722ca0cce2d47397c (patch) | |
| tree | 73b75f132de70c5a6298c0b2c5de1c1e28ea0d34 /internal/proxy/repeat.go | |
| parent | a02d612ba5efb5f1e79a2f9a0778890e8c3e6acf (diff) | |
| download | mitmux-aae93b4575e10d223c6cdd8722ca0cce2d47397c.tar.gz mitmux-aae93b4575e10d223c6cdd8722ca0cce2d47397c.zip | |
Fix hang and data-race bugs found while re-verifying steps 1-5
Audited every file in the proxy/store/ipc/TUI stack before starting
step 6, per request. Found and fixed three real bugs in already-shipped
code, all confirmed with live tests (including a race-detector build)
rather than just read:
1. No timeout covered the write-request/read-response phase of an
upstream exchange, in either the main proxy path (roundTripH1/
roundTripH2) or Repeater - only the dial itself was bounded. A
server that accepted the connection and then never finished
responding hung the request forever. Fixed with conn.SetDeadline
after a successful dial in both forward() and Repeat() (new
upstreamTimeout constant, 60s). Verified against a real hung TCP
listener: the daemon returned a clean "i/o timeout" error at exactly
60s instead of hanging.
2. ipc.Client shared one connection/encoder/decoder with no locking.
Bubble Tea dispatches each request as its own goroutine, and
viewList's 'r' key doesn't change mode while its loadDetail call is
in flight - pressing it again (or 'enter' on another row) before the
first response arrives calls Get/List/Repeat concurrently on the same
connection, which can interleave JSON on the wire or hand one call
another's response. Fixed with a mutex serializing round trips.
Stress-tested with rapid overlapping key input against a -race build
of both binaries: no warnings, no corruption.
3. The IPC "subscribe" handler only noticed a disconnected client when
the next broadcast's Encode failed - a subscriber that quit while the
daemon was otherwise idle leaked its goroutine and channel
indefinitely. Fixed by reading the connection in the background too,
so disconnection is detected immediately regardless of traffic.
Also removed a dead, misleading parameter: captureResponse took a *teeConn
it was never actually called with (the exact-capture path is handled
directly in forward()), so the branch using it was unreachable.
Re-verified all five prior steps end-to-end against a fresh build:
plain HTTP, HTTPS H1.1/H2/untrusted-CA-rejection, exact vs reconstructed
capture flags cross-checked directly in SQLite, Repeater over both HTTP
and HTTPS, and search (plain text, dotted domains, hyphenated terms,
column filters) - all correct.
Diffstat (limited to 'internal/proxy/repeat.go')
| -rw-r--r-- | internal/proxy/repeat.go | 5 |
1 files changed, 5 insertions, 0 deletions
diff --git a/internal/proxy/repeat.go b/internal/proxy/repeat.go index 6373ee1..cecf481 100644 --- a/internal/proxy/repeat.go +++ b/internal/proxy/repeat.go @@ -33,6 +33,11 @@ func (s *Server) Repeat(ctx context.Context, scheme, host string, raw []byte) (* return s.recordRepeat(started, time.Since(started), scheme, host, method, path, raw, nil, 0, err.Error()) } defer conn.Close() + // See the matching comment in forward(): without this, a hung + // server - or a user-edited request malformed enough that nothing + // ever replies - blocks this Repeat call, and the IPC connection + // handling it, forever. + conn.SetDeadline(time.Now().Add(upstreamTimeout)) if _, err := conn.Write(raw); err != nil { return s.recordRepeat(started, time.Since(started), scheme, host, method, path, raw, nil, 0, err.Error()) |