Files
proxy-god/proxy_chain_manager/dns_leak.py
Indiana Holmes d4296763ee Build comprehensive privacy suite and hardened browser controls.
Adds VPN-aware leak handling, chain testing UX improvements, hardened Firefox launch/profile management, privacy/device hardening modules, and tray/status upgrades so the app is production-ready as the new baseline.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 13:50:05 -07:00

113 lines
3.4 KiB
Python

"""DNS leak checks and cache flush."""
from __future__ import annotations
import logging
import subprocess
from dataclasses import dataclass
import httpx
log = logging.getLogger(__name__)
_DOH_GOOGLE = "https://dns.google/resolve?name=whoami.dnsleaktest.com&type=A"
@dataclass
class DnsLeakResult:
ok: bool
system_resolvers: list[str]
message: str
doh_ip: str | None = None
def get_system_dns_servers() -> list[str]:
try:
r = subprocess.run(
["powershell", "-NoProfile", "-Command",
"(Get-DnsClientServerAddress -AddressFamily IPv4 | "
"Where-Object { $_.ServerAddresses } | "
"Select-Object -ExpandProperty ServerAddresses) -join ','"],
capture_output=True,
text=True,
timeout=12,
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
)
raw = (r.stdout or "").strip()
if not raw:
return []
return [x.strip() for x in raw.replace(";", ",").split(",") if x.strip()]
except Exception:
return []
def flush_dns_cache() -> tuple[bool, str]:
try:
r = subprocess.run(
["ipconfig", "/flushdns"],
capture_output=True,
text=True,
timeout=15,
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
)
msg = (r.stdout or r.stderr or "").strip().splitlines()[-1] if r.returncode == 0 else "flush failed"
return r.returncode == 0, msg
except Exception as e:
return False, str(e)
def check_dns_leak_hint(local_proxy: str | None = None) -> DnsLeakResult:
"""Heuristic: list configured DNS servers; note if any are public ISP resolvers.
Full DNS leak testing needs OS-level routing; this flags obvious misconfig.
"""
resolvers = get_system_dns_servers()
private_prefixes = ("127.", "10.", "192.168.", "172.16.", "172.17.", "172.18.",
"172.19.", "172.2", "172.30.", "172.31.", "0.0.0.0")
public = [r for r in resolvers if not any(r.startswith(p) for p in private_prefixes)]
doh_ip: str | None = None
try:
with httpx.Client(timeout=8.0, verify=True) as c:
r = c.get(_DOH_GOOGLE)
if r.status_code == 200:
data = r.json()
answers = data.get("Answer") or []
if answers:
doh_ip = str(answers[0].get("data", ""))
except Exception:
pass
if not resolvers:
return DnsLeakResult(
ok=True,
system_resolvers=[],
message="No IPv4 DNS servers reported (DHCP may assign later).",
doh_ip=doh_ip,
)
if public and local_proxy:
return DnsLeakResult(
ok=False,
system_resolvers=resolvers,
message=(
f"DNS may bypass proxy chain: public resolvers {', '.join(public)}. "
"Use kill-switch + VPN, or set DNS to localhost when hardened."
),
doh_ip=doh_ip,
)
if public and not local_proxy:
return DnsLeakResult(
ok=False,
system_resolvers=resolvers,
message=f"Public DNS resolvers active: {', '.join(public)}",
doh_ip=doh_ip,
)
return DnsLeakResult(
ok=True,
system_resolvers=resolvers,
message=f"DNS servers: {', '.join(resolvers)}",
doh_ip=doh_ip,
)