feat(dns): protect provider DNS from unconfirmed overrides (#7902)

Fixes #7292
This commit is contained in:
wonfen
2026-09-09 23:19:56 +08:00
committed by GitHub
parent b06a966a4b
commit 35a2b6c75c
36 changed files with 565 additions and 35 deletions
+4 -2
View File
@@ -33,6 +33,7 @@ import {
Switch,
} from '@/components/base'
import { useClash } from '@/hooks/use-clash'
import { useVerge } from '@/hooks/use-verge'
import { showNotice } from '@/services/notice-service'
import { useThemeMode } from '@/services/states'
import type { MonacoEditorInstance } from '@/types/monaco'
@@ -187,7 +188,8 @@ const DEFAULT_DNS_CONFIG = {
export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
const { t } = useTranslation()
const { clash, mutateClash } = useClash()
const { mutateClash } = useClash()
const { verge } = useVerge()
const themeMode = useThemeMode()
const [open, setOpen] = useState(false)
@@ -563,7 +565,7 @@ export function DnsViewer({ ref }: { ref?: Ref<DialogRef> }) {
return
}
if (clash?.dns?.enable) {
if (verge?.enable_dns_settings) {
await invoke('apply_dns_config', { apply: true })
mutateClash()
}
+47 -24
View File
@@ -1,17 +1,16 @@
import { LanRounded, SettingsRounded } from '@mui/icons-material'
import { MenuItem, Select, TextField, Typography } from '@mui/material'
import { invoke } from '@tauri-apps/api/core'
import { useLockFn } from 'ahooks'
import { useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { updateGeo, type LogLevel } from 'tauri-plugin-mihomo-api'
import { DialogRef, Switch, TooltipIcon } from '@/components/base'
import { BaseDialog, DialogRef, Switch, TooltipIcon } from '@/components/base'
import { useClash } from '@/hooks/use-clash'
import { useClashLog } from '@/hooks/use-clash-log'
import { useDisplayedMixedPort } from '@/hooks/use-displayed-mixed-port'
import { useVerge } from '@/hooks/use-verge'
import { invoke_uwp_tool } from '@/services/cmds'
import { invoke_uwp_tool, setDnsOverride } from '@/services/cmds'
import { showNotice } from '@/services/notice-service'
import getSystem from '@/utils/get-system'
@@ -36,7 +35,7 @@ const SettingClash = ({ onError }: Props) => {
const { t } = useTranslation()
const { clash, version, mutateClash, patchClash } = useClash()
const { verge, patchVerge } = useVerge()
const { verge, mutateVerge } = useVerge()
const displayedMixedPort = useDisplayedMixedPort()
const [, setClashLog] = useClashLog()
@@ -47,10 +46,8 @@ const SettingClash = ({ onError }: Props) => {
'unified-delay': unifiedDelay,
} = clash ?? {}
// 独立跟踪DNS设置开关状态
const [dnsSettingsEnabled, setDnsSettingsEnabled] = useState(() => {
return verge?.enable_dns_settings ?? false
})
const [dnsConfirmation, setDnsConfirmation] = useState<string | null>(null)
const [dnsUpdating, setDnsUpdating] = useState(false)
const webRef = useRef<DialogRef>(null)
const portRef = useRef<DialogRef>(null)
@@ -74,22 +71,29 @@ const SettingClash = ({ onError }: Props) => {
}
}
// 实现DNS设置开关处理函数
const handleDnsToggle = useLockFn(async (enable: boolean) => {
try {
setDnsSettingsEnabled(enable)
await patchVerge({ enable_dns_settings: enable })
await invoke('apply_dns_config', { apply: enable })
setTimeout(() => {
const handleDnsToggle = useLockFn(
async (enable: boolean, confirmation?: string) => {
setDnsUpdating(true)
try {
const outcome = await setDnsOverride(enable, confirmation)
if (outcome.status === 'confirmation_required') {
setDnsConfirmation(outcome.source)
} else {
setDnsConfirmation(null)
}
mutateVerge()
mutateClash()
}, 500)
} catch (err: any) {
setDnsSettingsEnabled(!enable)
showNotice.error(err)
await patchVerge({ enable_dns_settings: !enable }).catch(() => {})
throw err
}
})
} catch (err: any) {
showNotice.error(err)
} finally {
setDnsUpdating(false)
}
},
)
const closeDnsConfirmation = () => {
if (!dnsUpdating) setDnsConfirmation(null)
}
return (
<SettingList title={t('settings.sections.clash.title')}>
@@ -99,6 +103,24 @@ const SettingClash = ({ onError }: Props) => {
<ClashCoreViewer ref={coreRef} />
<NetworkInterfaceViewer ref={networkRef} />
<DnsViewer ref={dnsRef} />
<BaseDialog
open={dnsConfirmation !== null}
title={t('settings.modals.dns.protection.title')}
okBtn={t('settings.modals.dns.protection.enableAnyway')}
cancelBtn={t('settings.modals.dns.protection.keepDisabled')}
contentSx={{ width: { xs: 320, sm: 420 } }}
loading={dnsUpdating}
disableCancel={dnsUpdating}
onCancel={closeDnsConfirmation}
onClose={closeDnsConfirmation}
onOk={() => {
if (dnsConfirmation) void handleDnsToggle(true, dnsConfirmation)
}}
>
<Typography variant="body2">
{t('settings.modals.dns.protection.message')}
</Typography>
</BaseDialog>
<HeaderConfiguration ref={corsRef} />
<TunnelsViewer ref={tunnelRef} />
<SettingItem
@@ -137,7 +159,8 @@ const SettingClash = ({ onError }: Props) => {
>
<Switch
edge="end"
checked={dnsSettingsEnabled}
checked={verge?.enable_dns_settings ?? false}
disabled={dnsUpdating || dnsConfirmation !== null}
onChange={(_, checked) => handleDnsToggle(checked)}
/>
</SettingItem>