diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/Singbox/CoreConfigSingboxServiceTests.cs b/v2rayN/ServiceLib.Tests/CoreConfig/Singbox/CoreConfigSingboxServiceTests.cs index 5f081a39..75d70920 100644 --- a/v2rayN/ServiceLib.Tests/CoreConfig/Singbox/CoreConfigSingboxServiceTests.cs +++ b/v2rayN/ServiceLib.Tests/CoreConfig/Singbox/CoreConfigSingboxServiceTests.cs @@ -46,6 +46,72 @@ public class CoreConfigSingboxServiceTests await cfg.inbounds.Should().Contain(i => i.type == "tun"); } + [Test] + [Arguments(null)] + [Arguments("Default")] + public async Task GenerateClientConfigContent_TunDnsModeDefault_ShouldNotIncludeDnsMode(string? dnsMode) + { + var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box); + config.TunModeItem.EnableTun = true; + config.TunModeItem.DnsMode = dnsMode; + CoreConfigTestFactory.BindAppManagerConfig(config); + var node = CoreConfigTestFactory.CreateSocksNode(ECoreType.sing_box); + var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with + { + IsTunEnabled = true, + }; + + var result = new CoreConfigSingboxService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}"); + await result.Data!.ToString().Should().NotContain("\"dns_mode\""); + } + + [Test] + public async Task GenerateClientConfigContent_TunDnsModeDisabled_ShouldIncludeDnsMode() + { + var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box); + config.TunModeItem.EnableTun = true; + config.TunModeItem.DnsMode = "disabled"; + CoreConfigTestFactory.BindAppManagerConfig(config); + var node = CoreConfigTestFactory.CreateSocksNode(ECoreType.sing_box); + var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with + { + IsTunEnabled = true, + }; + + var result = new CoreConfigSingboxService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}"); + var cfg = JsonUtils.Deserialize(result.Data!.ToString())!; + var tun = cfg.inbounds.First(i => i.type == "tun"); + await tun.dns_mode.Should().BeEqualTo("disabled"); + } + + [Test] + [Arguments(true)] + [Arguments(false)] + public async Task GenerateClientConfigContent_TunDnsModeDisabled_ShouldNotIncludeGlobalDnsHijackRule(bool sniffingEnabled) + { + var config = CoreConfigTestFactory.CreateConfig(ECoreType.sing_box); + config.TunModeItem.EnableTun = true; + config.TunModeItem.DnsMode = "disabled"; + config.Inbound.First().SniffingEnabled = sniffingEnabled; + CoreConfigTestFactory.BindAppManagerConfig(config); + var node = CoreConfigTestFactory.CreateSocksNode(ECoreType.sing_box); + var context = CoreConfigTestFactory.CreateContext(config, node, ECoreType.sing_box) with + { + IsTunEnabled = true, + }; + + var result = new CoreConfigSingboxService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue().Because($"ret msg: {result.Msg}"); + var cfg = JsonUtils.Deserialize(result.Data!.ToString())!; + await cfg.route.rules.Any( + r => r.action == "hijack-dns" && r.process_path == null).Should().BeFalse(); + } + [Test] public async Task GenerateClientConfigContent_TunEnabled_ShouldKeepEmbeddedTunRules() { diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 78d98561..3808f57b 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -607,6 +607,16 @@ public class Global "mixed" ]; + public const string TunDnsModeDefault = "Default"; + + public static readonly List TunDnsModes = + [ + TunDnsModeDefault, + "disabled", + "native", + "hijack" + ]; + public static readonly List PresetMsgFilters = [ "proxy", diff --git a/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs index 796d783b..5d4dedb4 100644 --- a/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs @@ -137,6 +137,7 @@ public class TunModeItem public bool EnableIPv6Address { get; set; } public string IcmpRouting { get; set; } public bool EnableLegacyProtect { get; set; } = true; + public string? DnsMode { get; set; } public List? RouteExcludeAddress { get; set; } public string IPv4Address { get; set; } public string IPv6Address { get; set; } diff --git a/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs b/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs index 6e549d1a..c62cc09f 100644 --- a/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/CoreConfigs/SingboxConfig.cs @@ -114,6 +114,7 @@ public class Inbound4Sbox public bool? auto_route { get; set; } public bool? strict_route { get; set; } public string? stack { get; set; } + public string? dns_mode { get; set; } public List users { get; set; } public List? route_exclude_address { get; set; } } diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 373fef1a..17f41af2 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -4913,6 +4913,15 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsTunAutoRoute", resourceCulture); } } + + /// + /// 查找类似 DNS mode 的本地化字符串。 + /// + public static string TbSettingsTunDnsMode { + get { + return ResourceManager.GetString("TbSettingsTunDnsMode", resourceCulture); + } + } /// /// 查找类似 Tun Mode settings 的本地化字符串。 diff --git a/v2rayN/ServiceLib/Resx/ResUI.az.resx b/v2rayN/ServiceLib/Resx/ResUI.az.resx index f6f50f26..846f283a 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.az.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.az.resx @@ -1080,6 +1080,9 @@ Stek + + DNS rejimi + MTU diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa.resx b/v2rayN/ServiceLib/Resx/ResUI.fa.resx index 0e4229f6..ce312a5c 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa.resx @@ -1065,6 +1065,9 @@ پشته شبکه + + حالت DNS + MTU @@ -1773,4 +1776,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if New Update - \ No newline at end of file + diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 06517fa1..0f85bc63 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1059,6 +1059,9 @@ Pile de protocoles + + Mode DNS + MTU @@ -1767,4 +1770,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Nouvelle MAJ - \ No newline at end of file + diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index bdfb1c32..42ac8f20 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1062,6 +1062,9 @@ Hálózati verem + + DNS-mód + MTU @@ -1770,4 +1773,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if New Update - \ No newline at end of file + diff --git a/v2rayN/ServiceLib/Resx/ResUI.id.resx b/v2rayN/ServiceLib/Resx/ResUI.id.resx index a4d44764..3fab4a97 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.id.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.id.resx @@ -1062,6 +1062,9 @@ Stack + + Mode DNS + MTU @@ -1794,4 +1797,4 @@ Tindakan "Ambil sertifikat" mungkin gagal jika sertifikat self-signed digunakan Memecah bagian akhir paket menjadi fragmen yang lebih kecil. Hal ini dapat memengaruhi throughput dan latensi. - \ No newline at end of file + diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 39cd5cf2..4ccbe7f4 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1083,6 +1083,9 @@ Stack + + DNS mode + MTU diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 6e7d53c8..a33b7fc6 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1083,6 +1083,9 @@ Сетевой стек + + Режим DNS + MTU @@ -1905,4 +1908,4 @@ Добавить сервер [MASQUE] - \ No newline at end of file + diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index 9fbdc952..6fb8131f 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1071,6 +1071,9 @@ 协议栈 + + DNS 模式 + MTU diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index b9f2f18f..1fff4de2 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1080,6 +1080,9 @@ 協定堆疊 + + DNS 模式 + MTU diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs index ddca2f14..90652505 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs @@ -68,6 +68,9 @@ public partial class CoreConfigSingboxService tunInbound.auto_route = _config.TunModeItem.AutoRoute; tunInbound.strict_route = _config.TunModeItem.StrictRoute; tunInbound.stack = _config.TunModeItem.Stack; + tunInbound.dns_mode = _config.TunModeItem.DnsMode == Global.TunDnsModeDefault + ? null + : _config.TunModeItem.DnsMode.NullIfEmpty(); var address = _config.TunModeItem.IPv4Address.NullIfEmpty() ?? Global.TunIPv4Address.First(); tunInbound.address = [address]; diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs index 96117897..8914b087 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs @@ -113,23 +113,27 @@ public partial class CoreConfigSingboxService } } + var disableDnsHijack = string.Equals(_config.TunModeItem.DnsMode, "disabled", StringComparison.OrdinalIgnoreCase); if (_config.Inbound.First().SniffingEnabled) { _coreConfig.route.rules.Add(new() { action = "sniff" }); - _coreConfig.route.rules.Add(new() + if (!disableDnsHijack) { - type = "logical", - mode = "or", - action = "hijack-dns", - rules = - [ - new() { port = [53] }, - new() { protocol = ["dns"] }, - ], - }); + _coreConfig.route.rules.Add(new() + { + type = "logical", + mode = "or", + action = "hijack-dns", + rules = + [ + new() { port = [53] }, + new() { protocol = ["dns"] }, + ], + }); + } if (_config.CoreBasicItem.EnableFinalFragment) { _coreConfig.route.rules.Add(new() @@ -142,11 +146,14 @@ public partial class CoreConfigSingboxService } else { - _coreConfig.route.rules.Add(new() + if (!disableDnsHijack) { - port = [53], - action = "hijack-dns", - }); + _coreConfig.route.rules.Add(new() + { + port = [53], + action = "hijack-dns", + }); + } if (_config.CoreBasicItem.EnableFinalFragment) { _coreConfig.route.rules.Add(new() diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 4bb29272..5c81d1de 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -97,6 +97,7 @@ public partial class OptionSettingViewModel : MyReactiveObject, ICloseable [Reactive] public partial bool TunEnableIPv6Address { get; set; } [Reactive] public partial string TunIcmpRouting { get; set; } [Reactive] public partial bool TunEnableLegacyProtect { get; set; } + [Reactive] public partial string TunDnsMode { get; set; } [Reactive] public partial string TunRouteExcludeAddress { get; set; } [Reactive] public partial string TunIPv4Address { get; set; } [Reactive] public partial string TunIPv6Address { get; set; } @@ -222,6 +223,7 @@ public partial class OptionSettingViewModel : MyReactiveObject, ICloseable TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address; TunIcmpRouting = _config.TunModeItem.IcmpRouting; TunEnableLegacyProtect = _config.TunModeItem.EnableLegacyProtect; + TunDnsMode = _config.TunModeItem.DnsMode.NullIfEmpty() ?? Global.TunDnsModeDefault; TunRouteExcludeAddress = Utils.List2String(_config.TunModeItem.RouteExcludeAddress, true); TunIPv4Address = _config.TunModeItem.IPv4Address; TunIPv6Address = _config.TunModeItem.IPv6Address; @@ -395,6 +397,7 @@ public partial class OptionSettingViewModel : MyReactiveObject, ICloseable _config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address; _config.TunModeItem.IcmpRouting = TunIcmpRouting; _config.TunModeItem.EnableLegacyProtect = TunEnableLegacyProtect; + _config.TunModeItem.DnsMode = TunDnsMode == Global.TunDnsModeDefault ? null : TunDnsMode; _config.TunModeItem.RouteExcludeAddress = Utils.String2List(TunRouteExcludeAddress); _config.TunModeItem.IPv4Address = TunIPv4Address; _config.TunModeItem.IPv6Address = TunIPv6Address; diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index 046b0e49..0217f67a 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -1035,7 +1035,7 @@ Margin="{StaticResource Margin4}" ColumnDefinitions="Auto,Auto,Auto" DockPanel.Dock="Top" - RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + + + diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs index bb1b20a8..c7b229e6 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml.cs @@ -33,6 +33,7 @@ public partial class OptionSettingWindow : WindowBase cmbmux4RayXudpProxyUDP443.ItemsSource = new[] { "reject", "skip" }; cmbMtu.ItemsSource = Global.TunMtus; cmbStack.ItemsSource = Global.TunStacks; + cmbDnsMode.ItemsSource = Global.TunDnsModes; cmbIcmpRoutingPolicy.ItemsSource = Global.TunIcmpRoutingPolicies; cmbIpv4Address.ItemsSource = Global.TunIPv4Address; cmbIpv6Address.ItemsSource = Global.TunIPv6Address; @@ -135,6 +136,7 @@ public partial class OptionSettingWindow : WindowBase this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunIcmpRouting, v => v.cmbIcmpRoutingPolicy.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunEnableLegacyProtect, v => v.togEnableLegacyProtect.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.TunDnsMode, v => v.cmbDnsMode.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunRouteExcludeAddress, v => v.txtRouteExcludeAddress.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunIPv4Address, v => v.cmbIpv4Address.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunIPv6Address, v => v.cmbIpv6Address.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 7d96bbeb..ceed94f5 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -1278,6 +1278,7 @@ + @@ -1458,6 +1459,22 @@ Margin="{StaticResource Margin8}" HorizontalAlignment="Left" Style="{StaticResource DefComboBox}" /> + + + diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs index 6997b45b..c01e2895 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml.cs @@ -29,6 +29,7 @@ public partial class OptionSettingWindow cmbmux4RayXudpProxyUDP443.ItemsSource = new[] { "reject", "skip" }; cmbMtu.ItemsSource = Global.TunMtus; cmbStack.ItemsSource = Global.TunStacks; + cmbDnsMode.ItemsSource = Global.TunDnsModes; cmbIcmpRoutingPolicy.ItemsSource = Global.TunIcmpRoutingPolicies; cmbIpv4Address.ItemsSource = Global.TunIPv4Address; cmbIpv6Address.ItemsSource = Global.TunIPv6Address; @@ -131,6 +132,7 @@ public partial class OptionSettingWindow this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunIcmpRouting, v => v.cmbIcmpRoutingPolicy.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunEnableLegacyProtect, v => v.togEnableLegacyProtect.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.TunDnsMode, v => v.cmbDnsMode.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunRouteExcludeAddress, v => v.txtRouteExcludeAddress.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunIPv4Address, v => v.cmbIpv4Address.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunIPv6Address, v => v.cmbIpv6Address.Text).DisposeWith(disposables);