This commit is contained in:
EonaCat 2023-07-17 12:53:25 +02:00
parent 41a3f72560
commit 16c642dd20
3 changed files with 166 additions and 226 deletions

View File

@ -1,8 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,10 +11,12 @@ namespace EonaCat.DnsTester.Helpers
class DnsHelper class DnsHelper
{ {
public static event EventHandler<string> OnLog; public static event EventHandler<string> OnLog;
private static readonly Random random = new Random();
public static async Task<DnsResponse> SendDnsQueryPacket(string dnsId, string server, int port, byte[] queryBytes) public static async Task<DnsResponse> SendDnsQueryPacket(string dnsId, string server, int port, byte[] queryBytes)
{ {
// Start the clock var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var startTime = DateTime.Now.Ticks;
var endPoint = new IPEndPoint(IPAddress.Parse(server), port); var endPoint = new IPEndPoint(IPAddress.Parse(server), port);
using (var client = new UdpClient(endPoint.AddressFamily)) using (var client = new UdpClient(endPoint.AddressFamily))
@ -23,10 +25,11 @@ namespace EonaCat.DnsTester.Helpers
client.EnableBroadcast = false; client.EnableBroadcast = false;
client.Client.SendTimeout = DnsSendTimeout; client.Client.SendTimeout = DnsSendTimeout;
client.Client.ReceiveTimeout = DnsReceiveTimeout; client.Client.ReceiveTimeout = DnsReceiveTimeout;
byte[] responseBytes = null;
byte[] responseBytes;
if (FakeResponse) if (FakeResponse)
{ {
responseBytes = DnsHelper.GetExampleResponse(); responseBytes = GetExampleResponse();
} }
else else
{ {
@ -35,94 +38,55 @@ namespace EonaCat.DnsTester.Helpers
responseBytes = responseResult.Buffer; responseBytes = responseResult.Buffer;
} }
DnsResponse response = ParseDnsResponsePacket(dnsId, startTime, server, responseBytes); var response = ParseDnsResponsePacket(dnsId, stopwatch.ElapsedTicks, server, responseBytes);
return response; return response;
} }
} }
// For testing purposes // For testing purposes
public static bool FakeResponse { get; set; } public static bool FakeResponse { get; set; }
public static int DnsSendTimeout { get; set; } = 5; public static int DnsSendTimeout { get; set; } = 5;
public static int DnsReceiveTimeout { get; set; } = 5; public static int DnsReceiveTimeout { get; set; } = 5;
public static byte[] CreateDnsQueryPacket(string domainName, DnsRecordType recordType) 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 using (var stream = new MemoryStream())
ushort id = (ushort)random.Next(0, 65536); using (var writer = new BinaryWriter(stream))
ushort flags = (ushort)0x0100; // recursion desired
ushort qdcount = 1;
ushort ancount = 0;
ushort nscount = 0;
ushort arcount = 0;
byte[] headerBytes = new byte[]
{ {
(byte)(id >> 8), (byte)(id & 0xff), writer.Write(id);
(byte)(flags >> 8), (byte)(flags & 0xff), writer.Write(flags);
(byte)(qdcount >> 8), (byte)(qdcount & 0xff), writer.Write(qdcount);
(byte)(ancount >> 8), (byte)(ancount & 0xff), writer.Write(ancount);
(byte)(nscount >> 8), (byte)(nscount & 0xff), writer.Write(nscount);
(byte)(arcount >> 8), (byte)(arcount & 0xff), writer.Write(arcount);
};
// DNS question var labels = domainName.Split('.');
string[] labels = domainName.Split('.'); foreach (var label in labels)
byte[] qnameBytes = new byte[domainName.Length + 2];
int qnameIndex = 0;
foreach (string label in labels)
{ {
qnameBytes[qnameIndex++] = (byte)label.Length; writer.Write((byte)label.Length);
foreach (char c in label) writer.Write(Encoding.ASCII.GetBytes(label));
{
qnameBytes[qnameIndex++] = (byte)c;
}
}
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) writer.Write((byte)0); // Null terminator
{ writer.Write((ushort)recordType);
// Parse the DNS name writer.Write((ushort)1); // Record class: IN (Internet)
string name = DnsNameParser.ParseName(queryBytes, ref offset);
if (name == null) return stream.ToArray();
{
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() public static byte[] GetExampleResponse()
{ {
// Example response bytes for the A record of google.com // Example response bytes for the A record of google.com
byte[] response = { return new byte[]
{
0x9d, 0xa9, // Query ID 0x9d, 0xa9, // Query ID
0x81, 0x80, // Flags 0x81, 0x80, // Flags
0x00, 0x01, // Questions: 1 0x00, 0x01, // Questions: 1
@ -141,58 +105,45 @@ namespace EonaCat.DnsTester.Helpers
0x00, 0x04, // Data length: 4 bytes 0x00, 0x04, // Data length: 4 bytes
0xac, 0xd9, 0x03, 0x3d // Data: 172.217.3.61 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) if (responseBytes.Length < 12)
{ {
throw new Exception("Invalid DNS response"); throw new Exception("Invalid DNS response");
} }
// Set the offset to the start
var offset = 0; var offset = 0;
var id = (ushort)((responseBytes[offset++] << 8) | responseBytes[offset++]);
// Parse the DNS header var flags = (ushort)((responseBytes[offset++] << 8) | responseBytes[offset++]);
ushort id = (ushort)((responseBytes[0] << 8) | responseBytes[1]); var isResponse = (flags & 0x8000) != 0;
ushort flags = (ushort)((responseBytes[2] << 8) | responseBytes[3]); var qdcount = (ushort)((responseBytes[offset++] << 8) | responseBytes[offset++]);
bool isResponse = (flags & 0x8000) != 0;
ushort qdcount = (ushort)((responseBytes[4] << 8) | responseBytes[5]);
ushort ancount = (ushort)((responseBytes[6] << 8) | responseBytes[7]);
if (!isResponse) if (!isResponse)
{ {
throw new Exception("Invalid DNS response"); throw new Exception("Invalid DNS response");
} }
ushort nscount = (ushort)((responseBytes[8] << 8) | responseBytes[9]); var nscount = (ushort)((responseBytes[offset++] << 8) | responseBytes[offset++]);
ushort arcount = (ushort)((responseBytes[10] << 8) | responseBytes[11]); var arcount = (ushort)((responseBytes[offset++] << 8) | responseBytes[offset++]);
// We parsed the header set the offset past the header
offset = 12;
List<DnsQuestion> questions = new List<DnsQuestion>();
var questions = new List<DnsQuestion>();
for (int i = 0; i < qdcount; i++) for (int i = 0; i < qdcount; i++)
{ {
DnsQuestion question = ParseDnsQuestionRecord(responseBytes, ref offset); var question = ParseDnsQuestionRecord(responseBytes, ref offset);
if (question != null) if (question != null)
{ {
questions.Add(question); questions.Add(question);
} }
} }
// Parse the DNS answer records var answers = new List<ResourceRecord>();
List<ResourceRecord> answers = new List<ResourceRecord>(); for (int i = 0; i < qdcount; i++)
for (int i = 0; i < ancount; i++)
{ {
try try
{ {
ResourceRecord answer = ParseDnsAnswerRecord(responseBytes, ref offset); var answer = ParseDnsAnswerRecord(responseBytes, ref offset);
if (answer != null) if (answer != null)
{ {
answers.Add(answer); answers.Add(answer);
@ -200,17 +151,17 @@ namespace EonaCat.DnsTester.Helpers
} }
catch (Exception exception) catch (Exception exception)
{ {
OnLog?.Invoke(null, $"Answer exception: " + exception.Message); OnLog?.Invoke(null, $"Answer exception: {exception.Message}");
} }
} }
// Parse the DNS authority records // Parse the DNS authority records
List<ResourceRecord> authorities = new List<ResourceRecord>(); var authorities = new List<ResourceRecord>();
for (int i = 0; i < nscount; i++) for (int i = 0; i < nscount; i++)
{ {
try try
{ {
ResourceRecord authority = ParseDnsAnswerRecord(responseBytes, ref offset); var authority = ParseDnsAnswerRecord(responseBytes, ref offset);
if (authority != null) if (authority != null)
{ {
authorities.Add(authority); authorities.Add(authority);
@ -218,17 +169,17 @@ namespace EonaCat.DnsTester.Helpers
} }
catch (Exception exception) catch (Exception exception)
{ {
OnLog?.Invoke(null, $"Authority answer exception: " + exception.Message); OnLog?.Invoke(null, $"Authority answer exception: {exception.Message}");
} }
} }
// Parse the DNS additional records // Parse the DNS additional records
List<ResourceRecord> additionals = new List<ResourceRecord>(); var additionals = new List<ResourceRecord>();
for (int i = 0; i < arcount; i++) for (int i = 0; i < arcount; i++)
{ {
try try
{ {
ResourceRecord additional = ParseDnsAnswerRecord(responseBytes, ref offset); var additional = ParseDnsAnswerRecord(responseBytes, ref offset);
if (additional != null) if (additional != null)
{ {
additionals.Add(additional); additionals.Add(additional);
@ -236,7 +187,7 @@ namespace EonaCat.DnsTester.Helpers
} }
catch (Exception exception) 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 var name = DnsNameParser.ParseName(queryBytes, ref offset);
string name = DnsNameParser.ExtractDomainName(responseBytes, ref offset);
if (name == null) if (name == null)
{ {
return null; return null;
} }
// Parse the DNS type, class, ttl, and data length var type = (DnsRecordType)((queryBytes[offset++] << 8) | queryBytes[offset++]);
DnsRecordType type = (DnsRecordType)((responseBytes[offset++] << 8) + responseBytes[offset++]); var qclass = (DnsRecordClass)((queryBytes[offset++] << 8) | queryBytes[offset++]);
DnsRecordClass klass = (DnsRecordClass)((responseBytes[offset++] << 8) + responseBytes[offset++]);
int ttl = (responseBytes[offset++] << 24) + (responseBytes[offset++] << 16) + (responseBytes[offset++] << 8) + responseBytes[offset++];
// Extract record data length return new DnsQuestion
int dataLength = (responseBytes[offset] << 8) + responseBytes[offset + 1]; {
offset += 2; Name = name,
Type = type,
Class = qclass,
};
}
// Extract record data private static ResourceRecord ParseDnsAnswerRecord(byte[] responseBytes, ref int offset)
byte[] recordData = new byte[dataLength]; {
Buffer.BlockCopy(responseBytes, offset, recordData, 0, dataLength); var name = DnsNameParser.ExtractDomainName(responseBytes, ref offset);
if (name == null)
{
return null;
}
string recordDataAsString = null; var type = (DnsRecordType)((responseBytes[offset++] << 8) + responseBytes[offset++]);
switch ((DnsRecordType)type) 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: case DnsRecordType.A:
if (dataLength != 4) if (dataLength != 4)
{ {
return null; return null;
} }
recordDataAsString = new IPAddress(recordData).ToString(); dataAsString = new IPAddress(responseBytes, offset).ToString();
offset += recordData.Length; offset += dataLength;
break; break;
case DnsRecordType.CNAME: case DnsRecordType.CNAME:
recordDataAsString = DnsNameParser.ExtractDomainName(responseBytes, ref offset);
break;
case DnsRecordType.NS: case DnsRecordType.NS:
recordDataAsString = DnsNameParser.ExtractDomainName(responseBytes, ref offset); dataAsString = DnsNameParser.ExtractDomainName(responseBytes, ref offset);
break; break;
case DnsRecordType.MX: case DnsRecordType.MX:
int preference = (responseBytes[0] << 8) + responseBytes[1]; var preference = (responseBytes[offset++] << 8) + responseBytes[offset++];
offset += 2; var exchange = DnsNameParser.ExtractDomainName(responseBytes, ref offset);
string exchange = DnsNameParser.ExtractDomainName(responseBytes, ref offset); dataAsString = $"{preference} {exchange}";
recordDataAsString = $"{preference} {exchange}";
break; break;
case DnsRecordType.TXT: case DnsRecordType.TXT:
recordDataAsString = Encoding.ASCII.GetString(recordData); dataAsString = Encoding.ASCII.GetString(responseBytes, offset, dataLength);
offset += dataLength;
break; break;
default: default:
offset += dataLength; offset += dataLength;
@ -314,19 +273,10 @@ namespace EonaCat.DnsTester.Helpers
Type = type, Type = type,
Class = klass, Class = klass,
Ttl = TimeSpan.FromSeconds(ttl), 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 public class DnsQuestion
@ -372,35 +322,11 @@ namespace EonaCat.DnsTester.Helpers
public enum DnsRecordClass : ushort public enum DnsRecordClass : ushort
{ {
/// <summary>
/// The Internet.
/// </summary>
Internet = 1, Internet = 1,
/// <summary>
/// The CSNET class (Obsolete - used only for examples insome obsolete RFCs).
/// </summary>
CS = 2, CS = 2,
/// <summary>
/// The CHAOS class.
/// </summary>
CH = 3, CH = 3,
/// <summary>
/// Hesiod[Dyer 87].
/// </summary>
HS = 4, HS = 4,
/// <summary>
/// Used in UPDATE message to signify no class.
/// </summary>
None = 254, None = 254,
/// <summary>
/// Only used in QCLASS.
/// </summary>
/// <seealso cref="Question.Class"/>
ANY = 255 ANY = 255
} }
} }

View File

@ -21,19 +21,17 @@ namespace EonaCat.DnsTester.Helpers
public static bool UseSearchEngineStartPage { get; set; } public static bool UseSearchEngineStartPage { get; set; }
public static bool UseSearchEngineYandex { get; set; } public static bool UseSearchEngineYandex { get; set; }
private static async Task<List<string>> GetRandomUrls(int totalUrls) private static List<string> GetRandomUrls(int totalUrls)
{ {
var letters = GetRandomLetters(); var letters = GetRandomLetters();
var searchEngineUrls = GetSearchEngines(); var searchEngineUrls = GetSearchEngines();
Random rand = new Random();
List<string> urls = new List<string>(); List<string> urls = new List<string>();
while (urls.Count < totalUrls)
Parallel.ForEach(searchEngineUrls, searchEngine =>
{ {
int index = rand.Next(searchEngineUrls.Count); if (urls.Count >= totalUrls)
KeyValuePair<string, string> searchEngine = searchEngineUrls.ElementAt(index); return;
string url = searchEngine.Value + letters; string url = searchEngine.Value + letters;
@ -51,9 +49,7 @@ namespace EonaCat.DnsTester.Helpers
} }
if (responseString == null) if (responseString == null)
{ return;
continue;
}
// find all .xxx.com addresses // find all .xxx.com addresses
MatchCollection hostNames = Regex.Matches(responseString, @"[.](\w+[.]com)"); MatchCollection hostNames = Regex.Matches(responseString, @"[.](\w+[.]com)");
@ -69,24 +65,20 @@ namespace EonaCat.DnsTester.Helpers
} }
} }
lock (urls)
{
// Add the names to the list // Add the names to the list
foreach (string name in uniqueNames) foreach (string name in uniqueNames)
{ {
if (urls.Count >= totalUrls) if (urls.Count >= totalUrls)
{
break; break;
}
if (!urls.Contains(name)) if (!urls.Contains(name))
{
urls.Add(name); urls.Add(name);
} }
} }
} }
});
letters = GetRandomLetters();
await Task.Delay(100);
}
var urlText = "url" + (urls.Count > 1 ? "'s" : string.Empty); var urlText = "url" + (urls.Count > 1 ? "'s" : string.Empty);
SetStatus($"{urls.Count} random {urlText} found"); SetStatus($"{urls.Count} random {urlText} found");
@ -139,20 +131,18 @@ namespace EonaCat.DnsTester.Helpers
return searchEngineUrls; return searchEngineUrls;
} }
public static async Task<List<string>> RetrieveUrls(int numThreads, int numUrlsPerThread) public static List<string> RetrieveUrls(int numThreads, int numUrlsPerThread)
{ {
Task[] tasks = new Task[numThreads];
List<string> urlList = new List<string>(); List<string> urlList = new List<string>();
// start each thread to retrieve a subset of unique URLs Parallel.For(0, numThreads, _ =>
for (int i = 0; i < numThreads; i++)
{ {
tasks[i] = Task.Run(async () => urlList.AddRange(await GetRandomUrls(numUrlsPerThread))); var threadUrls = GetRandomUrls(numUrlsPerThread);
lock (urlList)
{
urlList.AddRange(threadUrls);
} }
});
// wait for all threads to complete
await Task.WhenAll(tasks);
return urlList; return urlList;
} }

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using EonaCat.DnsTester.Helpers; using EonaCat.DnsTester.Helpers;
@ -57,11 +58,11 @@ namespace EonaCat.DnsTester
} }
SetSearchEngines(); SetSearchEngines();
urls = await UrlHelper.RetrieveUrls(numThreads, numUrlsPerThread); urls = UrlHelper.RetrieveUrls(numThreads, numUrlsPerThread);
AddUrlToView(urls); AddUrlToView(urls);
IsRunning = true; IsRunning = true;
await Process(_recordType, urls.ToArray(), _dnsServer1, _dnsServer2); await Process(_recordType, urls.ToArray(), _dnsServer1, _dnsServer2).ConfigureAwait(false);
IsRunning = false; IsRunning = false;
} }
@ -350,21 +351,7 @@ namespace EonaCat.DnsTester
return; return;
} }
await Task.Run(() => await ResolveIP().ConfigureAwait(false);
{
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}'");
}
});
} }
private async void btnResolveHost_Click(object sender, EventArgs e) private async void btnResolveHost_Click(object sender, EventArgs e)
@ -374,38 +361,75 @@ namespace EonaCat.DnsTester
MessageBox.Show("Please enter a hostname to resolve"); MessageBox.Show("Please enter a hostname to resolve");
return; return;
} }
await ResolveHost().ConfigureAwait(false);
}
private async Task ResolveIP()
await Task.Run(() =>
{ {
if (string.IsNullOrWhiteSpace(txtResolveIP.Text))
{
MessageBox.Show("Please enter an IP address to resolve");
return;
}
if (!IPAddress.TryParse(txtResolveIP.Text, out IPAddress iPAddress))
{
MessageBox.Show("Please enter a valid IP address");
return;
}
try try
{ {
var dnsEntry = Dns.GetHostEntry(txtResolveHost.Text); var dnsEntry = await Dns.GetHostEntryAsync(iPAddress);
txtResolveHost.Text = dnsEntry.HostName;
txtResolveHost.Invoke(() => }
catch (Exception)
{ {
txtResolveIP.Text = dnsEntry.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork).FirstOrDefault().Address.ToString(); 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();
}
else
{
MessageBox.Show($"Could not get IP address for hostname '{txtResolveHost.Text}'");
}
} }
catch (Exception) catch (Exception)
{ {
MessageBox.Show($"Could not get IP address for hostname '{txtResolveHost.Text}'"); MessageBox.Show($"Could not get IP address for hostname '{txtResolveHost.Text}'");
} }
});
} }
private void SetStatus(string text) private void SetStatus(string text)
{ {
StringBuilder sb = new StringBuilder(StatusBox.Items.Count + 1);
sb.AppendLine($"{DateTime.Now} {text}");
StatusBox.Invoke(() => StatusBox.Invoke(() =>
{ {
StatusBox.Items.Add($"{DateTime.Now} {text}"); StatusBox.Items.Add(sb.ToString());
StatusBox.TopIndex = StatusBox.Items.Count - 1; StatusBox.TopIndex = StatusBox.Items.Count - 1;
if (StatusBox.Items.Count > STATUS_BAR_SIZE) if (StatusBox.Items.Count > STATUS_BAR_SIZE)
{ {
StatusBox.Items.RemoveAt(0); StatusBox.Items.RemoveAt(0);
} }
StatusBox.Update();
}); });
} }