using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using System.Net; using System.Net.Http; using System.Collections.Concurrent; using System.Threading; using System.Text.Json; using System.Net.Security; using System.Net.Sockets; using DnsClientX; using System.Text; using System.Text.RegularExpressions; namespace DnsClient { public partial class DnsClientForm : Form { public DnsClientForm() { InitializeComponent(); } private (string result, int time) fastestDohResult; private (string result, int time) fastestDotResult; public async Task<(List dohResults, List dotResults)> GetNotice(string domain) { fastestDohResult = (null, int.MaxValue); fastestDotResult = (null, int.MaxValue); // 创建DoH客户端列表 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" }; // 创建DoT客户端列表 var dotServers = new List { // 360 new NameServer(IPAddress.Parse("101.226.4.6"), 853), // Aliyun new NameServer(IPAddress.Parse("223.5.5.5"), 853), new NameServer(IPAddress.Parse("223.6.6.6"), 853), // Tencent new NameServer(IPAddress.Parse("1.12.12.12"), 853), // Cloudflare DoT new NameServer(IPAddress.Parse("1.1.1.1"), 853), new NameServer(IPAddress.Parse("1.0.0.1"), 853), // Google DoT new NameServer(IPAddress.Parse("8.8.8.8"), 853), new NameServer(IPAddress.Parse("8.8.4.4"), 853), // Quad9 DoT 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 dohResults = new ConcurrentBag(); var dotResults = new ConcurrentBag(); var dohTasks = dohServers.Select(server => QueryDohAsync(server, domain, dohResults, cts.Token)).ToList(); var dotTasks = dotServers.Select(server => QueryDotAsync(server, domain, dotResults, cts.Token)).ToList(); await Task.WhenAll(dohTasks.Concat(dotTasks)); return (dohResults.ToList(), dotResults.ToList()); } private async Task QueryDohAsync(string server, string domain, ConcurrentBag results, CancellationToken token) { var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { var httpClient = new HttpClient(); httpClient.Timeout = TimeSpan.FromSeconds(5); httpClient.DefaultRequestHeaders.Add("Accept", "application/dns-json"); // Change type=1 (A record) to type=16 (TXT record) 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()) { // For TXT records, the data might be in quotes, so we'll just display it as-is string txtAnswer = answer.GetProperty("data").GetString(); string txtData = NormalizeResult(txtAnswer); string result = $"{server} => {txtData} (耗时: {elapsedMs}ms)"; results.Add(result); } } else { string result = $"{server} => 无 TXT 记录 (耗时: {elapsedMs}ms)"; results.Add(result); } } catch (Exception ex) { stopwatch.Stop(); var elapsedMs = (int)stopwatch.ElapsedMilliseconds; string result = $"{server} => 错误: {ex.Message} (耗时: {elapsedMs}ms)"; results.Add(result); if (fastestDohResult.result == null) { fastestDohResult = (result, elapsedMs); } } } 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)); 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) { 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("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); } } else { string result = $"{serverInfo} => 无 TXT 记录 (耗时: {elapsedMs}ms)"; results.Add(result); } } } } catch (Exception ex) { stopwatch.Stop(); var elapsedMs = (int)stopwatch.ElapsedMilliseconds; string serverInfo = $"{server.Address}:{server.Port}"; 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) { var random = new Random(); ushort transactionId = (ushort)random.Next(0, ushort.MaxValue); var query = new List(); // Transaction ID query.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)transactionId))); // Flags: standard query (0x0100) query.Add(0x01); query.Add(0x00); // Questions: 1 query.Add(0x00); query.Add(0x01); // Answer RRs: 0 query.Add(0x00); query.Add(0x00); // Authority RRs: 0 query.Add(0x00); query.Add(0x00); // Additional RRs: 0 query.Add(0x00); query.Add(0x00); // Question section var labels = domain.Split('.'); foreach (var label in labels) { query.Add((byte)label.Length); query.AddRange(System.Text.Encoding.ASCII.GetBytes(label)); } query.Add(0x00); // End of domain name // Type TXT (0x0010) instead of A (0x0001) query.Add(0x00); query.Add(0x10); // Class IN (0x0001) query.Add(0x00); query.Add(0x01); // Prefix length for TLS framing var length = query.Count; var framed = new List { (byte)(length >> 8), (byte)(length & 0xFF) }; framed.AddRange(query); return framed.ToArray(); } private List ParseDnsResponse(byte[] response, int length) { var txtRecords = new List(); // Skip TLS length prefix (2 bytes) int offset = 2; // Transaction ID (2 bytes) + Flags (2 bytes) offset += 4; // Questions int qdCount = (response[offset] << 8) | response[offset + 1]; offset += 2; // Answer RRs int anCount = (response[offset] << 8) | response[offset + 1]; offset += 6; // Skip authority + additional too // Skip question section for (int i = 0; i < qdCount; i++) { while (response[offset] != 0) { offset += response[offset] + 1; } offset += 5; // null byte + QTYPE(2) + QCLASS(2) } // Parse answer section for (int i = 0; i < anCount; i++) { // Skip name (compressed) offset += 2; ushort type = (ushort)((response[offset] << 8) | response[offset + 1]); offset += 8; // TYPE(2) + CLASS(2) + TTL(4) ushort dataLength = (ushort)((response[offset] << 8) | response[offset + 1]); offset += 2; if (type == 16) // TXT record { int end = offset + dataLength; var txtBuilder = new List(); 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; } txtRecords.Add(string.Join("", txtBuilder)); } else { offset += dataLength; } } return txtRecords; } private async Task GetPreferredResult() { var dnsClientTool = new DnsClientTool(); string test; try { // 获取 test 结果 test = await dnsClientTool.GetNotices("notice.suwin.cc"); } 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 class DnsResult { public ResultType Type { get; set; } public string FullResult { get; set; } public string IP { get; set; } public int Time { get; set; } } private async void Start_Button_Click(object sender, EventArgs e) { Select_Text.Clear(); Result_Text.Clear(); string domain = Domain_Text.Text.Trim(); if (string.IsNullOrEmpty(domain)) { Select_Text.AppendText("请输出正确的域名!\n"); return; } try { Select_Text.AppendText($"正在查询: {domain}\n"); var (dohResults, dotResults) = await GetNotice(domain); Select_Text.AppendText("=== DoH 查询结果 ===\n"); foreach (var result in dohResults.OrderBy(r => r)) { Select_Text.AppendText(result + "\n"); } Select_Text.AppendText("=== DoT 查询结果 ===\n"); foreach (var result in dotResults.OrderBy(r => r)) { Select_Text.AppendText(result + "\n"); } Result_Text.AppendText(await GetPreferredResult()); } catch (Exception ex) { Select_Text.AppendText($"查询出错: {ex.Message}\n"); } } private void Exit_Button_Click(object sender, EventArgs e) { 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(); } } }