This commit is contained in:
dong 2025-04-11 01:18:45 +08:00
parent 1af6a6f4a6
commit 5168b118b1
4 changed files with 418 additions and 101 deletions

View File

@ -49,6 +49,8 @@
// //
// Domain_Text // Domain_Text
// //
this.Domain_Text.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.Domain_Text.Location = new System.Drawing.Point(53, 6); this.Domain_Text.Location = new System.Drawing.Point(53, 6);
this.Domain_Text.Name = "Domain_Text"; this.Domain_Text.Name = "Domain_Text";
this.Domain_Text.Size = new System.Drawing.Size(499, 21); this.Domain_Text.Size = new System.Drawing.Size(499, 21);
@ -65,15 +67,19 @@
// //
// Select_Text // Select_Text
// //
this.Select_Text.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.Select_Text.Location = new System.Drawing.Point(53, 41); this.Select_Text.Location = new System.Drawing.Point(53, 41);
this.Select_Text.Name = "Select_Text"; this.Select_Text.Name = "Select_Text";
this.Select_Text.Size = new System.Drawing.Size(499, 228); this.Select_Text.Size = new System.Drawing.Size(499, 284);
this.Select_Text.TabIndex = 3; this.Select_Text.TabIndex = 3;
this.Select_Text.Text = ""; this.Select_Text.Text = "";
// //
// Start_Button // Start_Button
// //
this.Start_Button.Location = new System.Drawing.Point(12, 386); this.Start_Button.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.Start_Button.Location = new System.Drawing.Point(12, 449);
this.Start_Button.Name = "Start_Button"; this.Start_Button.Name = "Start_Button";
this.Start_Button.Size = new System.Drawing.Size(75, 23); this.Start_Button.Size = new System.Drawing.Size(75, 23);
this.Start_Button.TabIndex = 4; this.Start_Button.TabIndex = 4;
@ -83,7 +89,8 @@
// //
// Exit_Button // Exit_Button
// //
this.Exit_Button.Location = new System.Drawing.Point(477, 386); this.Exit_Button.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.Exit_Button.Location = new System.Drawing.Point(477, 449);
this.Exit_Button.Name = "Exit_Button"; this.Exit_Button.Name = "Exit_Button";
this.Exit_Button.Size = new System.Drawing.Size(75, 23); this.Exit_Button.Size = new System.Drawing.Size(75, 23);
this.Exit_Button.TabIndex = 5; this.Exit_Button.TabIndex = 5;
@ -93,26 +100,29 @@
// //
// Result_Text // Result_Text
// //
this.Result_Text.Location = new System.Drawing.Point(53, 275); this.Result_Text.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.Result_Text.Location = new System.Drawing.Point(53, 331);
this.Result_Text.Name = "Result_Text"; this.Result_Text.Name = "Result_Text";
this.Result_Text.Size = new System.Drawing.Size(499, 105); this.Result_Text.Size = new System.Drawing.Size(499, 112);
this.Result_Text.TabIndex = 6; this.Result_Text.TabIndex = 6;
this.Result_Text.Text = ""; this.Result_Text.Text = "";
// //
// label1 // label1
// //
this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.label1.AutoSize = true; this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 275); this.label1.Location = new System.Drawing.Point(12, 334);
this.label1.Name = "label1"; this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 12); this.label1.Size = new System.Drawing.Size(35, 12);
this.label1.TabIndex = 7; this.label1.TabIndex = 7;
this.label1.Text = "结果:"; this.label1.Text = "结果:";
// //
// DnsClient // DnsClientForm
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(564, 421); this.ClientSize = new System.Drawing.Size(564, 484);
this.Controls.Add(this.label1); this.Controls.Add(this.label1);
this.Controls.Add(this.Result_Text); this.Controls.Add(this.Result_Text);
this.Controls.Add(this.Exit_Button); this.Controls.Add(this.Exit_Button);
@ -121,8 +131,8 @@
this.Controls.Add(this.Select_Txt); this.Controls.Add(this.Select_Txt);
this.Controls.Add(this.Domain_Text); this.Controls.Add(this.Domain_Text);
this.Controls.Add(this.Domain_Txt); this.Controls.Add(this.Domain_Txt);
this.Name = "DnsClient"; this.Name = "DnsClientForm";
this.Text = "Form1"; this.Text = "DnsClient";
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout(); this.PerformLayout();

View File

@ -12,6 +12,8 @@ using System.Text.Json;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using DnsClientX; using DnsClientX;
using System.Text;
using System.Text.RegularExpressions;
namespace DnsClient namespace DnsClient
{ {
@ -105,25 +107,16 @@ namespace DnsClient
foreach (var answer in answers.EnumerateArray()) foreach (var answer in answers.EnumerateArray())
{ {
// For TXT records, the data might be in quotes, so we'll just display it as-is // For TXT records, the data might be in quotes, so we'll just display it as-is
string txtData = answer.GetProperty("data").GetString(); string txtAnswer = answer.GetProperty("data").GetString();
string txtData = NormalizeResult(txtAnswer);
string result = $"{server} => {txtData} (耗时: {elapsedMs}ms)"; string result = $"{server} => {txtData} (耗时: {elapsedMs}ms)";
results.Add(result); results.Add(result);
if (elapsedMs < fastestDohResult.time)
{
fastestDohResult = (server, elapsedMs);
}
} }
} }
else else
{ {
string result = $"{server} => 无 TXT 记录 (耗时: {elapsedMs}ms)"; string result = $"{server} => 无 TXT 记录 (耗时: {elapsedMs}ms)";
results.Add(result); results.Add(result);
if (fastestDohResult.result == null || elapsedMs < fastestDohResult.time)
{
fastestDohResult = (result, elapsedMs);
}
} }
} }
catch (Exception ex) catch (Exception ex)
@ -143,14 +136,14 @@ namespace DnsClient
private async Task QueryDotAsync(NameServer server, string domain, ConcurrentBag<string> results, CancellationToken token) private async Task QueryDotAsync(NameServer server, string domain, ConcurrentBag<string> results, CancellationToken token)
{ {
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); var stopwatch = System.Diagnostics.Stopwatch.StartNew();
TcpClient tcpClient = null;
try try
{ {
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token)) using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token))
{ {
cts.CancelAfter(TimeSpan.FromSeconds(5)); cts.CancelAfter(TimeSpan.FromSeconds(5));
using (var tcpClient = new TcpClient()) tcpClient = new TcpClient();
{
var connectTask = tcpClient.ConnectAsync(server.Address.ToString(), server.Port); var connectTask = tcpClient.ConnectAsync(server.Address.ToString(), server.Port);
var timeoutTask = Task.Delay(5000, cts.Token); var timeoutTask = Task.Delay(5000, cts.Token);
@ -159,6 +152,9 @@ namespace DnsClient
throw new TimeoutException("连接超时"); throw new TimeoutException("连接超时");
} }
// 确保连接成功完成
await connectTask;
using (var sslStream = new SslStream(tcpClient.GetStream(), false, using (var sslStream = new SslStream(tcpClient.GetStream(), false,
(sender, certificate, chain, sslPolicyErrors) => true)) (sender, certificate, chain, sslPolicyErrors) => true))
{ {
@ -197,11 +193,6 @@ namespace DnsClient
{ {
string result = $"{serverInfo} => {txt} (耗时: {elapsedMs}ms)"; string result = $"{serverInfo} => {txt} (耗时: {elapsedMs}ms)";
results.Add(result); results.Add(result);
if (elapsedMs < fastestDotResult.time)
{
fastestDotResult = (serverInfo, elapsedMs);
}
} }
} }
else else
@ -212,15 +203,27 @@ namespace DnsClient
} }
} }
} }
}
catch (Exception ex) catch (Exception ex)
{ {
stopwatch.Stop(); stopwatch.Stop();
var elapsedMs = (int)stopwatch.ElapsedMilliseconds; var elapsedMs = (int)stopwatch.ElapsedMilliseconds;
string serverInfo = $"{server.Address}:{server.Port}"; string serverInfo = $"{server.Address}:{server.Port}";
string result = $"{serverInfo} => 错误: {ex.Message} (耗时: {elapsedMs}ms)"; string result = $"{serverInfo} => 错误: {GetErrorMessage(ex)} (耗时: {elapsedMs}ms)";
results.Add(result); results.Add(result);
} }
finally
{
tcpClient?.Dispose();
}
}
private string GetErrorMessage(Exception ex)
{
if (ex is AggregateException aggEx)
{
return string.Join("; ", aggEx.InnerExceptions.Select(iex => iex.Message));
}
return ex.Message;
} }
private byte[] CreateDnsQuery(string domain) private byte[] CreateDnsQuery(string domain)
@ -324,7 +327,7 @@ namespace DnsClient
offset += 2; offset += 2;
if (type == 16) // TXT record if (type == 16) // TXT record
{ {
int end = offset + dataLength; int end = offset + dataLength;
var txtBuilder = new List<string>(); var txtBuilder = new List<string>();
@ -339,29 +342,37 @@ namespace DnsClient
} }
txtRecords.Add(string.Join("", txtBuilder)); txtRecords.Add(string.Join("", txtBuilder));
} }
else else
{ {
offset += dataLength; offset += dataLength;
} }
} }
return txtRecords; return txtRecords;
} }
private string GetPreferredResult() private async Task<string> GetPreferredResult()
{ {
// 只返回成功的解析结果 var dnsClientTool = new DnsClientTool();
string preferredDoh = fastestDohResult.time != int.MaxValue ? string test;
fastestDohResult.result : "无可用DoH结果"; try
{
string preferredDot = fastestDotResult.time != int.MaxValue ? // 获取 test 结果
fastestDotResult.result : "无可用DoT结果"; test = await dnsClientTool.GetNotices("notice.suwin.cc");
return $"=== 优选结果 ===\n" +
$"DoH: {preferredDoh}\n" +
$"DoT: {preferredDot}";
} }
catch (Exception ex)
{
test = $"查询失败: {ex.Message}";
}
var result = new StringBuilder();
result.AppendLine("=== 优选结果 ===");
result.AppendLine($"{test}");
return result.ToString();
}
// 辅助类型定义 // 辅助类型定义
private enum ResultType { Success, NoRecord, Error } private enum ResultType { Success, NoRecord, Error }
@ -403,7 +414,7 @@ else
Select_Text.AppendText(result + "\n"); Select_Text.AppendText(result + "\n");
} }
Result_Text.AppendText(GetPreferredResult()); Result_Text.AppendText(await GetPreferredResult());
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -416,5 +427,37 @@ else
this.Close(); this.Close();
Application.Exit(); Application.Exit();
} }
public static string DecodeByteValuesToUtf8(string input)
{
string pattern = @"\\(\d{1,3})";
string decodedString = Regex.Replace(input, pattern, match =>
{
byte byteValue = byte.Parse(match.Groups[1].Value);
return ((char)byteValue).ToString();
});
byte[] byteArray = new byte[decodedString.Length];
for (int i = 0; i < decodedString.Length; i++)
{
byteArray[i] = (byte)decodedString[i];
}
return Encoding.UTF8.GetString(byteArray);
}
public static string NormalizeResult(string input)
{
if (!Regex.IsMatch(input, @"\\\d{1,3}"))
{
return input;
}
string decodedBytes = DecodeByteValuesToUtf8(input);
return decodedBytes.Trim();
}
} }
} }

View File

@ -136,6 +136,7 @@
<Compile Include="DnsClientForm.Designer.cs"> <Compile Include="DnsClientForm.Designer.cs">
<DependentUpon>DnsClientForm.cs</DependentUpon> <DependentUpon>DnsClientForm.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="DnsClientTool.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="DnsClientForm.resx"> <EmbeddedResource Include="DnsClientForm.resx">

263
DnsClientTool.cs Normal file
View File

@ -0,0 +1,263 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace DnsClient
{
class DnsClientTool
{
public async Task<string> GetNotices(string domain)
{
var dohServers = new List<string>
{
"https://cloudflare-dns.com/dns-query",
"https://dns.cloudflare.com/dns-query",
"https://1.1.1.1/dns-query",
"https://1.0.0.1/dns-query",
"https://dns.google/resolve",
"https://sm2.doh.pub/dns-query",
"https://doh.pub/dns-query",
"https://dns.alidns.com/resolve",
"https://223.5.5.5/resolve",
"https://223.6.6.6/resolve",
"https://doh.360.cn/resolve"
};
var dotServers = new List<NameServer>
{
new NameServer(IPAddress.Parse("101.226.4.6"), 853),
new NameServer(IPAddress.Parse("223.5.5.5"), 853),
new NameServer(IPAddress.Parse("223.6.6.6"), 853),
new NameServer(IPAddress.Parse("1.12.12.12"), 853),
new NameServer(IPAddress.Parse("1.1.1.1"), 853),
new NameServer(IPAddress.Parse("1.0.0.1"), 853),
new NameServer(IPAddress.Parse("8.8.8.8"), 853),
new NameServer(IPAddress.Parse("8.8.4.4"), 853),
new NameServer(IPAddress.Parse("9.9.9.9"), 853),
new NameServer(IPAddress.Parse("149.112.112.112"), 853)
};
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var results = new List<string>();
var dohTasks = dohServers.Select(server =>
QueryDohAsync(server, domain, cts.Token));
var dotTasks = dotServers.Select(server =>
QueryDotAsync(server, domain, cts.Token));
var dohResults = await Task.WhenAll(dohTasks);
var dotResults = await Task.WhenAll(dotTasks);
foreach (var list in dohResults.Concat(dotResults))
{
results.AddRange(list);
}
var recordCounts = results.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count());
var mostFrequentRecord = recordCounts.OrderByDescending(x => x.Value).FirstOrDefault();
return mostFrequentRecord.Key ?? "没有 TXT 记录";
}
private async Task<List<string>> QueryDohAsync(string server, string domain, CancellationToken token)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
List<string> results = new List<string>();
try
{
var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromSeconds(5);
httpClient.DefaultRequestHeaders.Add("Accept", "application/dns-json");
var response = await httpClient.GetAsync($"{server}?name={domain}&type=16", token);
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
var json = JsonDocument.Parse(responseString);
stopwatch.Stop();
var elapsedMs = (int)stopwatch.ElapsedMilliseconds;
if (json.RootElement.TryGetProperty("Answer", out var answers))
{
foreach (var answer in answers.EnumerateArray())
{
string txtData = NormalizeResult(answer.GetProperty("data").GetString());
results.Add(txtData);
}
}
}
catch (Exception ex)
{
stopwatch.Stop();
}
return results;
}
private async Task<List<string>> QueryDotAsync(NameServer server, string domain, CancellationToken token)
{
var results = new List<string>();
TcpClient tcpClient = null;
SslStream sslStream = null;
try
{
tcpClient = new TcpClient();
var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
cts.CancelAfter(TimeSpan.FromSeconds(5));
await ConnectWithTimeoutAsync(tcpClient, server.Address.ToString(), server.Port, cts.Token);
sslStream = new SslStream(tcpClient.GetStream(), false, (sender, certificate, chain, errors) => true);
await sslStream.AuthenticateAsClientAsync(server.Address.ToString());
byte[] query = BuildDnsQuery(domain);
byte[] lengthPrefixed = new byte[query.Length + 2];
lengthPrefixed[0] = (byte)(query.Length >> 8);
lengthPrefixed[1] = (byte)(query.Length & 0xFF);
Buffer.BlockCopy(query, 0, lengthPrefixed, 2, query.Length);
await sslStream.WriteAsync(lengthPrefixed, 0, lengthPrefixed.Length, cts.Token);
byte[] lengthBytes = new byte[2];
await sslStream.ReadAsync(lengthBytes, 0, 2, cts.Token);
int responseLength = (lengthBytes[0] << 8) | lengthBytes[1];
byte[] response = new byte[responseLength];
int totalRead = 0;
while (totalRead < responseLength)
{
int read = await sslStream.ReadAsync(response, totalRead, responseLength - totalRead, cts.Token);
if (read == 0) break;
totalRead += read;
}
results.AddRange(ParseTxtRecordsFromResponse(response));
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred while querying DoT server: {ex.Message}");
}
finally
{
sslStream?.Dispose();
tcpClient?.Close();
}
return results;
}
private byte[] BuildDnsQuery(string domain)
{
var rnd = new Random();
var id = (ushort)rnd.Next(0, ushort.MaxValue);
var message = new List<byte>();
message.AddRange(BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)id))); // Transaction ID
message.AddRange(new byte[] { 0x01, 0x00 });
message.AddRange(new byte[] { 0x00, 0x01 });
message.AddRange(new byte[] { 0x00, 0x00 });
message.AddRange(new byte[] { 0x00, 0x00 });
message.AddRange(new byte[] { 0x00, 0x00 });
foreach (var label in domain.Split('.'))
{
message.Add((byte)label.Length);
message.AddRange(Encoding.ASCII.GetBytes(label));
}
message.Add(0);
message.AddRange(new byte[] { 0x00, 0x10 });
message.AddRange(new byte[] { 0x00, 0x01 });
return message.ToArray();
}
private List<string> ParseTxtRecordsFromResponse(byte[] response)
{
var results = new List<string>();
int answerCount = (response[6] << 8) | response[7];
int position = 12;
while (response[position] != 0)
{
position += response[position] + 1;
}
position += 5;
for (int i = 0; i < answerCount; i++)
{
position += 2;
ushort type = (ushort)((response[position] << 8) | response[position + 1]);
position += 8;
ushort rdLength = (ushort)((response[position] << 8) | response[position + 1]);
position += 2;
if (type == 16)
{
int txtLength = response[position];
var txt = NormalizeResult(Encoding.UTF8.GetString(response, position + 1, txtLength));
results.Add(txt);
}
position += rdLength;
}
return results;
}
private async Task ConnectWithTimeoutAsync(TcpClient client, string address, int port, CancellationToken token)
{
var connectTask = client.ConnectAsync(address, port);
var timeoutTask = Task.Delay(Timeout.InfiniteTimeSpan, token);
var completedTask = await Task.WhenAny(connectTask, timeoutTask);
if (completedTask == timeoutTask)
{
client?.Close();
throw new TimeoutException("TCP 连接超时或已取消。");
}
await connectTask;
}
public static string DecodeByteValuesToUtf8(string input)
{
string pattern = @"\\(\d{1,3})";
string decodedString = Regex.Replace(input, pattern, match =>
{
byte byteValue = byte.Parse(match.Groups[1].Value);
return ((char)byteValue).ToString();
});
byte[] byteArray = new byte[decodedString.Length];
for (int i = 0; i < decodedString.Length; i++)
{
byteArray[i] = (byte)decodedString[i];
}
return Encoding.UTF8.GetString(byteArray);
}
public static string NormalizeResult(string input)
{
if (!Regex.IsMatch(input, @"\\\d{1,3}"))
{
return input;
}
string decodedBytes = DecodeByteValuesToUtf8(input);
return decodedBytes.Trim();
}
}
}