DnsClientForm/DnsClientTool.cs
2025-04-11 01:18:45 +08:00

264 lines
10 KiB
C#

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