From b8a654967f7421ffed31439bfcd62379649478e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rouzbeh=E2=80=A0?= <78313022+rqzbeh@users.noreply.github.com> Date: Thu, 9 Jul 2026 02:15:35 +0330 Subject: [PATCH] Add encrypted DNS presets (#5837) --- .gitignore | 1 - docs/xray-dns.md | 19 ++++++ .../src/pages/xray/dns/DnsPresetsModal.css | 4 ++ .../src/pages/xray/dns/DnsPresetsModal.tsx | 63 ++++++++++++++++--- frontend/src/pages/xray/dns/DnsTab.tsx | 8 ++- frontend/src/test/dns-presets.test.ts | 19 ++++++ internal/web/translation/ar-EG.json | 3 +- internal/web/translation/en-US.json | 3 +- internal/web/translation/es-ES.json | 3 +- internal/web/translation/fa-IR.json | 3 +- internal/web/translation/id-ID.json | 3 +- internal/web/translation/ja-JP.json | 3 +- internal/web/translation/pt-BR.json | 3 +- internal/web/translation/ru-RU.json | 3 +- internal/web/translation/tr-TR.json | 3 +- internal/web/translation/uk-UA.json | 3 +- internal/web/translation/vi-VN.json | 3 +- internal/web/translation/zh-CN.json | 3 +- internal/web/translation/zh-TW.json | 3 +- 19 files changed, 128 insertions(+), 25 deletions(-) create mode 100644 docs/xray-dns.md create mode 100644 frontend/src/test/dns-presets.test.ts diff --git a/.gitignore b/.gitignore index fd67a2625..69e2eb2f3 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,3 @@ docker-compose.override.yml # Ignore .env (Environment Variables) file .env - diff --git a/docs/xray-dns.md b/docs/xray-dns.md new file mode 100644 index 000000000..d32d29d7e --- /dev/null +++ b/docs/xray-dns.md @@ -0,0 +1,19 @@ +# Xray DNS presets and leak notes + +The panel DNS preset menu supports plain DNS, DoH, and DoQ server entries accepted by Xray's built-in DNS config. These notes follow Xray's DNS server address forms documented in . + +- Plain DNS uses UDP/53 and follows routing unless a local-mode scheme is used. It is easy to observe on the server network path. +- DoH uses `https://.../dns-query` and goes through Xray routing. +- DoQ uses `quic+local://...` in Xray. Local mode bypasses Xray routing and connects directly through Freedom, which avoids DNS routing loops but can reveal resolver traffic from the server IP. +- Xray DNS does not have a direct DoT URL scheme. Use DoH/DoQ, or wrap DNS-over-TCP/TLS outside this DNS config if DoT is required. + +Leak-prone settings: + +- `localhost` uses the host resolver and is outside Xray control. +- `tcp+local://`, `https+local://`, and `quic+local://` bypass Xray routing by design. +- Domain resolver names can need system DNS in local mode unless pinned in `hosts`. +- `disableFallback: false` or `enableParallelQuery: true` can query fallback servers and reveal domains to more than one provider. +- `clientIp` sends EDNS Client Subnet data upstream. +- Plain IP/UDP DNS presets are not encrypted. + +For privacy-sensitive setups, prefer DoH through routed outbounds, add `hosts` pins for resolver hostnames, set `disableFallback` or `skipFallback` where fallback is not wanted, and keep `clientIp` empty. diff --git a/frontend/src/pages/xray/dns/DnsPresetsModal.css b/frontend/src/pages/xray/dns/DnsPresetsModal.css index d79778db9..678c15165 100644 --- a/frontend/src/pages/xray/dns/DnsPresetsModal.css +++ b/frontend/src/pages/xray/dns/DnsPresetsModal.css @@ -1,3 +1,7 @@ +.preset-warning { + margin-bottom: 12px; +} + .preset-list { border: 1px solid var(--ant-color-border-secondary); border-radius: 8px; diff --git a/frontend/src/pages/xray/dns/DnsPresetsModal.tsx b/frontend/src/pages/xray/dns/DnsPresetsModal.tsx index 97a5b5dab..e765b7687 100644 --- a/frontend/src/pages/xray/dns/DnsPresetsModal.tsx +++ b/frontend/src/pages/xray/dns/DnsPresetsModal.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next'; -import { Button, Modal, Space, Tag } from 'antd'; +import { Alert, Button, Modal, Space, Tag } from 'antd'; import './DnsPresetsModal.css'; interface DnsPresetsModalProps { @@ -8,34 +8,69 @@ interface DnsPresetsModalProps { onInstall: (servers: string[]) => void; } -const PRESETS: { name: string; family: boolean; data: string[] }[] = [ +export const PRESETS: { name: string; tags: string[]; data: string[] }[] = [ { name: 'Google DNS', - family: false, + tags: ['UDP'], data: ['8.8.8.8', '8.8.4.4', '2001:4860:4860::8888', '2001:4860:4860::8844'], }, { name: 'Cloudflare DNS', - family: false, + tags: ['UDP'], data: ['1.1.1.1', '1.0.0.1', '2606:4700:4700::1111', '2606:4700:4700::1001'], }, { name: 'AdGuard DNS', - family: false, + tags: ['UDP'], data: ['94.140.14.14', '94.140.15.15', '2a10:50c0::ad1:ff', '2a10:50c0::ad2:ff'], }, { name: 'AdGuard Family DNS', - family: true, + tags: ['UDP', 'Family'], data: ['94.140.14.15', '94.140.15.16', '2a10:50c0::bad1:ff', '2a10:50c0::bad2:ff'], }, { name: 'Cloudflare Family DNS', - family: true, + tags: ['UDP', 'Family'], data: ['1.1.1.3', '1.0.0.3', '2606:4700:4700::1113', '2606:4700:4700::1003'], }, + { + name: 'Cloudflare DoH', + tags: ['DoH'], + data: ['https://cloudflare-dns.com/dns-query'], + }, + { + name: 'Google DoH', + tags: ['DoH'], + data: ['https://dns.google/dns-query'], + }, + { + name: 'Quad9 Secure DoH', + tags: ['DoH', 'Malware'], + data: ['https://dns.quad9.net/dns-query'], + }, + { + name: 'AdGuard DoH + DoQ', + tags: ['DoH', 'DoQ', 'Ads'], + data: ['https://dns.adguard-dns.com/dns-query', 'quic+local://dns.adguard-dns.com'], + }, + { + name: 'Control D Ads DoH + DoQ', + tags: ['DoH', 'DoQ', 'Ads'], + data: ['https://freedns.controld.com/p2', 'quic+local://p2.freedns.controld.com'], + }, + { + name: 'Control D Family DoH + DoQ', + tags: ['DoH', 'DoQ', 'Family'], + data: ['https://freedns.controld.com/p4', 'quic+local://p4.freedns.controld.com'], + }, ]; +function tagLabel(tag: string, t: (key: string) => string): string { + if (tag === 'Family') return t('pages.xray.dns.dnsPresetFamily'); + return tag; +} + export default function DnsPresetsModal({ open, onClose, onInstall }: DnsPresetsModalProps) { const { t } = useTranslation(); @@ -47,13 +82,21 @@ export default function DnsPresetsModal({ open, onClose, onInstall }: DnsPresets mask={{ closable: false }} onCancel={onClose} > +
{PRESETS.map((preset) => (
- - {preset.family ? t('pages.xray.dns.dnsPresetFamily') : 'DNS'} - + {preset.tags.map((tag) => ( + + {tagLabel(tag, t)} + + ))} {preset.name}