Add ParallelQuery and ServeStale features

This commit is contained in:
DHR60 2026-01-31 11:10:31 +08:00
parent 229b239497
commit 57ba378072
18 changed files with 234 additions and 97 deletions

View file

@ -409,6 +409,7 @@ public class Global
[
"https://dns.alidns.com/dns-query",
"https://doh.pub/dns-query",
"https://dns.alidns.com/dns-query,https://doh.pub/dns-query",
"223.5.5.5",
"119.29.29.29",
"localhost"
@ -417,8 +418,9 @@ public class Global
public static readonly List<string> DomainRemoteDNSAddress =
[
"https://cloudflare-dns.com/dns-query",
"https://dns.cloudflare.com/dns-query",
"https://dns.google/dns-query",
"https://cloudflare-dns.com/dns-query,https://dns.google/dns-query,8.8.8.8",
"https://dns.cloudflare.com/dns-query",
"https://doh.dns.sb/dns-query",
"https://doh.opendns.com/dns-query",
"https://common.dot.dns.yandex.net",

View file

@ -114,6 +114,8 @@ public static class ConfigHandler
config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
config.SimpleDNSItem.GlobalFakeIp ??= true;
config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
config.SimpleDNSItem.ServeStale ??= false;
config.SimpleDNSItem.ParallelQuery ??= false;
config.SpeedTestItem ??= new();
if (config.SpeedTestItem.SpeedTestTimeout < 10)

View file

@ -267,6 +267,8 @@ public class SimpleDNSItem
public string? BootstrapDNS { get; set; }
public string? Strategy4Freedom { get; set; }
public string? Strategy4Proxy { get; set; }
public bool? ServeStale { get; set; }
public bool? ParallelQuery { get; set; }
public string? Hosts { get; set; }
public string? DirectExpectedIPs { get; set; }
}

View file

@ -3,7 +3,7 @@ namespace ServiceLib.Models;
public class V2rayConfig
{
public Log4Ray log { get; set; }
public Dns4Ray dns { get; set; }
public object dns { get; set; }
public List<Inbounds4Ray> inbounds { get; set; }
public List<Outbounds4Ray> outbounds { get; set; }
public Routing4Ray routing { get; set; }
@ -208,12 +208,8 @@ public class Dns4Ray
{
public Dictionary<string, object>? hosts { get; set; }
public List<object> servers { get; set; }
public string? clientIp { get; set; }
public string? queryStrategy { get; set; }
public bool? disableCache { get; set; }
public bool? disableFallback { get; set; }
public bool? disableFallbackIfMatch { get; set; }
public bool? useSystemHosts { get; set; }
public bool? serveStale { get; set; }
public bool? enableParallelQuery { get; set; }
public string? tag { get; set; }
}

View file

@ -3069,6 +3069,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Parallel Query 的本地化字符串。
/// </summary>
public static string TbParallelQuery {
get {
return ResourceManager.GetString("TbParallelQuery", resourceCulture);
}
}
/// <summary>
/// 查找类似 Path 的本地化字符串。
/// </summary>
@ -3438,6 +3447,15 @@ namespace ServiceLib.Resx {
}
}
/// <summary>
/// 查找类似 Serve Stale 的本地化字符串。
/// </summary>
public static string TbServeStale {
get {
return ResourceManager.GetString("TbServeStale", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set system proxy 的本地化字符串。
/// </summary>

View file

@ -1650,4 +1650,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
</root>

View file

@ -1647,4 +1647,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
</root>

View file

@ -1650,4 +1650,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
</root>

View file

@ -1650,4 +1650,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
</root>

View file

@ -1650,4 +1650,10 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
</root>

View file

@ -1647,4 +1647,10 @@
<data name="TbCertSha256Tips" xml:space="preserve">
<value>证书指纹SHA-256</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>乐观缓存</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>并行查询</value>
</data>
</root>

View file

@ -1647,4 +1647,10 @@
<data name="TbCertSha256Tips" xml:space="preserve">
<value>Certificate fingerprint (SHA-256)</value>
</data>
<data name="TbServeStale" xml:space="preserve">
<value>Serve Stale</value>
</data>
<data name="TbParallelQuery" xml:space="preserve">
<value>Parallel Query</value>
</data>
</root>

View file

@ -7,25 +7,35 @@ public partial class CoreConfigV2rayService
try
{
var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
if (item != null && item.Enabled == true)
if (item is { Enabled: true })
{
var result = await GenDnsCompatible(node, v2rayConfig);
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
if (v2rayConfig.routing.domainStrategy != Global.IPIfNonMatch)
{
// DNS routing
v2rayConfig.dns.tag = Global.DnsTag;
v2rayConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
inboundTag = new List<string> { Global.DnsTag },
outboundTag = Global.ProxyTag,
});
return result;
}
// DNS routing
var dnsObj = JsonUtils.SerializeToNode(v2rayConfig.dns);
if (dnsObj == null)
{
return result;
}
dnsObj["tag"] = Global.DnsTag;
v2rayConfig.dns = JsonUtils.Deserialize<Dns4Ray>(JsonUtils.Serialize(dnsObj));
v2rayConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
inboundTag = new List<string> { Global.DnsTag },
outboundTag = Global.ProxyTag,
});
return result;
}
var simpleDnsItem = _config.SimpleDNSItem;
var dnsItem = v2rayConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray();
var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs;
//Outbound Freedom domainStrategy
@ -55,13 +65,16 @@ public partial class CoreConfigV2rayService
.ForEach(outbound => outbound.targetStrategy = strategy4Proxy);
}
await GenDnsServers(node, v2rayConfig, simpleDnsItem);
await GenDnsHosts(v2rayConfig, simpleDnsItem);
await GenDnsServers(node, dnsItem, simpleDnsItem);
await GenDnsHosts(dnsItem, simpleDnsItem);
dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null;
dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null;
if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch)
{
// DNS routing
v2rayConfig.dns.tag = Global.DnsTag;
dnsItem.tag = Global.DnsTag;
v2rayConfig.routing.rules.Add(new RulesItem4Ray
{
type = "field",
@ -69,6 +82,8 @@ public partial class CoreConfigV2rayService
outboundTag = Global.ProxyTag,
});
}
v2rayConfig.dns = dnsItem;
}
catch (Exception ex)
{
@ -77,7 +92,7 @@ public partial class CoreConfigV2rayService
return 0;
}
private async Task<int> GenDnsServers(ProfileItem? node, V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
private async Task<int> GenDnsServers(ProfileItem? node, Dns4Ray dnsItem, SimpleDNSItem simpleDNSItem)
{
static List<string> ParseDnsAddresses(string? dnsInput, string defaultAddress)
{
@ -90,7 +105,7 @@ public partial class CoreConfigV2rayService
return addresses.Count > 0 ? addresses : new List<string> { defaultAddress };
}
static object CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
static object? CreateDnsServer(string dnsAddress, List<string> domains, List<string>? expectedIPs = null)
{
var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress);
var domainFinal = dnsAddress;
@ -119,8 +134,8 @@ public partial class CoreConfigV2rayService
});
}
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.FirstOrDefault());
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.FirstOrDefault());
var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.First());
var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.First());
var directDomainList = new List<string>();
var directGeositeList = new List<string>();
@ -130,7 +145,7 @@ public partial class CoreConfigV2rayService
var expectedIPs = new List<string>();
var regionNames = new HashSet<string>();
var bootstrapDNSAddress = ParseDnsAddresses(simpleDNSItem?.BootstrapDNS, Global.DomainPureIPDNSAddress.FirstOrDefault());
var bootstrapDNSAddress = ParseDnsAddresses(simpleDNSItem?.BootstrapDNS, Global.DomainPureIPDNSAddress.First());
var dnsServerDomains = new List<string>();
foreach (var dns in directDNSAddress)
@ -184,51 +199,48 @@ public partial class CoreConfigV2rayService
var routing = await ConfigHandler.GetDefaultRouting(_config);
List<RulesItem>? rules = null;
if (routing != null)
rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
foreach (var item in rules)
{
rules = JsonUtils.Deserialize<List<RulesItem>>(routing.RuleSet) ?? [];
foreach (var item in rules)
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
{
if (!item.Enabled || item.Domain is null || item.Domain.Count == 0)
continue;
}
if (item.RuleType == ERuleType.Routing)
{
continue;
}
foreach (var domain in item.Domain)
{
if (domain.StartsWith('#'))
{
continue;
}
if (item.RuleType == ERuleType.Routing)
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
if (item.OutboundTag == Global.DirectTag)
{
continue;
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
{
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
}
else
{
directDomainList.Add(normalizedDomain);
}
}
foreach (var domain in item.Domain)
else if (item.OutboundTag != Global.BlockTag)
{
if (domain.StartsWith('#'))
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
{
continue;
proxyGeositeList.Add(normalizedDomain);
}
var normalizedDomain = domain.Replace(Global.RoutingRuleComma, ",");
if (item.OutboundTag == Global.DirectTag)
else
{
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
{
(regionNames.Contains(normalizedDomain) ? expectedDomainList : directGeositeList).Add(normalizedDomain);
}
else
{
directDomainList.Add(normalizedDomain);
}
}
else if (item.OutboundTag != Global.BlockTag)
{
if (normalizedDomain.StartsWith("geosite:") || normalizedDomain.StartsWith("ext:"))
{
proxyGeositeList.Add(normalizedDomain);
}
else
{
proxyDomainList.Add(normalizedDomain);
}
proxyDomainList.Add(normalizedDomain);
}
}
}
@ -257,8 +269,7 @@ public partial class CoreConfigV2rayService
}
}
v2rayConfig.dns ??= new Dns4Ray();
v2rayConfig.dns.servers ??= new List<object>();
dnsItem.servers ??= [];
void AddDnsServers(List<string> dnsAddresses, List<string> domains, List<string>? expectedIPs = null)
{
@ -266,7 +277,7 @@ public partial class CoreConfigV2rayService
{
foreach (var dnsAddress in dnsAddresses)
{
v2rayConfig.dns.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
dnsItem.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs));
}
}
}
@ -288,22 +299,21 @@ public partial class CoreConfigV2rayService
|| lastRule.Ip?.Contains("0.0.0.0/0") == true);
var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress;
v2rayConfig.dns.servers.AddRange(defaultDnsServers);
dnsItem.servers.AddRange(defaultDnsServers);
return 0;
}
private async Task<int> GenDnsHosts(V2rayConfig v2rayConfig, SimpleDNSItem simpleDNSItem)
private async Task<int> GenDnsHosts(Dns4Ray dnsItem, SimpleDNSItem simpleDNSItem)
{
if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty())
{
return await Task.FromResult(0);
}
v2rayConfig.dns ??= new Dns4Ray();
v2rayConfig.dns.hosts ??= new Dictionary<string, object>();
dnsItem.hosts ??= new Dictionary<string, object>();
if (simpleDNSItem.AddCommonHosts == true)
{
v2rayConfig.dns.hosts = Global.PredefinedHosts.ToDictionary(
dnsItem.hosts = Global.PredefinedHosts.ToDictionary(
kvp => kvp.Key,
kvp => (object)kvp.Value
);
@ -312,7 +322,7 @@ public partial class CoreConfigV2rayService
if (simpleDNSItem.UseSystemHosts == true)
{
var systemHosts = Utils.GetSystemHosts();
var normalHost = v2rayConfig?.dns?.hosts;
var normalHost = dnsItem.hosts;
if (normalHost != null && systemHosts?.Count > 0)
{
@ -329,7 +339,7 @@ public partial class CoreConfigV2rayService
foreach (var kvp in userHostsMap)
{
v2rayConfig.dns.hosts[kvp.Key] = kvp.Value;
dnsItem.hosts[kvp.Key] = kvp.Value;
}
}
return await Task.FromResult(0);

View file

@ -13,6 +13,8 @@ public class DNSSettingViewModel : MyReactiveObject
[Reactive] public string? Strategy4Proxy { get; set; }
[Reactive] public string? Hosts { get; set; }
[Reactive] public string? DirectExpectedIPs { get; set; }
[Reactive] public bool? ParallelQuery { get; set; }
[Reactive] public bool? ServeStale { get; set; }
[Reactive] public bool UseSystemHostsCompatible { get; set; }
[Reactive] public string DomainStrategy4FreedomCompatible { get; set; }
@ -73,6 +75,8 @@ public class DNSSettingViewModel : MyReactiveObject
Strategy4Proxy = item.Strategy4Proxy;
Hosts = item.Hosts;
DirectExpectedIPs = item.DirectExpectedIPs;
ParallelQuery = item.ParallelQuery;
ServeStale = item.ServeStale;
var item1 = await AppManager.Instance.GetDNSItem(ECoreType.Xray);
RayCustomDNSEnableCompatible = item1.Enabled;
@ -102,6 +106,8 @@ public class DNSSettingViewModel : MyReactiveObject
_config.SimpleDNSItem.Strategy4Proxy = Strategy4Proxy;
_config.SimpleDNSItem.Hosts = Hosts;
_config.SimpleDNSItem.DirectExpectedIPs = DirectExpectedIPs;
_config.SimpleDNSItem.ParallelQuery = ParallelQuery;
_config.SimpleDNSItem.ServeStale = ServeStale;
if (NormalDNSCompatible.IsNotEmpty())
{

View file

@ -132,19 +132,32 @@
Width="200"
Margin="{StaticResource Margin4}"
PlaceholderText="Default" />
<TextBlock
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" />
Text="{x:Static resx:ResUI.TbParallelQuery}" />
<ToggleSwitch
x:Name="togAddCommonHosts"
x:Name="togParallelQuery"
Grid.Row="7"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbServeStale}" />
<ToggleSwitch
x:Name="togServeStale"
Grid.Row="8"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
</Grid>
</ScrollViewer>
</TabItem>
@ -155,7 +168,7 @@
x:Name="gridAdvancedDNSSettings"
Margin="{StaticResource Margin8}"
ColumnDefinitions="Auto,Auto,*"
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*">
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,*">
<TextBlock
x:Name="txtAdvancedDNSSettingsInvalid"
@ -183,15 +196,28 @@
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFakeIP}" />
Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" />
<ToggleSwitch
x:Name="togFakeIP"
x:Name="togAddCommonHosts"
Grid.Row="2"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="2"
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbFakeIP}" />
<ToggleSwitch
x:Name="togFakeIP"
Grid.Row="3"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
@ -199,19 +225,19 @@
TextWrapping="Wrap" />
<TextBlock
Grid.Row="3"
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbBlockSVCBHTTPSQueries}" />
<ToggleSwitch
x:Name="togBlockBindingQuery"
Grid.Row="3"
Grid.Row="4"
Grid.Column="1"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Row="4"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
@ -219,20 +245,20 @@
TextWrapping="Wrap" />
<TextBlock
Grid.Row="4"
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbValidateDirectExpectedIPs}" />
<ComboBox
x:Name="cmbDirectExpectedIPs"
Grid.Row="4"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
IsEditable="True" />
<TextBlock
Grid.Row="4"
Grid.Row="5"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
@ -240,7 +266,7 @@
TextWrapping="Wrap" />
<TextBlock
Grid.Row="5"
Grid.Row="6"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="{StaticResource Margin4}"
@ -249,7 +275,7 @@
<TextBox
x:Name="txtHosts"
Grid.Row="6"
Grid.Row="7"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="{StaticResource Margin4}"

View file

@ -40,6 +40,8 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ParallelQuery, v => v.togParallelQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServeStale, v => v.togServeStale.IsChecked).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);

View file

@ -171,13 +171,27 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" />
Text="{x:Static resx:ResUI.TbParallelQuery}" />
<ToggleButton
x:Name="togAddCommonHosts"
x:Name="togParallelQuery"
Grid.Row="7"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="8"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbServeStale}" />
<ToggleButton
x:Name="togServeStale"
Grid.Row="8"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
</Grid>
</ScrollViewer>
</TabItem>
@ -191,6 +205,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@ -229,15 +244,29 @@
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbFakeIP}" />
Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" />
<ToggleButton
x:Name="togFakeIP"
x:Name="togAddCommonHosts"
Grid.Row="2"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="2"
Grid.Row="3"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbFakeIP}" />
<ToggleButton
x:Name="togFakeIP"
Grid.Row="3"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Column="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
@ -246,7 +275,7 @@
TextWrapping="Wrap" />
<TextBlock
Grid.Row="3"
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
@ -254,12 +283,12 @@
Text="{x:Static resx:ResUI.TbBlockSVCBHTTPSQueries}" />
<ToggleButton
x:Name="togBlockBindingQuery"
Grid.Row="3"
Grid.Row="4"
Grid.Column="1"
Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" />
<TextBlock
Grid.Row="3"
Grid.Row="4"
Grid.Column="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
@ -268,7 +297,7 @@
TextWrapping="Wrap" />
<TextBlock
Grid.Row="4"
Grid.Row="5"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
@ -276,14 +305,14 @@
Text="{x:Static resx:ResUI.TbValidateDirectExpectedIPs}" />
<ComboBox
x:Name="cmbDirectExpectedIPs"
Grid.Row="4"
Grid.Row="5"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin8}"
IsEditable="True"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="4"
Grid.Row="5"
Grid.Column="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
@ -292,7 +321,7 @@
TextWrapping="Wrap" />
<TextBlock
Grid.Row="5"
Grid.Row="6"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="{StaticResource Margin8}"
@ -301,7 +330,7 @@
Text="{x:Static resx:ResUI.TbDNSHostsConfig}" />
<TextBox
x:Name="txtHosts"
Grid.Row="6"
Grid.Row="7"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="{StaticResource Margin8}"

View file

@ -38,6 +38,8 @@ public partial class DNSSettingWindow
this.Bind(ViewModel, vm => vm.Strategy4Proxy, v => v.cmbRemoteDNSStrategy.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Hosts, v => v.txtHosts.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectExpectedIPs, v => v.cmbDirectExpectedIPs.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ParallelQuery, v => v.togParallelQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.ServeStale, v => v.togServeStale.IsChecked).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables);