From 16c642dd20991a7bf288d44a418cc22e13d02038 Mon Sep 17 00:00:00 2001 From: EonaCat Date: Mon, 17 Jul 2023 12:53:25 +0200 Subject: [PATCH] Updated --- EonaCat.DnsTester/Helpers/DnsHelper.cs | 252 +++++++++---------------- EonaCat.DnsTester/Helpers/UrlHelper.cs | 54 +++--- EonaCat.DnsTester/MainForm.cs | 86 ++++++--- 3 files changed, 166 insertions(+), 226 deletions(-) diff --git a/EonaCat.DnsTester/Helpers/DnsHelper.cs b/EonaCat.DnsTester/Helpers/DnsHelper.cs index 0148186..d70a384 100644 --- a/EonaCat.DnsTester/Helpers/DnsHelper.cs +++ b/EonaCat.DnsTester/Helpers/DnsHelper.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; +using System.IO; using System.Net; using System.Net.Sockets; -using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -11,10 +11,12 @@ namespace EonaCat.DnsTester.Helpers class DnsHelper { public static event EventHandler OnLog; + + private static readonly Random random = new Random(); + public static async Task SendDnsQueryPacket(string dnsId, string server, int port, byte[] queryBytes) { - // Start the clock - var startTime = DateTime.Now.Ticks; + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); var endPoint = new IPEndPoint(IPAddress.Parse(server), port); using (var client = new UdpClient(endPoint.AddressFamily)) @@ -23,10 +25,11 @@ namespace EonaCat.DnsTester.Helpers client.EnableBroadcast = false; client.Client.SendTimeout = DnsSendTimeout; client.Client.ReceiveTimeout = DnsReceiveTimeout; - byte[] responseBytes = null; + + byte[] responseBytes; if (FakeResponse) { - responseBytes = DnsHelper.GetExampleResponse(); + responseBytes = GetExampleResponse(); } else { @@ -35,94 +38,55 @@ namespace EonaCat.DnsTester.Helpers responseBytes = responseResult.Buffer; } - DnsResponse response = ParseDnsResponsePacket(dnsId, startTime, server, responseBytes); + var response = ParseDnsResponsePacket(dnsId, stopwatch.ElapsedTicks, server, responseBytes); return response; } } // For testing purposes public static bool FakeResponse { get; set; } - public static int DnsSendTimeout { get; set; } = 5; public static int DnsReceiveTimeout { get; set; } = 5; - public static byte[] CreateDnsQueryPacket(string domainName, DnsRecordType recordType) { - Random random = new Random(); + var id = (ushort)random.Next(0, 65536); + var flags = (ushort)0x0100; // recursion desired + var qdcount = (ushort)1; + var ancount = (ushort)0; + var nscount = (ushort)0; + var arcount = (ushort)0; - // DNS header - ushort id = (ushort)random.Next(0, 65536); - ushort flags = (ushort)0x0100; // recursion desired - ushort qdcount = 1; - ushort ancount = 0; - ushort nscount = 0; - ushort arcount = 0; - byte[] headerBytes = new byte[] + using (var stream = new MemoryStream()) + using (var writer = new BinaryWriter(stream)) { - (byte)(id >> 8), (byte)(id & 0xff), - (byte)(flags >> 8), (byte)(flags & 0xff), - (byte)(qdcount >> 8), (byte)(qdcount & 0xff), - (byte)(ancount >> 8), (byte)(ancount & 0xff), - (byte)(nscount >> 8), (byte)(nscount & 0xff), - (byte)(arcount >> 8), (byte)(arcount & 0xff), - }; + writer.Write(id); + writer.Write(flags); + writer.Write(qdcount); + writer.Write(ancount); + writer.Write(nscount); + writer.Write(arcount); - // DNS question - string[] labels = domainName.Split('.'); - byte[] qnameBytes = new byte[domainName.Length + 2]; - int qnameIndex = 0; - foreach (string label in labels) - { - qnameBytes[qnameIndex++] = (byte)label.Length; - foreach (char c in label) + var labels = domainName.Split('.'); + foreach (var label in labels) { - qnameBytes[qnameIndex++] = (byte)c; + writer.Write((byte)label.Length); + writer.Write(Encoding.ASCII.GetBytes(label)); } + + writer.Write((byte)0); // Null terminator + writer.Write((ushort)recordType); + writer.Write((ushort)1); // Record class: IN (Internet) + + return stream.ToArray(); } - qnameBytes[qnameIndex++] = 0; - - byte[] qtypeBytes = new byte[] { (byte)((ushort)recordType >> 8), (byte)((ushort)recordType & 0xff) }; - byte[] qclassBytes = new byte[] { 0, 1 }; // internet class - byte[] questionBytes = new byte[qnameBytes.Length + qtypeBytes.Length + qclassBytes.Length]; - qnameBytes.CopyTo(questionBytes, 0); - qtypeBytes.CopyTo(questionBytes, qnameBytes.Length); - qclassBytes.CopyTo(questionBytes, qnameBytes.Length + qtypeBytes.Length); - - // Combine the header and question to form the DNS query packet - byte[] queryBytes = new byte[headerBytes.Length + questionBytes.Length]; - headerBytes.CopyTo(queryBytes, 0); - questionBytes.CopyTo(queryBytes, headerBytes.Length); - - return queryBytes; - } - - static DnsQuestion ParseDnsQuestionRecord(byte[] queryBytes, ref int offset) - { - // Parse the DNS name - string name = DnsNameParser.ParseName(queryBytes, ref offset); - if (name == null) - { - return null; - } - - // Parse the DNS type and class - ushort type = (ushort)((queryBytes[offset] << 8) | queryBytes[offset + 1]); - ushort qclass = (ushort)((queryBytes[offset + 2] << 8) | queryBytes[offset + 3]); - offset += 4; - - return new DnsQuestion - { - Name = name, - Type = (DnsRecordType)type, - Class = (DnsRecordClass)qclass, - }; } public static byte[] GetExampleResponse() { // Example response bytes for the A record of google.com - byte[] response = { + return new byte[] + { 0x9d, 0xa9, // Query ID 0x81, 0x80, // Flags 0x00, 0x01, // Questions: 1 @@ -141,58 +105,45 @@ namespace EonaCat.DnsTester.Helpers 0x00, 0x04, // Data length: 4 bytes 0xac, 0xd9, 0x03, 0x3d // Data: 172.217.3.61 }; - return response; } - static DnsResponse ParseDnsResponsePacket(string dnsId, long startTime, string server, byte[] responseBytes) + private static DnsResponse ParseDnsResponsePacket(string dnsId, long startTime, string server, byte[] responseBytes) { - Console.WriteLine("new byte[] { " + responseBytes + " }"); - - // Check if response is valid if (responseBytes.Length < 12) { throw new Exception("Invalid DNS response"); } - // Set the offset to the start var offset = 0; - - // Parse the DNS header - ushort id = (ushort)((responseBytes[0] << 8) | responseBytes[1]); - ushort flags = (ushort)((responseBytes[2] << 8) | responseBytes[3]); - bool isResponse = (flags & 0x8000) != 0; - ushort qdcount = (ushort)((responseBytes[4] << 8) | responseBytes[5]); - ushort ancount = (ushort)((responseBytes[6] << 8) | responseBytes[7]); + var id = (ushort)((responseBytes[offset++] << 8) | responseBytes[offset++]); + var flags = (ushort)((responseBytes[offset++] << 8) | responseBytes[offset++]); + var isResponse = (flags & 0x8000) != 0; + var qdcount = (ushort)((responseBytes[offset++] << 8) | responseBytes[offset++]); if (!isResponse) { throw new Exception("Invalid DNS response"); } - ushort nscount = (ushort)((responseBytes[8] << 8) | responseBytes[9]); - ushort arcount = (ushort)((responseBytes[10] << 8) | responseBytes[11]); - - // We parsed the header set the offset past the header - offset = 12; - - List questions = new List(); + var nscount = (ushort)((responseBytes[offset++] << 8) | responseBytes[offset++]); + var arcount = (ushort)((responseBytes[offset++] << 8) | responseBytes[offset++]); + var questions = new List(); for (int i = 0; i < qdcount; i++) { - DnsQuestion question = ParseDnsQuestionRecord(responseBytes, ref offset); + var question = ParseDnsQuestionRecord(responseBytes, ref offset); if (question != null) { questions.Add(question); } } - // Parse the DNS answer records - List answers = new List(); - for (int i = 0; i < ancount; i++) + var answers = new List(); + for (int i = 0; i < qdcount; i++) { try { - ResourceRecord answer = ParseDnsAnswerRecord(responseBytes, ref offset); + var answer = ParseDnsAnswerRecord(responseBytes, ref offset); if (answer != null) { answers.Add(answer); @@ -200,17 +151,17 @@ namespace EonaCat.DnsTester.Helpers } catch (Exception exception) { - OnLog?.Invoke(null, $"Answer exception: " + exception.Message); + OnLog?.Invoke(null, $"Answer exception: {exception.Message}"); } } // Parse the DNS authority records - List authorities = new List(); + var authorities = new List(); for (int i = 0; i < nscount; i++) { try { - ResourceRecord authority = ParseDnsAnswerRecord(responseBytes, ref offset); + var authority = ParseDnsAnswerRecord(responseBytes, ref offset); if (authority != null) { authorities.Add(authority); @@ -218,17 +169,17 @@ namespace EonaCat.DnsTester.Helpers } catch (Exception exception) { - OnLog?.Invoke(null, $"Authority answer exception: " + exception.Message); + OnLog?.Invoke(null, $"Authority answer exception: {exception.Message}"); } } // Parse the DNS additional records - List additionals = new List(); + var additionals = new List(); for (int i = 0; i < arcount; i++) { try { - ResourceRecord additional = ParseDnsAnswerRecord(responseBytes, ref offset); + var additional = ParseDnsAnswerRecord(responseBytes, ref offset); if (additional != null) { additionals.Add(additional); @@ -236,7 +187,7 @@ namespace EonaCat.DnsTester.Helpers } catch (Exception exception) { - OnLog?.Invoke(null, $"Additional answer exception: " + exception.Message); + OnLog?.Invoke(null, $"Additional answer exception: {exception.Message}"); } } @@ -255,53 +206,61 @@ namespace EonaCat.DnsTester.Helpers }; } - static ResourceRecord ParseDnsAnswerRecord(byte[] responseBytes, ref int offset) + private static DnsQuestion ParseDnsQuestionRecord(byte[] queryBytes, ref int offset) { - // Parse the DNS name - string name = DnsNameParser.ExtractDomainName(responseBytes, ref offset); + var name = DnsNameParser.ParseName(queryBytes, ref offset); if (name == null) { return null; } - // Parse the DNS type, class, ttl, and data length - DnsRecordType type = (DnsRecordType)((responseBytes[offset++] << 8) + responseBytes[offset++]); - DnsRecordClass klass = (DnsRecordClass)((responseBytes[offset++] << 8) + responseBytes[offset++]); - int ttl = (responseBytes[offset++] << 24) + (responseBytes[offset++] << 16) + (responseBytes[offset++] << 8) + responseBytes[offset++]; + var type = (DnsRecordType)((queryBytes[offset++] << 8) | queryBytes[offset++]); + var qclass = (DnsRecordClass)((queryBytes[offset++] << 8) | queryBytes[offset++]); - // Extract record data length - int dataLength = (responseBytes[offset] << 8) + responseBytes[offset + 1]; - offset += 2; + return new DnsQuestion + { + Name = name, + Type = type, + Class = qclass, + }; + } - // Extract record data - byte[] recordData = new byte[dataLength]; - Buffer.BlockCopy(responseBytes, offset, recordData, 0, dataLength); + private static ResourceRecord ParseDnsAnswerRecord(byte[] responseBytes, ref int offset) + { + var name = DnsNameParser.ExtractDomainName(responseBytes, ref offset); + if (name == null) + { + return null; + } - string recordDataAsString = null; - switch ((DnsRecordType)type) + var type = (DnsRecordType)((responseBytes[offset++] << 8) + responseBytes[offset++]); + var klass = (DnsRecordClass)((responseBytes[offset++] << 8) + responseBytes[offset++]); + var ttl = (responseBytes[offset++] << 24) + (responseBytes[offset++] << 16) + (responseBytes[offset++] << 8) + responseBytes[offset++]; + var dataLength = (responseBytes[offset++] << 8) + responseBytes[offset++]; + + string dataAsString = null; + switch (type) { case DnsRecordType.A: if (dataLength != 4) { return null; } - recordDataAsString = new IPAddress(recordData).ToString(); - offset += recordData.Length; + dataAsString = new IPAddress(responseBytes, offset).ToString(); + offset += dataLength; break; case DnsRecordType.CNAME: - recordDataAsString = DnsNameParser.ExtractDomainName(responseBytes, ref offset); - break; case DnsRecordType.NS: - recordDataAsString = DnsNameParser.ExtractDomainName(responseBytes, ref offset); + dataAsString = DnsNameParser.ExtractDomainName(responseBytes, ref offset); break; case DnsRecordType.MX: - int preference = (responseBytes[0] << 8) + responseBytes[1]; - offset += 2; - string exchange = DnsNameParser.ExtractDomainName(responseBytes, ref offset); - recordDataAsString = $"{preference} {exchange}"; + var preference = (responseBytes[offset++] << 8) + responseBytes[offset++]; + var exchange = DnsNameParser.ExtractDomainName(responseBytes, ref offset); + dataAsString = $"{preference} {exchange}"; break; case DnsRecordType.TXT: - recordDataAsString = Encoding.ASCII.GetString(recordData); + dataAsString = Encoding.ASCII.GetString(responseBytes, offset, dataLength); + offset += dataLength; break; default: offset += dataLength; @@ -314,19 +273,10 @@ namespace EonaCat.DnsTester.Helpers Type = type, Class = klass, Ttl = TimeSpan.FromSeconds(ttl), - Data = recordDataAsString, + Data = dataAsString, + DataLength = (ushort)dataLength, }; } - - static string GetString(byte[] bytes, ref int index, int length) - { - string str = ""; - for (int i = 0; i < length; i++) - { - str += (char)bytes[index++]; - } - return str; - } } public class DnsQuestion @@ -372,35 +322,11 @@ namespace EonaCat.DnsTester.Helpers public enum DnsRecordClass : ushort { - /// - /// The Internet. - /// Internet = 1, - - /// - /// The CSNET class (Obsolete - used only for examples insome obsolete RFCs). - /// CS = 2, - - /// - /// The CHAOS class. - /// CH = 3, - - /// - /// Hesiod[Dyer 87]. - /// HS = 4, - - /// - /// Used in UPDATE message to signify no class. - /// None = 254, - - /// - /// Only used in QCLASS. - /// - /// ANY = 255 } } diff --git a/EonaCat.DnsTester/Helpers/UrlHelper.cs b/EonaCat.DnsTester/Helpers/UrlHelper.cs index 6d1f7d9..5ca1235 100644 --- a/EonaCat.DnsTester/Helpers/UrlHelper.cs +++ b/EonaCat.DnsTester/Helpers/UrlHelper.cs @@ -21,19 +21,17 @@ namespace EonaCat.DnsTester.Helpers public static bool UseSearchEngineStartPage { get; set; } public static bool UseSearchEngineYandex { get; set; } - private static async Task> GetRandomUrls(int totalUrls) + private static List GetRandomUrls(int totalUrls) { var letters = GetRandomLetters(); - var searchEngineUrls = GetSearchEngines(); - Random rand = new Random(); - List urls = new List(); - while (urls.Count < totalUrls) + + Parallel.ForEach(searchEngineUrls, searchEngine => { - int index = rand.Next(searchEngineUrls.Count); - KeyValuePair searchEngine = searchEngineUrls.ElementAt(index); + if (urls.Count >= totalUrls) + return; string url = searchEngine.Value + letters; @@ -51,9 +49,7 @@ namespace EonaCat.DnsTester.Helpers } if (responseString == null) - { - continue; - } + return; // find all .xxx.com addresses MatchCollection hostNames = Regex.Matches(responseString, @"[.](\w+[.]com)"); @@ -69,24 +65,20 @@ namespace EonaCat.DnsTester.Helpers } } - // Add the names to the list - foreach (string name in uniqueNames) + lock (urls) { - if (urls.Count >= totalUrls) + // Add the names to the list + foreach (string name in uniqueNames) { - break; - } + if (urls.Count >= totalUrls) + break; - if (!urls.Contains(name)) - { - urls.Add(name); + if (!urls.Contains(name)) + urls.Add(name); } } } - - letters = GetRandomLetters(); - await Task.Delay(100); - } + }); var urlText = "url" + (urls.Count > 1 ? "'s" : string.Empty); SetStatus($"{urls.Count} random {urlText} found"); @@ -139,20 +131,18 @@ namespace EonaCat.DnsTester.Helpers return searchEngineUrls; } - public static async Task> RetrieveUrls(int numThreads, int numUrlsPerThread) + public static List RetrieveUrls(int numThreads, int numUrlsPerThread) { - Task[] tasks = new Task[numThreads]; - List urlList = new List(); - // start each thread to retrieve a subset of unique URLs - for (int i = 0; i < numThreads; i++) + Parallel.For(0, numThreads, _ => { - tasks[i] = Task.Run(async () => urlList.AddRange(await GetRandomUrls(numUrlsPerThread))); - } - - // wait for all threads to complete - await Task.WhenAll(tasks); + var threadUrls = GetRandomUrls(numUrlsPerThread); + lock (urlList) + { + urlList.AddRange(threadUrls); + } + }); return urlList; } diff --git a/EonaCat.DnsTester/MainForm.cs b/EonaCat.DnsTester/MainForm.cs index d6587a2..b240762 100644 --- a/EonaCat.DnsTester/MainForm.cs +++ b/EonaCat.DnsTester/MainForm.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; +using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using EonaCat.DnsTester.Helpers; @@ -57,11 +58,11 @@ namespace EonaCat.DnsTester } SetSearchEngines(); - urls = await UrlHelper.RetrieveUrls(numThreads, numUrlsPerThread); + urls = UrlHelper.RetrieveUrls(numThreads, numUrlsPerThread); AddUrlToView(urls); IsRunning = true; - await Process(_recordType, urls.ToArray(), _dnsServer1, _dnsServer2); + await Process(_recordType, urls.ToArray(), _dnsServer1, _dnsServer2).ConfigureAwait(false); IsRunning = false; } @@ -350,21 +351,7 @@ namespace EonaCat.DnsTester return; } - await Task.Run(() => - { - try - { - var dnsEntry = Dns.GetHostEntry(iPAddress); - txtResolveHost.Invoke(() => - { - txtResolveHost.Text = dnsEntry.HostName; - }); - } - catch (Exception) - { - MessageBox.Show($"Could not get hostname for IP address '{txtResolveIP.Text}'"); - } - }); + await ResolveIP().ConfigureAwait(false); } private async void btnResolveHost_Click(object sender, EventArgs e) @@ -374,38 +361,75 @@ namespace EonaCat.DnsTester MessageBox.Show("Please enter a hostname to resolve"); return; } + await ResolveHost().ConfigureAwait(false); + } - - await Task.Run(() => + private async Task ResolveIP() + { + if (string.IsNullOrWhiteSpace(txtResolveIP.Text)) { - try - { - var dnsEntry = Dns.GetHostEntry(txtResolveHost.Text); + MessageBox.Show("Please enter an IP address to resolve"); + return; + } - txtResolveHost.Invoke(() => - { - txtResolveIP.Text = dnsEntry.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork).FirstOrDefault().Address.ToString(); - }); + if (!IPAddress.TryParse(txtResolveIP.Text, out IPAddress iPAddress)) + { + MessageBox.Show("Please enter a valid IP address"); + return; + } + + try + { + var dnsEntry = await Dns.GetHostEntryAsync(iPAddress); + txtResolveHost.Text = dnsEntry.HostName; + } + catch (Exception) + { + MessageBox.Show($"Could not get hostname for IP address '{txtResolveIP.Text}'"); + } + } + + private async Task ResolveHost() + { + if (string.IsNullOrWhiteSpace(txtResolveHost.Text)) + { + MessageBox.Show("Please enter a hostname to resolve"); + return; + } + + try + { + var dnsEntry = await Dns.GetHostEntryAsync(txtResolveHost.Text); + var ipAddress = dnsEntry.AddressList.FirstOrDefault(x => x.AddressFamily == AddressFamily.InterNetwork); + if (ipAddress != null) + { + txtResolveIP.Text = ipAddress.ToString(); } - catch (Exception) + else { MessageBox.Show($"Could not get IP address for hostname '{txtResolveHost.Text}'"); } - }); + } + catch (Exception) + { + MessageBox.Show($"Could not get IP address for hostname '{txtResolveHost.Text}'"); + } } private void SetStatus(string text) { + StringBuilder sb = new StringBuilder(StatusBox.Items.Count + 1); + sb.AppendLine($"{DateTime.Now} {text}"); + StatusBox.Invoke(() => { - StatusBox.Items.Add($"{DateTime.Now} {text}"); + StatusBox.Items.Add(sb.ToString()); StatusBox.TopIndex = StatusBox.Items.Count - 1; + if (StatusBox.Items.Count > STATUS_BAR_SIZE) { StatusBox.Items.RemoveAt(0); } - - StatusBox.Update(); }); }