From 5168b118b17fbee5732abff01e7cb6e7c81336af Mon Sep 17 00:00:00 2001 From: dong <1278815766@qq.com> Date: Fri, 11 Apr 2025 01:18:45 +0800 Subject: [PATCH] UTF-8 --- DnsClientForm.Designer.cs | 30 +++-- DnsClientForm.cs | 225 +++++++++++++++++++------------- DnsClientForm.csproj | 1 + DnsClientTool.cs | 263 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 418 insertions(+), 101 deletions(-) create mode 100644 DnsClientTool.cs diff --git a/DnsClientForm.Designer.cs b/DnsClientForm.Designer.cs index 4ebc19f..1b481da 100644 --- a/DnsClientForm.Designer.cs +++ b/DnsClientForm.Designer.cs @@ -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(); diff --git a/DnsClientForm.cs b/DnsClientForm.cs index 42bc2ef..d8419c0 100644 --- a/DnsClientForm.cs +++ b/DnsClientForm.cs @@ -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 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(); + { + int end = offset + dataLength; + var txtBuilder = new List(); - 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 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(); + } + } } diff --git a/DnsClientForm.csproj b/DnsClientForm.csproj index 29301ff..861297d 100644 --- a/DnsClientForm.csproj +++ b/DnsClientForm.csproj @@ -136,6 +136,7 @@ DnsClientForm.cs + diff --git a/DnsClientTool.cs b/DnsClientTool.cs new file mode 100644 index 0000000..a80150c --- /dev/null +++ b/DnsClientTool.cs @@ -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 GetNotices(string domain) + { + var dohServers = new List + { + "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 + { + 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(); + + 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> QueryDohAsync(string server, string domain, CancellationToken token) + { + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + List results = new List(); + 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> QueryDotAsync(NameServer server, string domain, CancellationToken token) + { + var results = new List(); + 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(); + 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 ParseTxtRecordsFromResponse(byte[] response) + { + var results = new List(); + + 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(); + } + + } +}