mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 13:10:30 +08:00
add dns and sniff (#3)
Co-authored-by: pompurin404 <pompurin404@mihomo.party>
This commit is contained in:
parent
e52a583ea7
commit
95ae550216
@ -18,6 +18,16 @@ export function setControledMihomoConfig(patch: Partial<IMihomoConfig>): void {
|
|||||||
const newTun = Object.assign(oldTun, patch.tun)
|
const newTun = Object.assign(oldTun, patch.tun)
|
||||||
patch.tun = newTun
|
patch.tun = newTun
|
||||||
}
|
}
|
||||||
|
if (patch.dns) {
|
||||||
|
const oldDns = controledMihomoConfig.dns || {}
|
||||||
|
const newDns = Object.assign(oldDns, patch.dns)
|
||||||
|
patch.dns = newDns
|
||||||
|
}
|
||||||
|
if (patch.sniffer) {
|
||||||
|
const oldSniffer = controledMihomoConfig.sniffer || {}
|
||||||
|
const newSniffer = Object.assign(oldSniffer, patch.sniffer)
|
||||||
|
patch.sniffer = newSniffer
|
||||||
|
}
|
||||||
controledMihomoConfig = Object.assign(controledMihomoConfig, patch)
|
controledMihomoConfig = Object.assign(controledMihomoConfig, patch)
|
||||||
if (patch['external-controller'] || patch.secret) {
|
if (patch['external-controller'] || patch.secret) {
|
||||||
getAxios(true)
|
getAxios(true)
|
||||||
|
|||||||
@ -9,7 +9,15 @@ export function generateProfile(): void {
|
|||||||
const { tun: profileTun = {} } = currentProfile
|
const { tun: profileTun = {} } = currentProfile
|
||||||
const { tun: controledTun } = controledMihomoConfig
|
const { tun: controledTun } = controledMihomoConfig
|
||||||
const tun = Object.assign(profileTun, controledTun)
|
const tun = Object.assign(profileTun, controledTun)
|
||||||
|
const { dns: profileDns = {} } = currentProfile
|
||||||
|
const { dns: controledDns } = controledMihomoConfig
|
||||||
|
const dns = Object.assign(profileDns, controledDns)
|
||||||
|
const { sniffer: profileSniffer = {} } = currentProfile
|
||||||
|
const { sniffer: controledSniffer } = controledMihomoConfig
|
||||||
|
const sniffer = Object.assign(profileSniffer, controledSniffer)
|
||||||
const profile = Object.assign(currentProfile, controledMihomoConfig)
|
const profile = Object.assign(currentProfile, controledMihomoConfig)
|
||||||
profile.tun = tun
|
profile.tun = tun
|
||||||
|
profile.dns = dns
|
||||||
|
profile.sniffer = sniffer
|
||||||
fs.writeFileSync(mihomoWorkConfigPath(), yaml.stringify(profile))
|
fs.writeFileSync(mihomoWorkConfigPath(), yaml.stringify(profile))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,32 @@ export const defaultControledMihomoConfig: Partial<IMihomoConfig> = {
|
|||||||
'auto-detect-interface': true,
|
'auto-detect-interface': true,
|
||||||
'dns-hijack': ['any:53'],
|
'dns-hijack': ['any:53'],
|
||||||
mtu: 1500
|
mtu: 1500
|
||||||
|
},
|
||||||
|
dns: {
|
||||||
|
enable: false,
|
||||||
|
ipv6: false,
|
||||||
|
'enhanced-mode': 'fake-ip',
|
||||||
|
'fake-ip-range': '198.18.0.1/16',
|
||||||
|
'use-hosts': false,
|
||||||
|
'use-system-hosts': false,
|
||||||
|
nameserver: ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query']
|
||||||
|
},
|
||||||
|
sniffer: {
|
||||||
|
enable: true,
|
||||||
|
'parse-pure-ip': true,
|
||||||
|
'override-destination': false,
|
||||||
|
sniff: {
|
||||||
|
HTTP: {
|
||||||
|
ports: [80, 443]
|
||||||
|
},
|
||||||
|
TLS: {
|
||||||
|
ports: [443]
|
||||||
|
},
|
||||||
|
QUIC: {
|
||||||
|
ports: [443]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'skip-domain': ['+.push.apple.com']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import routes from '@renderer/routes'
|
|||||||
import ProfileCard from '@renderer/components/sider/profile-card'
|
import ProfileCard from '@renderer/components/sider/profile-card'
|
||||||
import ProxyCard from '@renderer/components/sider/proxy-card'
|
import ProxyCard from '@renderer/components/sider/proxy-card'
|
||||||
import RuleCard from '@renderer/components/sider/rule-card'
|
import RuleCard from '@renderer/components/sider/rule-card'
|
||||||
|
import DNSCard from '@renderer/components/sider/dns-card'
|
||||||
|
import SniffCard from '@renderer/components/sider/sniff-card'
|
||||||
import OverrideCard from '@renderer/components/sider/override-card'
|
import OverrideCard from '@renderer/components/sider/override-card'
|
||||||
import ConnCard from '@renderer/components/sider/conn-card'
|
import ConnCard from '@renderer/components/sider/conn-card'
|
||||||
import LogCard from '@renderer/components/sider/log-card'
|
import LogCard from '@renderer/components/sider/log-card'
|
||||||
@ -75,6 +77,10 @@ const App: React.FC = () => {
|
|||||||
<ConnCard />
|
<ConnCard />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between mx-2">
|
||||||
|
<DNSCard />
|
||||||
|
<SniffCard />
|
||||||
|
</div>
|
||||||
<div className="flex justify-between mx-2">
|
<div className="flex justify-between mx-2">
|
||||||
<LogCard />
|
<LogCard />
|
||||||
<RuleCard />
|
<RuleCard />
|
||||||
|
|||||||
56
src/renderer/src/components/sider/dns-card.tsx
Normal file
56
src/renderer/src/components/sider/dns-card.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Button, Card, CardBody, CardFooter } from '@nextui-org/react'
|
||||||
|
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||||
|
import BorderSwitch from '@renderer/components/base/border-swtich'
|
||||||
|
import { MdOutlineDns } from 'react-icons/md'
|
||||||
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
import { patchMihomoConfig } from '@renderer/utils/ipc'
|
||||||
|
|
||||||
|
const DNSCard: React.FC = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/dns')
|
||||||
|
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig(true)
|
||||||
|
const { dns, tun } = controledMihomoConfig || {}
|
||||||
|
const { enable } = dns || {}
|
||||||
|
|
||||||
|
const onChange = async (enable: boolean): Promise<void> => {
|
||||||
|
await patchControledMihomoConfig({ dns: { enable } })
|
||||||
|
await patchMihomoConfig({ dns: { enable } })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className={`w-[50%] mr-1 mb-2 ${match ? 'bg-primary' : ''}`}
|
||||||
|
isPressable
|
||||||
|
onPress={() => navigate('/dns')}
|
||||||
|
>
|
||||||
|
<CardBody className="pb-1 pt-0 px-0">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
className="bg-transparent pointer-events-none"
|
||||||
|
variant="flat"
|
||||||
|
color="default"
|
||||||
|
>
|
||||||
|
<MdOutlineDns
|
||||||
|
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<BorderSwitch
|
||||||
|
isShowBorder={match && enable}
|
||||||
|
isSelected={enable}
|
||||||
|
isDisabled={tun?.enable}
|
||||||
|
onValueChange={onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
<CardFooter className="pt-1">
|
||||||
|
<h3 className={`select-none text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
|
||||||
|
DNS
|
||||||
|
</h3>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DNSCard
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Chip } from '@nextui-org/react'
|
import { Button, Card, CardBody, CardFooter, Chip } from '@nextui-org/react'
|
||||||
import { mihomoProxies } from '@renderer/utils/ipc'
|
import { mihomoProxies } from '@renderer/utils/ipc'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { MdTableChart } from 'react-icons/md'
|
import { LuGroup } from 'react-icons/lu'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ const ProxyCard: React.FC = () => {
|
|||||||
variant="flat"
|
variant="flat"
|
||||||
color="default"
|
color="default"
|
||||||
>
|
>
|
||||||
<MdTableChart
|
<LuGroup
|
||||||
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`}
|
className={`${match ? 'text-white' : 'text-foreground'} text-[24px] font-bold`}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Button, Card, CardBody, CardFooter, Chip } from '@nextui-org/react'
|
import { Button, Card, CardBody, CardFooter, Chip } from '@nextui-org/react'
|
||||||
import { mihomoRules } from '@renderer/utils/ipc'
|
import { mihomoRules } from '@renderer/utils/ipc'
|
||||||
import { IoGitNetwork } from 'react-icons/io5'
|
import { MdOutlineAltRoute } from 'react-icons/md'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ const RuleCard: React.FC = () => {
|
|||||||
variant="flat"
|
variant="flat"
|
||||||
color="default"
|
color="default"
|
||||||
>
|
>
|
||||||
<IoGitNetwork
|
<MdOutlineAltRoute
|
||||||
color="default"
|
color="default"
|
||||||
className={`${match ? 'text-white' : 'text-foreground'} text-[20px]`}
|
className={`${match ? 'text-white' : 'text-foreground'} text-[20px]`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
56
src/renderer/src/components/sider/sniff-card.tsx
Normal file
56
src/renderer/src/components/sider/sniff-card.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Button, Card, CardBody, CardFooter } from '@nextui-org/react'
|
||||||
|
import BorderSwitch from '@renderer/components/base/border-swtich'
|
||||||
|
import { GrDomain } from 'react-icons/gr'
|
||||||
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
import { patchMihomoConfig } from '@renderer/utils/ipc'
|
||||||
|
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||||
|
|
||||||
|
const SniffCard: React.FC = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
const match = location.pathname.includes('/sniffer')
|
||||||
|
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig(true)
|
||||||
|
const { sniffer } = controledMihomoConfig || {}
|
||||||
|
const { enable } = sniffer || {}
|
||||||
|
|
||||||
|
const onChange = async (enable: boolean): Promise<void> => {
|
||||||
|
await patchControledMihomoConfig({ sniffer: { enable } })
|
||||||
|
await patchMihomoConfig({ sniffer: { enable } })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className={`w-[50%] ml-1 mb-2 ${match ? 'bg-primary' : ''}`}
|
||||||
|
isPressable
|
||||||
|
onPress={() => navigate('/sniffer')}
|
||||||
|
>
|
||||||
|
<CardBody className="pb-1 pt-0 px-0">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
className="bg-transparent pointer-events-none"
|
||||||
|
variant="flat"
|
||||||
|
color="default"
|
||||||
|
>
|
||||||
|
<GrDomain
|
||||||
|
color="default"
|
||||||
|
className={`${match ? 'text-white' : 'text-foreground'} text-[24px]`}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<BorderSwitch
|
||||||
|
isShowBorder={match && enable}
|
||||||
|
isSelected={enable}
|
||||||
|
onValueChange={onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
<CardFooter className="pt-1">
|
||||||
|
<h3 className={`select-none text-md font-bold ${match ? 'text-white' : 'text-foreground'}`}>
|
||||||
|
域名嗅探
|
||||||
|
</h3>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SniffCard
|
||||||
@ -31,9 +31,13 @@ const TunSwitcher: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await patchControledMihomoConfig({ tun: { enable } })
|
if (enable) {
|
||||||
|
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||||
|
} else {
|
||||||
|
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||||
await patchMihomoConfig({ tun: { enable } })
|
await patchMihomoConfig({ tun: { enable } })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
209
src/renderer/src/pages/dns.tsx
Normal file
209
src/renderer/src/pages/dns.tsx
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
import { Button, Tab, Input, Switch, Tabs, Divider } from '@nextui-org/react'
|
||||||
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
|
import { MdDeleteForever } from 'react-icons/md'
|
||||||
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
|
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||||
|
import { restartCore } from '@renderer/utils/ipc'
|
||||||
|
import React, { Key, useState } from 'react'
|
||||||
|
|
||||||
|
const DNS: React.FC = () => {
|
||||||
|
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
||||||
|
const { dns, hosts } = controledMihomoConfig || {}
|
||||||
|
const {
|
||||||
|
ipv6 = false,
|
||||||
|
'enhanced-mode': enhancedMode = 'fake-ip',
|
||||||
|
'use-hosts': useHosts = false,
|
||||||
|
'use-system-hosts': useSystemHosts = false,
|
||||||
|
nameserver = ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query']
|
||||||
|
} = dns || {}
|
||||||
|
|
||||||
|
const [values, setValues] = useState({
|
||||||
|
ipv6,
|
||||||
|
useHosts,
|
||||||
|
enhancedMode,
|
||||||
|
useSystemHosts,
|
||||||
|
nameserver,
|
||||||
|
hosts: Object.entries(hosts || {}).map(([domain, value]) => ({ domain, value }))
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleNameserverChange = (value: string, index: number): void => {
|
||||||
|
const newNameservers = [...values.nameserver]
|
||||||
|
if (index === newNameservers.length) {
|
||||||
|
if (value.trim() !== '') {
|
||||||
|
newNameservers.push(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value.trim() === '') {
|
||||||
|
newNameservers.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
newNameservers[index] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setValues({ ...values, nameserver: newNameservers })
|
||||||
|
}
|
||||||
|
const handleHostsChange = (domain: string, value: string, index: number): void => {
|
||||||
|
const newHosts = [...values.hosts]
|
||||||
|
|
||||||
|
if (index === newHosts.length) {
|
||||||
|
if (domain.trim() !== '' || value.trim() !== '') {
|
||||||
|
newHosts.push({ domain: domain.trim(), value: value.trim() })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (domain.trim() === '' && value.trim() === '') {
|
||||||
|
newHosts.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
newHosts[index] = { domain: domain.trim(), value: value.trim() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setValues({ ...values, hosts: newHosts })
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSave = async (patch: Partial<IMihomoConfig>): Promise<void> => {
|
||||||
|
await patchControledMihomoConfig(patch)
|
||||||
|
await restartCore()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BasePage
|
||||||
|
title="DNS 设置"
|
||||||
|
header={
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="primary"
|
||||||
|
onPress={() => {
|
||||||
|
const hostsObject = values.hosts.reduce((acc, { domain, value }) => {
|
||||||
|
if (domain) {
|
||||||
|
acc[domain] = value
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
onSave({
|
||||||
|
dns: {
|
||||||
|
ipv6: values.ipv6,
|
||||||
|
'enhanced-mode': values.enhancedMode,
|
||||||
|
'use-hosts': values.useHosts,
|
||||||
|
'use-system-hosts': values.useSystemHosts,
|
||||||
|
nameserver: values.nameserver
|
||||||
|
},
|
||||||
|
hosts: hostsObject
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SettingCard>
|
||||||
|
<SettingItem title="域名映射模式" divider>
|
||||||
|
<Tabs
|
||||||
|
size="sm"
|
||||||
|
color="primary"
|
||||||
|
selectedKey={values.enhancedMode}
|
||||||
|
onSelectionChange={(key: Key) => setValues({ ...values, enhancedMode: key as DnsMode })}
|
||||||
|
>
|
||||||
|
<Tab key="fake-ip" title="虚假IP" className="select-none" />
|
||||||
|
<Tab key="redir-host" title="真实IP" className="select-none" />
|
||||||
|
<Tab key="normal" title="取消映射" className="select-none" />
|
||||||
|
</Tabs>
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem title="IPv6" divider>
|
||||||
|
<Switch
|
||||||
|
size="sm"
|
||||||
|
isSelected={values.ipv6}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setValues({ ...values, ipv6: v })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
<div className="flex flex-col items-stretch">
|
||||||
|
<h3 className="select-none mb-2">DNS服务器</h3>
|
||||||
|
{[...values.nameserver, ''].map((ns, index) => (
|
||||||
|
<div key={index} className="mb-2 flex">
|
||||||
|
<Input
|
||||||
|
fullWidth
|
||||||
|
size="sm"
|
||||||
|
placeholder="例: tls://223.5.5.5"
|
||||||
|
value={ns}
|
||||||
|
onValueChange={(v) => handleNameserverChange(v, index)}
|
||||||
|
/>
|
||||||
|
{index < values.nameserver.length && (
|
||||||
|
<Button
|
||||||
|
className="ml-2"
|
||||||
|
size="sm"
|
||||||
|
variant="flat"
|
||||||
|
color="warning"
|
||||||
|
onClick={() => handleNameserverChange('', index)}
|
||||||
|
>
|
||||||
|
<MdDeleteForever className="text-lg" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
<SettingItem title="使用系统hosts" divider>
|
||||||
|
<Switch
|
||||||
|
size="sm"
|
||||||
|
isSelected={values.useSystemHosts}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setValues({ ...values, useSystemHosts: v })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem title="自定义hosts">
|
||||||
|
<Switch
|
||||||
|
size="sm"
|
||||||
|
isSelected={values.useHosts}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setValues({ ...values, useHosts: v })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
{values.useHosts && (
|
||||||
|
<div className="flex flex-col items-stretch">
|
||||||
|
<h3 className="mb-2"></h3>
|
||||||
|
{[...values.hosts, { domain: '', value: '' }].map(({ domain, value }, index) => (
|
||||||
|
<div key={index} className="flex mb-2">
|
||||||
|
<div className="flex-[4]">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
fullWidth
|
||||||
|
placeholder="域名"
|
||||||
|
value={domain}
|
||||||
|
onValueChange={(v) =>
|
||||||
|
handleHostsChange(v, Array.isArray(value) ? value.join(', ') : value, index)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="select-none mx-2">:</span>
|
||||||
|
<div className="flex-[6] flex">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
fullWidth
|
||||||
|
placeholder="IP 或域名"
|
||||||
|
value={Array.isArray(value) ? value.join(', ') : value}
|
||||||
|
onValueChange={(v) => handleHostsChange(domain, v, index)}
|
||||||
|
/>
|
||||||
|
{index < values.hosts.length && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="warning"
|
||||||
|
variant="flat"
|
||||||
|
className="ml-2"
|
||||||
|
onClick={() => handleHostsChange('', '', index)}
|
||||||
|
>
|
||||||
|
<MdDeleteForever className="text-lg" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</SettingCard>
|
||||||
|
</BasePage>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DNS
|
||||||
188
src/renderer/src/pages/sniffer.tsx
Normal file
188
src/renderer/src/pages/sniffer.tsx
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
import { Button, Divider, Input, Switch } from '@nextui-org/react'
|
||||||
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
|
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||||
|
import { restartCore } from '@renderer/utils/ipc'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { MdDeleteForever } from 'react-icons/md'
|
||||||
|
|
||||||
|
const Sniffer: React.FC = () => {
|
||||||
|
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
||||||
|
const { sniffer } = controledMihomoConfig || {}
|
||||||
|
const {
|
||||||
|
'parse-pure-ip': parsePureIP = true,
|
||||||
|
'override-destination': overrideDestination = false,
|
||||||
|
sniff = {
|
||||||
|
HTTP: { ports: [80, 443] },
|
||||||
|
TLS: { ports: [443] },
|
||||||
|
QUIC: { ports: [443] }
|
||||||
|
},
|
||||||
|
'skip-domain': skipDomain = ['+.push.apple.com'],
|
||||||
|
'force-domain': forceDomain = []
|
||||||
|
} = sniffer || {}
|
||||||
|
|
||||||
|
const [values, setValues] = useState({
|
||||||
|
parsePureIP,
|
||||||
|
overrideDestination,
|
||||||
|
sniff,
|
||||||
|
skipDomain,
|
||||||
|
forceDomain
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSave = async (patch: Partial<IMihomoConfig>): Promise<void> => {
|
||||||
|
await patchControledMihomoConfig(patch)
|
||||||
|
await restartCore()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSniffPortChange = (protocol: keyof typeof sniff, value: string): void => {
|
||||||
|
setValues({
|
||||||
|
...values,
|
||||||
|
sniff: {
|
||||||
|
...values.sniff,
|
||||||
|
[protocol]: {
|
||||||
|
...values.sniff[protocol],
|
||||||
|
ports: value.split(',').map((port) => port.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handleDomainChange = (type: string, value: string, index: number): void => {
|
||||||
|
const newDomains = [...values[type]]
|
||||||
|
if (index === newDomains.length) {
|
||||||
|
if (value.trim() !== '') {
|
||||||
|
newDomains.push(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (value.trim() === '') {
|
||||||
|
newDomains.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
newDomains[index] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setValues({ ...values, [type]: newDomains })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BasePage
|
||||||
|
title="域名嗅探设置"
|
||||||
|
header={
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="primary"
|
||||||
|
onPress={() =>
|
||||||
|
onSave({
|
||||||
|
sniffer: {
|
||||||
|
'parse-pure-ip': values.parsePureIP,
|
||||||
|
'override-destination': values.overrideDestination,
|
||||||
|
sniff: values.sniff,
|
||||||
|
'skip-domain': values.skipDomain,
|
||||||
|
'force-domain': values.forceDomain
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SettingCard>
|
||||||
|
<SettingItem title="覆盖连接地址" divider>
|
||||||
|
<Switch
|
||||||
|
size="sm"
|
||||||
|
isSelected={values.overrideDestination}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setValues({ ...values, overrideDestination: v })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem title="强制嗅探IP地址" divider>
|
||||||
|
<Switch
|
||||||
|
size="sm"
|
||||||
|
isSelected={values.parsePureIP}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
setValues({ ...values, parsePureIP: v })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem title="嗅探 HTTP 端口" divider>
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
className="w-[50%]"
|
||||||
|
value={values.sniff.HTTP?.ports.join(',')}
|
||||||
|
onValueChange={(v) => handleSniffPortChange('HTTP', v)}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem title="嗅探 TLS 端口" divider>
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
className="w-[50%]"
|
||||||
|
value={values.sniff.TLS?.ports.join(',')}
|
||||||
|
onValueChange={(v) => handleSniffPortChange('TLS', v)}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem title="嗅探 QUIC 端口" divider>
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
className="w-[50%]"
|
||||||
|
value={values.sniff.QUIC?.ports.join(',')}
|
||||||
|
onValueChange={(v) => handleSniffPortChange('QUIC', v)}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
<div className="flex flex-col items-stretch">
|
||||||
|
<h3 className="select-none mb-2">跳过嗅探</h3>
|
||||||
|
{[...values.skipDomain, ''].map((d, index) => (
|
||||||
|
<div key={index} className="flex mb-2">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
fullWidth
|
||||||
|
placeholder="例: push.apple.com"
|
||||||
|
value={d}
|
||||||
|
onValueChange={(v) => handleDomainChange('skipDomain', v, index)}
|
||||||
|
/>
|
||||||
|
{index < values.skipDomain.length && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="warning"
|
||||||
|
variant="flat"
|
||||||
|
className="ml-2"
|
||||||
|
onClick={() => handleDomainChange('skipDomain', '', index)}
|
||||||
|
>
|
||||||
|
<MdDeleteForever className="text-lg" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
<div className="flex flex-col items-stretch">
|
||||||
|
<h3 className="select-none mb-2">强制嗅探</h3>
|
||||||
|
{[...values.forceDomain, ''].map((d, index) => (
|
||||||
|
<div key={index} className="flex mb-2">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
fullWidth
|
||||||
|
placeholder="例: v2ex.com"
|
||||||
|
value={d}
|
||||||
|
onValueChange={(v) => handleDomainChange('forceDomain', v, index)}
|
||||||
|
/>
|
||||||
|
{index < values.forceDomain.length && (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="warning"
|
||||||
|
variant="flat"
|
||||||
|
className="ml-2"
|
||||||
|
onClick={() => handleDomainChange('forceDomain', '', index)}
|
||||||
|
>
|
||||||
|
<MdDeleteForever className="text-lg" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SettingCard>
|
||||||
|
</BasePage>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Sniffer
|
||||||
@ -10,6 +10,8 @@ import Mihomo from '@renderer/pages/mihomo'
|
|||||||
import Sysproxy from '@renderer/pages/syspeoxy'
|
import Sysproxy from '@renderer/pages/syspeoxy'
|
||||||
import Tun from '@renderer/pages/tun'
|
import Tun from '@renderer/pages/tun'
|
||||||
import Tests from '@renderer/pages/tests'
|
import Tests from '@renderer/pages/tests'
|
||||||
|
import DNS from '@renderer/pages/dns'
|
||||||
|
import Sniffer from '@renderer/pages/sniffer'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@ -36,6 +38,14 @@ const routes = [
|
|||||||
path: '/tests',
|
path: '/tests',
|
||||||
element: <Tests />
|
element: <Tests />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/dns',
|
||||||
|
element: <DNS />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/sniffer',
|
||||||
|
element: <Sniffer />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/logs',
|
path: '/logs',
|
||||||
element: <Logs />
|
element: <Logs />
|
||||||
|
|||||||
60
src/shared/types.d.ts
vendored
60
src/shared/types.d.ts
vendored
@ -1,10 +1,30 @@
|
|||||||
type OutboundMode = 'rule' | 'global' | 'direct'
|
type OutboundMode = 'rule' | 'global' | 'direct'
|
||||||
type LogLevel = 'info' | 'debug' | 'warning' | 'error' | 'silent'
|
type LogLevel = 'info' | 'debug' | 'warning' | 'error' | 'silent'
|
||||||
type SysProxyMode = 'auto' | 'manual'
|
type SysProxyMode = 'auto' | 'manual'
|
||||||
type MihomoGroupType = 'Selector'
|
type MihomoGroupType = 'Selector' | 'URLTest' | 'LoadBalance' | 'Relay'
|
||||||
type MihomoProxyType = 'Shadowsocks'
|
type MihomoProxyType =
|
||||||
|
| 'Direct'
|
||||||
|
| 'Reject'
|
||||||
|
| 'RejectDrop'
|
||||||
|
| 'Pass'
|
||||||
|
| 'Dns'
|
||||||
|
| 'Compatible'
|
||||||
|
| 'Socks5'
|
||||||
|
| 'Http'
|
||||||
|
| 'Ssh'
|
||||||
|
| 'Shadowsocks'
|
||||||
|
| 'ShadowsocksR'
|
||||||
|
| 'Snell'
|
||||||
|
| 'Vmess'
|
||||||
|
| 'Vless'
|
||||||
|
| 'Trojan'
|
||||||
|
| 'Hysteria'
|
||||||
|
| 'Hysteria2'
|
||||||
|
| 'Tuic'
|
||||||
|
| 'WireGuard'
|
||||||
type TunStack = 'gvisor' | 'mixed' | 'system'
|
type TunStack = 'gvisor' | 'mixed' | 'system'
|
||||||
type FindProcessMode = 'off' | 'strict' | 'always'
|
type FindProcessMode = 'off' | 'strict' | 'always'
|
||||||
|
type DnsMode = 'normal' | 'fake-ip' | 'redir-host'
|
||||||
|
|
||||||
interface IMihomoVersion {
|
interface IMihomoVersion {
|
||||||
version: string
|
version: string
|
||||||
@ -174,6 +194,39 @@ interface IMihomoTunConfig {
|
|||||||
'include-package'?: string[]
|
'include-package'?: string[]
|
||||||
'exclude-package'?: string[]
|
'exclude-package'?: string[]
|
||||||
}
|
}
|
||||||
|
interface IMihomoDNSConfig {
|
||||||
|
enable?: boolean
|
||||||
|
ipv6?: boolean
|
||||||
|
'enhanced-mode'?: DnsMode
|
||||||
|
'fake-ip-range'?: string
|
||||||
|
'use-hosts'?: boolean
|
||||||
|
'use-system-hosts'?: boolean
|
||||||
|
'respect-rules'?: boolean
|
||||||
|
nameserver?: string[]
|
||||||
|
'proxy-server-nameserver'?: string[]
|
||||||
|
'nameserver-policy'?: { [key: string]: string | string[] }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMihomoSnifferConfig {
|
||||||
|
enable?: boolean
|
||||||
|
'parse-pure-ip'?: boolean
|
||||||
|
'override-destination'?: boolean
|
||||||
|
'force-dns-mapping'?: boolean
|
||||||
|
'force-domain'?: string[]
|
||||||
|
'skip-domain'?: string[]
|
||||||
|
sniff?: {
|
||||||
|
HTTP?: {
|
||||||
|
ports: (number | string)[]
|
||||||
|
'override-destination'?: boolean
|
||||||
|
}
|
||||||
|
TLS?: {
|
||||||
|
ports: (number | string)[]
|
||||||
|
}
|
||||||
|
QUIC?: {
|
||||||
|
ports: (number | string)[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
interface IMihomoConfig {
|
interface IMihomoConfig {
|
||||||
'external-controller': string
|
'external-controller': string
|
||||||
secret?: string
|
secret?: string
|
||||||
@ -191,7 +244,10 @@ interface IMihomoConfig {
|
|||||||
proxies?: []
|
proxies?: []
|
||||||
'proxy-groups'?: []
|
'proxy-groups'?: []
|
||||||
rules?: []
|
rules?: []
|
||||||
|
hosts?: { [key: string]: string | string[] }
|
||||||
tun: IMihomoTunConfig
|
tun: IMihomoTunConfig
|
||||||
|
dns: IMihomoDNSConfig
|
||||||
|
sniffer: IMihomoSnifferConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProfileConfig {
|
interface IProfileConfig {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user