From 6ad480f89825d28515bcfce253ef129e2e1f3756 Mon Sep 17 00:00:00 2001 From: doudouabc <65523788+doudouabc@users.noreply.github.com> Date: Sun, 12 Jul 2026 02:05:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B1=80=E5=9F=9F=E7=BD=91?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E4=BD=BF=E7=94=A8=E7=9A=84BUG=20(#7428)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: wonfen <96291150+wonfen@users.noreply.github.com> --- src-tauri/src/enhance/mod.rs | 86 +++++++++++++++++++++++++++++++++++- src-tauri/src/feat/config.rs | 2 + 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs index d0bba4d7f..02cbd352d 100644 --- a/src-tauri/src/enhance/mod.rs +++ b/src-tauri/src/enhance/mod.rs @@ -368,6 +368,48 @@ fn enforce_dns_ipv6(mut config: Mapping, dns_ipv6: Option) -> Mapping { config } +fn is_loopback_bind_address(addr: &str) -> bool { + let addr = addr.trim(); + let addr = addr + .strip_prefix('[') + .and_then(|addr| addr.strip_suffix(']')) + .unwrap_or(addr); + + addr.eq_ignore_ascii_case("localhost") + || addr.parse::().is_ok_and(|addr| addr.is_loopback()) + || is_ipv4_shorthand_loopback(addr) +} + +fn is_ipv4_shorthand_loopback(addr: &str) -> bool { + let parts = addr.split('.').map(str::parse::).collect::, _>>(); + + let Ok(parts) = parts else { + return false; + }; + + match parts.as_slice() { + [first, rest] => *first == 127 && *rest <= 0x00ff_ffff, + [first, second, rest] => *first == 127 && *second <= 0xff && *rest <= 0xffff, + [first, second, third, fourth] => *first == 127 && *second <= 0xff && *third <= 0xff && *fourth <= 0xff, + _ => false, + } +} + +fn ensure_lan_bind_address(mut config: Mapping) -> Mapping { + let allow_lan = config.get("allow-lan").and_then(Value::as_bool).unwrap_or(false); + + if allow_lan + && config + .get("bind-address") + .and_then(Value::as_str) + .is_some_and(is_loopback_bind_address) + { + config.insert(Value::from("bind-address"), Value::from("*")); + } + + config +} + async fn process_profile_items( mut config: Mapping, mut exists_keys: Vec, @@ -720,6 +762,7 @@ pub async fn enhance() -> Result<(Mapping, HashSet, HashMap Result<(Mapping, HashSet, HashMap serde_yaml_ng::Mapping { @@ -883,6 +929,44 @@ mod tests { ); } + #[test] + fn lan_bind_address_loopback_is_widened() { + for bind_address in [ + "localhost", + "127.0.0.1", + "127.0.0.2", + "127.1", + "::1", + "[::1]", + "0:0:0:0:0:0:0:1", + ] { + let result = ensure_lan_bind_address(mapping(&format!( + r#"{{allow-lan: true, bind-address: "{bind_address}"}}"# + ))); + + assert_eq!( + result.get("bind-address").and_then(serde_yaml_ng::Value::as_str), + Some("*"), + "bind-address {bind_address} should be widened" + ); + } + } + + #[test] + fn lan_bind_address_preserves_custom_or_disabled() { + let custom = ensure_lan_bind_address(mapping(r#"{allow-lan: true, bind-address: "192.168.1.2"}"#)); + assert_eq!( + custom.get("bind-address").and_then(serde_yaml_ng::Value::as_str), + Some("192.168.1.2") + ); + + let disabled = ensure_lan_bind_address(mapping(r#"{allow-lan: false, bind-address: "127.0.0.1"}"#)); + assert_eq!( + disabled.get("bind-address").and_then(serde_yaml_ng::Value::as_str), + Some("127.0.0.1") + ); + } + #[test] fn control_plane_removes_reenabled_disabled_port() { let app_config = mapping(r"{mixed-port: 7890, mode: rule}"); diff --git a/src-tauri/src/feat/config.rs b/src-tauri/src/feat/config.rs index 255a53b9a..ef7b49d8b 100644 --- a/src-tauri/src/feat/config.rs +++ b/src-tauri/src/feat/config.rs @@ -18,6 +18,8 @@ pub async fn patch_clash(patch: &Mapping) -> Result<()> { if patch.get("secret").is_some() || patch.get("external-controller").is_some() { Config::generate().await?; CoreManager::global().restart_core().await?; + } else if patch.get("allow-lan").is_some() { + CoreManager::global().update_config_checked().await?; } else { if patch.get("mode").is_some() { tray::Tray::global().update_menu_and_icon().await;