Bind outbound connections to a local address/interface via local_addr (#834)#1307
Merged
Conversation
…#834) Adds a `local_addr` keyword to `Transport` and `Client` that binds outbound connections to a specific source IP — and therefore the outgoing interface — mirroring Go's `net.Dialer.LocalAddr` (and `curl --interface`). Useful on multi-homed hosts and for separating traffic by interface. Reseau's HostResolver/TCP.connect already accept a local bind endpoint; this threads it through HTTP's transport. `local_addr` is a connection-pool property (each bound Client keeps its own pool), so it lives on the Transport's resolver — no pool-key changes and no cross-binding reuse, which is also why it is set per-Transport/Client rather than per-request (matching Go, where LocalAddr is a Dialer property, not a per-request option). Accepts an IP-literal String (kernel-chosen ephemeral source port) or a Reseau TCP.SocketAddrV4/SocketAddrV6 for a fixed source port; interface names are not accepted (resolve to an IP first, as Go does). Binding to an address not assigned locally fails fast at bind(). Passing local_addr alongside an explicit transport is rejected as ambiguous. Tests cover the normalizer, client- and transport-level binding, the ambiguity guard, and that an unassigned source address fails (proving the bind is actually applied). Documents the knob in the client guide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1307 +/- ##
==========================================
- Coverage 87.16% 87.16% -0.01%
==========================================
Files 28 29 +1
Lines 11042 11289 +247
==========================================
+ Hits 9625 9840 +215
- Misses 1417 1449 +32 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
quinnj
commented
Jun 15, 2026
Keep the local_addr normalization in one method with early-return isa checks instead of four dispatched methods, so inference does not have to pick between them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Closes #834.
Adds a
local_addrkeyword toTransportandClientthat binds outbound connections to a specific source IP — and therefore the outgoing interface — modeled on Go'snet.Dialer.LocalAddr(andcurl --interface). Useful on multi-homed hosts and for separating traffic by interface.Design
Reseau's
HostResolver/TCP.connectalready accept a local bind endpoint (Reseau = "1.3"has it); this just threads it through HTTP's transport. Two deliberate choices, both following Go:local_addris a connection-identity property: a connection bound to source A cannot serve a request that wants source B. Go puts it on theDialer, not the request, precisely so it folds into the pool. Each boundClientkeeps its own pool, so binding needs no pool-key changes and can't cause cross-binding connection reuse.String(kernel-chosen ephemeral source port) or aReseau.TCP.SocketAddrV4/SocketAddrV6for a fixed source port. Interface names (curl --interface eth0) are intentionally not accepted — like Go, the address is an IP, keeping name resolution out of the transport.Binding to an address not assigned to a local interface fails fast at
bind(). Passinglocal_addralongside an explicittransportis rejected as ambiguous.Tests
http_client_transport_tests.jl: the normalizer (string→endpoint, IPv6, passthrough, invalid→ArgumentError), client- and transport-level binding round-trips, the ambiguity guard, and that an unassigned source address fails — the deterministic proof the bind is actually applied rather than ignored. Documents the knob in the client guide.🤖 Generated with Claude Code