diff --git a/common/maxmind-db/GeoLite2-ASN.mmdb b/common/maxmind-db/GeoLite2-ASN.mmdb index 74bff0f1..7a5e21c3 100644 Binary files a/common/maxmind-db/GeoLite2-ASN.mmdb and b/common/maxmind-db/GeoLite2-ASN.mmdb differ diff --git a/common/maxmind-db/GeoLite2-City.mmdb b/common/maxmind-db/GeoLite2-City.mmdb index 9c5a4f0a..5f16aaf5 100644 Binary files a/common/maxmind-db/GeoLite2-City.mmdb and b/common/maxmind-db/GeoLite2-City.mmdb differ diff --git a/frontend/components/DnsLeaksTest.vue b/frontend/components/DnsLeaksTest.vue index 7901f013..9a2cb659 100644 --- a/frontend/components/DnsLeaksTest.vue +++ b/frontend/components/DnsLeaksTest.vue @@ -30,15 +30,25 @@ leak.ip }}

-
- - {{ t('dnsleaktest.EndpointCountry') }}: {{ leak.country }}  - + + + + {{ t('ipInfos.ISP') }}: {{ leak.org }} + + + + + {{ t('ipInfos.Country') }}: {{ leak.country + }}  + +
@@ -67,9 +77,10 @@ const lang = computed(() => store.lang); const createDefaultCard = () => ({ name: t('dnsleaktest.Name'), - country_code: t('dnsleaktest.StatusWait'), + country_code: '', country: t('dnsleaktest.StatusWait'), ip: t('dnsleaktest.StatusWait'), + org: t('dnsleaktest.StatusWait'), }); const leakTest = reactive([ @@ -116,6 +127,7 @@ const fetchLeakTestIpApiCom = (index) => { const geoSplit = data.dns.geo.split(" - "); leakTest[index].country_code = countryLookup.byCountry(geoSplit[0]).iso2; leakTest[index].country = getCountryName(leakTest[index].country_code, lang.value); + leakTest[index].org = geoSplit[1] || ''; leakTest[index].ip = data.dns.ip; resolve(); } else { @@ -126,8 +138,9 @@ const fetchLeakTestIpApiCom = (index) => { .catch((error) => { console.error("Error fetching leak test data:", error); leakTest[index].country = t('dnsleaktest.StatusError'); - leakTest[index].country_code = t('dnsleaktest.StatusError'); leakTest[index].ip = t('dnsleaktest.StatusError'); + leakTest[index].country_code = ''; + leakTest[index].org = ''; reject(error); }); }); @@ -153,6 +166,7 @@ const fetchLeakTestSfSharkCom = (index, key) => { if (keyEntry && keyEntry.CountryCode && keyEntry.IP) { leakTest[index].country_code = keyEntry.CountryCode; leakTest[index].country = getCountryName(keyEntry.CountryCode, lang.value); + leakTest[index].org = keyEntry.ISP || ''; leakTest[index].ip = keyEntry.IP; resolve(); } else { @@ -162,8 +176,10 @@ const fetchLeakTestSfSharkCom = (index, key) => { }) .catch((error) => { console.error("Error fetching leak test data:", error); - leakTest[index].geo = t('dnsleaktest.StatusError'); leakTest[index].ip = t('dnsleaktest.StatusError'); + leakTest[index].country = t('dnsleaktest.StatusError'); + leakTest[index].country_code = ''; + leakTest[index].org = ''; reject(error); }); }); @@ -178,7 +194,8 @@ const checkAllDNSLeakTest = async (isRefresh) => { server.geo = t('dnsleaktest.StatusWait'); server.ip = t('dnsleaktest.StatusWait'); server.country = t('dnsleaktest.StatusWait'); - server.country_code = t('dnsleaktest.StatusWait'); + server.country_code = ''; + server.org = t('dnsleaktest.StatusWait'); }); } @@ -220,4 +237,10 @@ defineExpose({ }); - + diff --git a/frontend/components/WebRtcTest.vue b/frontend/components/WebRtcTest.vue index 21574496..a311d690 100644 --- a/frontend/components/WebRtcTest.vue +++ b/frontend/components/WebRtcTest.vue @@ -26,15 +26,30 @@ }">   - {{ stun.ip }} + {{ stun.ip }} + +

-
- {{ - stun.natType }} + + NAT: + {{ + stun.natType }} + + + + + {{ t('ipInfos.Country') }}: {{ + stun.country }}  + + + +
@@ -48,12 +63,15 @@ import { ref, computed, onMounted, reactive, watch } from 'vue'; import { useMainStore } from '@/store'; import { useI18n } from 'vue-i18n'; import { trackEvent } from '@/utils/use-analytics'; +import { transformDataFromIPapi } from '@/utils/transform-ip-data.js'; +import getCountryName from '@/utils/country-name.js'; const { t } = useI18n(); const store = useMainStore(); const isDarkMode = computed(() => store.isDarkMode); const isMobile = computed(() => store.isMobile); +const lang = computed(() => store.lang); const isStarted = ref(false); @@ -65,6 +83,8 @@ const stunServers = reactive([ url: "stun.l.google.com:19302", ip: t('webrtc.StatusWait'), natType: t('webrtc.StatusWait'), + country: t('webrtc.StatusWait'), + country_code: '', }, { id: "blackberry", @@ -72,6 +92,8 @@ const stunServers = reactive([ url: "stun.voip.blackberry.com:3478", ip: t('webrtc.StatusWait'), natType: t('webrtc.StatusWait'), + country: t('webrtc.StatusWait'), + country_code: '', }, { id: "twilio", @@ -79,6 +101,8 @@ const stunServers = reactive([ url: "global.stun.twilio.com", ip: t('webrtc.StatusWait'), natType: t('webrtc.StatusWait'), + country: t('webrtc.StatusWait'), + country_code: '', }, { id: "cloudflare", @@ -86,6 +110,8 @@ const stunServers = reactive([ url: "stun.cloudflare.com", ip: t('webrtc.StatusWait'), natType: t('webrtc.StatusWait'), + country: t('webrtc.StatusWait'), + country_code: '', }, ]); @@ -98,17 +124,28 @@ const checkSTUNServer = async (stun) => { const pc = new RTCPeerConnection(servers); let candidateReceived = false; - pc.onicecandidate = (event) => { + // 分别获取 STUN 服务器的 IP 地址和 NAT 类型 + pc.onicecandidate = async (event) => { if (event.candidate) { candidateReceived = true; const candidate = event.candidate.candidate; const ipMatch = /([0-9a-f]{1,4}(:[0-9a-f]{1,4}){7}|[0-9a-f]{0,4}(:[0-9a-f]{1,4}){0,6}::[0-9a-f]{0,4}|::[0-9a-f]{1,4}(:[0-9a-f]{1,4}){0,6}|[0-9]{1,3}(\.[0-9]{1,3}){3})/i.exec(candidate); if (ipMatch) { stun.ip = ipMatch[0]; + try { + let countryInfo = await fetchCountryCode(stun.ip); + stun.country_code = countryInfo[0]; + stun.country = countryInfo[1]; + } catch (error) { + console.error("Error fetching country code:", error); + reject(error); + pc.close(); + return; + } IPArray.value = [...IPArray.value, stun.ip]; stun.natType = determineNATType(candidate); pc.close(); - resolve(); // 成功时解析 Promise + resolve(); } } }; @@ -126,11 +163,12 @@ const checkSTUNServer = async (stun) => { } catch (error) { console.error("STUN Server Test Error:", error); stun.ip = t('webrtc.StatusError'); - reject(error); // 捕获异常时拒绝 Promise + reject(error); } }); }; + // 分析ICE候选信息,推断NAT类型 const determineNATType = (candidate) => { const parts = candidate.split(' '); @@ -149,6 +187,31 @@ const determineNATType = (candidate) => { } }; +// 通过 Maxmind 获取 IP 地区归属 +const fetchCountryCode = async (ip) => { + let setLang = lang.value; + if (setLang === 'zh') { + setLang = 'zh-CN'; + } + const source = store.ipDBs.find(source => source.text === "MaxMind"); + + try { + const url = store.getDbUrl(source.id, ip, setLang); + const response = await fetch(url); + const data = await response.json(); + const ipData = transformDataFromIPapi(data, source.id, t, lang.value); + + if (ipData) { + let country_code = ipData.country_code.toLowerCase(); + let country = getCountryName(ipData.country_code, lang.value); + return [country_code, country]; + } + } catch (error) { + console.error("Error fetching IP country code", error); + } +} + + // 测试所有 STUN 服务器 const checkAllWebRTC = async (isRefresh) => { if (isRefresh) { @@ -158,6 +221,8 @@ const checkAllWebRTC = async (isRefresh) => { const promises = stunServers.map((server) => { server.ip = t('webrtc.StatusWait'); server.natType = t('webrtc.StatusWait'); + server.country = t('webrtc.StatusWait'); + server.country_code = ''; return checkSTUNServer(server); }); diff --git a/frontend/locales/en.json b/frontend/locales/en.json index df30cbae..528c2cf8 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -339,11 +339,11 @@ "StatusWait": "Awaiting Test or Connection Error", "StatusError": "Test Error", "NATType": { - "srflx": "Possibly Port Restricted Cone or Symmetric NAT", - "prflx": "Possibly Port Restricted Cone NAT", - "relay": "Possibly Symmetric NAT", - "host": "Possibly Full Cone NAT", - "unknown": "Unknown NAT Type" + "srflx": "Port Restricted Cone or Symmetric", + "prflx": "Port Restricted Cone", + "relay": "Symmetric", + "host": "Full Cone", + "unknown": "Unknown Type" } }, "dnsleaktest": { diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index ece93559..3aeaa42b 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -339,11 +339,11 @@ "StatusWait": "En attente du test ou erreur de connexion", "StatusError": "Erreur de test", "NATType": { - "srflx": "Possiblement Port Restricted Cone ou Symmetric NAT", - "prflx": "Possiblement Port Restricted Cone NAT", - "relay": "Possiblement Symmetric NAT", - "host": "Possiblement Full Cone NAT", - "unknown": "Type de NAT inconnu" + "srflx": "Port Restricted Cone ou Symmetric", + "prflx": "Port Restricted Cone", + "relay": "Symmetric", + "host": "Full Cone", + "unknown": "Type de inconnu" } }, "dnsleaktest": { diff --git a/frontend/locales/zh.json b/frontend/locales/zh.json index a2efeb82..a8b06d20 100644 --- a/frontend/locales/zh.json +++ b/frontend/locales/zh.json @@ -339,11 +339,11 @@ "StatusWait": "待检测或连接错误", "StatusError": "测试出错", "NATType": { - "srflx": "可能是端口限制型或对称型 NAT", - "prflx": "可能是端口限制型 NAT", - "relay": "可能是对称型 NAT", - "host": "可能是全锥型 NAT", - "unknown": "未知 NAT 类型" + "srflx": "端口限制型或对称型", + "prflx": "端口限制型", + "relay": "对称型", + "host": "全锥型", + "unknown": "未知类型" } }, "dnsleaktest": { diff --git a/package.json b/package.json index b0a1196c..d1f63e51 100644 --- a/package.json +++ b/package.json @@ -22,20 +22,20 @@ "code-inspector-plugin": "^0.16.1", "concurrently": "^9.0.1", "country-code-lookup": "^0.1.3", - "detect-gpu": "^5.0.49", + "detect-gpu": "^5.0.51", "dotenv": "^16.4.5", "express": "^4.21.0", - "express-rate-limit": "^7.4.0", + "express-rate-limit": "^7.4.1", "express-slow-down": "^2.0.3", "flag-icons": "^7.2.3", - "http-proxy-middleware": "^3.0.2", - "maxmind": "^4.3.21", + "http-proxy-middleware": "^3.0.3", + "maxmind": "^4.3.22", "nodemon": "^3.1.7", - "pinia": "^2.2.2", + "pinia": "^2.2.4", "svgmap": "^2.11.1", "ua-parser-js": "^1.0.39", - "vue": "^3.5.8", - "vue-i18n": "^10.0.3", + "vue": "^3.5.11", + "vue-i18n": "^10.0.4", "vue-router": "^4.4.5", "whoiser": "^1.18.0" },