diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/DualStackFakeIPTests.cs b/v2rayN/ServiceLib.Tests/CoreConfig/DualStackFakeIPTests.cs new file mode 100644 index 00000000..0ab80e60 --- /dev/null +++ b/v2rayN/ServiceLib.Tests/CoreConfig/DualStackFakeIPTests.cs @@ -0,0 +1,185 @@ +namespace ServiceLib.Tests.CoreConfig; + +public class DualStackFakeIPTests +{ + [Test] + [Arguments(null, "fc00::/18", 65535L)] + [Arguments(" 2001:2::/48 ", "2001:2::/48", 65535L)] + [Arguments("fc00::/120", "fc00::/120", 255L)] + [Arguments("fc00::/127", "fc00::/127", 1L)] + public async Task IPv6Range_ShouldUseAnXrayCompatiblePoolSize(string? input, string expectedRange, long expectedSize) + { + var valid = FakeIPUtils.TryParseIPv6Range(input, out var range, out var size); + + await valid.Should().BeTrue(); + await range.Should().BeEqualTo(expectedRange); + await size.Should().BeEqualTo(expectedSize); + } + + [Test] + [Arguments("198.18.0.0/15")] + [Arguments("not-a-network")] + [Arguments("fc00::/128")] + public async Task IPv6Range_ShouldRejectInvalidOrSingleAddressPools(string input) + { + await FakeIPUtils.TryParseIPv6Range(input, out _, out _).Should().BeFalse(); + } + + [Test] + public async Task OldDnsSettings_ShouldKeepIPv6FakeIPDisabled() + { + var settings = JsonUtils.Deserialize(""" + { "FakeIP": true, "FakeIPRange": "198.18.0.0/15" } + """)!; + + await (settings.FakeIPv6 == true).Should().BeFalse(); + await settings.FakeIPRange.Should().BeEqualTo("198.18.0.0/15"); + } + + [Test] + public async Task DnsSettings_ShouldRoundTripBothAddressRanges() + { + var settings = new SimpleDNSItem + { + FakeIP = true, + FakeIPRange = "198.18.0.0/15", + FakeIPv6 = true, + FakeIPv6Range = "2001:2::/48", + }; + + var restored = JsonUtils.Deserialize(JsonUtils.Serialize(settings))!; + + await restored.FakeIP.Should().BeEqualTo(true); + await restored.FakeIPRange.Should().BeEqualTo(settings.FakeIPRange); + await restored.FakeIPv6.Should().BeEqualTo(true); + await restored.FakeIPv6Range.Should().BeEqualTo(settings.FakeIPv6Range); + } + + [Test] + public async Task AppConfig_ShouldRoundTripIPv6Settings() + { + var config = CoreConfigTestFactory.CreateConfig(); + config.SimpleDNSItem.FakeIPv6 = true; + config.SimpleDNSItem.FakeIPv6Range = "2001:2::/48"; + + var restored = JsonUtils.Deserialize(JsonUtils.Serialize(config))!; + + await restored.Should().NotBeNull(); + await restored.SimpleDNSItem.FakeIPv6.Should().BeEqualTo(true); + await restored.SimpleDNSItem.FakeIPv6Range.Should().BeEqualTo("2001:2::/48"); + } + + [Test] + [Arguments(false)] + [Arguments(true)] + public async Task Xray_ShouldRoundTripSingleAndDualPools(bool enableIPv6) + { + var context = CreateContext(ECoreType.Xray, enableIPv6, false); + var result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue(); + var restored = JsonUtils.Deserialize(result.Data!.ToString())!; + await restored.Should().NotBeNull(); + var roundTrip = JsonUtils.ParseJson(JsonUtils.Serialize(restored))!; + var original = JsonUtils.ParseJson(result.Data!.ToString())!; + await JsonNode.DeepEquals(original["fakedns"], roundTrip["fakedns"]).Should().BeTrue(); + await (restored.fakedns is JsonArray).Should().BeEqualTo(enableIPv6); + await restored.fakedns.Should().NotBeNull(); + } + + [Test] + [Arguments(false, false)] + [Arguments(true, false)] + [Arguments(true, true)] + public async Task Xray_ShouldAddIPv6OnlyWhenEnabledAndAAAAAllowed(bool enableIPv6, bool blockAAAA) + { + var context = CreateContext(ECoreType.Xray, enableIPv6, blockAAAA); + var result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue(); + var root = JsonUtils.ParseJson(result.Data!.ToString())!; + var dualStack = enableIPv6 && !blockAAAA; + await (root["fakedns"] is JsonArray).Should().BeEqualTo(dualStack); + var pool = dualStack ? root["fakedns"]![0]! : root["fakedns"]!; + await pool["ipPool"]!.GetValue().Should().BeEqualTo("198.18.0.0/15"); + if (dualStack) + { + await root["fakedns"]!.AsArray().Count.Should().BeEqualTo(2); + await root["fakedns"]![1]!["ipPool"]!.GetValue().Should().BeEqualTo("2001:2::/48"); + await root["fakedns"]![1]!["poolSize"]!.GetValue().Should().BeEqualTo(65535L); + } + if (blockAAAA) + { + await root["dns"]!["queryStrategy"]!.GetValue().Should().BeEqualTo("UseIPv4"); + } + } + + [Test] + [Arguments(false, false)] + [Arguments(true, false)] + [Arguments(true, true)] + public async Task Singbox_ShouldAddIPv6OnlyWhenEnabledAndAAAAAllowed(bool enableIPv6, bool blockAAAA) + { + var context = CreateContext(ECoreType.sing_box, enableIPv6, blockAAAA); + var result = new CoreConfigSingboxService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue(); + var config = JsonUtils.Deserialize(result.Data!.ToString())!; + var fakeDns = config.dns.servers.Single(server => server.type == "fakeip"); + + await fakeDns.inet4_range.Should().BeEqualTo("198.18.0.0/15"); + await fakeDns.inet6_range.Should().BeEqualTo(enableIPv6 && !blockAAAA ? "2001:2::/48" : null); + } + + [Test] + public async Task DisabledFakeIP_ShouldNotCreateEitherPool() + { + foreach (var coreType in new[] { ECoreType.Xray, ECoreType.sing_box }) + { + var context = CreateContext(coreType, true, false); + context.AppConfig.SimpleDNSItem.FakeIP = false; + var result = coreType == ECoreType.Xray + ? new CoreConfigV2rayService(context).GenerateClientConfigContent() + : new CoreConfigSingboxService(context).GenerateClientConfigContent(); + + await result.Success.Should().BeTrue(); + var root = JsonUtils.ParseJson(result.Data!.ToString())!; + await root["fakedns"].Should().BeNull(); + await root["dns"]!["servers"]!.AsArray() + .Any(server => server is JsonObject obj && obj["type"]?.GetValue() == "fakeip") + .Should().BeFalse(); + } + } + + private static CoreConfigContext CreateContext(ECoreType coreType, bool enableIPv6, bool blockAAAA) + { + var config = CoreConfigTestFactory.CreateConfig(coreType); + config.SimpleDNSItem.FakeIP = true; + config.SimpleDNSItem.FakeIPRange = "198.18.0.0/15"; + config.SimpleDNSItem.FakeIPv6 = enableIPv6; + config.SimpleDNSItem.FakeIPv6Range = "2001:2::/48"; + config.SimpleDNSItem.BlockAAAAQuery = blockAAAA; + CoreConfigTestFactory.BindAppManagerConfig(config); + var node = CoreConfigTestFactory.CreateVmessNode(coreType); + return CoreConfigTestFactory.CreateContext(config, node, coreType) with + { + RoutingItem = new RoutingItem + { + Id = "fakeip-test", + Remarks = "FakeIP test", + DomainStrategy = Global.AsIs, + DomainStrategy4Singbox = string.Empty, + RuleSet = JsonUtils.Serialize(new List + { + new() + { + Enabled = true, + RuleType = ERuleType.DNS, + OutboundTag = Global.ProxyTag, + Domain = ["full:example.com"], + }, + }), + }, + }; + } +} diff --git a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayIPNetworkTests.cs b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayIPNetworkTests.cs index 36641aad..760884af 100644 --- a/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayIPNetworkTests.cs +++ b/v2rayN/ServiceLib.Tests/CoreConfig/V2ray/CoreConfigV2rayIPNetworkTests.cs @@ -109,8 +109,8 @@ public class CoreConfigV2rayIPNetworkTests var generated = JsonUtils.Deserialize(result.Data!.ToString())!; await generated.fakedns.Should().NotBeNull(); - await generated.fakedns!.ipPool.Should().BeEqualTo(range); - await generated.fakedns.poolSize.Should().BeEqualTo(expectedPoolSize); + await generated.fakedns!["ipPool"]!.GetValue().Should().BeEqualTo(range); + await generated.fakedns["poolSize"]!.GetValue().Should().BeEqualTo(expectedPoolSize); } private static async Task GenerateConfig(Config config) diff --git a/v2rayN/ServiceLib/Common/FakeIPUtils.cs b/v2rayN/ServiceLib/Common/FakeIPUtils.cs new file mode 100644 index 00000000..8a8ea017 --- /dev/null +++ b/v2rayN/ServiceLib/Common/FakeIPUtils.cs @@ -0,0 +1,25 @@ +namespace ServiceLib.Common; + +public static class FakeIPUtils +{ + public static bool TryParseIPv6Range(string? value, out string range, out long poolSize) + { + range = string.IsNullOrWhiteSpace(value) ? Global.FakeIPv6Ranges.First() : value.Trim(); + poolSize = 65535; + if (!System.Net.IPNetwork.TryParse(range, out var network) + || network.BaseAddress.AddressFamily != AddressFamily.InterNetworkV6 + || network.PrefixLength == 128) + { + return false; + } + + range = network.ToString(); + var hostBits = 128 - network.PrefixLength; + if (hostBits < 16) + { + // Xray requires the mapping capacity to be smaller than the address pool. + poolSize = (1L << hostBits) - 1; + } + return true; + } +} diff --git a/v2rayN/ServiceLib/Common/JsonUtils.cs b/v2rayN/ServiceLib/Common/JsonUtils.cs index 6aa22381..f77a154c 100644 --- a/v2rayN/ServiceLib/Common/JsonUtils.cs +++ b/v2rayN/ServiceLib/Common/JsonUtils.cs @@ -240,6 +240,7 @@ public class JsonUtils [JsonSerializable(typeof(List))] [JsonSerializable(typeof(List))] [JsonSerializable(typeof(V2rayConfig))] +[JsonSerializable(typeof(FakeDns4Ray))] [JsonSerializable(typeof(Dns4Ray))] [JsonSerializable(typeof(DnsServer4Ray))] [JsonSerializable(typeof(Inbounds4Ray))] diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index 55a5880c..78d98561 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -773,6 +773,12 @@ public class Global "11.0.0.0/8", ]; + public static readonly List FakeIPv6Ranges = + [ + "fc00::/18", + "2001:2::/48", + ]; + public static readonly List RootCertProviders = [ "system", diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index b0ac5879..55d2ab6b 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -115,6 +115,8 @@ public static class ConfigHandler config.SimpleDNSItem ??= InitBuiltinSimpleDNS(); config.SimpleDNSItem.BlockAAAAQuery ??= false; config.SimpleDNSItem.FakeIPRange ??= Global.FakeIPRanges.FirstOrDefault(); + config.SimpleDNSItem.FakeIPv6 ??= false; + config.SimpleDNSItem.FakeIPv6Range ??= Global.FakeIPv6Ranges.First(); config.SimpleDNSItem.GlobalFakeIp ??= true; config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault(); config.SimpleDNSItem.ServeStale ??= false; diff --git a/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs index 54c91fc4..796d783b 100644 --- a/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs @@ -254,6 +254,8 @@ public class SimpleDNSItem public bool? FakeIP { get; set; } public bool? GlobalFakeIp { get; set; } public string? FakeIPRange { get; set; } + public bool? FakeIPv6 { get; set; } + public string? FakeIPv6Range { get; set; } public bool? BlockBindingQuery { get; set; } public bool? BlockAAAAQuery { get; set; } public string? DirectDNS { get; set; } diff --git a/v2rayN/ServiceLib/Models/CoreConfigs/V2rayConfig.cs b/v2rayN/ServiceLib/Models/CoreConfigs/V2rayConfig.cs index 9f14f021..957a61f6 100644 --- a/v2rayN/ServiceLib/Models/CoreConfigs/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/CoreConfigs/V2rayConfig.cs @@ -4,7 +4,7 @@ public class V2rayConfig { public Log4Ray log { get; set; } public JsonObject? dns { get; set; } - public FakeDns4Ray? fakedns { get; set; } + public JsonNode? fakedns { get; set; } public List inbounds { get; set; } public List outbounds { get; set; } public Routing4Ray routing { get; set; } diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index cb543aec..9f79f3cb 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // 此代码由工具生成。 // 运行时版本:4.0.30319.42000 @@ -5444,5 +5444,32 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("UpgradeAppNotExistTip", resourceCulture); } } + /// + /// IPv6 FakeIP + /// + public static string TbFakeIPv6 { + get { + return ResourceManager.GetString("TbFakeIPv6", resourceCulture); + } + } + + /// + /// Return fake IPv6 addresses as well. Requires FakeIP to be enabled and AAAA queries to be allowed. + /// + public static string TbFakeIPv6Tips { + get { + return ResourceManager.GetString("TbFakeIPv6Tips", resourceCulture); + } + } + + /// + /// Enter a valid IPv6 CIDR range with at least two addresses, for example fc00::/18. + /// + public static string FillCorrectFakeIPv6Range { + get { + return ResourceManager.GetString("FillCorrectFakeIPv6Range", resourceCulture); + } + } + } } diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 246eb67e..39cd5cf2 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1905,6 +1905,15 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Add [MASQUE] + + IPv6 FakeIP + + + Return fake IPv6 addresses as well. Requires FakeIP to be enabled and AAAA queries to be allowed. + + + Enter a valid IPv6 CIDR range with at least two addresses, for example fc00::/18. + Columns @@ -1914,4 +1923,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Pre-resolve server domains - \ 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 cf6855c2..9fbdc952 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1897,6 +1897,15 @@ 添加 [MASQUE] + + IPv6 FakeIP + + + 同时返回 IPv6 假地址,需启用 FakeIP 且未屏蔽 AAAA 查询。 + + + 请输入至少包含两个地址的有效 IPv6 网段,例如 fc00::/18。 + 列 @@ -1906,4 +1915,4 @@ 预解析节点域名 - \ No newline at end of file + diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index da166a31..b9f2f18f 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1903,4 +1903,13 @@ 行程 + + IPv6 FakeIP + + + 同時傳回 IPv6 假位址,需啟用 FakeIP 且未封鎖 AAAA 查詢。 + + + 請輸入至少包含兩個位址的有效 IPv6 網段,例如 fc00::/18。 + diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index 74f27cd0..b9771235 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -131,6 +131,14 @@ public partial class CoreConfigSingboxService type = "fakeip", inet4_range = fakeipRange, }; + if (simpleDnsItem.FakeIPv6 == true && simpleDnsItem.BlockAAAAQuery != true) + { + if (!FakeIPUtils.TryParseIPv6Range(simpleDnsItem.FakeIPv6Range, out var range6, out _)) + { + throw new ArgumentException("Invalid IPv6 FakeIP range."); + } + fakeip.inet6_range = range6; + } _coreConfig.dns.servers.Add(fakeip); } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs index 836d75f0..7a388625 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs @@ -145,11 +145,22 @@ public partial class CoreConfigV2rayService { // Ignore } - _coreConfig.fakedns = new() + var fakeDns = new FakeDns4Ray { ipPool = fakeipRange, poolSize = poolSize, }; + _coreConfig.fakedns = JsonUtils.SerializeToNode(fakeDns); + if (_config.SimpleDNSItem.FakeIPv6 == true && _config.SimpleDNSItem.BlockAAAAQuery != true) + { + if (!FakeIPUtils.TryParseIPv6Range(_config.SimpleDNSItem.FakeIPv6Range, out var range6, out var poolSize6)) + { + throw new ArgumentException("Invalid IPv6 FakeIP range."); + } + _coreConfig.fakedns = new JsonArray( + _coreConfig.fakedns, + JsonUtils.SerializeToNode(new FakeDns4Ray { ipPool = range6, poolSize = poolSize6 })); + } } private void FillDnsServers(Dns4Ray dnsItem) diff --git a/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs index c1314749..c4a63db4 100644 --- a/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/DNSSettingViewModel.cs @@ -8,6 +8,8 @@ public partial class DNSSettingViewModel : MyReactiveObject, ICloseable [Reactive] public partial bool AddCommonHosts { get; set; } [Reactive] public partial bool FakeIP { get; set; } [Reactive] public partial string FakeIPRange { get; set; } + [Reactive] public partial bool FakeIPv6 { get; set; } + [Reactive] public partial string FakeIPv6Range { get; set; } [Reactive] public partial bool BlockBindingQuery { get; set; } [Reactive] public partial bool BlockAAAAQuery { get; set; } [Reactive] public partial string DirectDNS { get; set; } @@ -36,6 +38,7 @@ public partial class DNSSettingViewModel : MyReactiveObject, ICloseable [Reactive] public partial bool SBCustomDNSEnableCompatible { get; set; } [ObservableAsProperty] public partial bool IsSimpleDNSEnabled { get; } + [ObservableAsProperty] public partial bool IsFakeIPv6Available { get; } public ReactiveCommand SaveCmd { get; } public ReactiveCommand ImportDefConfig4V2rayCompatibleCmd { get; } @@ -66,6 +69,12 @@ public partial class DNSSettingViewModel : MyReactiveObject, ICloseable (a, b) => !(a && b)) .ToProperty(this, nameof(IsSimpleDNSEnabled)); + _isFakeIPv6AvailableHelper = this.WhenAnyValue( + x => x.FakeIP, + x => x.BlockAAAAQuery, + (fakeIP, blockAAAA) => fakeIP && !blockAAAA) + .ToProperty(this, nameof(IsFakeIPv6Available)); + _ = Init(); } @@ -77,6 +86,8 @@ public partial class DNSSettingViewModel : MyReactiveObject, ICloseable AddCommonHosts = item.AddCommonHosts ?? false; FakeIP = item.FakeIP ?? false; FakeIPRange = item.FakeIPRange ?? string.Empty; + FakeIPv6 = item.FakeIPv6 ?? false; + FakeIPv6Range = item.FakeIPv6Range ?? Global.FakeIPv6Ranges.First(); BlockBindingQuery = item.BlockBindingQuery ?? false; BlockAAAAQuery = item.BlockAAAAQuery ?? false; DirectDNS = item.DirectDNS ?? string.Empty; @@ -108,10 +119,19 @@ public partial class DNSSettingViewModel : MyReactiveObject, ICloseable private async Task SaveSettingAsync() { + if (IsSimpleDNSEnabled && IsFakeIPv6Available && FakeIPv6 + && !FakeIPUtils.TryParseIPv6Range(FakeIPv6Range, out _, out _)) + { + NoticeManager.Instance.Enqueue(ResUI.FillCorrectFakeIPv6Range); + return; + } + _config.SimpleDNSItem.UseSystemHosts = UseSystemHosts; _config.SimpleDNSItem.AddCommonHosts = AddCommonHosts; _config.SimpleDNSItem.FakeIP = FakeIP; _config.SimpleDNSItem.FakeIPRange = FakeIPRange; + _config.SimpleDNSItem.FakeIPv6 = FakeIPv6; + _config.SimpleDNSItem.FakeIPv6Range = FakeIPv6Range; _config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery; _config.SimpleDNSItem.BlockAAAAQuery = BlockAAAAQuery; _config.SimpleDNSItem.DirectDNS = DirectDNS; diff --git a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml index eca805e4..87488574 100644 --- a/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/DNSSettingWindow.axaml @@ -242,7 +242,7 @@ x:Name="gridAdvancedDNSSettings" Margin="{StaticResource Margin8}" ColumnDefinitions="Auto,Auto,*" - RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,*"> + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,*"> + + + + + + + + + + + cmbRemoteDNS.ItemsSource = Global.DomainRemoteDNSAddress; cmbBootstrapDNS.ItemsSource = Global.DomainPureIPDNSAddress; cmbFakeIPRange.ItemsSource = Global.FakeIPRanges; + cmbFakeIPv6Range.ItemsSource = Global.FakeIPv6Ranges; cmbDirectExpectedIPs.ItemsSource = Global.ExpectedIPs; cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy; @@ -34,6 +35,9 @@ public partial class DNSSettingWindow : WindowBase this.Bind(ViewModel, vm => vm.AddCommonHosts, v => v.togAddCommonHosts.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.FakeIPRange, v => v.cmbFakeIPRange.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.FakeIPv6, v => v.togFakeIPv6.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.FakeIPv6Range, v => v.cmbFakeIPv6Range.Text).DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.IsFakeIPv6Available, v => v.gridFakeIPv6.IsEnabled).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.BlockAAAAQuery, v => v.togBlockAAAAQuery.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/DNSSettingWindow.xaml b/v2rayN/v2rayN/Views/DNSSettingWindow.xaml index aef23b09..bbe141c4 100644 --- a/v2rayN/v2rayN/Views/DNSSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/DNSSettingWindow.xaml @@ -279,6 +279,7 @@ + @@ -366,15 +367,51 @@ Margin="{StaticResource Margin8}" VerticalAlignment="Center" Style="{StaticResource ToolbarTextBlock}" + Text="{x:Static resx:ResUI.TbFakeIPv6}" /> + + + + + + + + + + + vm.AddCommonHosts, v => v.togAddCommonHosts.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.FakeIP, v => v.togFakeIP.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.FakeIPRange, v => v.cmbFakeIPRange.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.FakeIPv6, v => v.togFakeIPv6.IsChecked).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.FakeIPv6Range, v => v.cmbFakeIPv6Range.Text).DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.IsFakeIPv6Available, v => v.gridFakeIPv6.IsEnabled).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.BlockAAAAQuery, v => v.togBlockAAAAQuery.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables);