File size: 4,842 Bytes
0790cf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e18cd83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0790cf1
 
 
 
 
 
 
e18cd83
 
 
 
 
 
 
 
 
0790cf1
 
 
 
 
 
 
 
 
 
 
 
e18cd83
 
 
 
 
 
 
0790cf1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/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()