mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 04:33:45 +08:00
feat: Support Tun Config (#416)
This commit is contained in:
parent
bca3685eda
commit
7551b45da2
@ -23,6 +23,14 @@ impl IClashTemp {
|
|||||||
|
|
||||||
pub fn template() -> Self {
|
pub fn template() -> Self {
|
||||||
let mut map = Mapping::new();
|
let mut map = Mapping::new();
|
||||||
|
let mut tun = Mapping::new();
|
||||||
|
tun.insert("stack".into(), "gVisor".into());
|
||||||
|
tun.insert("device".into(), "Meta".into());
|
||||||
|
tun.insert("auto-route".into(), true.into());
|
||||||
|
tun.insert("strict-route".into(), true.into());
|
||||||
|
tun.insert("auto-detect-interface".into(), true.into());
|
||||||
|
tun.insert("dns-hijack".into(), vec!["any:53", "tcp://any:53"].into());
|
||||||
|
tun.insert("mtu".into(), 9000.into());
|
||||||
|
|
||||||
map.insert("mixed-port".into(), 7897.into());
|
map.insert("mixed-port".into(), 7897.into());
|
||||||
map.insert("socks-port".into(), 7898.into());
|
map.insert("socks-port".into(), 7898.into());
|
||||||
@ -32,6 +40,7 @@ impl IClashTemp {
|
|||||||
map.insert("mode".into(), "rule".into());
|
map.insert("mode".into(), "rule".into());
|
||||||
map.insert("external-controller".into(), "127.0.0.1:9097".into());
|
map.insert("external-controller".into(), "127.0.0.1:9097".into());
|
||||||
map.insert("secret".into(), "".into());
|
map.insert("secret".into(), "".into());
|
||||||
|
map.insert("tun".into(), tun.into());
|
||||||
|
|
||||||
Self(map)
|
Self(map)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
use crate::enhance::field::use_keys;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::{Mapping, Value};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct IRuntime {
|
pub struct IRuntime {
|
||||||
pub config: Option<Mapping>,
|
pub config: Option<Mapping>,
|
||||||
@ -16,7 +16,7 @@ impl IRuntime {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 这里只更改 allow-lan | ipv6 | log-level
|
// 这里只更改 allow-lan | ipv6 | log-level | tun
|
||||||
pub fn patch_config(&mut self, patch: Mapping) {
|
pub fn patch_config(&mut self, patch: Mapping) {
|
||||||
if let Some(config) = self.config.as_mut() {
|
if let Some(config) = self.config.as_mut() {
|
||||||
["allow-lan", "ipv6", "log-level"]
|
["allow-lan", "ipv6", "log-level"]
|
||||||
@ -26,6 +26,20 @@ impl IRuntime {
|
|||||||
config.insert(key.into(), value.clone());
|
config.insert(key.into(), value.clone());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let tun = config.get("tun");
|
||||||
|
let mut tun = tun.map_or(Mapping::new(), |val| {
|
||||||
|
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||||
|
});
|
||||||
|
let patch_tun = patch.get("tun");
|
||||||
|
let patch_tun = patch_tun.map_or(Mapping::new(), |val| {
|
||||||
|
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||||
|
});
|
||||||
|
use_keys(&patch_tun).into_iter().for_each(|key| {
|
||||||
|
if let Some(value) = patch_tun.get(&key).to_owned() {
|
||||||
|
tun.insert(key.into(), value.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
config.insert("tun".into(), Value::from(tun));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
mod chain;
|
mod chain;
|
||||||
mod field;
|
pub mod field;
|
||||||
mod merge;
|
mod merge;
|
||||||
mod script;
|
mod script;
|
||||||
mod tun;
|
mod tun;
|
||||||
@ -78,7 +78,18 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
|
|
||||||
// 合并默认的config
|
// 合并默认的config
|
||||||
for (key, value) in clash_config.into_iter() {
|
for (key, value) in clash_config.into_iter() {
|
||||||
config.insert(key, value);
|
if key.as_str() == Some("tun") {
|
||||||
|
let mut tun = config.get_mut("tun").map_or(Mapping::new(), |val| {
|
||||||
|
val.as_mapping().cloned().unwrap_or(Mapping::new())
|
||||||
|
});
|
||||||
|
let patch_tun = value.as_mapping().cloned().unwrap_or(Mapping::new());
|
||||||
|
for (key, value) in patch_tun.into_iter() {
|
||||||
|
tun.insert(key, value);
|
||||||
|
}
|
||||||
|
config.insert("tun".into(), tun.into());
|
||||||
|
} else {
|
||||||
|
config.insert(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 内建脚本最后跑
|
// 内建脚本最后跑
|
||||||
|
@ -30,12 +30,6 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
|||||||
});
|
});
|
||||||
|
|
||||||
revise!(tun_val, "enable", enable);
|
revise!(tun_val, "enable", enable);
|
||||||
if enable {
|
|
||||||
append!(tun_val, "stack", "gvisor");
|
|
||||||
append!(tun_val, "dns-hijack", vec!["any:53"]);
|
|
||||||
append!(tun_val, "auto-route", true);
|
|
||||||
append!(tun_val, "auto-detect-interface", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
revise!(config, "tun", tun_val);
|
revise!(config, "tun", tun_val);
|
||||||
|
|
||||||
|
192
src/components/setting/mods/tun-viewer.tsx
Normal file
192
src/components/setting/mods/tun-viewer.tsx
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||||
|
import { useLockFn } from "ahooks";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemText,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { useClash } from "@/hooks/use-clash";
|
||||||
|
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||||
|
|
||||||
|
export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { clash, mutateClash, patchClash } = useClash();
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [values, setValues] = useState({
|
||||||
|
stack: "gVisor",
|
||||||
|
device: "Mihomo",
|
||||||
|
autoRoute: true,
|
||||||
|
autoDetectInterface: true,
|
||||||
|
dnsHijack: ["any:53", "tcp://any:53"],
|
||||||
|
strictRoute: true,
|
||||||
|
mtu: 9000,
|
||||||
|
});
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
open: () => {
|
||||||
|
setOpen(true);
|
||||||
|
setValues({
|
||||||
|
stack: clash?.tun.stack ?? "gVisor",
|
||||||
|
device: clash?.tun.device ?? "Mihomo",
|
||||||
|
autoRoute: clash?.tun["auto-route"] ?? true,
|
||||||
|
autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
|
||||||
|
dnsHijack: clash?.tun["dns-hijack"] ?? ["any:53", "tcp://any:53"],
|
||||||
|
strictRoute: clash?.tun["strict-route"] ?? true,
|
||||||
|
mtu: clash?.tun.mtu ?? 9000,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
close: () => setOpen(false),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const onSave = useLockFn(async () => {
|
||||||
|
try {
|
||||||
|
let tun = {
|
||||||
|
stack: values.stack,
|
||||||
|
device: values.device,
|
||||||
|
"auto-route": values.autoRoute,
|
||||||
|
"auto-detect-interface": values.autoDetectInterface,
|
||||||
|
"dns-hijack": values.dnsHijack,
|
||||||
|
"strict-route": values.strictRoute,
|
||||||
|
mtu: values.mtu,
|
||||||
|
};
|
||||||
|
await patchClash({ tun });
|
||||||
|
await mutateClash(
|
||||||
|
(old) => ({
|
||||||
|
...(old! || {}),
|
||||||
|
tun,
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
setOpen(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
Notice.error(err.message || err.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog
|
||||||
|
open={open}
|
||||||
|
title={t("Tun Mode")}
|
||||||
|
contentSx={{ width: 450 }}
|
||||||
|
okBtn={t("Save")}
|
||||||
|
cancelBtn={t("Cancel")}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
onOk={onSave}
|
||||||
|
>
|
||||||
|
<List>
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Stack")} />
|
||||||
|
<Select
|
||||||
|
size="small"
|
||||||
|
sx={{ width: 100, "> div": { py: "7.5px" } }}
|
||||||
|
value={values.stack}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValues((v) => ({
|
||||||
|
...v,
|
||||||
|
stack: e.target.value as string,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{["System", "gVisor", "Mixed"].map((i) => (
|
||||||
|
<MenuItem value={i} key={i}>
|
||||||
|
{i}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Device")} />
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
sx={{ width: 250 }}
|
||||||
|
value={values.device}
|
||||||
|
placeholder="Mihomo"
|
||||||
|
onChange={(e) =>
|
||||||
|
setValues((v) => ({ ...v, device: e.target.value }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Auto Route")} />
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={values.autoRoute}
|
||||||
|
onChange={(_, c) => setValues((v) => ({ ...v, autoRoute: c }))}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Strict Route")} />
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={values.strictRoute}
|
||||||
|
onChange={(_, c) => setValues((v) => ({ ...v, strictRoute: c }))}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("Auto Detect Interface")} />
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
checked={values.autoDetectInterface}
|
||||||
|
onChange={(_, c) =>
|
||||||
|
setValues((v) => ({ ...v, autoDetectInterface: c }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("DNS Hijack")} />
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
sx={{ width: 250 }}
|
||||||
|
value={values.dnsHijack.join(",")}
|
||||||
|
placeholder="Please use , to separate multiple DNS servers"
|
||||||
|
onChange={(e) =>
|
||||||
|
setValues((v) => ({ ...v, dnsHijack: e.target.value.split(",") }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem sx={{ padding: "5px 2px" }}>
|
||||||
|
<ListItemText primary={t("MTU")} />
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
type="number"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
sx={{ width: 250 }}
|
||||||
|
value={values.mtu}
|
||||||
|
placeholder="9000"
|
||||||
|
onChange={(e) =>
|
||||||
|
setValues((v) => ({
|
||||||
|
...v,
|
||||||
|
mtu: parseInt(e.target.value),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
});
|
@ -41,7 +41,6 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
const { enable_random_port = false, verge_mixed_port } = verge ?? {};
|
const { enable_random_port = false, verge_mixed_port } = verge ?? {};
|
||||||
|
|
||||||
const webRef = useRef<DialogRef>(null);
|
const webRef = useRef<DialogRef>(null);
|
||||||
const fieldRef = useRef<DialogRef>(null);
|
|
||||||
const portRef = useRef<DialogRef>(null);
|
const portRef = useRef<DialogRef>(null);
|
||||||
const ctrlRef = useRef<DialogRef>(null);
|
const ctrlRef = useRef<DialogRef>(null);
|
||||||
const coreRef = useRef<DialogRef>(null);
|
const coreRef = useRef<DialogRef>(null);
|
||||||
|
@ -10,6 +10,7 @@ import { SettingList, SettingItem } from "./mods/setting-comp";
|
|||||||
import { GuardState } from "./mods/guard-state";
|
import { GuardState } from "./mods/guard-state";
|
||||||
import { ServiceViewer } from "./mods/service-viewer";
|
import { ServiceViewer } from "./mods/service-viewer";
|
||||||
import { SysproxyViewer } from "./mods/sysproxy-viewer";
|
import { SysproxyViewer } from "./mods/sysproxy-viewer";
|
||||||
|
import { TunViewer } from "./mods/tun-viewer";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -36,6 +37,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
|
|
||||||
const serviceRef = useRef<DialogRef>(null);
|
const serviceRef = useRef<DialogRef>(null);
|
||||||
const sysproxyRef = useRef<DialogRef>(null);
|
const sysproxyRef = useRef<DialogRef>(null);
|
||||||
|
const tunRef = useRef<DialogRef>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
enable_tun_mode,
|
enable_tun_mode,
|
||||||
@ -53,6 +55,7 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<SettingList title={t("System Setting")}>
|
<SettingList title={t("System Setting")}>
|
||||||
<SysproxyViewer ref={sysproxyRef} />
|
<SysproxyViewer ref={sysproxyRef} />
|
||||||
|
<TunViewer ref={tunRef} />
|
||||||
{isWIN && (
|
{isWIN && (
|
||||||
<ServiceViewer ref={serviceRef} enable={!!enable_service_mode} />
|
<ServiceViewer ref={serviceRef} enable={!!enable_service_mode} />
|
||||||
)}
|
)}
|
||||||
@ -60,17 +63,31 @@ const SettingSystem = ({ onError }: Props) => {
|
|||||||
<SettingItem
|
<SettingItem
|
||||||
label={t("Tun Mode")}
|
label={t("Tun Mode")}
|
||||||
extra={
|
extra={
|
||||||
<Tooltip
|
<>
|
||||||
title={isWIN ? t("Tun Mode Info Windows") : t("Tun Mode Info Unix")}
|
<Tooltip
|
||||||
placement="top"
|
title={
|
||||||
>
|
isWIN ? t("Tun Mode Info Windows") : t("Tun Mode Info Unix")
|
||||||
<IconButton color="inherit" size="small">
|
}
|
||||||
<InfoRounded
|
placement="top"
|
||||||
|
>
|
||||||
|
<IconButton color="inherit" size="small">
|
||||||
|
<InfoRounded
|
||||||
|
fontSize="inherit"
|
||||||
|
style={{ cursor: "pointer", opacity: 0.75 }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => tunRef.current?.open()}
|
||||||
|
>
|
||||||
|
<Settings
|
||||||
fontSize="inherit"
|
fontSize="inherit"
|
||||||
style={{ cursor: "pointer", opacity: 0.75 }}
|
style={{ cursor: "pointer", opacity: 0.75 }}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<GuardState
|
<GuardState
|
||||||
|
9
src/services/types.d.ts
vendored
9
src/services/types.d.ts
vendored
@ -32,6 +32,15 @@ interface IConfigData {
|
|||||||
"tproxy-port": number;
|
"tproxy-port": number;
|
||||||
"external-controller": string;
|
"external-controller": string;
|
||||||
secret: string;
|
secret: string;
|
||||||
|
tun: {
|
||||||
|
stack: string;
|
||||||
|
device: string;
|
||||||
|
"auto-route": boolean;
|
||||||
|
"auto-detect-interface": boolean;
|
||||||
|
"dns-hijack": string[];
|
||||||
|
"strict-route": boolean;
|
||||||
|
mtu: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRuleItem {
|
interface IRuleItem {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user