Download scripts/cloudflared_doh_dns.py from Good12313/c2a: direct link, hf CLI and curl.
- Browser
- Download file 3.79 kB
-
https://huggingface.co/spaces/Good12313/c2a/resolve/main/scripts/cloudflared_doh_dns.py
- Command line
-
hf download hf://spaces/Good12313/c2a/scripts/cloudflared_doh_dns.py
-
curl -L -o cloudflared_doh_dns.py https://huggingface.co/spaces/Good12313/c2a/resolve/main/scripts/cloudflared_doh_dns.py
3.79 kB
| """Tiny local DNS forwarder for cloudflared when FlClash Fake-IP DNS is enabled. | |
| It resolves A/AAAA records through Cloudflare DoH over the configured HTTP proxy, | |
| then returns a normal DNS response on loopback. It deliberately listens only on | |
| 127.0.0.1 and does not persist credentials or query data. | |
| """ | |
| from __future__ import annotations | |
| import ipaddress | |
| import json | |
| import os | |
| import socket | |
| import struct | |
| import sys | |
| from urllib.parse import urlencode | |
| from urllib.request import ProxyHandler, Request, build_opener | |
| LISTEN_HOST = "127.0.0.1" | |
| LISTEN_PORT = int(os.environ.get("DOH_DNS_PORT", "53535")) | |
| DOH_URL = "https://cloudflare-dns.com/dns-query" | |
| LOG_PATH = os.environ.get("DOH_DNS_LOG", "D:/work/code/ai-ecology/c2a/data/cloudflared-doh-dns.log") | |
| def log(message: str) -> None: | |
| try: | |
| with open(LOG_PATH, "a", encoding="utf-8") as stream: | |
| stream.write(message + "\n") | |
| except OSError: | |
| pass | |
| def read_question(packet: bytes) -> tuple[str, int, int, int]: | |
| offset = 12 | |
| labels: list[str] = [] | |
| while offset < len(packet): | |
| length = packet[offset] | |
| offset += 1 | |
| if length == 0: | |
| break | |
| if length & 0xC0: | |
| raise ValueError("compressed question name is unsupported") | |
| labels.append(packet[offset : offset + length].decode("ascii")) | |
| offset += length | |
| qname = ".".join(labels) + "." | |
| qtype, qclass = struct.unpack("!HH", packet[offset : offset + 4]) | |
| return qname, qtype, qclass, offset + 4 | |
| def doh_lookup(name: str, qtype: int) -> list[tuple[int, str, int]]: | |
| query = urlencode({"name": name, "type": "A" if qtype == 1 else "AAAA"}) | |
| request = Request( | |
| f"{DOH_URL}?{query}", | |
| headers={"Accept": "application/dns-json", "User-Agent": "c2a-cloudflared-dns/1"}, | |
| ) | |
| proxy = os.environ.get("HTTPS_PROXY") or os.environ.get("https_proxy") or "http://127.0.0.1:7890" | |
| opener = build_opener(ProxyHandler({"https": proxy, "http": proxy}) if proxy else ProxyHandler({})) | |
| with opener.open(request, timeout=15) as response: | |
| payload = json.load(response) | |
| records: list[tuple[int, str, int]] = [] | |
| for answer in payload.get("Answer") or []: | |
| if int(answer.get("type") or 0) == qtype: | |
| records.append((qtype, str(answer.get("data") or ""), int(answer.get("TTL") or 60))) | |
| return records | |
| def build_response(query: bytes) -> bytes: | |
| txid = query[:2] | |
| qname, qtype, qclass, question_end = read_question(query) | |
| question = query[12:question_end] | |
| records: list[tuple[int, str, int]] = [] | |
| if qclass == 1 and qtype in (1, 28): | |
| records = doh_lookup(qname, qtype) | |
| log(f"query name={qname} type={qtype} records={[item[1] for item in records]}") | |
| answers = bytearray() | |
| for record_type, value, ttl in records: | |
| try: | |
| packed = ipaddress.ip_address(value).packed | |
| except ValueError: | |
| continue | |
| answers.extend(struct.pack("!HHHIH", 0xC00C, record_type, 1, max(1, ttl), len(packed))) | |
| answers.extend(packed) | |
| flags = 0x8180 if records else 0x8183 | |
| header = txid + struct.pack("!HHHHH", flags, 1, len(records), 0, 0) | |
| return header + question + bytes(answers) | |
| def main() -> int: | |
| sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
| sock.bind((LISTEN_HOST, LISTEN_PORT)) | |
| print(f"DNS forwarder listening on {LISTEN_HOST}:{LISTEN_PORT}", flush=True) | |
| while True: | |
| packet, address = sock.recvfrom(4096) | |
| try: | |
| response = build_response(packet) | |
| except Exception: | |
| log("query failed") | |
| response = packet[:2] + struct.pack("!HHHHH", 0x8182, 1, 0, 0, 0) + packet[12:] | |
| sock.sendto(response, address) | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |