Skip to content

feat(proxy): add Proxy::resolve_local to tunnel CONNECT to a locally-resolved IP#1185

Open
eav93 wants to merge 1 commit into
0x676e67:mainfrom
eav93:resolve-local-main
Open

feat(proxy): add Proxy::resolve_local to tunnel CONNECT to a locally-resolved IP#1185
eav93 wants to merge 1 commit into
0x676e67:mainfrom
eav93:resolve-local-main

Conversation

@eav93

@eav93 eav93 commented May 28, 2026

Copy link
Copy Markdown

Summary

Adds an opt-in Proxy::resolve_local(bool) for HTTP(S) proxies.

By default, an HTTPS request through an HTTP proxy opens the tunnel with the
destination hostname (CONNECT example.com:443), leaving DNS resolution to
the proxy. When resolve_local(true) is set, the destination host is resolved
by the client's own resolver (honoring ClientBuilder::resolve /
resolve_to_addrs overrides) and the CONNECT request is sent to the resolved
IP instead (CONNECT 93.184.216.34:443). The IP is used for both the
CONNECT request-target and that request's Host header; the original port is
preserved.

The TLS SNI and the tunneled HTTP request keep using the original hostname, so
certificate validation and virtual hosting are unaffected.

Motivation

This mirrors the existing socks5:// (local DNS) vs socks5h:// (remote DNS)
distinction, but for HTTP(S) proxies, which currently have no equivalent. It is
useful when the proxy's own DNS is unreliable or when the hostname in the
CONNECT line is filtered upstream — resolving locally and tunneling to the IP
sidesteps both, while the connection pool still keys on the original hostname.

Behavior / scope

  • Opt-in; default behavior is unchanged (CONNECT still uses the hostname).
  • Applies only to HTTPS tunneled through an HTTP(S) proxy. No effect on SOCKS or
    Unix-socket proxies, or on plain HTTP requests.
  • IP-literal destinations (incl. IPv6, e.g. [::1]) are passed through unchanged.
  • The destination port always comes from the URI, never from the resolved
    SocketAddr (a custom resolver may return a synthetic/zero port).

Tests

tests/proxy.rs covers: hostname → resolved IP in CONNECT; opt-out leaves the
hostname even with a resolve override present; explicit non-default port is
preserved; IPv6 literal passthrough. cargo fmt, cargo check (with and
without socks), clippy and the proxy suite all pass.

Implementation note

The resolver field on the connector was previously gated behind the socks
feature; it is now always present, since the HTTP tunnel path also needs it.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a resolve_local option for proxies, allowing the client to resolve the destination host locally and send the HTTP CONNECT request to the resolved IP address instead of the hostname. This is implemented by making the DNS resolver always available in ConnectorBuilder and ConnectorService, and adding a resolve_connect_dst helper. Feedback on the changes suggests a more idiomatic way to format the resolved IP and port using SocketAddr's built-in formatting rather than manually matching on the IP address type.

Comment thread src/conn/connector.rs Outdated
Comment on lines +388 to +391
let authority = match addr.ip() {
IpAddr::V4(ip) => format!("{ip}:{port}"),
IpAddr::V6(ip) => format!("[{ip}]:{port}"),
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of manually matching on the IP address type and formatting it, you can leverage the standard library's SocketAddr formatting by updating the port on the resolved address directly. This is more concise and idiomatic.

        let mut addr = addr;
        addr.set_port(port);
        let authority = addr.to_string();

…resolved IP

By default an HTTP CONNECT tunnel is opened to the destination hostname,
leaving DNS resolution to the proxy. With Proxy::resolve_local(true) the
destination is resolved by the client's own resolver (honoring resolve
overrides) and CONNECT is sent to the resolved IP instead, while TLS SNI
and the tunneled request keep using the original hostname.

This mirrors the socks5:// (local DNS) vs socks5h:// (remote DNS)
distinction for HTTP(S) proxies, and helps when the proxy's own DNS is
unreliable or the hostname in the CONNECT line is filtered upstream.
@eav93 eav93 force-pushed the resolve-local-main branch from b8f6e81 to ef3d136 Compare May 28, 2026 20:55
eav93 added a commit to eav93/wreq-php that referenced this pull request May 28, 2026
…d IP

New client option `proxy_resolve_local` (bool). With an HTTP(S) `proxy`,
the destination host is resolved by the client and CONNECT is sent to the
resolved IP instead of the hostname; TLS SNI and Host keep the original
hostname. Helps when the proxy's own DNS is unreliable or the hostname in
the CONNECT line is filtered upstream.

Backed by a patched wreq (Proxy::resolve_local) via [patch.crates-io],
pending upstream PR 0x676e67/wreq#1185.
@0x676e67

Copy link
Copy Markdown
Owner

I would like to know if this functionality has been implemented in other HTTP client libraries.

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.

2 participants