Skip to content

Bind outbound connections to a local address/interface via local_addr (#834)#1307

Merged
quinnj merged 2 commits into
masterfrom
jq-local-addr
Jun 15, 2026
Merged

Bind outbound connections to a local address/interface via local_addr (#834)#1307
quinnj merged 2 commits into
masterfrom
jq-local-addr

Conversation

@quinnj

@quinnj quinnj commented Jun 15, 2026

Copy link
Copy Markdown
Member

Closes #834.

Adds a local_addr keyword to Transport and Client that binds outbound connections to a specific source IP — and therefore the outgoing interface — modeled on Go's net.Dialer.LocalAddr (and curl --interface). Useful on multi-homed hosts and for separating traffic by interface.

# every request from this client leaves via 192.0.2.10
client = HTTP.Client(local_addr = "192.0.2.10")
HTTP.get("http://example.com"; client = client)

# canonical home — local_addr is a connection-pool property:
client = HTTP.Client(transport = HTTP.Transport(local_addr = "192.0.2.10"))

Design

Reseau's HostResolver/TCP.connect already accept a local bind endpoint (Reseau = "1.3" has it); this just threads it through HTTP's transport. Two deliberate choices, both following Go:

  • Per-Transport/Client, not per-request. local_addr is a connection-identity property: a connection bound to source A cannot serve a request that wants source B. Go puts it on the Dialer, not the request, precisely so it folds into the pool. Each bound Client keeps its own pool, so binding needs no pool-key changes and can't cause cross-binding connection reuse.
  • IP, not interface name. Accepts an IP-literal String (kernel-chosen ephemeral source port) or a Reseau.TCP.SocketAddrV4/SocketAddrV6 for 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(). Passing local_addr alongside an explicit transport is 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

…#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

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.16%. Comparing base (427b525) to head (33685f2).
⚠️ Report is 2 commits behind head on master.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread src/http_transport.jl Outdated
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>
@quinnj quinnj merged commit b4b0a5e into master Jun 15, 2026
8 checks passed
@quinnj quinnj deleted the jq-local-addr branch June 15, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Send http request through specific network interface

1 participant