diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/DnsQuery.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/DnsQuery.cs new file mode 100644 index 0000000..31ff7fb --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/DnsQuery.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace WelsonJS.Launcher +{ + public class DnsQuery + { + private readonly string _dnsServer; + private const int DnsPort = 53; + private const int Timeout = 5000; + + public DnsQuery(string dnsServer = "8.8.8.8") + { + _dnsServer = dnsServer; + } + + public List QueryA(string domain) => QueryDns(domain, 1); + public List QueryNS(string domain) => QueryDns(domain, 2); + public List QueryCNAME(string domain) => QueryDns(domain, 5); + public List QuerySOA(string domain) => QueryDns(domain, 6); + public List QueryPTR(string domain) => QueryDns(domain, 12); + public List QueryMX(string domain) => QueryDns(domain, 15); + public List QueryTXT(string domain) => QueryDns(domain, 16); + public List QueryAAAA(string domain) => QueryDns(domain, 28); + public List QuerySRV(string domain) => QueryDns(domain, 33); + public List QueryNAPTR(string domain) => QueryDns(domain, 35); + public List QueryCAA(string domain) => QueryDns(domain, 257); + + public Dictionary> QueryAll(string domain) + { + var results = new Dictionary>(); + + results["A"] = QueryA(domain); + results["NS"] = QueryNS(domain); + results["CNAME"] = QueryCNAME(domain); + results["SOA"] = QuerySOA(domain); + results["PTR"] = QueryPTR(domain); + results["MX"] = QueryMX(domain); + results["TXT"] = QueryTXT(domain); + results["AAAA"] = QueryAAAA(domain); + results["SRV"] = QuerySRV(domain); + results["NAPTR"] = QueryNAPTR(domain); + results["CAA"] = QueryCAA(domain); + + return results; + } + + private List QueryDns(string domain, ushort type) + { + List records = new List(); + + try + { + UdpClient udpClient = new UdpClient(_dnsServer, DnsPort); + udpClient.Client.ReceiveTimeout = Timeout; + + byte[] request = CreateDnsQuery(domain, type); + udpClient.Send(request, request.Length); + + IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, DnsPort); + byte[] response = udpClient.Receive(ref remoteEP); + + records.AddRange(ParseDnsResponse(response, type)); + udpClient.Close(); + } + catch (Exception ex) + { + records.Add($"Error: {ex.Message}"); + } + + return records; + } + + private byte[] CreateDnsQuery(string domain, ushort type) + { + Random rand = new Random(); + byte[] query = new byte[512]; + + query[0] = (byte)rand.Next(0, 256); + query[1] = (byte)rand.Next(0, 256); + query[2] = 0x01; + query[3] = 0x00; + query[4] = 0x00; + query[5] = 0x01; + + for (int i = 6; i < 12; i++) + query[i] = 0x00; + + int position = 12; + foreach (string part in domain.Split('.')) + { + query[position++] = (byte)part.Length; + byte[] label = Encoding.ASCII.GetBytes(part); + Array.Copy(label, 0, query, position, label.Length); + position += label.Length; + } + query[position++] = 0x00; + + query[position++] = (byte)(type >> 8); + query[position++] = (byte)(type & 0xFF); + query[position++] = 0x00; + query[position++] = 0x01; + + byte[] finalQuery = new byte[position]; + Array.Copy(query, finalQuery, position); + + return finalQuery; + } + + private List ParseDnsResponse(byte[] response, ushort queryType) + { + List results = new List(); + + int answerCount = (response[6] << 8) | response[7]; + if (answerCount == 0) + { + results.Add("No records found."); + return results; + } + + int position = 12; // Skip DNS header + + while (response[position] != 0) + position += response[position] + 1; + position += 5; // End of Question section + + for (int i = 0; i < answerCount; i++) + { + while (response[position] != 0 && (response[position] & 0xC0) == 0) + position += response[position] + 1; + position += 2; // Skip Type, Class fields + + ushort recordType = (ushort)((response[position] << 8) | response[position + 1]); + position += 8; // Skip TTL and Data length fields + + ushort dataLength = (ushort)((response[position] << 8) | response[position + 1]); + position += 2; // Read Data Length + + byte[] data = new byte[dataLength]; + Array.Copy(response, position, data, 0, dataLength); + position += dataLength; + + switch (recordType) + { + case 1: + results.Add($"A: {new IPAddress(data)}"); + break; + case 2: + results.Add($"NS: {DecodeDomainName(response, data, 0)}"); + break; + case 5: + results.Add($"CNAME: {DecodeDomainName(response, data, 0)}"); + break; + case 6: + results.Add($"SOA: {DecodeDomainName(response, data, 0)}"); + break; + case 12: + results.Add($"PTR: {DecodeDomainName(response, data, 0)}"); + break; + case 15: + // MX record processing (priority and exchange) + if (data.Length >= 2) + { + ushort priority = (ushort)((data[0] << 8) | data[1]); + string exchange = DecodeDomainName(response, data, 2); // Decode domain name after 2 bytes + results.Add($"MX: Priority {priority}, Exchange {exchange}"); + } + else + { + results.Add($"MX: Invalid data length."); + } + break; + case 16: + int txtPos = 0; + while (txtPos < data.Length) + { + int txtLength = data[txtPos++]; + results.Add($"TXT: {Encoding.UTF8.GetString(data, txtPos, txtLength)}"); + txtPos += txtLength; + } + break; + case 28: + results.Add($"AAAA: {new IPAddress(data)}"); + break; + case 33: + ushort prioritySrv = (ushort)((data[0] << 8) | data[1]); + ushort weight = (ushort)((data[2] << 8) | data[3]); + ushort port = (ushort)((data[4] << 8) | data[5]); + string target = DecodeDomainName(response, data, 6); + results.Add($"SRV: Priority {prioritySrv}, Weight {weight}, Port {port}, Target {target}"); + break; + case 35: + results.Add($"NAPTR: {BitConverter.ToString(data)}"); + break; + case 257: + results.Add($"CAA: {BitConverter.ToString(data)}"); + break; + default: + results.Add($"Unknown Type {recordType}: {BitConverter.ToString(data)}"); + break; + } + } + + return results; + } + + private string DecodeDomainName(byte[] response, byte[] data, int startIndex) + { + int position = startIndex; + List labels = new List(); + + while (data[position] != 0) + { + // Handle 0xC0 pointer (compressed domain name handling) + if ((data[position] & 0xC0) == 0xC0) + { + int pointer = ((data[position] & 0x3F) << 8) | data[position + 1]; + return DecodeDomainName(response, response, pointer); // Recursive call to decode from the pointer + } + + int labelLength = data[position++]; + labels.Add(Encoding.ASCII.GetString(data, position, labelLength)); + position += labelLength; + } + + return string.Join(".", labels); + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs index 3d8c1bb..572bfd9 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -117,6 +117,14 @@ namespace WelsonJS.Launcher return; } + // Serve DNS Query request (use Google DNS server) + const string dnsQueryPrefix = "dns-query/"; + if (path.StartsWith(dnsQueryPrefix, StringComparison.OrdinalIgnoreCase)) + { + ServeDnsQueryRequest(context, path.Substring(dnsQueryPrefix.Length)); + return; + } + // Serve a resource ServeResource(context, GetResource(_resourceName), "text/html"); } @@ -211,6 +219,38 @@ namespace WelsonJS.Launcher } } + private void ServeDnsQueryRequest(HttpListenerContext context, string query) + { + if (string.IsNullOrWhiteSpace(query) || query.Length > 255) + { + ServeResource(context, "Invalid query parameter", "application/xml", 400); + return; + } + + try + { + DnsQuery dns = new DnsQuery(); + Dictionary> allRecords = dns.QueryAll(query); + + StringBuilder result = new StringBuilder(); + foreach (var recordType in allRecords.Keys) + { + result.AppendLine($"\n{recordType} Records:"); + foreach (var record in allRecords[recordType]) + { + result.AppendLine(record); + } + } + + string data = result.ToString(); + ServeResource(context, data, "text/plain", 200); + } + catch (Exception ex) + { + ServeResource(context, $"Failed to process DNS query. {ex.Message}", "application/xml", 500); + } + } + private void ServeResource(HttpListenerContext context, byte[] data, string mimeType = "text/html", int statusCode = 200) { string xmlHeader = ""; diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index c865a4b..370ad0a 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -72,6 +72,7 @@ + Form diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html index 34620fc..18ccf90 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html @@ -66,8 +66,12 @@ Whois + - Specific + Network tools @@ -275,6 +279,21 @@ }); }; + document.getElementById("btnDnsQuery").onclick = function () { + const hostname = prompt("Enter a hostname or IP address:", ''); + if (!hostname || hostname.trim() == '') { + alert("A hostname or IP address is required."); + return; + } + + axios.get(`${serverBaseUrl}/dns-query/${hostname}`).then(response => { + const responseText = response.data; + appendTextToEditor(`/*\n${responseText}\n*/`); + }).catch(error => { + console.error(error); + }); + }; + async function getTargetByUrl(urlPart) { const response = await fetch(`${serverBaseUrl}/devtools/json`); const targets = await response.json();