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
//
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.Name = "Domain_Text";
this.Domain_Text.Size = new System.Drawing.Size(499, 21);
@ -65,15 +67,19 @@
//
// 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.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.Text = "";
//
// 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.Size = new System.Drawing.Size(75, 23);
this.Start_Button.TabIndex = 4;
@ -83,7 +89,8 @@
//
// 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.Size = new System.Drawing.Size(75, 23);
this.Exit_Button.TabIndex = 5;
@ -93,26 +100,29 @@
//
// 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.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.Text = "";
//
// label1
//
this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
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.Size = new System.Drawing.Size(35, 12);
this.label1.TabIndex = 7;
this.label1.Text = "结果:";
//
// DnsClient
// DnsClientForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
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.Result_Text);
this.Controls.Add(this.Exit_Button);
@ -121,8 +131,8 @@
this.Controls.Add(this.Select_Txt);
this.Controls.Add(this.Domain_Text);
this.Controls.Add(this.Domain_Txt);
this.Name = "DnsClient";
this.Text = "Form1";
this.Name = "DnsClientForm";
this.Text = "DnsClient";
this.ResumeLayout(false);
this.PerformLayout();

View File

@ -12,6 +12,8 @@ using System.Text.Json;
using System.Net.Security;
using System.Net.Sockets;
using DnsClientX;
using System.Text;
using System.Text.RegularExpressions;
namespace DnsClient
{
@ -105,25 +107,16 @@ namespace DnsClient
foreach (var answer in answers.EnumerateArray())
{
// 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)";
results.Add(result);
if (elapsedMs < fastestDohResult.time)
{
fastestDohResult = (server, elapsedMs);
}
}
}
else
{
string result = $"{server} => 无 TXT 记录 (耗时: {elapsedMs}ms)";
results.Add(result);
if (fastestDohResult.result == null || elapsedMs < fastestDohResult.time)
{
fastestDohResult = (result, elapsedMs);
}
}
}
catch (Exception ex)
@ -143,73 +136,70 @@ namespace DnsClient
private async Task QueryDotAsync(NameServer server, string domain, ConcurrentBag<string> results, CancellationToken token)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
TcpClient tcpClient = null;
try
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token))
{
cts.CancelAfter(TimeSpan.FromSeconds(5));
using (var tcpClient = new TcpClient())
{
var connectTask = tcpClient.ConnectAsync(server.Address.ToString(), server.Port);
var timeoutTask = Task.Delay(5000, cts.Token);
tcpClient = new TcpClient();
var connectTask = tcpClient.ConnectAsync(server.Address.ToString(), server.Port);
var timeoutTask = Task.Delay(5000, cts.Token);
if (await Task.WhenAny(connectTask, timeoutTask) == timeoutTask)
if (await Task.WhenAny(connectTask, timeoutTask) == timeoutTask)
{
throw new TimeoutException("连接超时");
}
// 确保连接成功完成
await connectTask;
using (var sslStream = new SslStream(tcpClient.GetStream(), false,
(sender, certificate, chain, sslPolicyErrors) => true))
{
var authTask = sslStream.AuthenticateAsClientAsync(server.Address.ToString());
if (await Task.WhenAny(authTask, timeoutTask) == timeoutTask)
{
throw new TimeoutException("连接超时");
throw new TimeoutException("SSL握手超时");
}
using (var sslStream = new SslStream(tcpClient.GetStream(), false,
(sender, certificate, chain, sslPolicyErrors) => true))
var request = CreateDnsQuery(domain);
var writeTask = sslStream.WriteAsync(request, 0, request.Length, cts.Token);
if (await Task.WhenAny(writeTask, timeoutTask) == timeoutTask)
{
var authTask = sslStream.AuthenticateAsClientAsync(server.Address.ToString());
if (await Task.WhenAny(authTask, timeoutTask) == timeoutTask)
throw new TimeoutException("写入请求超时");
}
var response = new byte[512];
var readTask = sslStream.ReadAsync(response, 0, response.Length, cts.Token);
if (await Task.WhenAny(readTask, timeoutTask) == timeoutTask)
{
throw new TimeoutException("读取响应超时");
}
int bytesRead = await readTask;
stopwatch.Stop();
var elapsedMs = (int)stopwatch.ElapsedMilliseconds;
var txtRecords = ParseDnsResponse(response, bytesRead);
string serverInfo = $"{server.Address}:{server.Port}";
if (txtRecords.Any())
{
foreach (var txt in txtRecords)
{
throw new TimeoutException("SSL握手超时");
}
var request = CreateDnsQuery(domain);
var writeTask = sslStream.WriteAsync(request, 0, request.Length, cts.Token);
if (await Task.WhenAny(writeTask, timeoutTask) == timeoutTask)
{
throw new TimeoutException("写入请求超时");
}
var response = new byte[512];
var readTask = sslStream.ReadAsync(response, 0, response.Length, cts.Token);
if (await Task.WhenAny(readTask, timeoutTask) == timeoutTask)
{
throw new TimeoutException("读取响应超时");
}
int bytesRead = await readTask;
stopwatch.Stop();
var elapsedMs = (int)stopwatch.ElapsedMilliseconds;
var txtRecords = ParseDnsResponse(response, bytesRead);
string serverInfo = $"{server.Address}:{server.Port}";
if (txtRecords.Any())
{
foreach (var txt in txtRecords)
{
string result = $"{serverInfo} => {txt} (耗时: {elapsedMs}ms)";
results.Add(result);
if (elapsedMs < fastestDotResult.time)
{
fastestDotResult = (serverInfo, elapsedMs);
}
}
}
else
{
string result = $"{serverInfo} => 无 TXT 记录 (耗时: {elapsedMs}ms)";
string result = $"{serverInfo} => {txt} (耗时: {elapsedMs}ms)";
results.Add(result);
}
}
else
{
string result = $"{serverInfo} => 无 TXT 记录 (耗时: {elapsedMs}ms)";
results.Add(result);
}
}
}
}
@ -218,9 +208,22 @@ namespace DnsClient
stopwatch.Stop();
var elapsedMs = (int)stopwatch.ElapsedMilliseconds;
string serverInfo = $"{server.Address}:{server.Port}";
string result = $"{serverInfo} => 错误: {ex.Message} (耗时: {elapsedMs}ms)";
string result = $"{serverInfo} => 错误: {GetErrorMessage(ex)} (耗时: {elapsedMs}ms)";
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)
@ -324,45 +327,53 @@ namespace DnsClient
offset += 2;
if (type == 16) // TXT record
{
int end = offset + dataLength;
var txtBuilder = new List<string>();
{
int end = offset + dataLength;
var txtBuilder = new List<string>();
while (offset < end)
{
int txtLen = response[offset++];
if (offset + txtLen > end) break;
while (offset < end)
{
int txtLen = response[offset++];
if (offset + txtLen > end) break;
var txt = System.Text.Encoding.UTF8.GetString(response, offset, txtLen);
txtBuilder.Add(txt);
offset += txtLen;
}
var txt = System.Text.Encoding.UTF8.GetString(response, offset, txtLen);
txtBuilder.Add(txt);
offset += txtLen;
}
txtRecords.Add(string.Join("", txtBuilder));
}
else
{
offset += dataLength;
}
txtRecords.Add(string.Join("", txtBuilder));
}
else
{
offset += dataLength;
}
}
return txtRecords;
}
private string GetPreferredResult()
private async Task<string> GetPreferredResult()
{
// 只返回成功的解析结果
string preferredDoh = fastestDohResult.time != int.MaxValue ?
fastestDohResult.result : "无可用DoH结果";
var dnsClientTool = new DnsClientTool();
string test;
try
{
// 获取 test 结果
test = await dnsClientTool.GetNotices("notice.suwin.cc");
}
catch (Exception ex)
{
test = $"查询失败: {ex.Message}";
}
string preferredDot = fastestDotResult.time != int.MaxValue ?
fastestDotResult.result : "无可用DoT结果";
var result = new StringBuilder();
result.AppendLine("=== 优选结果 ===");
result.AppendLine($"{test}");
return $"=== 优选结果 ===\n" +
$"DoH: {preferredDoh}\n" +
$"DoT: {preferredDot}";
return result.ToString();
}
// 辅助类型定义
private enum ResultType { Success, NoRecord, Error }
@ -403,7 +414,7 @@ else
Select_Text.AppendText(result + "\n");
}
Result_Text.AppendText(GetPreferredResult());
Result_Text.AppendText(await GetPreferredResult());
}
catch (Exception ex)
{
@ -416,5 +427,37 @@ else
this.Close();
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">
<DependentUpon>DnsClientForm.cs</DependentUpon>
</Compile>
<Compile Include="DnsClientTool.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<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();
}
}
}