From 7b9bf73e46102969d5802300469862296f979ef6 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:11:00 +0200 Subject: Multiple proxy listeners and upstream proxy chaining Closes the last two items from the original "worth considering" list. Multiple listeners: -listen takes a comma-separated address list (-listen "127.0.0.1:8080,127.0.0.1:8081"). Server.Addr became Server.Addrs; ListenAndServe binds every address up front - before any of them start serving - so a bad address fails startup immediately rather than leaving the daemon partially listening, and rolls back already-opened listeners if a later one fails to bind. All addresses share the same handler/history/CA/rules: one logical proxy reachable on more than one address, not several independent proxies in one process. Upstream proxy chaining: -upstream-proxy host:port (optional http:// prefix, stripped for convenience) routes every outbound connection through another HTTP CONNECT proxy instead of dialing origins directly. dialViaProxy does the CONNECT handshake to the upstream and hands back a plain net.Conn as if it were a direct connection; dialUpstreamTLS (CONNECT/HTTPS path) and dialUpstreamPlain (plain-HTTP path) both take an upstreamProxy parameter and route through it when set. The two paths need different handling: CONNECT/HTTPS is transparent below the tunnel (once the CONNECT handshake succeeds, TLS and the request on top of it look identical to a direct connection, so roundTripH2 and the H1 read side need no changes at all), but plain HTTP has to send an absolute-form request line to the upstream proxy instead of origin-form - so roundTripH1 gained a proxyForm parameter, and forward() selects it based on scheme=="http" && UpstreamProxy!="". Chaining into another intercepting/MITM proxy (including another mitmuxd) needs that proxy's own CA trusted too, or TLS verification fails - this is inherent to chaining MITM proxies, not a gap here, and confirmed live below rather than left as a guess. internal/proxy/dialer_test.go: dialViaProxy against a real local CONNECT stub (not a mock) - direct dial, successful tunnel-and-echo through a proxy, and a proxy that refuses the CONNECT with a non-200. All three exercise the actual network code path, not just the string-building around it. Verified live: started a daemon with two -listen addresses, sent requests through both, confirmed a single shared history; killed it mid-flight with SIGTERM and confirmed both listeners closed cleanly; started it with one bad address in the list and confirmed startup failed immediately with the already-bound port released, no lingering process. For chaining: sent plain HTTP and HTTPS through a downstream mitmuxd configured with -upstream-proxy pointing at a genuine passthrough CONNECT stub (tunnels raw bytes, doesn't MITM) and got real content back on both; separately chained through a second mitmuxd instance and got the expected "certificate signed by unknown authority" error, cleanly recorded in history rather than hanging. go build/vet/gofmt/test/mod tidy all clean. --- PLAN.md | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) (limited to 'PLAN.md') diff --git a/PLAN.md b/PLAN.md index 28b80e3..edaf599 100644 --- a/PLAN.md +++ b/PLAN.md @@ -167,8 +167,44 @@ to test against; only the command text itself (sourced from each platform's standard, documented tooling) is confirmed correct by inspection. -Still open from "worth considering": multiple proxy listeners and -upstream proxy chaining. Not started yet. +Shipped since: multiple proxy listeners and upstream proxy chaining - +the last two items from the original "worth considering" list. + +Multiple listeners: `-listen` takes a comma-separated address list +(`-listen "127.0.0.1:8080,127.0.0.1:8081"`); all bound addresses share +the same handler, history store, CA and rules - one logical proxy +reachable on more than one address/port, not several independent +proxies in one process. Every address is bound up front before any of +them start serving, so a bad address fails startup immediately instead +of leaving the daemon partially listening. + +Upstream proxy chaining: `-upstream-proxy host:port` (optional +`http://` prefix, stripped) routes every outbound connection through +another HTTP CONNECT proxy instead of dialing origins directly - +chaining mitmux into Burp, a corporate proxy, or any other +CONNECT-speaking proxy. SOCKS5 upstreams aren't implemented. For the +CONNECT/HTTPS path, chaining is transparent below the tunnel: once the +CONNECT handshake to the upstream proxy succeeds, TLS and the +request/response on top of it are identical to a direct connection, so +no other code needed to change. The plain-HTTP path is different: an +absolute-form request line ("GET http://host/path HTTP/1.1") has to be +sent to the upstream proxy instead of origin-form, so `roundTripH1` +gained a `proxyForm` parameter and `forward()` selects it based on +whether the request is plain HTTP and an upstream proxy is configured. + +Chaining into another intercepting/MITM proxy (including another +mitmuxd instance) will fail TLS verification unless that proxy's own CA +is separately trusted - expected, not a mitmux-specific gap: the +upstream MITM terminates and re-signs the connection with its own CA, +which mitmux's outbound TLS client (verifying against the system root +store) has no reason to trust. Confirmed live: chaining through a +genuine passthrough CONNECT proxy (tunnels raw bytes, no MITM) works +correctly for both plain HTTP and HTTPS; chaining through a second +mitmuxd instance correctly fails with a clear +"certificate signed by unknown authority" error recorded in history, +rather than hanging or crashing. + +This closes every item from the original "worth considering" list. Skipped deliberately (from the research, matches this tool's stated scope): active/passive vulnerability scanning, plugin marketplace, -- cgit v1.2.3