feat(tun): auto-redirect GUI support for linux (#5565)

* feat(tun): auto-redirect GUI support for linux

* refactor(tun-viewer): unify Linux-only auto-redirect row layout
This commit is contained in:
Sline
2025-11-22 18:58:57 +08:00
committed by GitHub
parent 45020fceda
commit 4ff186497c
17 changed files with 110 additions and 30 deletions

View File

@@ -13,6 +13,7 @@ import { useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BaseDialog, DialogRef, Switch } from "@/components/base"; import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { useClash } from "@/hooks/use-clash"; import { useClash } from "@/hooks/use-clash";
import { enhanceProfiles } from "@/services/cmds"; import { enhanceProfiles } from "@/services/cmds";
import { showNotice } from "@/services/noticeService"; import { showNotice } from "@/services/noticeService";
@@ -32,6 +33,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
stack: "mixed", stack: "mixed",
device: OS === "macos" ? "utun1024" : "Mihomo", device: OS === "macos" ? "utun1024" : "Mihomo",
autoRoute: true, autoRoute: true,
autoRedirect: OS === "linux",
autoDetectInterface: true, autoDetectInterface: true,
dnsHijack: ["any:53"], dnsHijack: ["any:53"],
strictRoute: false, strictRoute: false,
@@ -41,10 +43,15 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
open: () => { open: () => {
setOpen(true); setOpen(true);
const nextAutoRoute = clash?.tun["auto-route"] ?? true;
const rawAutoRedirect = clash?.tun["auto-redirect"] ?? true;
const computedAutoRedirect =
OS === "linux" ? (nextAutoRoute ? rawAutoRedirect : false) : false;
setValues({ setValues({
stack: clash?.tun.stack ?? "gvisor", stack: clash?.tun.stack ?? "gvisor",
device: clash?.tun.device ?? (OS === "macos" ? "utun1024" : "Mihomo"), device: clash?.tun.device ?? (OS === "macos" ? "utun1024" : "Mihomo"),
autoRoute: clash?.tun["auto-route"] ?? true, autoRoute: nextAutoRoute,
autoRedirect: computedAutoRedirect,
autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true, autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
dnsHijack: clash?.tun["dns-hijack"] ?? ["any:53"], dnsHijack: clash?.tun["dns-hijack"] ?? ["any:53"],
strictRoute: clash?.tun["strict-route"] ?? false, strictRoute: clash?.tun["strict-route"] ?? false,
@@ -56,7 +63,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
const onSave = useLockFn(async () => { const onSave = useLockFn(async () => {
try { try {
const tun = { const tun: IConfigData["tun"] = {
stack: values.stack, stack: values.stack,
device: device:
values.device === "" values.device === ""
@@ -65,6 +72,11 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
: "Mihomo" : "Mihomo"
: values.device, : values.device,
"auto-route": values.autoRoute, "auto-route": values.autoRoute,
...(OS === "linux"
? {
"auto-redirect": values.autoRedirect,
}
: {}),
"auto-detect-interface": values.autoDetectInterface, "auto-detect-interface": values.autoDetectInterface,
"dns-hijack": values.dnsHijack[0] === "" ? [] : values.dnsHijack, "dns-hijack": values.dnsHijack[0] === "" ? [] : values.dnsHijack,
"strict-route": values.strictRoute, "strict-route": values.strictRoute,
@@ -100,10 +112,15 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
variant="outlined" variant="outlined"
size="small" size="small"
onClick={async () => { onClick={async () => {
const tun = { const tun: IConfigData["tun"] = {
stack: "gvisor", stack: "gvisor",
device: OS === "macos" ? "utun1024" : "Mihomo", device: OS === "macos" ? "utun1024" : "Mihomo",
"auto-route": true, "auto-route": true,
...(OS === "linux"
? {
"auto-redirect": true,
}
: {}),
"auto-detect-interface": true, "auto-detect-interface": true,
"dns-hijack": ["any:53"], "dns-hijack": ["any:53"],
"strict-route": false, "strict-route": false,
@@ -113,6 +130,7 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
stack: "gvisor", stack: "gvisor",
device: OS === "macos" ? "utun1024" : "Mihomo", device: OS === "macos" ? "utun1024" : "Mihomo",
autoRoute: true, autoRoute: true,
autoRedirect: OS === "linux" ? true : false,
autoDetectInterface: true, autoDetectInterface: true,
dnsHijack: ["any:53"], dnsHijack: ["any:53"],
strictRoute: false, strictRoute: false,
@@ -175,10 +193,41 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
<Switch <Switch
edge="end" edge="end"
checked={values.autoRoute} checked={values.autoRoute}
onChange={(_, c) => setValues((v) => ({ ...v, autoRoute: c }))} onChange={(_, c) =>
setValues((v) => ({
...v,
autoRoute: c,
autoRedirect: c ? v.autoRedirect : false,
}))
}
/> />
</ListItem> </ListItem>
{OS === "linux" && (
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("settings.modals.tun.fields.autoRedirect")}
sx={{ maxWidth: "fit-content" }}
/>
<TooltipIcon
title={t("settings.modals.tun.tooltips.autoRedirect")}
sx={{ opacity: values.autoRoute ? 0.7 : 0.3 }}
/>
<Switch
edge="end"
checked={values.autoRedirect}
onChange={(_, c) =>
setValues((v) => ({
...v,
autoRedirect: v.autoRoute ? c : v.autoRedirect,
}))
}
disabled={!values.autoRoute}
sx={{ marginLeft: "auto" }}
/>
</ListItem>
)}
<ListItem sx={{ padding: "5px 2px" }}> <ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("settings.modals.tun.fields.strictRoute")} /> <ListItemText primary={t("settings.modals.tun.fields.strictRoute")} />
<Switch <Switch

View File

@@ -427,10 +427,12 @@
"strictRoute": "توجيه صارم", "strictRoute": "توجيه صارم",
"autoDetectInterface": "الكشف التلقائي عن الواجهة", "autoDetectInterface": "الكشف التلقائي عن الواجهة",
"dnsHijack": "اختطاف DNS", "dnsHijack": "اختطاف DNS",
"mtu": "وحدة الإرسال القصوى" "mtu": "وحدة الإرسال القصوى",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers" "dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "تم تطبيق الإعدادات" "applied": "تم تطبيق الإعدادات"

View File

@@ -427,10 +427,12 @@
"strictRoute": "Strenges Routing", "strictRoute": "Strenges Routing",
"autoDetectInterface": "Netzwerkschnittstelle automatisch auswählen", "autoDetectInterface": "Netzwerkschnittstelle automatisch auswählen",
"dnsHijack": "DNS-Hijacking", "dnsHijack": "DNS-Hijacking",
"mtu": "Maximale Übertragungseinheit" "mtu": "Maximale Übertragungseinheit",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers" "dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "Einstellungen angewendet" "applied": "Einstellungen angewendet"

View File

@@ -427,10 +427,12 @@
"strictRoute": "Strict Route", "strictRoute": "Strict Route",
"autoDetectInterface": "Auto Detect Interface", "autoDetectInterface": "Auto Detect Interface",
"dnsHijack": "DNS Hijack", "dnsHijack": "DNS Hijack",
"mtu": "Max Transmission Unit" "mtu": "Max Transmission Unit",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers" "dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "Settings Applied" "applied": "Settings Applied"

View File

@@ -427,10 +427,12 @@
"strictRoute": "Enrutamiento estricto", "strictRoute": "Enrutamiento estricto",
"autoDetectInterface": "Detectar automáticamente la interfaz de salida del tráfico", "autoDetectInterface": "Detectar automáticamente la interfaz de salida del tráfico",
"dnsHijack": "Secuestro de DNS", "dnsHijack": "Secuestro de DNS",
"mtu": "Unidad máxima de transmisión" "mtu": "Unidad máxima de transmisión",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers" "dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "Ajustes aplicados" "applied": "Ajustes aplicados"

View File

@@ -427,10 +427,12 @@
"strictRoute": "مسیر دقیق", "strictRoute": "مسیر دقیق",
"autoDetectInterface": "تشخیص خودکار رابط", "autoDetectInterface": "تشخیص خودکار رابط",
"dnsHijack": "ربایش DNS", "dnsHijack": "ربایش DNS",
"mtu": "واحد حداکثر انتقال" "mtu": "واحد حداکثر انتقال",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers" "dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "تنظیمات اعمال شد" "applied": "تنظیمات اعمال شد"

View File

@@ -427,10 +427,12 @@
"strictRoute": "Rute Ketat", "strictRoute": "Rute Ketat",
"autoDetectInterface": "Deteksi Antarmuka Otomatis", "autoDetectInterface": "Deteksi Antarmuka Otomatis",
"dnsHijack": "Pembajakan DNS", "dnsHijack": "Pembajakan DNS",
"mtu": "Unit Transmisi Maksimum" "mtu": "Unit Transmisi Maksimum",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers" "dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "Pengaturan Diterapkan" "applied": "Pengaturan Diterapkan"

View File

@@ -427,10 +427,12 @@
"strictRoute": "厳格なルート", "strictRoute": "厳格なルート",
"autoDetectInterface": "トラフィックの出口インターフェースを自動選択", "autoDetectInterface": "トラフィックの出口インターフェースを自動選択",
"dnsHijack": "DNSハイジャック", "dnsHijack": "DNSハイジャック",
"mtu": "最大転送単位" "mtu": "最大転送単位",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers" "dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "設定が適用されました。" "applied": "設定が適用されました。"

View File

@@ -427,10 +427,12 @@
"strictRoute": "엄격 라우팅", "strictRoute": "엄격 라우팅",
"autoDetectInterface": "인터페이스 자동 감지", "autoDetectInterface": "인터페이스 자동 감지",
"dnsHijack": "DNS 하이재킹", "dnsHijack": "DNS 하이재킹",
"mtu": "MTU" "mtu": "MTU",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "여러 DNS 서버는 쉼표(,)로 구분하세요" "dnsHijack": "여러 DNS 서버는 쉼표(,)로 구분하세요",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "설정이 적용되었습니다" "applied": "설정이 적용되었습니다"

View File

@@ -427,10 +427,12 @@
"strictRoute": "Строгая маршрутизация", "strictRoute": "Строгая маршрутизация",
"autoDetectInterface": "Автоопределение интерфейса", "autoDetectInterface": "Автоопределение интерфейса",
"dnsHijack": "DNS-перехват", "dnsHijack": "DNS-перехват",
"mtu": "MTU" "mtu": "MTU",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers" "dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "Настройки применены" "applied": "Настройки применены"

View File

@@ -427,10 +427,12 @@
"strictRoute": "Katı Yönlendirme", "strictRoute": "Katı Yönlendirme",
"autoDetectInterface": "Arayüzü Otomatik Algıla", "autoDetectInterface": "Arayüzü Otomatik Algıla",
"dnsHijack": "DNS Ele Geçirme", "dnsHijack": "DNS Ele Geçirme",
"mtu": "Maksimum İletim Birimi" "mtu": "Maksimum İletim Birimi",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers" "dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "Ayarlar Uygulandı" "applied": "Ayarlar Uygulandı"

View File

@@ -427,10 +427,12 @@
"strictRoute": "Катгый маршрутлау", "strictRoute": "Катгый маршрутлау",
"autoDetectInterface": "Интерфейсны автоматик ачыклау", "autoDetectInterface": "Интерфейсны автоматик ачыклау",
"dnsHijack": "DNS'ны үзгәртеп тоту (hijack)", "dnsHijack": "DNS'ны үзгәртеп тоту (hijack)",
"mtu": "MTU (макс. тапшыру берәмлеге)" "mtu": "MTU (макс. тапшыру берәмлеге)",
"autoRedirect": "Auto Redirect"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "Please use , to separate multiple DNS servers" "dnsHijack": "Please use , to separate multiple DNS servers",
"autoRedirect": "Automatically configures nftables/iptables TCP redirects"
}, },
"messages": { "messages": {
"applied": "Көйләүләр кулланылды" "applied": "Көйләүләр кулланылды"

View File

@@ -427,10 +427,12 @@
"strictRoute": "严格路由", "strictRoute": "严格路由",
"autoDetectInterface": "自动选择流量出口接口", "autoDetectInterface": "自动选择流量出口接口",
"dnsHijack": "DNS 劫持", "dnsHijack": "DNS 劫持",
"mtu": "最大传输单元" "mtu": "最大传输单元",
"autoRedirect": "自动重定向"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "多个 DNS 服务器请使用 , 分隔" "dnsHijack": "多个 DNS 服务器请使用 , 分隔",
"autoRedirect": "自动配置 nftables/iptables 的 TCP 重定向"
}, },
"messages": { "messages": {
"applied": "设置已应用" "applied": "设置已应用"

View File

@@ -427,10 +427,12 @@
"strictRoute": "嚴格路由", "strictRoute": "嚴格路由",
"autoDetectInterface": "自動偵測流量輸出介面", "autoDetectInterface": "自動偵測流量輸出介面",
"dnsHijack": "DNS 綁架", "dnsHijack": "DNS 綁架",
"mtu": "最大傳輸單位" "mtu": "最大傳輸單位",
"autoRedirect": "自動重導"
}, },
"tooltips": { "tooltips": {
"dnsHijack": "請用半形逗號來區隔多個 DNS 伺服器" "dnsHijack": "請用半形逗號來區隔多個 DNS 伺服器",
"autoRedirect": "自動配置 nftables/iptables 的 TCP 重導"
}, },
"messages": { "messages": {
"applied": "設定已套用" "applied": "設定已套用"

View File

@@ -558,7 +558,9 @@ export const translationKeys = [
"settings.modals.tun.fields.autoDetectInterface", "settings.modals.tun.fields.autoDetectInterface",
"settings.modals.tun.fields.dnsHijack", "settings.modals.tun.fields.dnsHijack",
"settings.modals.tun.fields.mtu", "settings.modals.tun.fields.mtu",
"settings.modals.tun.fields.autoRedirect",
"settings.modals.tun.tooltips.dnsHijack", "settings.modals.tun.tooltips.dnsHijack",
"settings.modals.tun.tooltips.autoRedirect",
"settings.modals.tun.messages.applied", "settings.modals.tun.messages.applied",
"settings.modals.dns.dialog.title", "settings.modals.dns.dialog.title",
"settings.modals.dns.dialog.warning", "settings.modals.dns.dialog.warning",

View File

@@ -989,6 +989,7 @@ export interface TranslationResources {
tun: { tun: {
fields: { fields: {
autoDetectInterface: string; autoDetectInterface: string;
autoRedirect: string;
autoRoute: string; autoRoute: string;
device: string; device: string;
dnsHijack: string; dnsHijack: string;
@@ -1001,6 +1002,7 @@ export interface TranslationResources {
}; };
title: string; title: string;
tooltips: { tooltips: {
autoRedirect: string;
dnsHijack: string; dnsHijack: string;
}; };
}; };

View File

@@ -41,6 +41,7 @@ interface IConfigData {
stack: string; stack: string;
device: string; device: string;
"auto-route": boolean; "auto-route": boolean;
"auto-redirect"?: boolean;
"auto-detect-interface": boolean; "auto-detect-interface": boolean;
"dns-hijack": string[]; "dns-hijack": string[];
"strict-route": boolean; "strict-route": boolean;