import { useState, useCallback } from 'react' import { motion } from 'framer-motion' import { Shield, ShieldCheck, ShieldAlert, ShieldX, Play, RotateCw, Wifi, Lock, Unlock, Server, Globe, AlertCircle } from 'lucide-react' import clsx from 'clsx' // DNS leak test using multiple public APIs to detect DNS resolver info const DNS_CHECK_APIS = [ { id: 'dnsleaktest', name: 'DNS Leak Test (ipleak.net)', url: 'https://ipleak.net/json/dnsleak/', extract: (data) => { if (Array.isArray(data)) { return data.map(item => ({ ip: item.ip || item.dns_ip || '', country: item.country || '', isp: item.isp || item.asn || '', })) } return [] }, type: 'dns_resolvers', }, { id: 'browserleaks', name: 'BrowserLeaks (DNS)', url: 'https://ipleak.net/json/dnsleak/', extract: (data) => { if (Array.isArray(data)) { return data.map(item => ({ ip: item.ip || item.dns_ip || '', country: item.country || '', isp: item.isp || item.asn || '', })) } return [] }, type: 'dns_resolvers', }, ] // IP check for comparison const IP_CHECK_APIS = [ { id: 'ipinfo', name: 'IPInfo.io', url: 'https://ipinfo.io/json' }, { id: 'ipapi', name: 'IPAPI.co', url: 'https://ipapi.co/json/' }, ] // WebRTC leak detection function detectWebRTCLeak() { return new Promise((resolve) => { const ips = new Set() const timeout = setTimeout(() => { try { window.RTCPeerConnection = null } catch (e) { /* ignore */ } resolve({ ips: [...ips], leaked: ips.size > 0 }) }, 5000) try { const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }], }) pc.createDataChannel('') pc.createOffer().then(offer => pc.setLocalDescription(offer)).catch(() => {}) pc.onicecandidate = (e) => { if (!e.candidate) { clearTimeout(timeout) pc.close() resolve({ ips: [...ips], leaked: ips.size > 0 }) return } const parts = e.candidate.candidate.split(' ') const ip = parts[4] if (ip && !ips.has(ip)) { // Filter out mDNS addresses if (!ip.endsWith('.local') && ip !== '0.0.0.0') { ips.add(ip) } } } } catch (e) { clearTimeout(timeout) resolve({ ips: [], leaked: false, error: 'WebRTC 不可用' }) } }) } // Step component function CheckStep({ step, isActive, isDone, status }) { const statusIcon = { pending:
, active:
, success:
, warning:
, error:
, } return (
{statusIcon[status] || statusIcon.pending}
{step}
{isActive && (
)}
) } // DNS resolver card function ResolverCard({ resolver, index }) { return (
{resolver.ip}
{resolver.isp &&
{resolver.isp}
} {resolver.country &&
{resolver.country}
}
) } // Result summary card function ResultSummary({ type, title, status, description, icon: Icon }) { const config = { safe: { bg: 'bg-secondary-50', border: 'border-secondary-200', iconColor: 'text-secondary-500', badge: 'bg-secondary-100 text-secondary-700' }, warning: { bg: 'bg-accent-50', border: 'border-accent-200', iconColor: 'text-accent-500', badge: 'bg-accent-100 text-accent-700' }, danger: { bg: 'bg-red-50', border: 'border-red-200', iconColor: 'text-red-500', badge: 'bg-red-100 text-red-700' }, pending: { bg: 'bg-slate-50', border: 'border-slate-200', iconColor: 'text-slate-400', badge: 'bg-slate-100 text-slate-600' }, } const c = config[status] || config.pending return (

{title}

{status === 'safe' ? '安全' : status === 'warning' ? '注意' : status === 'danger' ? '泄漏' : '待检测'}

{description}

) } export default function DNSLeak() { const [isRunning, setIsRunning] = useState(false) const [hasRun, setHasRun] = useState(false) const [currentStep, setCurrentStep] = useState(-1) // Results const [dnsResolvers, setDnsResolvers] = useState([]) const [webRTCResult, setWebRTCResult] = useState(null) const [publicIPs, setPublicIPs] = useState([]) const [errors, setErrors] = useState([]) // DNS leak analysis const [dnsLeakStatus, setDnsLeakStatus] = useState('pending') const [webRTCLeakStatus, setWebRTCLeakStatus] = useState('pending') const [ipConsistencyStatus, setIpConsistencyStatus] = useState('pending') const runCheck = useCallback(async () => { setIsRunning(true) setHasRun(true) setDnsResolvers([]) setWebRTCResult(null) setPublicIPs([]) setErrors([]) setDnsLeakStatus('pending') setWebRTCLeakStatus('pending') setIpConsistencyStatus('pending') // Step 1: Check DNS resolvers setCurrentStep(0) try { const response = await fetch('https://ipleak.net/json/dnsleak/', { signal: AbortSignal.timeout(10000), }) if (response.ok) { const data = await response.json() if (Array.isArray(data) && data.length > 0) { const resolvers = data.map(item => ({ ip: item.ip || '', country: item.country || '', isp: item.isp || item.asn || '', })).filter(r => r.ip) setDnsResolvers(resolvers) setDnsLeakStatus('safe') } else { setDnsLeakStatus('warning') } } else { setDnsLeakStatus('warning') setErrors(prev => [...prev, 'DNS 查询 API 返回异常']) } } catch (e) { setDnsLeakStatus('warning') setErrors(prev => [...prev, `DNS 查询失败: ${e.message}`]) } // Step 2: WebRTC leak detection setCurrentStep(1) const webrtc = await detectWebRTCLeak() setWebRTCResult(webrtc) if (webrtc.error) { setWebRTCLeakStatus('pending') } else if (webrtc.leaked) { setWebRTCLeakStatus('danger') } else { setWebRTCLeakStatus('safe') } // Step 3: IP consistency check setCurrentStep(2) const ipResults = [] for (const api of IP_CHECK_APIS) { try { const resp = await fetch(api.url, { signal: AbortSignal.timeout(8000) }) if (resp.ok) { const data = await resp.json() const ip = data.ip || data.query if (ip) ipResults.push(ip) } } catch (e) { // ignore } } setPublicIPs(ipResults) const uniqueIPs = [...new Set(ipResults)] if (uniqueIPs.length <= 1) { setIpConsistencyStatus('safe') } else { setIpConsistencyStatus('warning') } setCurrentStep(3) setIsRunning(false) }, []) // Determine overall status const overallStatus = hasRun && !isRunning ? [dnsLeakStatus, webRTCLeakStatus, ipConsistencyStatus].includes('danger') ? 'danger' : [dnsLeakStatus, webRTCLeakStatus, ipConsistencyStatus].includes('warning') ? 'warning' : 'safe' : 'pending' const steps = [ '检测 DNS 递归解析服务器', '检测 WebRTC 本地 IP 泄漏', '检测出口 IP 一致性', ] return (
{/* Hero */}
隐私安全检测

DNS 泄漏检查

检测您的 DNS 查询是否通过安全通道,是否存在 WebRTC 泄漏和 IP 一致性问题

{/* Action Button */}
{/* Overall Status */} {hasRun && !isRunning && (
{overallStatus === 'safe' && } {overallStatus === 'warning' && } {overallStatus === 'danger' && } {overallStatus === 'safe' ? '未检测到 DNS 泄漏' : overallStatus === 'warning' ? '存在潜在风险' : '检测到 DNS 泄漏!'}

{overallStatus === 'safe' ? '您的 DNS 查询看起来是安全的,未发现明显泄漏' : overallStatus === 'warning' ? '部分检测项目存在异常,建议进一步检查' : '您的 DNS 查询可能正在泄漏,请检查 VPN/代理配置'}

)} {/* Steps Progress */} {isRunning && (

检测进度

{steps.map((step, i) => ( i} status={currentStep > i ? 'success' : currentStep === i ? 'active' : 'pending'} /> ))}
)} {/* Results */} {hasRun && !isRunning && (
{/* Result summaries */}
0 ? `检测到 ${dnsResolvers.length} 个 DNS 解析服务器` : '未能获取 DNS 解析服务器信息'} icon={Server} /> 0 ? `检测到 ${[...new Set(publicIPs)].length} 个不同出口 IP` : '未能获取出口 IP 信息'} icon={Globe} />
{/* DNS Resolvers Detail */} {dnsResolvers.length > 0 && (

DNS 解析服务器列表

{dnsResolvers.map((resolver, i) => ( ))}

如果 DNS 解析服务器与您的 VPN/代理出口 IP 所属的 ISP 不同,可能存在 DNS 泄漏

)} {/* WebRTC IPs */} {webRTCResult?.leaked && (

WebRTC 泄漏的本地 IP

{webRTCResult.ips.map(ip => (
{ip}
))}

这些 IP 地址通过 WebRTC 泄漏,可能暴露您的真实网络位置。建议在浏览器中禁用 WebRTC

)} {/* Errors */} {errors.length > 0 && (

检测过程中的提示

    {errors.map((err, i) => (
  • {err}
  • ))}
)}
)} {/* Explanation */} {!hasRun && (

什么是 DNS 泄漏?

当您使用 VPN 或代理时,DNS 查询应该通过加密隧道发送。如果 DNS 请求被发送到了 ISP 的 DNS 服务器, 而非 VPN 提供商的服务器,就发生了 DNS 泄漏。

DNS 泄漏会导致:
ISP 可以追踪:您的互联网服务提供商可以看到您访问的所有域名
位置暴露:DNS 服务器的地理位置可能暴露您的真实位置
WebRTC 泄漏:浏览器的 WebRTC 功能可能绕过 VPN 暴露真实 IP

DNS 解析器检测

检测正在使用的 DNS 服务器

WebRTC 泄漏检测

检测浏览器是否泄漏本地 IP

出口 IP 一致性

检测多个出口 IP 是否一致

)}
) }