fix(tracer): resolve discord community cidr filter (#50) and improve leak check display

This commit is contained in:
Dushnilin
2026-09-14 06:27:31 +03:00
parent 959bcb59f5
commit ea0de78cca
5 changed files with 463 additions and 170 deletions
@@ -128,7 +128,7 @@ export function renderLeakCheckModal() {
const ipAlertClass = !ip_leak.proxy_online const ipAlertClass = !ip_leak.proxy_online
? 'alert-message info' ? 'alert-message info'
: isDirectRouting : isDirectRouting
? 'alert-message info' ? 'alert-message warning'
: 'alert-message success'; : 'alert-message success';
const ipAlertText = !ip_leak.proxy_online const ipAlertText = !ip_leak.proxy_online
@@ -266,17 +266,19 @@ export function renderLeakCheckModal() {
); );
// --- 2. DNS Leak Section --- // --- 2. DNS Leak Section ---
const dnsAlertClass = const proxyDnsServers = dns_leak.dns_servers || [];
!dns_leak.proxy_online || (dns_leak.dns_servers || []).length === 0 const directDnsServers = dns_leak.direct_dns_servers || [];
const hasProxyDns = proxyDnsServers.length > 0;
const dnsAlertClass = !hasProxyDns
? 'alert-message info' ? 'alert-message info'
: dns_leak.dns_leaked : dns_leak.dns_leaked
? 'alert-message warning' ? 'alert-message warning'
: 'alert-message success'; : 'alert-message success';
const dnsAlertText = const dnsAlertText = !hasProxyDns
!dns_leak.proxy_online || (dns_leak.dns_servers || []).length === 0
? _( ? _(
'No DNS resolvers captured through proxy (proxy is offline or test domain is not intercepted).', 'DNS resolvers through proxy are not captured (proxy is offline or test domain is not intercepted).',
) )
: dns_leak.dns_leaked : dns_leak.dns_leaked
? _( ? _(
@@ -286,12 +288,14 @@ export function renderLeakCheckModal() {
'🛡️ SECURE: All DNS queries are resolved through independent secure DNS servers.', '🛡️ SECURE: All DNS queries are resolved through independent secure DNS servers.',
); );
const dnsTableRows = (dns_leak.dns_servers || []).map((s) => const makeDnsRow = (s: Tachyon.DNSResolverInfo, pathLabel: string) =>
E('tr', { class: 'tr cbi-section-table-row' }, [ E('tr', { class: 'tr cbi-section-table-row' }, [
E('td', { class: 'td' }, [E('code', {}, s.ip)]), E('td', { class: 'td' }, [E('code', {}, s.ip)]),
E('td', { class: 'td' }, s.country || '—'), E('td', { class: 'td' }, s.country || '—'),
E('td', { class: 'td' }, s.isp || '—'), E('td', { class: 'td' }, s.isp || '—'),
E('td', { class: 'td', style: 'text-align: center;' }, [ E(
'td',
{ class: 'td', style: 'text-align: center;' },
s.is_isp s.is_isp
? E( ? E(
'span', 'span',
@@ -311,9 +315,24 @@ export function renderLeakCheckModal() {
}, },
_('SAFE'), _('SAFE'),
), ),
]), ),
]), E(
'td',
{
class: 'td',
style: 'font-size: 10px; color: var(--text-color-medium, #6c757d);',
},
pathLabel,
),
]);
const proxyDnsRows = proxyDnsServers.map((s) =>
makeDnsRow(s, _('via Proxy')),
); );
const directDnsRows = directDnsServers.map((s) =>
makeDnsRow(s, _('via WAN')),
);
const allDnsRows = [...proxyDnsRows, ...directDnsRows];
const dnsTable = E( const dnsTable = E(
'table', 'table',
@@ -332,20 +351,21 @@ export function renderLeakCheckModal() {
{ class: 'th', style: 'text-align: center;' }, { class: 'th', style: 'text-align: center;' },
_('Verdict'), _('Verdict'),
), ),
E('th', { class: 'th' }, _('Path')),
]), ]),
]), ]),
E( E(
'tbody', 'tbody',
{}, {},
dnsTableRows.length > 0 allDnsRows.length > 0
? dnsTableRows ? allDnsRows
: [ : [
E('tr', { class: 'tr' }, [ E('tr', { class: 'tr' }, [
E( E(
'td', 'td',
{ {
class: 'td', class: 'td',
colSpan: 4, colSpan: 5,
style: 'text-align: center; opacity: 0.7;', style: 'text-align: center; opacity: 0.7;',
}, },
_('No DNS resolvers recorded'), _('No DNS resolvers recorded'),
@@ -1,23 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest';
// ---------------------------------------------------------------------------
// Hoisted mocks — must come before any imports that transitively use globals
// ---------------------------------------------------------------------------
const mocks = vi.hoisted(() => { const mocks = vi.hoisted(() => {
class MockMutationObserver { // Minimal DOM mock that tracks tree structure for inspection
observe() {} function createEl(tag: string) {
disconnect() {} const el: any = {
takeRecords() {
return [];
}
}
(globalThis as any).MutationObserver = MockMutationObserver;
function createDummyElement(tag: string) {
return {
tagName: tag.toUpperCase(), tagName: tag.toUpperCase(),
children: [] as any[], children: [] as any[],
style: {} as Record<string, string>, style: {} as Record<string, string>,
textContent: '', textContent: '',
innerHTML: '', innerHTML: '',
class: '',
colSpan: 0,
setAttribute() {}, setAttribute() {},
getAttribute() { getAttribute() {
return null; return null;
@@ -25,27 +22,28 @@ const mocks = vi.hoisted(() => {
addEventListener() {}, addEventListener() {},
querySelectorAll: () => [], querySelectorAll: () => [],
querySelector: () => null, querySelector: () => null,
replaceChildren: function (...nodes: any[]) { replaceChildren(...nodes: any[]) {
(this as any).children = nodes; (this as any).children = nodes;
}, },
appendChild: function (node: any) { appendChild(node: any) {
if (node && typeof node === 'object') {
(this as any).children.push(node); (this as any).children.push(node);
} else if (node) {
(this as any).textContent += String(node);
}
}, },
append: function (...nodes: any[]) { append(...nodes: any[]) {
nodes.forEach((node) => (this as any).children.push(node)); nodes.forEach((n) => (this as any).appendChild(n));
},
classList: {
add() {},
remove() {},
}, },
classList: { add() {}, remove() {} },
}; };
return el;
} }
(globalThis as any).document = { (globalThis as any).document = {
body: {} as any, body: {} as any,
createElement: (tag: string) => createDummyElement(tag) as any, createElement: (tag: string) => createEl(tag),
createElementNS: (_ns: string, tag: string) => createElementNS: (_ns: string, tag: string) => createEl(tag),
createDummyElement(tag) as any,
getElementById: () => null, getElementById: () => null,
querySelector: () => null, querySelector: () => null,
querySelectorAll: () => [], querySelectorAll: () => [],
@@ -59,18 +57,16 @@ const mocks = vi.hoisted(() => {
if (style && typeof style === 'string') { if (style && typeof style === 'string') {
style.split(';').forEach((rule: string) => { style.split(';').forEach((rule: string) => {
const [k, v] = rule.split(':').map((s: string) => s.trim()); const [k, v] = rule.split(':').map((s: string) => s.trim());
if (k && v) { if (k && v) el.style[k] = v;
el.style[k] = v;
}
}); });
} else if (style && typeof style === 'object') { } else if (style && typeof style === 'object') {
Object.assign(el.style, style); Object.assign(el.style, style);
} }
} }
if (children) { if (children !== undefined && children !== null) {
if (Array.isArray(children)) { if (Array.isArray(children)) {
children.forEach((c) => { children.forEach((c) => {
if (c) el.appendChild(c); if (c != null) el.appendChild(c);
}); });
} else { } else {
el.appendChild(children); el.appendChild(children);
@@ -80,42 +76,79 @@ const mocks = vi.hoisted(() => {
}; };
(globalThis as any)._ = (str: string) => str; (globalThis as any)._ = (str: string) => str;
(globalThis as any).rpc = { declare: vi.fn() }; (globalThis as any).rpc = { declare: vi.fn() };
(globalThis as any).uci = { sections: vi.fn().mockResolvedValue([]) }; (globalThis as any).uci = { sections: vi.fn().mockResolvedValue([]) };
(globalThis as any).localStorage = { getItem: vi.fn(), setItem: vi.fn() }; (globalThis as any).localStorage = { getItem: vi.fn(), setItem: vi.fn() };
class MockMutationObserver {
observe() {}
disconnect() {}
takeRecords() {
return [];
}
}
(globalThis as any).MutationObserver = MockMutationObserver;
const showModal = vi.fn(); const showModal = vi.fn();
const hideModal = vi.fn(); const hideModal = vi.fn();
(globalThis as any).ui = { (globalThis as any).ui = { showModal, hideModal, addNotification: vi.fn() };
showModal,
hideModal,
addNotification: vi.fn(),
};
return { return { showModal, hideModal };
showModal,
hideModal,
};
}); });
// ---------------------------------------------------------------------------
// Imports after mocks
// ---------------------------------------------------------------------------
import { renderLeakCheckModal } from '../partials/renderLeakCheckModal'; import { renderLeakCheckModal } from '../partials/renderLeakCheckModal';
import { TachyonShellMethods } from '../../../methods/shell'; import { TachyonShellMethods } from '../../../methods/shell';
describe('renderLeakCheckModal', () => { // ---------------------------------------------------------------------------
beforeEach(() => { // Helpers
vi.clearAllMocks(); // ---------------------------------------------------------------------------
mocks.showModal.mockReset();
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue({ /** Recursively collect all text content from a mock element tree. */
success: true, function collectText(node: any): string {
if (!node || typeof node !== 'object') return String(node ?? '');
let text = node.textContent || '';
for (const child of node.children || []) {
text += collectText(child);
}
return text;
}
/** Find all elements matching a class attribute value anywhere in the tree. */
function findByClass(node: any, cls: string): any[] {
const results: any[] = [];
if (!node || typeof node !== 'object') return results;
if (typeof node.class === 'string' && node.class.includes(cls)) {
results.push(node);
}
for (const child of node.children || []) {
results.push(...findByClass(child, cls));
}
return results;
}
/** Extract text from the second argument of the last showModal call. */
function getModalContent(): any {
const calls = mocks.showModal.mock.calls;
return calls[calls.length - 1]?.[1];
}
// Default "happy path" leak result
function makeSuccessResult(overrides: Record<string, any> = {}) {
return {
success: true as const,
data: { data: {
ip_leak: { ip_leak: {
leaked: false, leaked: false,
direct_ip: '95.173.136.25', direct_ip: '95.173.136.25',
direct_country: 'Russia', direct_country: 'Russia',
direct_city: 'Moscow',
direct_isp: 'Rostelecom', direct_isp: 'Rostelecom',
proxy_ip: '185.220.101.5', proxy_ip: '185.220.101.5',
proxy_country: 'Netherlands', proxy_country: 'Netherlands',
proxy_city: 'Amsterdam',
proxy_org: 'Mullvad', proxy_org: 'Mullvad',
proxy_online: true, proxy_online: true,
}, },
@@ -141,13 +174,27 @@ describe('renderLeakCheckModal', () => {
], ],
proxy_online: true, proxy_online: true,
}, },
...overrides,
}, },
}); };
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe('renderLeakCheckModal', () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.showModal.mockReset();
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult(),
);
}); });
it('renders and displays the IP & DNS Leak Detection modal', () => { // ── Modal lifecycle ────────────────────────────────────────────────────────
it('renders the modal with correct title', () => {
renderLeakCheckModal(); renderLeakCheckModal();
expect(mocks.showModal).toHaveBeenCalledTimes(1); expect(mocks.showModal).toHaveBeenCalledTimes(1);
expect(mocks.showModal).toHaveBeenCalledWith( expect(mocks.showModal).toHaveBeenCalledWith(
expect.stringContaining('Tachyon IP & DNS Leak Detection'), expect.stringContaining('Tachyon IP & DNS Leak Detection'),
@@ -155,49 +202,118 @@ describe('renderLeakCheckModal', () => {
); );
}); });
it('calls TachyonShellMethods.leakCheck on start', async () => { it('auto-starts the leak check on open', () => {
const leakCheckSpy = vi.spyOn(TachyonShellMethods, 'leakCheck'); const spy = vi.spyOn(TachyonShellMethods, 'leakCheck');
renderLeakCheckModal(); renderLeakCheckModal();
expect(spy).toHaveBeenCalledTimes(1);
expect(leakCheckSpy).toHaveBeenCalledTimes(1);
}); });
it('handles leak detection when IP is leaked', async () => { // ── IP section — SECURE path ───────────────────────────────────────────────
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue({
success: true, it('shows SECURE alert when proxy is online and IPs differ', async () => {
data: { renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('SECURE');
expect(text).toContain('185.220.101.5');
});
});
it('shows SECURE badge (not DIRECT or INACTIVE) when IPs differ', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const badges = findByClass(content, 'badge');
const badgeTexts = badges.map((b) => collectText(b)).join(' ');
expect(badgeTexts).toContain('SECURE');
expect(badgeTexts).not.toContain('DIRECT');
expect(badgeTexts).not.toContain('INACTIVE');
});
});
it('alert class is success when proxy is online and IPs differ', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
// Find the IP alert div
const alertDivs = findByClass(content, 'alert-message');
const successAlerts = alertDivs.filter((d) =>
d.class?.includes('success'),
);
expect(successAlerts.length).toBeGreaterThan(0);
});
});
// ── IP section — DIRECT / leaked path ─────────────────────────────────────
it('shows DIRECT badge when proxy_ip equals direct_ip', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
ip_leak: { ip_leak: {
leaked: true, leaked: true,
direct_ip: '95.173.136.25', direct_ip: '95.173.136.25',
proxy_ip: '95.173.136.25', proxy_ip: '95.173.136.25',
proxy_online: true, proxy_online: true,
}, },
dns_leak: { }),
dns_leaked: true, );
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const badges = findByClass(content, 'badge');
const badgeTexts = badges.map((b) => collectText(b)).join(' ');
expect(badgeTexts).toContain('DIRECT');
});
});
it('shows warning alert class when IP is leaked', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
ip_leak: {
leaked: true,
direct_ip: '95.173.136.25', direct_ip: '95.173.136.25',
proxy_ip: '95.173.136.25', proxy_ip: '95.173.136.25',
dns_servers: [
{
ip: '212.188.4.10',
country: 'Russia',
isp: 'Rostelecom',
is_isp: true,
},
],
direct_dns_servers: [],
proxy_online: true, proxy_online: true,
}, },
}, }),
}); );
renderLeakCheckModal(); renderLeakCheckModal();
expect(mocks.showModal).toHaveBeenCalledTimes(1); await vi.waitFor(() => {
const content = getModalContent();
const alertDivs = findByClass(content, 'alert-message');
const warningAlerts = alertDivs.filter((d) =>
d.class?.includes('warning'),
);
expect(warningAlerts.length).toBeGreaterThan(0);
});
}); });
it('handles offline proxy gracefully', async () => { // ── IP section — proxy offline ─────────────────────────────────────────────
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue({
success: true, it('shows INACTIVE badge when proxy_online=false', async () => {
data: { vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
ip_leak: {
leaked: false,
direct_ip: '95.173.136.25',
proxy_ip: '—',
proxy_online: false,
},
}),
);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const badges = findByClass(content, 'badge');
const badgeTexts = badges.map((b) => collectText(b)).join(' ');
expect(badgeTexts).toContain('INACTIVE');
});
});
it('shows info alert class when proxy offline', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
ip_leak: { ip_leak: {
leaked: false, leaked: false,
direct_ip: '95.173.136.25', direct_ip: '95.173.136.25',
@@ -212,10 +328,151 @@ describe('renderLeakCheckModal', () => {
direct_dns_servers: [], direct_dns_servers: [],
proxy_online: false, proxy_online: false,
}, },
}, }),
);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const alertDivs = findByClass(content, 'alert-message');
const infoAlerts = alertDivs.filter((d) => d.class?.includes('info'));
expect(infoAlerts.length).toBeGreaterThan(0);
});
}); });
// ── DNS section ────────────────────────────────────────────────────────────
it('renders proxy DNS resolvers with "via Proxy" path label', async () => {
renderLeakCheckModal(); renderLeakCheckModal();
expect(mocks.showModal).toHaveBeenCalledTimes(1); await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('1.1.1.1');
expect(text).toContain('via Proxy');
});
});
it('renders direct_dns_servers with "via WAN" path label', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
// direct_dns_servers IP from default mock
expect(text).toContain('212.188.4.10');
expect(text).toContain('via WAN');
});
});
it('marks ISP DNS servers with ISP DNS badge', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const badges = findByClass(content, 'badge');
const badgeTexts = badges.map((b) => collectText(b)).join(' ');
expect(badgeTexts).toContain('ISP DNS');
});
});
it('marks non-ISP DNS servers with SAFE badge', async () => {
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const badges = findByClass(content, 'badge');
const badgeTexts = badges.map((b) => collectText(b)).join(' ');
expect(badgeTexts).toContain('SAFE');
});
});
it('shows direct DNS servers even when proxy offline', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
ip_leak: {
leaked: false,
direct_ip: '95.173.136.25',
proxy_ip: '—',
proxy_online: false,
},
dns_leak: {
dns_leaked: false,
direct_ip: '95.173.136.25',
proxy_ip: '—',
dns_servers: [],
direct_dns_servers: [
{ ip: '8.8.8.8', country: 'US', isp: 'Google', is_isp: false },
],
proxy_online: false,
},
}),
);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('8.8.8.8');
expect(text).toContain('via WAN');
});
});
it('shows "No DNS resolvers recorded" when both lists are empty', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue(
makeSuccessResult({
dns_leak: {
dns_leaked: false,
direct_ip: '95.173.136.25',
proxy_ip: '185.220.101.5',
dns_servers: [],
direct_dns_servers: [],
proxy_online: true,
},
}),
);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('No DNS resolvers recorded');
});
});
// ── Error handling ─────────────────────────────────────────────────────────
it('shows error alert when leakCheck returns success=false', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockResolvedValue({
success: false,
error: 'Connection timeout',
} as any);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('Connection timeout');
});
});
it('shows error alert on unexpected exception from leakCheck', async () => {
vi.spyOn(TachyonShellMethods, 'leakCheck').mockRejectedValue(
new Error('Network error'),
);
renderLeakCheckModal();
await vi.waitFor(() => {
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('Network error');
});
});
// ── Re-run button ──────────────────────────────────────────────────────────
it('renders Re-run Leak Test button', () => {
renderLeakCheckModal();
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('Re-run Leak Test');
});
it('renders Close button', () => {
renderLeakCheckModal();
const content = getModalContent();
const text = collectText(content);
expect(text).toContain('Close');
}); });
}); });
@@ -15082,7 +15082,7 @@ function renderLeakCheckModal() {
resultsContainer.innerHTML = ""; resultsContainer.innerHTML = "";
const { ip_leak, dns_leak } = data; const { ip_leak, dns_leak } = data;
const isDirectRouting = ip_leak.proxy_online && (ip_leak.leaked || ip_leak.direct_ip === ip_leak.proxy_ip); const isDirectRouting = ip_leak.proxy_online && (ip_leak.leaked || ip_leak.direct_ip === ip_leak.proxy_ip);
const ipAlertClass = !ip_leak.proxy_online ? "alert-message info" : isDirectRouting ? "alert-message info" : "alert-message success"; const ipAlertClass = !ip_leak.proxy_online ? "alert-message info" : isDirectRouting ? "alert-message warning" : "alert-message success";
const ipAlertText = !ip_leak.proxy_online ? _("Proxy outbound is inactive or not configured for local testing.") : isDirectRouting ? _( const ipAlertText = !ip_leak.proxy_online ? _("Proxy outbound is inactive or not configured for local testing.") : isDirectRouting ? _(
"ℹ️ Direct connection (WAN): Public IP matches your ISP. Under selective routing (by domains or blocklists), unblocked resources bypass the proxy — this is standard operation." "ℹ️ Direct connection (WAN): Public IP matches your ISP. Under selective routing (by domains or blocklists), unblocked resources bypass the proxy — this is standard operation."
) : _("🛡️ SECURE: Public IP is concealed behind the proxy outbound."); ) : _("🛡️ SECURE: Public IP is concealed behind the proxy outbound.");
@@ -15195,20 +15195,24 @@ function renderLeakCheckModal() {
ipTable ipTable
] ]
); );
const dnsAlertClass = !dns_leak.proxy_online || (dns_leak.dns_servers || []).length === 0 ? "alert-message info" : dns_leak.dns_leaked ? "alert-message warning" : "alert-message success"; const proxyDnsServers = dns_leak.dns_servers || [];
const dnsAlertText = !dns_leak.proxy_online || (dns_leak.dns_servers || []).length === 0 ? _( const directDnsServers = dns_leak.direct_dns_servers || [];
"No DNS resolvers captured through proxy (proxy is offline or test domain is not intercepted)." const hasProxyDns = proxyDnsServers.length > 0;
const dnsAlertClass = !hasProxyDns ? "alert-message info" : dns_leak.dns_leaked ? "alert-message warning" : "alert-message success";
const dnsAlertText = !hasProxyDns ? _(
"DNS resolvers through proxy are not captured (proxy is offline or test domain is not intercepted)."
) : dns_leak.dns_leaked ? _( ) : dns_leak.dns_leaked ? _(
"ℹ️ ISP DNS detected: DNS queries are handled by your local Internet Service Provider. If you use selective routing, this is standard behavior for direct connections." "ℹ️ ISP DNS detected: DNS queries are handled by your local Internet Service Provider. If you use selective routing, this is standard behavior for direct connections."
) : _( ) : _(
"🛡️ SECURE: All DNS queries are resolved through independent secure DNS servers." "🛡️ SECURE: All DNS queries are resolved through independent secure DNS servers."
); );
const dnsTableRows = (dns_leak.dns_servers || []).map( const makeDnsRow = (s, pathLabel) => E("tr", { class: "tr cbi-section-table-row" }, [
(s) => E("tr", { class: "tr cbi-section-table-row" }, [
E("td", { class: "td" }, [E("code", {}, s.ip)]), E("td", { class: "td" }, [E("code", {}, s.ip)]),
E("td", { class: "td" }, s.country || "—"), E("td", { class: "td" }, s.country || "—"),
E("td", { class: "td" }, s.isp || "—"), E("td", { class: "td" }, s.isp || "—"),
E("td", { class: "td", style: "text-align: center;" }, [ E(
"td",
{ class: "td", style: "text-align: center;" },
s.is_isp ? E( s.is_isp ? E(
"span", "span",
{ {
@@ -15224,9 +15228,23 @@ function renderLeakCheckModal() {
}, },
_("SAFE") _("SAFE")
) )
]) ),
]) E(
"td",
{
class: "td",
style: "font-size: 10px; color: var(--text-color-medium, #6c757d);"
},
pathLabel
)
]);
const proxyDnsRows = proxyDnsServers.map(
(s) => makeDnsRow(s, _("via Proxy"))
); );
const directDnsRows = directDnsServers.map(
(s) => makeDnsRow(s, _("via WAN"))
);
const allDnsRows = [...proxyDnsRows, ...directDnsRows];
const dnsTable = E( const dnsTable = E(
"table", "table",
{ {
@@ -15243,19 +15261,20 @@ function renderLeakCheckModal() {
"th", "th",
{ class: "th", style: "text-align: center;" }, { class: "th", style: "text-align: center;" },
_("Verdict") _("Verdict")
) ),
E("th", { class: "th" }, _("Path"))
]) ])
]), ]),
E( E(
"tbody", "tbody",
{}, {},
dnsTableRows.length > 0 ? dnsTableRows : [ allDnsRows.length > 0 ? allDnsRows : [
E("tr", { class: "tr" }, [ E("tr", { class: "tr" }, [
E( E(
"td", "td",
{ {
class: "td", class: "td",
colSpan: 4, colSpan: 5,
style: "text-align: center; opacity: 0.7;" style: "text-align: center; opacity: 0.7;"
}, },
_("No DNS resolvers recorded") _("No DNS resolvers recorded")
@@ -12130,9 +12130,6 @@ async function performTrace(query) {
for (const community of communityLists) { for (const community of communityLists) {
let subnets = await readSubnetListFile(community); let subnets = await readSubnetListFile(community);
if (community === "discord") {
subnets = subnets.filter((s) => !isCloudflareSharedCidr(s));
}
const matchedCommunityCidr = matchIpInCidrs(ip, subnets); const matchedCommunityCidr = matchIpInCidrs(ip, subnets);
if (matchedCommunityCidr) { if (matchedCommunityCidr) {
return { return {
+1 -1
View File
@@ -72,7 +72,7 @@ CONFIG="$WORK_DIR/singbox.json"
node -e ' node -e '
const fs = require("fs"); const fs = require("fs");
const cfg = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); const cfg = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
const rules = cfg.route?.rules || []; const rules = (cfg.route && cfg.route.rules) || [];
const hijackDnsPort = rules.find(r => r.action === "hijack-dns" && r.inbound === "server-home_node-in" && r.port === 53); const hijackDnsPort = rules.find(r => r.action === "hijack-dns" && r.inbound === "server-home_node-in" && r.port === 53);
if (!hijackDnsPort) { if (!hijackDnsPort) {