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
)}
)
}