feat: Support Tun Config (#416)

This commit is contained in:
MystiPanda 2024-02-20 23:27:03 +08:00 committed by GitHub
parent bca3685eda
commit 7551b45da2
8 changed files with 264 additions and 19 deletions

View File

@ -23,6 +23,14 @@ impl IClashTemp {
pub fn template() -> Self {
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("socks-port".into(), 7898.into());
@ -32,6 +40,7 @@ impl IClashTemp {
map.insert("mode".into(), "rule".into());
map.insert("external-controller".into(), "127.0.0.1:9097".into());
map.insert("secret".into(), "".into());
map.insert("tun".into(), tun.into());
Self(map)
}

View File

@ -1,7 +1,7 @@
use crate::enhance::field::use_keys;
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use serde_yaml::{Mapping, Value};
use std::collections::HashMap;
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IRuntime {
pub config: Option<Mapping>,
@ -16,7 +16,7 @@ impl IRuntime {
Self::default()
}
// 这里只更改 allow-lan | ipv6 | log-level
// 这里只更改 allow-lan | ipv6 | log-level | tun
pub fn patch_config(&mut self, patch: Mapping) {
if let Some(config) = self.config.as_mut() {
["allow-lan", "ipv6", "log-level"]
@ -26,6 +26,20 @@ impl IRuntime {
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));
}
}
}

View File

@ -1,5 +1,5 @@
mod chain;
mod field;
pub mod field;
mod merge;
mod script;
mod tun;
@ -78,8 +78,19 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
// 合并默认的config
for (key, value) in clash_config.into_iter() {
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);
}
}
// 内建脚本最后跑
if enable_builtin {

View File

@ -30,12 +30,6 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
});
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);

View 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>
);
});

View File

@ -41,7 +41,6 @@ const SettingClash = ({ onError }: Props) => {
const { enable_random_port = false, verge_mixed_port } = verge ?? {};
const webRef = useRef<DialogRef>(null);
const fieldRef = useRef<DialogRef>(null);
const portRef = useRef<DialogRef>(null);
const ctrlRef = useRef<DialogRef>(null);
const coreRef = useRef<DialogRef>(null);

View File

@ -10,6 +10,7 @@ import { SettingList, SettingItem } from "./mods/setting-comp";
import { GuardState } from "./mods/guard-state";
import { ServiceViewer } from "./mods/service-viewer";
import { SysproxyViewer } from "./mods/sysproxy-viewer";
import { TunViewer } from "./mods/tun-viewer";
import getSystem from "@/utils/get-system";
interface Props {
@ -36,6 +37,7 @@ const SettingSystem = ({ onError }: Props) => {
const serviceRef = useRef<DialogRef>(null);
const sysproxyRef = useRef<DialogRef>(null);
const tunRef = useRef<DialogRef>(null);
const {
enable_tun_mode,
@ -53,6 +55,7 @@ const SettingSystem = ({ onError }: Props) => {
return (
<SettingList title={t("System Setting")}>
<SysproxyViewer ref={sysproxyRef} />
<TunViewer ref={tunRef} />
{isWIN && (
<ServiceViewer ref={serviceRef} enable={!!enable_service_mode} />
)}
@ -60,8 +63,11 @@ const SettingSystem = ({ onError }: Props) => {
<SettingItem
label={t("Tun Mode")}
extra={
<>
<Tooltip
title={isWIN ? t("Tun Mode Info Windows") : t("Tun Mode Info Unix")}
title={
isWIN ? t("Tun Mode Info Windows") : t("Tun Mode Info Unix")
}
placement="top"
>
<IconButton color="inherit" size="small">
@ -71,6 +77,17 @@ const SettingSystem = ({ onError }: Props) => {
/>
</IconButton>
</Tooltip>
<IconButton
color="inherit"
size="small"
onClick={() => tunRef.current?.open()}
>
<Settings
fontSize="inherit"
style={{ cursor: "pointer", opacity: 0.75 }}
/>
</IconButton>
</>
}
>
<GuardState

View File

@ -32,6 +32,15 @@ interface IConfigData {
"tproxy-port": number;
"external-controller": 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 {