Explorar el Código

feat: Support Tun Config (#416)

MystiPanda hace 1 año
padre
commit
8378320e50

+ 9 - 0
src-tauri/src/config/clash.rs

@@ -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)
     }

+ 17 - 3
src-tauri/src/config/runtime.rs

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

+ 13 - 2
src-tauri/src/enhance/mod.rs

@@ -1,5 +1,5 @@
 mod chain;
-mod field;
+pub mod field;
 mod merge;
 mod script;
 mod tun;
@@ -78,7 +78,18 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
 
     // 合并默认的config
     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);
+        }
     }
 
     // 内建脚本最后跑

+ 0 - 6
src-tauri/src/enhance/tun.rs

@@ -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);
 

+ 192 - 0
src/components/setting/mods/tun-viewer.tsx

@@ -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>
+  );
+});

+ 0 - 1
src/components/setting/setting-clash.tsx

@@ -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);

+ 24 - 7
src/components/setting/setting-system.tsx

@@ -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,17 +63,31 @@ const SettingSystem = ({ onError }: Props) => {
       <SettingItem
         label={t("Tun Mode")}
         extra={
-          <Tooltip
-            title={isWIN ? t("Tun Mode Info Windows") : t("Tun Mode Info Unix")}
-            placement="top"
-          >
-            <IconButton color="inherit" size="small">
-              <InfoRounded
+          <>
+            <Tooltip
+              title={
+                isWIN ? t("Tun Mode Info Windows") : t("Tun Mode Info Unix")
+              }
+              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"
                 style={{ cursor: "pointer", opacity: 0.75 }}
               />
             </IconButton>
-          </Tooltip>
+          </>
         }
       >
         <GuardState

+ 9 - 0
src/services/types.d.ts

@@ -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 {