Parallelize IP detection and tighten OK() semantics
Review follow-ups:
- Run the IPv4 and IPv6 detection in runSNICheck concurrently. With
the new three-endpoint fallback in getIP, sequential detection could
extend proxy startup by up to 30s per family on a slow/blocked
network. Parallel detection bounds the worst case to roughly 30s
total instead of 60s.
- Make sniCheckResult.OK() self-consistent: it now returns false when
the hostname cannot be resolved or no public IP family is known,
so callers cannot mistakenly treat 'cannot check' as 'all clear'.
The runtime warning (warnSNIMismatch) and the diagnostic command
(doctor checkSecretHost) previously implemented the same SNI-DNS
check with different logic: the runtime path was tightened in #461
to require every detected IP family to match, but the doctor still
accepted any single match. The two now agree.
Changes:
- Extract the shared check into internal/cli/sni_check.go, returning
the resolved addresses and a per-family match status.
- Rewrite warnSNIMismatch and checkSecretHost on top of the helper.
- Doctor output now reports the mismatched IP family (IPv4, IPv6, or
both) and lists the server's public IP alongside the DNS result.
- getIP falls back through a short list of public-IP endpoints
(ifconfig.co, icanhazip.com, ifconfig.me) instead of relying on
a single third-party service.