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"
},