using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace EonaCat.DnsTester.Helpers { class DnsHelper { public static event EventHandler OnLog; public static async Task SendDnsQueryPacket(string dnsId, string server, int port, byte[] queryBytes) { // Start the clock var startTime = DateTime.Now.Ticks; var endPoint = new IPEndPoint(IPAddress.Parse(server), port); using (var client = new UdpClient(endPoint.AddressFamily)) { client.DontFragment = true; client.EnableBroadcast = false; client.Client.ReceiveTimeout = DnsReceiveTimeout; await client.SendAsync(queryBytes, queryBytes.Length, endPoint); var responseResult = await client.ReceiveAsync(); DnsResponse response = ParseDnsResponsePacket(dnsId, startTime, server, responseResult.Buffer); return response; } } public static int DnsReceiveTimeout { get; set; } = 5; public static byte[] CreateDnsQueryPacket(string domainName, DnsRecordType recordType) { Random random = new Random(); // 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[] { (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), }; // 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) { 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) { // Parse the DNS name string name = ParseDnsName(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, }; } static DnsResponse ParseDnsResponsePacket(string dnsId, long startTime, string server, byte[] responseBytes) { // Parse the DNS header ushort id = (ushort)((responseBytes[0] << 8) | responseBytes[1]); ushort flags = (ushort)((responseBytes[2] << 8) | responseBytes[3]); ushort qdcount = (ushort)((responseBytes[4] << 8) | responseBytes[5]); ushort ancount = (ushort)((responseBytes[6] << 8) | responseBytes[7]); ushort nscount = (ushort)((responseBytes[8] << 8) | responseBytes[9]); ushort arcount = (ushort)((responseBytes[10] << 8) | responseBytes[11]); List questions = new List(); //for (int i = 0; i < qdcount; i++) //{ // DnsQuestion question = ParseDnsQuestionRecord(responseBytes, ref offset); // if (question != null) // { // questions.Add(question); // } //} int offset = 12; // Parse the DNS answer records List answers = new List(); for (int i = 0; i < ancount; i++) { try { ResourceRecord answer = ParseDnsAnswerRecord(responseBytes, ref offset); if (answer != null) { answers.Add(answer); } } catch (Exception exception) { OnLog?.Invoke(null, $"Answer exception: " + exception.Message); } } // Parse the DNS authority records List authorities = new List(); for (int i = 0; i < nscount; i++) { try { ResourceRecord authority = ParseDnsAnswerRecord(responseBytes, ref offset); if (authority != null) { authorities.Add(authority); } } catch (Exception exception) { OnLog?.Invoke(null, $"Authority answer exception: " + exception.Message); } } // Parse the DNS additional records List additionals = new List(); for (int i = 0; i < arcount; i++) { try { ResourceRecord additional = ParseDnsAnswerRecord(responseBytes, ref offset); if (additional != null) { additionals.Add(additional); } } catch (Exception exception) { OnLog?.Invoke(null, $"Additional answer exception: " + exception.Message); } } return new DnsResponse { Id = id, StartTime = startTime, Resolver = server, Flags = flags, Class = (DnsRecordClass)((flags >> 3) & 0x0f), DnsId = dnsId, Questions = questions, Answers = answers, Authorities = authorities, Additionals = additionals, }; } static ResourceRecord ParseDnsAnswerRecord(byte[] responseBytes, ref int offset) { // Parse the DNS name string name = ParseDnsName(responseBytes, 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++]); uint ttl = (uint)((responseBytes[offset++] << 24) | (responseBytes[offset++] << 16) | (responseBytes[offset++] << 8) | responseBytes[offset++]); ushort rdlen = (ushort)((responseBytes[responseBytes.Length - 5])); offset += 10; // Parse the DNS data string data = null; switch ((DnsRecordType)type) { case DnsRecordType.A: if (rdlen != 4) { return null; } data = new IPAddress(new byte[] { responseBytes[offset], responseBytes[offset + 1], responseBytes[offset + 2], responseBytes[offset + 3] }).ToString(); offset += rdlen; break; case DnsRecordType.CNAME: case DnsRecordType.NS: data = ParseDnsName(responseBytes, ref offset); if (data == null) { return null; } break; case DnsRecordType.MX: ushort preference = (ushort)((responseBytes[offset] << 8) | responseBytes[offset + 1]); offset += 2; string exchange = ParseDnsName(responseBytes, ref offset); if (exchange == null) { return null; } data = $"{preference} {exchange}"; break; case DnsRecordType.TXT: data = Encoding.ASCII.GetString(responseBytes, offset, rdlen); offset += rdlen; break; default: offset += rdlen; break; } return new ResourceRecord { Name = name, Type = type, Class = klass, Ttl = TimeSpan.FromSeconds(ttl), Data = data, }; } static string ParseDnsName(byte[] responseBytes, ref int offset) { StringBuilder name = new StringBuilder(); int originalOffset = offset; while (true) { byte len = responseBytes[offset++]; if (len == 0) { break; } if ((len & 0xc0) == 0xc0) { ushort pointer = (ushort)(((len & 0x3f) << 8) | responseBytes[offset++]); int pointerOffset = pointer & 0x3fff; if (pointerOffset >= responseBytes.Length) { return null; } int pointerEnd = offset; offset = pointerOffset; if (offset < originalOffset) { return null; } name.Append(ParseDnsName(responseBytes, ref offset)); offset = pointerEnd; break; } if (len > 63) { return null; } name.Append(Encoding.ASCII.GetString(responseBytes, offset, len)); offset += len; if (responseBytes[offset - 1] != 0) { name.Append("."); } } if (originalOffset == offset) { return null; } return name.ToString(); } } public class DnsQuestion { public string Name { get; set; } public DnsRecordType Type { get; set; } public DnsRecordClass Class { get; set; } } public class ResourceRecord { public string Name { get; set; } public DnsRecordType Type { get; set; } public string Data { get; set; } public DnsRecordClass Class { get; set; } public TimeSpan Ttl { get; set; } public ushort DataLength { get; set; } } public class DnsResponse { public ushort Id { get; set; } public ushort Flags { get; set; } public DnsRecordClass Class { get; set; } public List Answers { get; set; } public long StartTime { get; set; } public string Resolver { get; set; } public string DnsId { get; set; } public List Questions { get; set; } public List Authorities { get; set; } public List Additionals { get; set; } } public enum DnsRecordType : ushort { A = 1, NS = 2, CNAME = 5, MX = 15, TXT = 16, AAAA = 28, } 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 } }