From 883705f17e382568fb82f247981b4a4b65c1c2ee Mon Sep 17 00:00:00 2001 From: Alan Edwardes Date: Thu, 13 Aug 2020 22:30:25 +0100 Subject: [PATCH] Initial commit of non-working code. --- .gitignore | 329 +++++++++++++++ Ae.DnsResolver.sln | 46 +++ .../Ae.DnsResolver.Console.csproj | 16 + src/Ae.DnsResolver.Console/Program.cs | 96 +++++ .../Ae.DnsResolver/Ae.DnsResolver.csproj | 7 + .../Ae.DnsResolver/DnsMessageReader.cs | 374 ++++++++++++++++++ .../Ae.DnsResolver.Tests.csproj | 20 + .../DnsMessageReaderTests.cs | 26 ++ .../Ae.DnsResolver.Tests/SampleDnsPackets.cs | 15 + 9 files changed, 929 insertions(+) create mode 100644 .gitignore create mode 100644 Ae.DnsResolver.sln create mode 100644 src/Ae.DnsResolver.Console/Ae.DnsResolver.Console.csproj create mode 100644 src/Ae.DnsResolver.Console/Program.cs create mode 100644 src/Ae.DnsResolver/Ae.DnsResolver/Ae.DnsResolver.csproj create mode 100644 src/Ae.DnsResolver/Ae.DnsResolver/DnsMessageReader.cs create mode 100644 tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests.csproj create mode 100644 tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/DnsMessageReaderTests.cs create mode 100644 tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/SampleDnsPackets.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5fd325 --- /dev/null +++ b/.gitignore @@ -0,0 +1,329 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ diff --git a/Ae.DnsResolver.sln b/Ae.DnsResolver.sln new file mode 100644 index 0000000..369c163 --- /dev/null +++ b/Ae.DnsResolver.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29806.167 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ae.DnsResolver.Console", "src\Ae.DnsResolver.Console\Ae.DnsResolver.Console.csproj", "{E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0DE60274-AA05-4E64-A7C1-175005219903}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ae.DnsResolver", "src\Ae.DnsResolver\Ae.DnsResolver\Ae.DnsResolver.csproj", "{0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ACB95C7B-DFE8-411A-8CC5-059505530ADB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ae.DnsResolver.Tests", "tests\Ae.DnsResolver.Tests\Ae.DnsResolver.Tests\Ae.DnsResolver.Tests.csproj", "{2408600D-30F7-4221-AAA1-4288B8050D30}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF}.Release|Any CPU.Build.0 = Release|Any CPU + {0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44}.Release|Any CPU.Build.0 = Release|Any CPU + {2408600D-30F7-4221-AAA1-4288B8050D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2408600D-30F7-4221-AAA1-4288B8050D30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2408600D-30F7-4221-AAA1-4288B8050D30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2408600D-30F7-4221-AAA1-4288B8050D30}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E2603FF3-F9BA-4B90-B98C-B0C1069FB0BF} = {0DE60274-AA05-4E64-A7C1-175005219903} + {0C54A6B5-5B1F-4FB5-8CAA-8AC4F57B0F44} = {0DE60274-AA05-4E64-A7C1-175005219903} + {2408600D-30F7-4221-AAA1-4288B8050D30} = {ACB95C7B-DFE8-411A-8CC5-059505530ADB} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2ABE6B23-17A6-4850-8DE8-02A4BF5684DC} + EndGlobalSection +EndGlobal diff --git a/src/Ae.DnsResolver.Console/Ae.DnsResolver.Console.csproj b/src/Ae.DnsResolver.Console/Ae.DnsResolver.Console.csproj new file mode 100644 index 0000000..03acfc8 --- /dev/null +++ b/src/Ae.DnsResolver.Console/Ae.DnsResolver.Console.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + diff --git a/src/Ae.DnsResolver.Console/Program.cs b/src/Ae.DnsResolver.Console/Program.cs new file mode 100644 index 0000000..ff1df61 --- /dev/null +++ b/src/Ae.DnsResolver.Console/Program.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace Ae.DnsResolver +{ + class Program + { + static void Main(string[] args) + { + DoWork().GetAwaiter().GetResult(); + Console.WriteLine("Hello World!"); + } + + public struct RecordType + { + public string Name; + public Qtype Type; + public Qclass Class; + } + + public static RecordType ToRecordType(DnsMessage message) + { + return new RecordType + { + Name = string.Join(".", message.Labels), + Type = message.Qtype, + Class = message.Qclass + }; + } + + private static ConcurrentDictionary> _cache = new ConcurrentDictionary>(); + + private static async Task DoWork() + { + var client = new UdpClient("1.1.1.1", 53); + + var listener = new UdpClient(53); + + _ = Task.Run(async () => + { + while (true) + { + var result = await client.ReceiveAsync(); + + var test = string.Join(",", result.Buffer.Select(x => x)); + + //var message = DnsMessageReader.ReadDnsResponse(result.Buffer); + + //Console.WriteLine(message); + + //if (_cache.TryGetValue(ToRecordType(message), out var completionSource)) + //{ + // completionSource.SetResult(result.Buffer); + //} + } + }); + + while (true) + { + UdpReceiveResult r; + try + { + r = await listener.ReceiveAsync(); + } + catch (SocketException e) + { + Console.WriteLine("Connection forcibly closed"); + continue; + } + + Respond(r, client, listener); + } + } + + public static async void Respond(UdpReceiveResult r, UdpClient client, UdpClient listener) + { + var message = DnsMessageReader.ReadDnsMessage(r.Buffer); + + Console.WriteLine(message); + + var completionSource = _cache.GetOrAdd(ToRecordType(message), key => + { + client.SendAsync(r.Buffer, r.Buffer.Length); + return new TaskCompletionSource(); + }); + + var result = await completionSource.Task; + + await listener.SendAsync(result, result.Length, r.RemoteEndPoint); + } + } +} diff --git a/src/Ae.DnsResolver/Ae.DnsResolver/Ae.DnsResolver.csproj b/src/Ae.DnsResolver/Ae.DnsResolver/Ae.DnsResolver.csproj new file mode 100644 index 0000000..f45bdfc --- /dev/null +++ b/src/Ae.DnsResolver/Ae.DnsResolver/Ae.DnsResolver.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.1 + + + diff --git a/src/Ae.DnsResolver/Ae.DnsResolver/DnsMessageReader.cs b/src/Ae.DnsResolver/Ae.DnsResolver/DnsMessageReader.cs new file mode 100644 index 0000000..46469e9 --- /dev/null +++ b/src/Ae.DnsResolver/Ae.DnsResolver/DnsMessageReader.cs @@ -0,0 +1,374 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; + +namespace Ae.DnsResolver +{ + public enum Qtype : ushort + { + A = 0x0001, + NS = 0x0002, + MD = 0x0003, + MF = 0x0004, + CNAME = 0x0005, + SOA = 0x0006, + MB = 0x0007, + MG = 0x0008, + MR = 0x0009, + NULL = 0x000a, + WKS = 0x000b, + PTR = 0x000c, + HINFO = 0x000d, + MINFO = 0x000e, + MX = 0x000f, + TEXT = 0x0010, + RP = 0x0011, + AFSDB = 0x0012, + X25 = 0x0013, + ISDN = 0x0014, + RT = 0x0015, + NSAP = 0x0016, + NSAPPTR = 0x0017, + SIG = 0x0018, + KEY = 0x0019, + PX = 0x001a, + GPOS = 0x001b, + AAAA = 0x001c, + LOC = 0x001d, + NXT = 0x001e, + EID = 0x001f, + NIMLOC = 0x0020, + SRV = 0x0021, + ATMA = 0x0022, + NAPTR = 0x0023, + KX = 0x0024, + CERT = 0x0025, + A6 = 0x0026, + DNAME = 0x0027, + SINK = 0x0028, + OPT = 0x0029, + DS = 0x002B, + RRSIG = 0x002E, + NSEC = 0x002F, + DNSKEY = 0x0030, + DHCID = 0x0031, + UINFO = 0x0064, + UID = 0x0065, + GID = 0x0066, + UNSPEC = 0x0067, + ADDRS = 0x00f8, + TKEY = 0x00f9, + TSIG = 0x00fa, + IXFR = 0x00fb, + AXFR = 0x00fc, + MAILB = 0x00fd, + MAILA = 0x00fe, + ALL = 0x00ff, + ANY = 0x00ff, + WINS = 0xff01, + WINSR = 0xff02, + NBSTAT = WINSR + } + + public enum Qclass : short + { + None = 0, + IN = 1, + CS = 2, + CH = 3, + HS = 4 + } + + public abstract class DnsMessage + { + public short Id; + public short Header; + public short Qdcount; + public short Ancount; + public short Nscount; + public short Arcount; + + public string[] Labels; + public Qtype Qtype; + public Qclass Qclass; + } + + public static class EndianExtensions + { + private static short SwapEndian(this short val) + { + return BitConverter.IsLittleEndian ? (short)((val << 8) | (val >> 8)) : val; + } + + private static ushort SwapEndian(this ushort val) + { + return BitConverter.IsLittleEndian ? (ushort)((val << 8) | (val >> 8)) : val; + } + + private static uint SwapEndian(this uint val) + { + return BitConverter.IsLittleEndian ? (uint)((val << 16) | (val >> 16)) : val; + } + + public static byte ReadByte(this byte[] bytes, ref int offset) + { + return bytes[offset++]; + } + + public static short ReadInt16(this byte[] bytes, ref int offset) + { + offset += sizeof(short); + return BitConverter.ToInt16(bytes, offset - sizeof(short)).SwapEndian(); + } + + public static ushort ReadUInt16(this byte[] bytes, ref int offset) + { + offset += sizeof(ushort); + return BitConverter.ToUInt16(bytes, offset - sizeof(ushort)).SwapEndian(); + } + + public static uint ReadUInt32(this byte[] bytes, ref int offset) + { + offset += sizeof(uint); + return BitConverter.ToUInt32(bytes, offset - sizeof(uint)).SwapEndian(); + } + + public static byte[] ReadBytes(this byte[] bytes, int length, ref int offset) + { + var data = new byte[length]; + Array.Copy(bytes, offset, data, 0, length); + offset += length; + return data; + } + + public static bool IsBitSet(this byte octet, int position) + { + // Endianness fix + //octet = (byte)((octet << 4) | (octet >> 4)); + return (octet & (1 << position)) != 0; + //return ((octet >> position) & 1) != 0; + } + + //public static string[] ReadString2(this byte[] bytes, ref int offset) + //{ + // var parts = new List(); + + // while (true) + // { + // var octet = bytes[offset]; + // if (octet == 0) + // { + // offset++; + // break; + // } + + // if ((octet & 0xC0) == 0xC0) + // { + // var pointer = (int)bytes[offset + 1]; + // parts.AddRange(DnsMessageReader.ReadName2(bytes, ref pointer)); + // offset += 2; + + // if (bytes[offset] == 0) + // { + // break; + // } + // } + // else + // { + // offset++; + // parts.Add(DnsMessageReader.ReadSingleString(bytes, octet, ref offset)); + // } + // } + + // return parts.ToArray(); + //} + + public static string[] ReadString(this byte[] bytes, ref int offset) + { + var parts = new List(); + + int compressionOffset = -1; + while (true) + { + // get segment length or detect termination of segments + int segmentLength = bytes[offset]; + + // compressed name + if ((segmentLength & 0xC0) == 0xC0) + { + offset++; + if (compressionOffset == -1) + { + // only record origin, and follow all pointers thereafter + compressionOffset = offset; + } + + //if (segmentLength != 192) + { + var mask = (1 << 14) - 1; + var pointer = ((ushort)(segmentLength + (bytes[offset] << 8))).SwapEndian() & mask; + offset = pointer; + segmentLength = pointer; + + if (offset != bytes[offset]) + { + Debugger.Break(); + } + } + //else + { + // move pointer to compression segment + //offset = bytes[offset]; + //segmentLength = bytes[offset]; + } + } + + if (segmentLength == 0x00) + { + if (compressionOffset != -1) + { + offset = compressionOffset; + } + // move past end of name \0 + offset++; + break; + } + + // move pass length and get segment text + offset++; + parts.Add(Encoding.ASCII.GetString(bytes, offset, segmentLength)); + offset += segmentLength; + } + + + + return parts.ToArray(); + } + } + + public class DnsRequestMessage : DnsMessage + { + public override string ToString() => string.Format("REQUEST: Domain: {0}, type: {1}, class: {2}", string.Join(".", Labels), Qtype, Qclass); + } + + public class DnsResponseMessage : DnsMessage + { + public DnsResourceRecord[] Records; + + public override string ToString() => string.Format("RESPONSE: Domain: {0}, type: {1}, class: {2}, records: {3}", string.Join(".", Labels), Qtype, Qclass, Records.Length); + } + + public class DnsResourceRecord + { + public string[] Name; + public Qtype Type; + public Qclass Class; + public TimeSpan Ttl; + public byte[] Data; + + public string DataAsString => Encoding.ASCII.GetString(Data); + public IPAddress DataAsIp => new IPAddress(Data); + } + + public static class DnsMessageReader + { + public static DnsMessage ReadDnsMessage(byte[] bytes) + { + var result = new DnsRequestMessage(); + var offset = 0; + ReadDnsMessage(bytes, result, ref offset); + return result; + } + + public static DnsResponseMessage ReadDnsResponse(byte[] bytes) + { + var result = new DnsResponseMessage(); + var offset = 0; + ReadDnsMessage(bytes, result, ref offset); + + var records = new List(); + + for (var i = 0; i < result.Ancount; i++) + { + records.Add(ReadResourceRecord(bytes, ref offset)); + } + + result.Records = records.ToArray(); + + return result; + } + + private static DnsResourceRecord ReadResourceRecord(byte[] bytes, ref int offset) + { + var test = bytes.Select(x => (char)x).ToArray(); + + //var originalOffset = offset; + var resourceName = bytes.ReadString(ref offset); + //var expectedOffset = offset; + //offset = originalOffset; + + //var resourceName2 = bytes.ReadString2(ref offset); + + //Debug.Assert(offset == expectedOffset); + //Debug.Assert(resourceName.SequenceEqual(resourceName2)); + + var resourceType = (Qtype)bytes.ReadUInt16(ref offset); + var resourceClass = (Qclass)bytes.ReadUInt16(ref offset); + var ttl = bytes.ReadUInt32(ref offset); + var rdlength = bytes.ReadUInt16(ref offset); + + byte[] rdata = bytes.ReadBytes(rdlength, ref offset); + + if (resourceType == Qtype.CNAME) + { + rdata = bytes.ReadString(ref offset).SelectMany(x => x.ToArray()).Select(x => (byte)x).ToArray(); + } + + return new DnsResourceRecord + { + Name = resourceName, + Type = resourceType, + Class = resourceClass, + Ttl = TimeSpan.FromSeconds(ttl), + Data = rdata + }; + } + + public static string[] ReadName2(byte[] bytes, ref int offset) + { + var parts = new List(); + + var octets = bytes.ReadByte(ref offset); + while (octets > 0 && (octets & 0xC0) != 0xC0) + { + parts.Add(ReadSingleString(bytes, octets, ref offset)); + octets = bytes.ReadByte(ref offset); + } + + return parts.ToArray(); + } + + public static string ReadSingleString(byte[] bytes, int length, ref int offset) + { + return Encoding.ASCII.GetString(bytes.ReadBytes(length, ref offset)); + } + + private static DnsMessage ReadDnsMessage(byte[] bytes, DnsMessage result, ref int offset) + { + result.Id = bytes.ReadInt16(ref offset); + result.Header = bytes.ReadInt16(ref offset); + result.Qdcount = bytes.ReadInt16(ref offset); + result.Ancount = bytes.ReadInt16(ref offset); + result.Nscount = bytes.ReadInt16(ref offset); + result.Arcount = bytes.ReadInt16(ref offset); + result.Labels = ReadName2(bytes, ref offset); + result.Qtype = (Qtype)bytes.ReadInt16(ref offset); + result.Qclass = (Qclass)bytes.ReadInt16(ref offset); + return result; + } + } +} diff --git a/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests.csproj b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests.csproj new file mode 100644 index 0000000..f4599c9 --- /dev/null +++ b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/DnsMessageReaderTests.cs b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/DnsMessageReaderTests.cs new file mode 100644 index 0000000..306db7c --- /dev/null +++ b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/DnsMessageReaderTests.cs @@ -0,0 +1,26 @@ +using Xunit; + +namespace Ae.DnsResolver.Tests +{ + public class DnsMessageReaderTests + { + [Fact] + public void ReadExampleArpaPacket() + { + var message = DnsMessageReader.ReadDnsResponse(SampleDnsPackets.Example1); + + Assert.Equal(Qclass.IN, message.Qclass); + Assert.Equal(Qtype.PTR, message.Qtype); + Assert.Equal(new[] { "1", "0", "0", "127", "in-addr", "arpa" }, message.Labels); + } + + [Fact] + public void ReadExampleExample1Packet() + { + var message = DnsMessageReader.ReadDnsResponse(SampleDnsPackets.Example2); + + Assert.Equal(Qclass.IN, message.Qclass); + Assert.Equal(Qtype.PTR, message.Qtype); + } + } +} diff --git a/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/SampleDnsPackets.cs b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/SampleDnsPackets.cs new file mode 100644 index 0000000..bbb6a5c --- /dev/null +++ b/tests/Ae.DnsResolver.Tests/Ae.DnsResolver.Tests/SampleDnsPackets.cs @@ -0,0 +1,15 @@ +namespace Ae.DnsResolver.Tests +{ + public static class SampleDnsPackets + { + public static readonly byte[] Example1 = new byte[] + { + 0,1,129,131,0,1,0,0,0,1,0,0,1,49,1,48,1,48,3,49,50,55,7,105,110,45,97,100,100,114,4,97,114,112,97,0,0,12,0,1,7,105,110,45,97,100,100,114,4,97,114,112,97,0,0,6,0,1,0,0,8,144,0,56,1,98,15,105,110,45,97,100,100,114,45,115,101,114,118,101,114,115,192,48,5,110,115,116,108,100,4,105,97,110,97,3,111,114,103,0,120,103,251,159,0,0,7,8,0,0,3,132,0,9,58,128,0,0,14,16 + }; + + public static readonly byte[] Example2 = new byte[] + { + 0,2,129,128,0,1,0,7,0,0,0,0,15,97,108,97,110,101,100,119,97,114,100,101,115,45,109,121,10,115,104,97,114,101,112,111,105,110,116,3,99,111,109,0,0,1,0,1,15,97,108,97,110,101,100,119,97,114,100,101,115,45,109,121,10,115,104,97,114,101,112,111,105,110,116,3,99,111,109,0,0,5,0,1,0,0,14,16,0,15,12,97,108,97,110,101,100,119,97,114,100,101,115,192,64,192,90,0,5,0,1,0,0,14,16,0,36,9,51,48,50,45,105,112,118,52,101,5,99,108,117,109,112,11,100,112,114,111,100,109,103,100,49,48,52,5,97,97,45,114,116,192,64,192,117,0,5,0,1,0,0,0,30,0,20,12,49,56,55,49,55,48,45,105,112,118,52,101,4,102,97,114,109,192,133,192,165,0,5,0,1,0,0,0,60,0,63,12,49,56,55,49,55,48,45,105,112,118,52,101,4,102,97,114,109,11,100,112,114,111,100,109,103,100,49,48,52,16,115,104,97,114,101,112,111,105,110,116,111,110,108,105,110,101,3,99,111,109,6,97,107,97,100,110,115,3,110,101,116,0,192,197,0,5,0,1,0,0,1,44,0,72,11,49,56,55,49,55,48,45,105,112,118,52,4,102,97,114,109,11,100,112,114,111,100,109,103,100,49,48,52,5,97,97,45,114,116,10,115,104,97,114,101,112,111,105,110,116,3,99,111,109,8,115,112,111,45,48,48,48,52,10,115,112,111,45,109,115,101,100,103,101,192,255,193,16,0,5,0,1,0,0,0,240,0,2,193,66,193,66,0,1,0,1,0,0,0,240,0,4,13,107,136,9 + }; + } +}