Skip to content

SOTA polish: connection-layer Happy Eyeballs + DNSSEC + IDN (post-#212) #213

Open

Description

@bluet

Goal

Push proxybroker's IPv6 + DNS handling from "standards-compliant" (#212 baseline) to bleeding-edge SOTA by addressing the architectural gaps that were intentionally split out of #212 to keep its scope tight.

Context

PR #212 shipped full IPv6 support across detection, validation, anonymity, SOCKS5 (ATYP=0x04), HTTP CONNECT bracketing, provider parsing, Proxy host formatting, and added Happy Eyeballs DNS (parallel A+AAAA query). What's left for true SOTA:

Scope

1. Connection-layer Happy Eyeballs (RFC 8305 § 5)

Current state: Resolver.resolve() returns a single canonical IP string; Proxy.connect() calls asyncio.open_connection(host, port) against that single IP.

SOTA: Resolve to BOTH v4 and v6 (when both available); race connections in parallel with the v6-first staggered start (RFC 8305 says ~250 ms gap, then v4); return the first ACK; cancel the loser.

Why: Broken-but-routed IPv6 networks (common in shared corp Wi-Fi with stale RA, mobile carriers with v6 backhaul issues) make every v6 connect time out before falling back. Connection Happy Eyeballs eliminates this latency entirely — the v4 connection succeeds while v6 is still waiting.

Surface area: Resolver.resolve() returns a list of (family, ip); Proxy.connect() learns to race them; Resolver._cached_hosts becomes per-family. ~150 lines + tests + careful asyncio task management.

2. DNSSEC opt-in via aiodns

Current state: aiodns.DNSResolver() constructed without DNSSEC validation flag. Spoofed DNS could redirect proxy traffic.

SOTA: Add a Resolver(dnssec=True) parameter; pass through to aiodns; document the trade-off (slightly slower resolution, requires DNSSEC-signed authoritative servers, partial protection only). Default off (current behavior preserved).

Why: Defense in depth for adversarial network paths.

Surface area: Resolver.__init__ + _resolve ~10 lines + 2-3 tests.

3. IDN / Punycode handling for hostnames

Current state: Hostnames pass through verbatim. International domain names (例え.テスト) fail at the DNS query level.

SOTA: Encode via idna.encode() (PEP 3490 / RFC 5891) before resolution; decode for display.

Why: International proxy lists / endpoints are a real use case; current code returns cryptic resolver errors.

Surface area: Resolver.resolve() + Proxy.create() + new idna dep + tests. ~30 lines.

4. Per-family resolution caching

Current state: _cached_hosts keyed by hostname, value is a single IP string. Loses the v4-vs-v6 distinction.

SOTA: Key by (hostname, family); cache both v4 and v6 results separately so a subsequent Proxy.create() doesn't re-query.

Surface area: Coupled with item 1 above. Probably implement together.

5. Out-of-scope here (separate concerns)

Acceptance criteria

  1. Proxy.connect() to a dual-stack judge with broken v6 path completes in <1s (currently 5+s timeout).
  2. Resolver(dnssec=True) validates signed responses; dnssec=False (default) preserves current speed.
  3. Proxy.create("xn--example-9rh.com", 80) and Proxy.create("例え.テスト", 80) both work.
  4. Cache hit rate measurable; _cached_hosts.get((host, AF_INET)) and (host, AF_INET6) return separately.
  5. Existing IPv4 fast-path performance unchanged (no regression in resolution latency for v4-only hosts).

References

Estimated effort

  • Item 1: 8-12 hours (architectural, test-heavy)
  • Item 2: 1-2 hours
  • Item 3: 2-3 hours
  • Item 4: bundled with item 1

Total: ~12-18 hours of careful work. Suggest splitting into separate PRs so each lands cleanly: (a) Connection HE + per-family cache, (b) DNSSEC, (c) IDN.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions