fix(go-core): local_address binding, Do() field drops, TLS13AutoRetry JA3 corruption (replaces #40)#65
Merged
Danny-Dasilva merged 6 commits intomainfrom May 7, 2026
Merged
Conversation
… a specific local IP - Add default_local_address to config mapping and validation - Add local_address field to Request schema with serialization - Thread localAddress through Go client pooling, key generation, and dialer creation - Apply local IP binding to both direct connections and proxy connections - Validate local_address is a valid IP address before use
…sync infinite loop on cancel/EOF
1. Do() silently dropped ServerName, TLS13AutoRetry, and DisableGrease when building the Browser struct. These fields were added to processRequest() in upstream commits 36f5171 (Draft Release #400, adds ServerName+TLS13AutoRetry) and a483b68 (Release 2.0.4 #390, adds DisableGrease) but were never propagated to Do().
2. dispatchSSEAsync used bare `break` inside `for { select { ... } }`, which only breaks out of the select, not the outer loop — causing an infinite spin on context cancellation, EOF, or read errors. Introduced in upstream 5990fef (Major Release 2.0.0 #387). Fixed by adding a `sseLoop:` label and using `break sseLoop`.
…grade convertJA3ForTLS13 was hard-coding tokens[3] = "29-23", silently dropping P-384 (24), P-521 (25), FFDHE2048 (256), FFDHE3072 (257) and any other groups from the JA3 string before the first handshake attempt. This made TLS13AutoRetry (enabled by default) corrupt the fingerprint being applied. Fix: filter the groups field using isTLS13CompatibleCurve() instead of replacing it, preserving all RFC 8446 §4.2.7 compatible NamedGroups in their original order. Also extend isTLS13CompatibleCurve() to include FFDHE groups (256-260) which are valid TLS 1.3 NamedGroups per RFC 7919 and are advertised by Firefox/Safari in the supported_groups extension.
…ation and live tests
The PR #40 / #65 localAddress implementation silently dropped the local-IP bind on three proxy/transport paths. This change closes those gaps so the LocalAddr the user provided actually controls the kernel-level bind on the client->proxy socket on every supported scheme. Gaps addressed (identified in the code-trace and network-research reports at .claude/cache/agents/{code-trace,network-research}-localaddress-proxy): 1. SOCKS proxies (connect.go) The localAddress -> baseDialer block ran AFTER the scheme switch's socks5/socks5h/socks4 early returns, so the SOCKS dialers never saw LocalAddr. baseDialer is now built before the switch and: - socks5/socks5h: passed as the forward Dialer to proxy.SOCKS5, so the TCP-to-proxy connect honors LocalAddr. - socks4: when localAddress is set, h12.io/socks (which always uses net.DialTimeout internally with no LocalAddr hook) is bypassed via a small in-package socks4ContextDialer that performs the CONNECT handshake over a baseDialer.DialContext'd conn. With localAddress unset, the existing h12.io/socks path is preserved verbatim. 2. HTTPS proxies (connect.go) The default TLS-to-proxy step used tls.Dial(network, addr, &tlsConf), which bypasses any custom net.Dialer and silently dropped LocalAddr. Replaced with c.tlsDialer.DialContext (LocalAddr-aware) followed by tls.Client + HandshakeContext so the bind takes effect on the client->HTTPS-proxy TCP socket. 3. HTTP/3 / QUIC (http3.go) net.ListenPacket("udp", "") ignored localAddress entirely. Threaded LocalAddress through Browser -> roundTripper -> http3Dial; when set, the UDP socket is bound to <ip>:0. Empty localAddress preserves the prior kernel-chooses behavior. Also expands the cycleTLSRequestOptions.LocalAddress field comment to document that, when a Proxy is in the path, the bind applies to the client->proxy hop only — the proxy host always sees this IP, but the destination server never does. This is routing/interface control, not an anonymizer against the proxy itself. Verified: go build ./..., go vet ./... (only pre-existing index.go cancel warnings remain, line numbers shifted by docstring), go test ./... -short all pass on Go 1.26.0.
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.
Summary
Replacement for #40 (Stefan Meinecke). Same three Go-core fixes, rebased on current main with conflicts resolved against #51 (IdleConnTimeout 60s) and #52 (DisableKeepAlives propagation) which both landed on the transport layer after #40 was opened.
Fixes (preserved from #40)
local_addressbinding — outbound IP selection now actually binds.Do()field drops — request fields no longer dropped on Do (ServerName/TLS13AutoRetry/DisableGrease) + dispatchSSEAsync infinite-loop on cancel/EOF.TLS13AutoRetryJA3 supported_groups corruption — preserves the original ClientHello fingerprint on TLS 1.3 retry. This is the most critical fix: previously every Firefox/Safari user (who advertise FFDHE/P-384/P-521) was getting silently corrupted JA3 fingerprints whenever TLS13AutoRetry kicked in.Conflict resolution
The only conflict was in
golang/client.gogetOrCreateClient()— combined #40'slocalAddressparameter with #52'sbrowser.DisableKeepAlives = truepropagation whenenableConnectionReuse=false. Both behaviors retained; neither dropped.Co-authored-by: Stefan Meinecke smeinecke@users.noreply.github.com
Test plan
go vet ./...— no new warnings vs main (one pre-existingindex.gocancel-leak warning shifted by one line; PR actually removed an unreachable-code warning)go test ./... -shortcleanuv run pytest -m "not live"clean post-rebaseCloses #40 once merged.