Skip to content

Support deterministic DNS overrides #128

Description

@ZilvinasKucinskas

Priority: P2

Describe the issue

wreq-ruby can bind outgoing sockets to a local address, but it cannot direct a hostname to a specific destination address while preserving that hostname in the URL, Host header, TLS SNI, and certificate verification.

DNS overrides are useful for testing a virtual host locally, selecting a known CDN edge, working around a resolver outage, and reproducing origin-specific behavior. Replacing the URL host with an IP address is not equivalent because it changes HTTP and TLS identity.

The underlying Rust client already supports one or more override addresses for a domain, so the first Ruby API can remain declarative and avoid Ruby callbacks from native resolver threads.

Proposed Ruby API

Use an explicit client-level map:

client = Wreq::Client.new(
  dns_overrides: {
    "example.com" => ["203.0.113.10", "2001:db8::10"]
  }
)

client.get("https://example.com/path")
  • Keys are hostname Strings, without a scheme, path, userinfo, or port.
  • Values are one IP literal or an ordered Array of IP literals. Accepting IPAddr objects in addition to Strings is a useful Ruby convenience.
  • Preserve address order and try later addresses according to the client's documented connection/fallback behavior.
  • The URL hostname continues to drive Host, SNI, and certificate hostname verification.
  • Validate the complete map during client construction; malformed hostnames or addresses raise before network I/O.
  • Keep the initial API client-scoped and immutable. A request-level override would also need to prevent reuse of a connection created under a different mapping.

Proxy semantics

An override cannot control target resolution when an HTTP proxy or remote-DNS SOCKS proxy resolves the destination. That limitation must be explicit:

  • document which proxy schemes resolve locally and can honor the override;
  • state that the mapping is not consulted when the proxy owns target resolution; and
  • never apply target-host overrides to resolution of the proxy server itself.

Client construction does not need to reject proxies and overrides merely because both are configured: one reusable client may make direct and proxied requests. The API must simply avoid claiming that a target override was applied on a path where resolution occurred remotely.

Arbitrary Ruby resolver callbacks are out of scope for the first version. They require a separate design for the GVL, Fiber scheduling, cancellation, caching, and callback exceptions.

Acceptance criteria

  • A hostname can be connected to through one IPv4 or IPv6 override while HTTP/TLS identity remains the hostname.
  • Multiple addresses have deterministic fallback behavior.
  • Invalid keys and values raise during client construction.
  • Connection pooling never reuses a socket established under a different DNS mapping.
  • HTTPS tests prove that SNI and hostname verification are not changed to the IP literal.
  • Proxy combinations follow documented local-versus-remote resolution rules and never misapply a target override to the proxy host.
  • No Ruby callback is invoked from a native resolver thread.

Ecosystem precedent

Relevant source

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions