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(); } } }