Asset / scripts /dns-resolve.py
Your Name
Add real TCP reachability probe alongside DNS resolution (distinguish DNS-blocked from network-egress-blocked)
e18cd83
Raw
History Blame Contribute Delete
4.84 kB
#!/usr/bin/env python3
"""
DNS-over-HTTPS resolver for HF Spaces.
HF Spaces containers cannot resolve certain domains (e.g. api.telegram.org,
web.whatsapp.com) via the default DNS resolver. This script resolves key
domains using Cloudflare / Google DoH and writes results to a JSON file
plus /etc/hosts so both Python and Node.js processes benefit.
Usage: python3 dns-resolve.py [output-file]
"""
import json
import socket
import ssl
import sys
import urllib.request
DOH_ENDPOINTS = [
"https://1.1.1.1/dns-query",
"https://8.8.8.8/resolve",
"https://dns.google/resolve",
]
DOMAINS = [
# Telegram Bot API
"api.telegram.org",
# Discord gateway / API
"discord.com",
"gateway.discord.gg",
"cdn.discordapp.com",
# Slack
"slack.com",
"api.slack.com",
# WhatsApp / Baileys
"web.whatsapp.com",
"g.whatsapp.net",
"mmg.whatsapp.net",
"pps.whatsapp.net",
"static.whatsapp.net",
"media.fmed1-1.fna.whatsapp.net",
]
def resolve_via_doh(domain: str, endpoint: str, timeout: int = 10) -> list[str]:
url = f"{endpoint}?name={domain}&type=A"
req = urllib.request.Request(url, headers={"Accept": "application/dns-json"})
ctx = ssl.create_default_context()
resp = urllib.request.urlopen(req, timeout=timeout, context=ctx)
data = json.loads(resp.read().decode())
return [a["data"] for a in data.get("Answer", []) if a.get("type") == 1]
def resolve_domain(domain: str) -> list[str]:
for endpoint in DOH_ENDPOINTS:
try:
ips = resolve_via_doh(domain, endpoint)
if ips:
return ips
except Exception:
continue
return []
def test_tcp_connect(ip: str, port: int = 443, timeout: int = 5) -> tuple[bool, str]:
"""Test actual TCP reachability to a resolved IP, not just DNS resolution.
DNS resolving successfully does NOT mean the IP is reachable β€” HF Spaces
(especially free CPU-basic, on shared cloud infra) can have specific IP
ranges null-routed/blocked at the network layer even when DoH resolution
works fine. This distinguishes "DNS blocked" (fixable by this script)
from "network egress blocked" (not fixable from inside the container β€”
needs an HF support ticket / different hardware tier / a proxy).
"""
try:
with socket.create_connection((ip, port), timeout=timeout):
return True, "connected"
except Exception as e:
return False, f"{type(e).__name__}: {e}"
def main() -> None:
output_file = sys.argv[1] if len(sys.argv) > 1 else "/tmp/dns-resolved.json"
# If system DNS works for the most-commonly-blocked domain, DoH isn't needed.
try:
socket.getaddrinfo("api.telegram.org", 443, socket.AF_INET)
print("[dns] System DNS works for api.telegram.org β€” DoH not needed")
ok, detail = test_tcp_connect("api.telegram.org", 443)
if ok:
print("[dns] TCP reachability OK: api.telegram.org:443")
else:
print(f"[dns] WARNING: DNS resolves but TCP connect FAILED to "
f"api.telegram.org:443 ({detail}) β€” this is a network "
f"egress block, not a DNS problem. No DNS workaround can "
f"fix this; it needs an HF support ticket or a different "
f"Space hardware tier.")
with open(output_file, "w") as f:
json.dump({}, f)
return
except (socket.gaierror, OSError) as e:
print(f"[dns] System DNS failed ({e}) β€” using DoH fallback")
results = {}
for domain in DOMAINS:
ips = resolve_domain(domain)
if ips:
results[domain] = ips[0]
print(f"[dns] {domain} -> {ips[0]}")
ok, detail = test_tcp_connect(ips[0], 443)
if ok:
print(f"[dns] TCP reachability OK: {domain} ({ips[0]}:443)")
else:
print(f"[dns] WARNING: DoH resolved {domain} but TCP connect "
f"FAILED ({detail}) β€” network egress to this IP is "
f"blocked; no DNS fix can resolve this.")
else:
print(f"[dns] WARNING: could not resolve {domain}")
with open(output_file, "w") as f:
json.dump(results, f, indent=2)
if results:
try:
with open("/etc/hosts", "a") as f:
f.write("\n# === HermesFace DoH resolved domains ===\n")
for domain, ip in results.items():
f.write(f"{ip} {domain}\n")
print(f"[dns] Wrote {len(results)} entries to /etc/hosts")
except PermissionError:
print("[dns] WARNING: cannot write /etc/hosts (permission denied)")
print(f"[dns] Resolved {len(results)}/{len(DOMAINS)} domains -> {output_file}")
if __name__ == "__main__":
main()