├── .gitattributes ├── .gitignore ├── .vs └── csharp-dns-server │ └── xs │ ├── UserPrefs.xml │ └── project-cache │ ├── dns-Debug.json │ └── dnstest-Debug.json ├── .vscode ├── launch.json └── tasks.json ├── Dns ├── Config │ └── AppConfig.cs ├── Contracts │ ├── IAddressDispenser.cs │ ├── IDnsCache.cs │ ├── IDnsResolver.cs │ └── IHtmlDump.cs ├── Data │ └── machineinfo.csv ├── Dns.csproj ├── DnsCache.cs ├── DnsMessage.cs ├── DnsProtocol.cs ├── DnsServer.cs ├── Extensions.cs ├── HttpServer.cs ├── OpCode.cs ├── Program.cs ├── Question.cs ├── QuestionList.cs ├── RCode.cs ├── RData.cs ├── ResourceClass.cs ├── ResourceList.cs ├── ResourceRecord.cs ├── ResourceType.cs ├── SmartAddressDispenser.cs ├── SmartZoneResolver.cs ├── SocketExtensions.cs ├── UdpListener.cs ├── Utility │ ├── BitPacker.cs │ ├── CsvParser.cs │ └── CsvRow.cs ├── Zone.cs ├── ZoneProvider │ ├── AP │ │ └── APZoneProvider.cs │ ├── BaseZoneProvider.cs │ ├── Bind │ │ └── BindZoneProvider.cs │ ├── FileWatcherProviderOptions.cs │ ├── FileWatcherZoneProvider.cs │ └── IPProbe │ │ ├── Host.cs │ │ ├── IPProbeProviderOptions.cs │ │ ├── IPProbeZoneProvider.cs │ │ ├── ProbeResult.cs │ │ ├── State.cs │ │ ├── Strategy.cs │ │ └── Target.cs ├── ZoneRecord.cs └── appsettings.json ├── README.md ├── appveyor.yml ├── csharp-dns-server.sln ├── dns-cli ├── Program.cs ├── appsettings.json └── dns-cli.csproj ├── dnstest ├── BitPackerTests.cs ├── ConfigTests.cs ├── Data │ ├── BindZoneFiles │ │ └── bindzonetest1.txt │ └── Config │ │ └── appsettings.json ├── DnsCacheTests.cs ├── DnsProtocolTest.cs └── dnstest.csproj ├── licence.txt └── nuget.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | .idea/ 18 | packages/ 19 | 20 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 21 | !packages/*/build/ 22 | 23 | # MSTest test Results 24 | [Tt]est[Rr]esult*/ 25 | [Bb]uild[Ll]og.* 26 | 27 | *_i.c 28 | *_p.c 29 | *.ilk 30 | *.meta 31 | *.obj 32 | *.pch 33 | *.pdb 34 | *.pgc 35 | *.pgd 36 | *.rsp 37 | *.sbr 38 | *.tlb 39 | *.tli 40 | *.tlh 41 | *.tmp 42 | *.tmp_proj 43 | *.log 44 | *.vspscc 45 | *.vssscc 46 | .builds 47 | *.pidb 48 | *.log 49 | *.scc 50 | 51 | # Visual C++ cache files 52 | ipch/ 53 | *.aps 54 | *.ncb 55 | *.opensdf 56 | *.sdf 57 | *.cachefile 58 | 59 | # Visual Studio profiler 60 | *.psess 61 | *.vsp 62 | *.vspx 63 | 64 | # Guidance Automation Toolkit 65 | *.gpState 66 | 67 | # ReSharper is a .NET coding add-in 68 | _ReSharper*/ 69 | *.[Rr]e[Ss]harper 70 | 71 | # TeamCity is a build add-in 72 | _TeamCity* 73 | 74 | # DotCover is a Code Coverage Tool 75 | *.dotCover 76 | 77 | # NCrunch 78 | *.ncrunch* 79 | .*crunch*.local.xml 80 | 81 | # Installshield output folder 82 | [Ee]xpress/ 83 | 84 | # DocProject is a documentation generator add-in 85 | DocProject/buildhelp/ 86 | DocProject/Help/*.HxT 87 | DocProject/Help/*.HxC 88 | DocProject/Help/*.hhc 89 | DocProject/Help/*.hhk 90 | DocProject/Help/*.hhp 91 | DocProject/Help/Html2 92 | DocProject/Help/html 93 | 94 | # Click-Once directory 95 | publish/ 96 | 97 | # Publish Web Output 98 | *.Publish.xml 99 | 100 | # NuGet Packages Directory 101 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 102 | #packages/ 103 | 104 | # Windows Azure Build Output 105 | csx 106 | *.build.csdef 107 | 108 | # Windows Store app package directory 109 | AppPackages/ 110 | 111 | # Others 112 | sql/ 113 | *.Cache 114 | ClientBin/ 115 | [Ss]tyle[Cc]op.* 116 | ~$* 117 | *~ 118 | *.dbmdl 119 | *.[Pp]ublish.xml 120 | *.pfx 121 | *.publishsettings 122 | 123 | # RIA/Silverlight projects 124 | Generated_Code/ 125 | 126 | # Backup & report files from converting an old project file to a newer 127 | # Visual Studio version. Backup files are not needed, because we have git ;-) 128 | _UpgradeReport_Files/ 129 | Backup*/ 130 | UpgradeLog*.XML 131 | UpgradeLog*.htm 132 | 133 | # SQL Server files 134 | App_Data/*.mdf 135 | App_Data/*.ldf 136 | 137 | 138 | #LightSwitch generated files 139 | GeneratedArtifacts/ 140 | _Pvt_Extensions/ 141 | ModelManifest.xml 142 | 143 | # ========================= 144 | # Windows detritus 145 | # ========================= 146 | 147 | # Windows image file caches 148 | Thumbs.db 149 | ehthumbs.db 150 | 151 | # Folder config file 152 | Desktop.ini 153 | 154 | # Recycle Bin used on file shares 155 | $RECYCLE.BIN/ 156 | 157 | # Mac desktop service store files 158 | .DS_Store 159 | 160 | # Project Lock File 161 | project.lock.json 162 | -------------------------------------------------------------------------------- /.vs/csharp-dns-server/xs/UserPrefs.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /.vs/csharp-dns-server/xs/project-cache/dnstest-Debug.json: -------------------------------------------------------------------------------- 1 | {"Format":1,"ProjectReferences":[{"FilePath":"/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dns/dns.csproj","Aliases":[],"Framework":null}],"MetadataReferences":[{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/Microsoft.CSharp.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.dotnet.platformabstractions/1.0.3/lib/netstandard1.3/Microsoft.DotNet.PlatformAbstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.caching.abstractions/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Caching.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.caching.memory/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Caching.Memory.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.configuration.abstractions/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Configuration.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.configuration.binder/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Configuration.Binder.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.configuration.commandline/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Configuration.CommandLine.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.configuration/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Configuration.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.configuration.environmentvariables/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Configuration.EnvironmentVariables.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.configuration.fileextensions/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Configuration.FileExtensions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.configuration.json/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Configuration.Json.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.configuration.usersecrets/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Configuration.UserSecrets.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/3.1.9/lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.dependencyinjection/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.DependencyInjection.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.dependencymodel/1.0.3/lib/netstandard1.6/Microsoft.Extensions.DependencyModel.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.fileproviders.abstractions/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.FileProviders.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.fileproviders.physical/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.FileProviders.Physical.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.filesystemglobbing/3.1.9/lib/netstandard2.0/Microsoft.Extensions.FileSystemGlobbing.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.hosting.abstractions/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Hosting.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.hosting/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Hosting.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.logging.abstractions/3.1.9/lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.logging.configuration/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Logging.Configuration.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.logging.console/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Logging.Console.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.logging.debug/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Logging.Debug.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.logging/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Logging.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.logging.eventlog/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Logging.EventLog.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.logging.eventsource/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Logging.EventSource.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.options.configurationextensions/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Options.ConfigurationExtensions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.options/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Options.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.extensions.primitives/3.1.9/lib/netcoreapp3.1/Microsoft.Extensions.Primitives.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.testplatform.testhost/15.9.0/lib/netstandard1.5/Microsoft.TestPlatform.CommunicationUtilities.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.testplatform.objectmodel/15.9.0/lib/netstandard1.5/Microsoft.TestPlatform.CoreUtilities.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.testplatform.testhost/15.9.0/lib/netstandard1.5/Microsoft.TestPlatform.CrossPlatEngine.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.testplatform.objectmodel/15.9.0/lib/netstandard1.5/Microsoft.TestPlatform.PlatformAbstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/Microsoft.VisualBasic.Core.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/Microsoft.VisualBasic.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.codecoverage/15.9.0/lib/netcoreapp1.0/Microsoft.VisualStudio.CodeCoverage.Shim.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.testplatform.testhost/15.9.0/lib/netstandard1.5/Microsoft.VisualStudio.TestPlatform.Common.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.testplatform.objectmodel/15.9.0/lib/netstandard1.5/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/Microsoft.Win32.Primitives.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/mscorlib.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/netstandard.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/newtonsoft.json/9.0.1/lib/netstandard1.0/Newtonsoft.Json.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/ninject/3.3.4/lib/netstandard2.0/Ninject.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.AppContext.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Buffers.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Collections.Concurrent.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Collections.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Collections.Immutable.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Collections.NonGeneric.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Collections.Specialized.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.ComponentModel.Annotations.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.ComponentModel.DataAnnotations.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.ComponentModel.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.ComponentModel.EventBasedAsync.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.ComponentModel.Primitives.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.ComponentModel.TypeConverter.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Configuration.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Console.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Core.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Data.Common.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Data.DataSetExtensions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Data.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Diagnostics.Contracts.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Diagnostics.Debug.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Diagnostics.DiagnosticSource.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/system.diagnostics.eventlog/4.7.0/ref/netstandard2.0/System.Diagnostics.EventLog.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Diagnostics.FileVersionInfo.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Diagnostics.Process.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Diagnostics.StackTrace.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Diagnostics.TextWriterTraceListener.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Diagnostics.Tools.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Diagnostics.TraceSource.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Diagnostics.Tracing.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Drawing.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Drawing.Primitives.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Dynamic.Runtime.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Globalization.Calendars.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Globalization.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Globalization.Extensions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.Compression.Brotli.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.Compression.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.Compression.FileSystem.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.Compression.ZipFile.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.FileSystem.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.FileSystem.DriveInfo.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.FileSystem.Primitives.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.FileSystem.Watcher.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.IsolatedStorage.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.MemoryMappedFiles.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.Pipes.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.IO.UnmanagedMemoryStream.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Linq.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Linq.Expressions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Linq.Parallel.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Linq.Queryable.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Memory.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.Http.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.HttpListener.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.Mail.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.NameResolution.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.NetworkInformation.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.Ping.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.Primitives.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.Requests.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.Security.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.ServicePoint.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.Sockets.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.WebClient.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.WebHeaderCollection.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.WebProxy.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.WebSockets.Client.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Net.WebSockets.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Numerics.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Numerics.Vectors.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.ObjectModel.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Reflection.DispatchProxy.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Reflection.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Reflection.Emit.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Reflection.Emit.ILGeneration.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Reflection.Emit.Lightweight.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Reflection.Extensions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Reflection.Metadata.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Reflection.Primitives.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Reflection.TypeExtensions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Resources.Reader.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Resources.ResourceManager.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Resources.Writer.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.CompilerServices.Unsafe.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.CompilerServices.VisualC.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.Extensions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.Handles.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.InteropServices.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.InteropServices.RuntimeInformation.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.InteropServices.WindowsRuntime.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.Intrinsics.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.Loader.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.Numerics.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.Serialization.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.Serialization.Formatters.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.Serialization.Json.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.Serialization.Primitives.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Runtime.Serialization.Xml.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Security.Claims.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Security.Cryptography.Algorithms.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Security.Cryptography.Csp.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Security.Cryptography.Encoding.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Security.Cryptography.Primitives.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Security.Cryptography.X509Certificates.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Security.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Security.Principal.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/system.security.principal.windows/4.7.0/ref/netcoreapp3.0/System.Security.Principal.Windows.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Security.SecureString.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.ServiceModel.Web.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.ServiceProcess.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Text.Encoding.CodePages.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Text.Encoding.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Text.Encoding.Extensions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Text.Encodings.Web.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Text.Json.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Text.RegularExpressions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Threading.Channels.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Threading.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Threading.Overlapped.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Threading.Tasks.Dataflow.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Threading.Tasks.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Threading.Tasks.Extensions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Threading.Tasks.Parallel.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Threading.Thread.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Threading.ThreadPool.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Threading.Timer.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Transactions.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Transactions.Local.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.ValueTuple.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Web.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Web.HttpUtility.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Windows.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Xml.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Xml.Linq.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Xml.ReaderWriter.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Xml.Serialization.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Xml.XDocument.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Xml.XmlDocument.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Xml.XmlSerializer.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Xml.XPath.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/System.Xml.XPath.XDocument.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/system.xml.xpath.xmldocument/4.0.1/ref/netstandard1.3/System.Xml.XPath.XmlDocument.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/microsoft.testplatform.testhost/15.9.0/lib/netstandard1.5/testhost.dll","Aliases":[],"Framework":null},{"FilePath":"/usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/3.1.0/ref/netcoreapp3.1/WindowsBase.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/xunit.abstractions/2.0.3/lib/netstandard2.0/xunit.abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/xunit.assert/2.4.1/lib/netstandard1.1/xunit.assert.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/xunit.extensibility.core/2.4.1/lib/netstandard1.1/xunit.core.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sbutler/.nuget/packages/xunit.extensibility.execution/2.4.1/lib/netstandard1.1/xunit.execution.dotnet.dll","Aliases":[],"Framework":null}],"Files":["/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dnstest/BitPackerTests.cs","/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dnstest/ConfigTests.cs","/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dnstest/DnsCacheTests.cs","/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dnstest/DnsProtocolTest.cs","/Users/sbutler/.nuget/packages/xunit.runner.visualstudio/2.4.0/build/netcoreapp1.0/xunit.runner.visualstudio.dotnetcore.testadapter.dll","/Users/sbutler/.nuget/packages/xunit.runner.visualstudio/2.4.0/build/netcoreapp1.0/xunit.runner.reporters.netcoreapp10.dll","/Users/sbutler/.nuget/packages/xunit.runner.visualstudio/2.4.0/build/netcoreapp1.0/xunit.runner.utility.netcoreapp10.dll","/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dnstest/mono_crash.11a6748113.0.json","/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dnstest/Data/BindZoneFiles/bindzonetest1.txt","/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dnstest/Data/Config/appsettings.json","/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dnstest/obj/Debug/netcoreapp3.1/dnstest.AssemblyInfo.cs","/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dnstest/obj/Debug/netcoreapp3.1/dnstest.AssemblyInfo.cs","/Users/sbutler/dev/go/src/github.com/stephbu/csharp-dns-server/dnstest/obj/Debug/netcoreapp3.1/dnstest.AssemblyInfo.cs"],"BuildActions":["Compile","Compile","Compile","Compile","None","None","None","None","None","None","Compile","Compile","Compile"],"Analyzers":["/Users/sbutler/.nuget/packages/xunit.analyzers/0.10.0/analyzers/dotnet/cs/xunit.analyzers.dll"],"AdditionalFiles":[],"EditorConfigFiles":[]} -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": ".NET Core Launch (console)", 10 | "type": "coreclr", 11 | "request": "launch", 12 | "preLaunchTask": "build", 13 | "program": "${workspaceFolder}/dns/bin/Debug/netcoreapp2.2/dns.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/dns", 16 | "console": "internalConsole", 17 | "stopAtEntry": false 18 | }, 19 | { 20 | "name": ".NET Core Attach", 21 | "type": "coreclr", 22 | "request": "attach", 23 | "processId": "${command:pickProcess}", 24 | "cwd": "${workspaceFolder}/dns" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/dns/dns.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/dns/dns.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/dns/dns.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Dns/Config/AppConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Dns.Config 2 | { 3 | public class AppConfig 4 | { 5 | public ServerOptions Server { get; set; } 6 | } 7 | 8 | public class ServerOptions 9 | { 10 | public ZoneOptions Zone { get; set; } 11 | public DnsListenerOptions DnsListener { get; set;} 12 | public WebServerOptions WebServer { get; set; } 13 | } 14 | 15 | public class ZoneOptions 16 | { 17 | public string Name { get; set; } 18 | public string Provider { get; set; } 19 | } 20 | 21 | public class DnsListenerOptions 22 | { 23 | public ushort Port { get; set; } 24 | } 25 | 26 | public class WebServerOptions 27 | { 28 | public bool Enabled { get; set; } 29 | public int Port { get; set; } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /Dns/Contracts/IAddressDispenser.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns.Contracts 8 | { 9 | using System.Collections.Generic; 10 | using System.Net; 11 | 12 | public interface IAddressDispenser : IHtmlDump 13 | { 14 | string HostName { get; } 15 | 16 | IEnumerable GetAddresses(); 17 | } 18 | } -------------------------------------------------------------------------------- /Dns/Contracts/IDnsCache.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns.Contracts 8 | { 9 | public interface IDnsCache 10 | { 11 | byte[] Get(string key); 12 | 13 | void Set(string key, byte[] bytes, int ttlSeconds); 14 | } 15 | } -------------------------------------------------------------------------------- /Dns/Contracts/IDnsResolver.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns.Contracts 8 | { 9 | using System.Net; 10 | 11 | /// Provides domain name resolver capabilities 12 | internal interface IDnsResolver : IHtmlDump 13 | { 14 | string GetZoneName(); 15 | 16 | uint GetZoneSerial(); 17 | 18 | bool TryGetHostEntry(string hostname, ResourceClass resClass, ResourceType resType, out IPHostEntry entry); 19 | } 20 | } -------------------------------------------------------------------------------- /Dns/Contracts/IHtmlDump.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns.Contracts 8 | { 9 | using System.IO; 10 | 11 | public interface IHtmlDump 12 | { 13 | void DumpHtml(TextWriter writer); 14 | } 15 | } -------------------------------------------------------------------------------- /Dns/Data/machineinfo.csv: -------------------------------------------------------------------------------- 1 | #Version:1.0 2 | #Fields:MachineName,PodName,MachineFunction,Port,Image,SKU,StaticIP,ScaleUnit,staticFunction,staticScaleunit,status,repair,netMask,Freeze,FreezeEndsAt,ConnectedTo,Enclosure,Environment,PhysicalMachineName,PhysicalIP,PodPower,PodSwitch,QualifiedFunction 3 | STEPHBU01,POD_Quincy,LCP,0,retail.amd64,NonStdHw,127.0.0.1,0,NU,0,H,None,16777215,0,1900-01-01 00:00:00.000,,,lsdplat-pvt-blulab,STEPHBU01,127.0.0.1,,, 4 | #EOF -------------------------------------------------------------------------------- /Dns/Dns.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | netcoreapp3.1 6 | Dns 7 | 65001 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Always 27 | 28 | 29 | 30 | 31 | Always 32 | 33 | 34 | -------------------------------------------------------------------------------- /Dns/DnsCache.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using Microsoft.Extensions.Caching.Memory; 11 | using Dns.Contracts; 12 | 13 | public class DnsCache : IDnsCache 14 | { 15 | private readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions()); 16 | 17 | byte[] IDnsCache.Get(string key) 18 | { 19 | byte[] entry; 20 | if (_cache.TryGetValue(key, out entry)) { 21 | return entry; 22 | } 23 | 24 | return null; 25 | } 26 | 27 | void IDnsCache.Set(string key, byte[] bytes, int ttlSeconds) 28 | { 29 | var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(DateTimeOffset.Now + TimeSpan.FromSeconds(ttlSeconds)); 30 | _cache.Set(key, bytes, cacheEntryOptions); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Dns/DnsMessage.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.IO; 11 | 12 | public class DnsMessage 13 | { 14 | public ResourceList Additionals = new ResourceList(); 15 | public ResourceList Answers = new ResourceList(); 16 | public ResourceList Authorities = new ResourceList(); 17 | public QuestionList Questions = new QuestionList(); 18 | 19 | private ushort _additionalCount; 20 | private ushort _answerCount; 21 | private ushort _flags; 22 | private byte[] _header = new byte[12]; 23 | private ushort _nameServerCount; 24 | private ushort _queryIdentifier; 25 | private ushort _questionCount; 26 | 27 | /// Provides direct access to the Flags WORD 28 | public ushort Flags 29 | { 30 | get { return _flags.SwapEndian(); } 31 | set 32 | { 33 | _flags = value.SwapEndian(); 34 | byte[] bytes = BitConverter.GetBytes(_flags); 35 | bytes.CopyTo(_header, 2); 36 | } 37 | } 38 | 39 | /// Is Query Response 40 | public bool QR 41 | { 42 | get { return (this.Flags & 0x8000) == 0x8000; } 43 | set 44 | { 45 | if (value) 46 | { 47 | this.Flags = (ushort)(this.Flags | 0x8000); 48 | } 49 | else 50 | { 51 | this.Flags = (ushort)(this.Flags & (~0x8000)); 52 | } 53 | } 54 | } 55 | 56 | /// Opcode 57 | public byte Opcode 58 | { 59 | get { return (byte) ((this.Flags & 0x7800) >> 11); } 60 | set { this.Flags = (ushort) ((this.Flags & ~0x7800) | (value << 11)); } 61 | } 62 | 63 | /// Is Authorative Answer 64 | public bool AA 65 | { 66 | get { return (this.Flags & 0x0400) == 0x0400; } 67 | set 68 | { 69 | if (value) 70 | { 71 | this.Flags = (ushort)(this.Flags | 0x0400); 72 | } 73 | else 74 | { 75 | this.Flags = (ushort)(this.Flags & (~0x0400)); 76 | } 77 | } 78 | } 79 | 80 | /// Is Truncated 81 | public bool TC 82 | { 83 | get { return (this.Flags & 0x0200) == 0x0200; } 84 | set 85 | { 86 | if (value) 87 | { 88 | this.Flags = (ushort)(this.Flags | 0x0200); 89 | } 90 | else 91 | { 92 | this.Flags = (ushort)(this.Flags & (~0x0200)); 93 | } 94 | } 95 | } 96 | 97 | /// Is Recursive Desired 98 | public bool RD 99 | { 100 | get { return (this.Flags & 0x0100) == 0x0100; } 101 | set 102 | { 103 | if (value) 104 | { 105 | this.Flags = (ushort)(this.Flags | 0x0100); 106 | } 107 | else 108 | { 109 | this.Flags = (ushort)(this.Flags & (~0x0100)); 110 | } 111 | } 112 | } 113 | 114 | /// Is Recursive Allowable 115 | public bool RA 116 | { 117 | get { return (this.Flags & 0x0080) == 0x0080; } 118 | set 119 | { 120 | if (value) 121 | { 122 | this.Flags = (ushort)(this.Flags | 0x0080); 123 | } 124 | else 125 | { 126 | this.Flags = (ushort)(this.Flags & (~0x0080)); 127 | } 128 | } 129 | } 130 | 131 | /// Reserved for future use 132 | public bool Zero 133 | { 134 | get { return (this.Flags & 0x0040) == 0x0040; } 135 | set 136 | { 137 | if (value) 138 | { 139 | this.Flags = (ushort)(this.Flags | 0x0040); 140 | } 141 | else 142 | { 143 | this.Flags = (ushort)(this.Flags & (~0x0040)); 144 | } 145 | } 146 | } 147 | 148 | public bool AuthenticatingData 149 | { 150 | get { return (this.Flags & 0x0020) == 0x0020; } 151 | set 152 | { 153 | if (value) 154 | { 155 | this.Flags = (ushort)(this.Flags | 0x0020); 156 | } 157 | else 158 | { 159 | this.Flags = (ushort)(this.Flags & (~0x0020)); 160 | } 161 | } 162 | } 163 | 164 | public bool CheckingDisabled 165 | { 166 | get { return (this.Flags & 0x0010) == 0x0010; } 167 | set 168 | { 169 | if (value) 170 | { 171 | this.Flags = (ushort)(this.Flags | 0x0010); 172 | } 173 | else 174 | { 175 | this.Flags = (ushort)(this.Flags & (~0x0010)); 176 | } 177 | } 178 | } 179 | 180 | public byte RCode 181 | { 182 | get { return (byte) (this.Flags & 0x000F); } 183 | set { this.Flags = (ushort) ((this.Flags & ~0x000F) | value); } 184 | } 185 | 186 | public ushort AdditionalCount 187 | { 188 | get { return _additionalCount; } 189 | set 190 | { 191 | _additionalCount = value; 192 | byte[] bytes = BitConverter.GetBytes(_additionalCount.SwapEndian()); 193 | bytes.CopyTo(_header, 10); 194 | } 195 | } 196 | 197 | public ushort AnswerCount 198 | { 199 | get { return _answerCount; } 200 | set 201 | { 202 | _answerCount = value; 203 | byte[] bytes = BitConverter.GetBytes(_answerCount.SwapEndian()); 204 | bytes.CopyTo(_header, 6); 205 | } 206 | } 207 | 208 | public ushort NameServerCount 209 | { 210 | get { return _nameServerCount; } 211 | set 212 | { 213 | _nameServerCount = value; 214 | byte[] bytes = BitConverter.GetBytes(_nameServerCount.SwapEndian()); 215 | bytes.CopyTo(_header, 8); 216 | } 217 | } 218 | 219 | public ushort QueryIdentifier 220 | { 221 | get { return _queryIdentifier; } 222 | set 223 | { 224 | _queryIdentifier = value; 225 | byte[] bytes = BitConverter.GetBytes(_queryIdentifier.SwapEndian()); 226 | bytes.CopyTo(_header, 0); 227 | } 228 | } 229 | 230 | public ushort QuestionCount 231 | { 232 | get { return _questionCount; } 233 | set 234 | { 235 | _questionCount = value; 236 | byte[] bytes = BitConverter.GetBytes(_questionCount.SwapEndian()); 237 | bytes.CopyTo(_header, 4); 238 | } 239 | } 240 | 241 | public bool IsQuery() 242 | { 243 | return this.QR == false; 244 | } 245 | 246 | /// 247 | /// 248 | private static DnsMessage Parse(byte[] bytes) 249 | { 250 | if (bytes == null) 251 | { 252 | throw new ArgumentNullException("bytes"); 253 | } 254 | 255 | DnsMessage result = new DnsMessage(); 256 | 257 | int byteOffset = 0; 258 | byteOffset = byteOffset + result.ParseHeader(bytes, byteOffset); 259 | byteOffset += result.Questions.LoadFrom(bytes, byteOffset, result.QuestionCount); 260 | byteOffset += result.Answers.LoadFrom(bytes, byteOffset, result.AnswerCount); 261 | byteOffset += result.Authorities.LoadFrom(bytes, byteOffset, result.NameServerCount); 262 | byteOffset += result.Additionals.LoadFrom(bytes, byteOffset, result.AdditionalCount); 263 | 264 | // Console.WriteLine("Bytes read: {0}", byteOffset); 265 | 266 | return result; 267 | } 268 | 269 | private int ParseHeader(byte[] bytes, int offset) 270 | { 271 | if (bytes.Length < 12 + offset) 272 | { 273 | throw new InvalidDataException("bytes"); 274 | } 275 | 276 | Buffer.BlockCopy(bytes, 0, _header, 0, 12); 277 | _queryIdentifier = BitConverter.ToUInt16(_header, 0).SwapEndian(); 278 | _flags = BitConverter.ToUInt16(_header, 2); 279 | _questionCount = BitConverter.ToUInt16(_header, 4).SwapEndian(); 280 | _answerCount = BitConverter.ToUInt16(_header, 6).SwapEndian(); 281 | _nameServerCount = BitConverter.ToUInt16(_header, 8).SwapEndian(); 282 | _additionalCount = BitConverter.ToUInt16(_header, 10).SwapEndian(); 283 | 284 | return 12; 285 | } 286 | 287 | public void Dump() 288 | { 289 | Console.WriteLine("QueryIdentifier: 0x{0:X4}", this.QueryIdentifier); 290 | Console.WriteLine("QR: ({0}... .... .... ....) {1}", this.QR ? 1 : 0, this.QR ? "Response" : "Query"); 291 | Console.WriteLine("Opcode: (.{0}{1}{2} {3}... .... ....) {4}", (this.Opcode & 1) > 1 ? 1 : 0, (this.Opcode & 2) > 1 ? 1 : 0, (this.Opcode & 4) > 1 ? 1 : 0, (this.Opcode & 8) > 1 ? 1 : 0, (OpCode) (this.Opcode)); 292 | Console.WriteLine("AA: (.... .{0}.. .... ....) {1}", this.AA ? 1 : 0, this.AA ? "Authoritative" : "Not Authoritative"); 293 | Console.WriteLine("TC: (.... ..{0}. .... ....) {1}", this.TC ? 1 : 0, this.TC ? "Truncated" : "Not Truncated"); 294 | Console.WriteLine("RD: (.... ...{0} .... ....) {1}", this.RD ? 1 : 0, this.RD ? "Recursion Desired" : "Recursion not desired"); 295 | Console.WriteLine("RA: (.... .... {0}... ....) {1}", this.RA ? 1 : 0, this.RA ? "Recursive Query Support Available" : "Recursive Query Support Not Available"); 296 | Console.WriteLine("Zero: (.... .... .0.. ....) 0"); 297 | Console.WriteLine("AuthenticatedData: (.... .... ..{0}. ....) {1}", this.AuthenticatingData ? 1 : 0, this.AuthenticatingData ? "AuthenticatingData" : "Not AuthenticatingData"); 298 | Console.WriteLine("CheckingDisabled: (.... .... ...{0} ....) {1}", this.CheckingDisabled ? 1 : 0, this.CheckingDisabled ? "Checking Disabled" : "Not CheckingEnabled"); 299 | Console.WriteLine("RCode: (.... .... .... {0}{1}{2}{3}) {4}", (this.RCode & 1) > 1 ? 1 : 0, (this.RCode & 2) > 1 ? 1 : 0, (this.RCode & 4) > 1 ? 1 : 0, (this.RCode & 8) > 1 ? 1 : 0, (RCode) (this.RCode)); 300 | Console.WriteLine("QuestionCount: 0x{0:X4}", this.QuestionCount); 301 | Console.WriteLine("AnswerCount: 0x{0:X4}", this.AnswerCount); 302 | Console.WriteLine("NameServerCount: 0x{0:X4}", this.NameServerCount); 303 | Console.WriteLine("AdditionalCount: 0x{0:X4}", this.AdditionalCount); 304 | Console.WriteLine(); 305 | 306 | if (Questions != null) 307 | { 308 | foreach (Question question in this.Questions) 309 | { 310 | Console.WriteLine("QRecord: {0} of type {1} on class {2}", question.Name, (ResourceType)question.Type, (ResourceClass)question.Class); 311 | } 312 | Console.WriteLine(); 313 | } 314 | 315 | if (Answers != null) 316 | { 317 | foreach (ResourceRecord resource in this.Answers) 318 | { 319 | Console.WriteLine("Record: {0} of type {1} on class {2}", resource.Name, (ResourceType) resource.Type, (ResourceClass)resource.Class); 320 | resource.Dump(); 321 | Console.WriteLine(); 322 | } 323 | } 324 | 325 | if (Authorities != null) 326 | { 327 | foreach (ResourceRecord resource in this.Authorities) 328 | { 329 | Console.WriteLine("Record: {0} of type {1} on class {2}", resource.Name, (ResourceType)resource.Type, (ResourceClass)resource.Class); 330 | resource.Dump(); 331 | Console.WriteLine(); 332 | } 333 | } 334 | 335 | } 336 | 337 | public byte[] GetBytes() 338 | { 339 | using (MemoryStream stream = new MemoryStream()) 340 | { 341 | this.WriteToStream(stream); 342 | return stream.GetBuffer(); 343 | } 344 | } 345 | 346 | public void WriteToStream(Stream stream) 347 | { 348 | // write header 349 | stream.Write(this._header, 0, _header.Length); 350 | Questions.WriteToStream(stream); 351 | Answers.WriteToStream(stream); 352 | Authorities.WriteToStream(stream); 353 | Additionals.WriteToStream(stream); 354 | } 355 | 356 | public static bool TryParse(byte[] bytes, out DnsMessage query) 357 | { 358 | try 359 | { 360 | query = Parse(bytes); 361 | return true; 362 | } 363 | catch (Exception ex) 364 | { 365 | Console.WriteLine(ex.ToString()); 366 | query = null; 367 | return false; 368 | } 369 | } 370 | } 371 | } -------------------------------------------------------------------------------- /Dns/DnsProtocol.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.Text; 11 | 12 | public class DnsProtocol 13 | { 14 | /// 15 | /// 16 | /// 17 | /// 18 | public static bool TryParse(byte[] bytes, out DnsMessage dnsMessage) 19 | { 20 | if (!DnsMessage.TryParse(bytes, out dnsMessage)) 21 | { 22 | return false; 23 | } 24 | 25 | return true; 26 | } 27 | 28 | public static ushort ReadUshort(byte[] bytes, ref int offset) 29 | { 30 | ushort ret = BitConverter.ToUInt16(bytes, offset); 31 | offset += sizeof (ushort); 32 | return ret; 33 | } 34 | 35 | public static uint ReadUint(byte[] bytes, ref int offset) 36 | { 37 | uint ret = BitConverter.ToUInt32(bytes, offset); 38 | offset += sizeof (uint); 39 | return ret; 40 | } 41 | 42 | 43 | public static string ReadString(byte[] bytes, ref int currentOffset) 44 | { 45 | StringBuilder resourceName = new StringBuilder(); 46 | int compressionOffset = -1; 47 | while (true) 48 | { 49 | // get segment length or detect termination of segments 50 | int segmentLength = bytes[currentOffset]; 51 | 52 | // compressed name 53 | if ((segmentLength & 0xC0) == 0xC0) 54 | { 55 | currentOffset++; 56 | if (compressionOffset == -1) 57 | { 58 | // only record origin, and follow all pointers thereafter 59 | compressionOffset = currentOffset; 60 | } 61 | 62 | // move pointer to compression segment 63 | currentOffset = bytes[currentOffset]; 64 | segmentLength = bytes[currentOffset]; 65 | } 66 | 67 | if (segmentLength == 0x00) 68 | { 69 | if (compressionOffset != -1) 70 | { 71 | currentOffset = compressionOffset; 72 | } 73 | // move past end of name \0 74 | currentOffset++; 75 | break; 76 | } 77 | 78 | // move pass length and get segment text 79 | currentOffset++; 80 | resourceName.AppendFormat("{0}.", Encoding.Default.GetString(bytes, currentOffset, segmentLength)); 81 | currentOffset += segmentLength; 82 | } 83 | return resourceName.ToString().TrimEnd(new[] {'.'}); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /Dns/DnsServer.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | using System.Net.NetworkInformation; 8 | 9 | namespace Dns 10 | { 11 | using System; 12 | using System.IO; 13 | using System.Net; 14 | using System.Net.Sockets; 15 | using System.Threading; 16 | using Dns.Contracts; 17 | using Microsoft.Win32; 18 | using System.Linq; 19 | using System.Collections.Generic; 20 | 21 | internal class DnsServer : IHtmlDump 22 | { 23 | private IPAddress[] _defaultDns; 24 | private UdpListener _udpListener; // listener for UDP53 traffic 25 | private IDnsResolver _resolver; // resolver for name entries 26 | private long _requests; 27 | private long _responses; 28 | private long _nacks; 29 | 30 | private Dictionary _requestResponseMap = new Dictionary(); 31 | 32 | private ReaderWriterLockSlim _requestResponseMapLock = new ReaderWriterLockSlim(); 33 | 34 | private ushort port; 35 | 36 | internal DnsServer(ushort port) 37 | { 38 | this.port = port; 39 | } 40 | 41 | /// Initialize server with specified domain name resolver 42 | /// 43 | public void Initialize(IDnsResolver resolver) 44 | { 45 | _resolver = resolver; 46 | 47 | _udpListener = new UdpListener(); 48 | 49 | _udpListener.Initialize(this.port); 50 | _udpListener.OnRequest += ProcessUdpRequest; 51 | 52 | _defaultDns = GetDefaultDNS().ToArray(); 53 | } 54 | 55 | /// Start DNS listener 56 | public void Start(CancellationToken ct) 57 | { 58 | _udpListener.Start(); 59 | ct.Register(_udpListener.Stop); 60 | } 61 | 62 | /// Process UDP Request 63 | /// 64 | private void ProcessUdpRequest(byte[] buffer, EndPoint remoteEndPoint) 65 | { 66 | DnsMessage message; 67 | if (!DnsProtocol.TryParse(buffer, out message)) 68 | { 69 | // TODO log bad message 70 | Console.WriteLine("unable to parse message"); 71 | return; 72 | } 73 | 74 | Interlocked.Increment(ref _requests); 75 | 76 | if (message.IsQuery()) 77 | { 78 | if (message.Questions.Count > 0) 79 | { 80 | foreach (Question question in message.Questions) 81 | { 82 | Console.WriteLine("{0} asked for {1} {2} {3}", remoteEndPoint.ToString(),question.Name, question.Class, question.Type); 83 | IPHostEntry entry; 84 | if (question.Type == ResourceType.PTR) 85 | { 86 | if (question.Name == "1.0.0.127.in-addr.arpa") // query for PTR record 87 | { 88 | message.QR = true; 89 | message.AA = true; 90 | message.RA = false; 91 | message.AnswerCount++; 92 | message.Answers.Add(new ResourceRecord {Name = question.Name, Class = ResourceClass.IN, Type = ResourceType.PTR, TTL = 3600, DataLength = 0xB, RData = new DomainNamePointRData() {Name = "localhost"}}); 93 | } 94 | } 95 | else if (_resolver.TryGetHostEntry(question.Name, question.Class, question.Type, out entry)) // Right zone, hostname/machine function does exist 96 | { 97 | message.QR = true; 98 | message.AA = true; 99 | message.RA = false; 100 | message.RCode = (byte) RCode.NOERROR; 101 | foreach (IPAddress address in entry.AddressList) 102 | { 103 | message.AnswerCount++; 104 | message.Answers.Add(new ResourceRecord {Name = question.Name, Class = ResourceClass.IN, Type = ResourceType.A, TTL = 10, RData = new ANameRData {Address = address}}); 105 | } 106 | } 107 | else if (question.Name.EndsWith(_resolver.GetZoneName())) // Right zone, but the hostname/machine function doesn't exist 108 | { 109 | message.QR = true; 110 | message.AA = true; 111 | message.RA = false; 112 | message.RCode = (byte) RCode.NXDOMAIN; 113 | message.AnswerCount = 0; 114 | message.Answers.Clear(); 115 | 116 | var soaResourceData = new StatementOfAuthorityRData() {PrimaryNameServer = Environment.MachineName, ResponsibleAuthoritativeMailbox = "stephbu." + Environment.MachineName, Serial = _resolver.GetZoneSerial(), ExpirationLimit = 86400, RetryInterval = 300, RefreshInterval = 300, MinimumTTL = 300}; 117 | var soaResourceRecord = new ResourceRecord {Class = ResourceClass.IN, Type = ResourceType.SOA, TTL = 300, RData = soaResourceData}; 118 | message.NameServerCount++; 119 | message.Authorities.Add(soaResourceRecord); 120 | } 121 | // 122 | else // Referral to regular DC DNS servers 123 | { 124 | // store current IP address and Query ID. 125 | try 126 | { 127 | string key = GetKeyName(message); 128 | _requestResponseMapLock.EnterWriteLock(); 129 | _requestResponseMap.Add(key, remoteEndPoint); 130 | } 131 | finally 132 | { 133 | _requestResponseMapLock.ExitWriteLock(); 134 | } 135 | } 136 | 137 | using (MemoryStream responseStream = new MemoryStream(512)) 138 | { 139 | message.WriteToStream(responseStream); 140 | if (message.IsQuery()) 141 | { 142 | // send to upstream DNS servers 143 | foreach (IPAddress dnsServer in _defaultDns) 144 | { 145 | SendUdp(responseStream.GetBuffer(), 0, (int) responseStream.Position, new IPEndPoint(dnsServer, 53)); 146 | } 147 | } 148 | else 149 | { 150 | Interlocked.Increment(ref _responses); 151 | SendUdp(responseStream.GetBuffer(), 0, (int) responseStream.Position, remoteEndPoint); 152 | } 153 | } 154 | } 155 | } 156 | } 157 | else 158 | { 159 | // message is response to a delegated query 160 | string key = GetKeyName(message); 161 | try 162 | { 163 | _requestResponseMapLock.EnterUpgradeableReadLock(); 164 | 165 | EndPoint ep; 166 | if (_requestResponseMap.TryGetValue(key, out ep)) 167 | { 168 | // first test establishes presence 169 | try 170 | { 171 | _requestResponseMapLock.EnterWriteLock(); 172 | // second test within lock means exclusive access 173 | if (_requestResponseMap.TryGetValue(key, out ep)) 174 | { 175 | using (MemoryStream responseStream = new MemoryStream(512)) 176 | { 177 | message.WriteToStream(responseStream); 178 | Interlocked.Increment(ref _responses); 179 | 180 | Console.WriteLine("{0} answered {1} {2} {3} to {4}", remoteEndPoint.ToString(), message.Questions[0].Name, message.Questions[0].Class, message.Questions[0].Type, ep.ToString()); 181 | 182 | SendUdp(responseStream.GetBuffer(), 0, (int)responseStream.Position, ep); 183 | } 184 | _requestResponseMap.Remove(key); 185 | } 186 | 187 | } 188 | finally 189 | { 190 | _requestResponseMapLock.ExitWriteLock(); 191 | } 192 | } 193 | else 194 | { 195 | Interlocked.Increment(ref _nacks); 196 | } 197 | } 198 | finally 199 | { 200 | _requestResponseMapLock.ExitUpgradeableReadLock(); 201 | } 202 | } 203 | } 204 | 205 | private string GetKeyName(DnsMessage message) 206 | { 207 | if (message.QuestionCount > 0) 208 | { 209 | return string.Format("{0}|{1}|{2}|{3}", message.QueryIdentifier, message.Questions[0].Class, message.Questions[0].Type, message.Questions[0].Name); 210 | } 211 | else 212 | { 213 | return message.QueryIdentifier.ToString(); 214 | } 215 | } 216 | 217 | /// Send UDP response via UDP listener socket 218 | /// 219 | /// 220 | /// 221 | /// 222 | private void SendUdp(byte[] bytes, int offset, int count, EndPoint remoteEndpoint) 223 | { 224 | //for (int index = 0; index < count; index++) 225 | //{ 226 | // Console.Write("0x{0:X},", bytes[offset + index]); 227 | //} 228 | //Console.WriteLine(); 229 | 230 | SocketAsyncEventArgs args = new SocketAsyncEventArgs(); 231 | args.RemoteEndPoint = remoteEndpoint; 232 | args.SetBuffer(bytes, offset, count); 233 | 234 | _udpListener.SendToAsync(args); 235 | } 236 | 237 | /// Returns list of manual or DHCP specified DNS addresses 238 | /// List of configured DNS names 239 | private IEnumerable GetDefaultDNS() 240 | { 241 | NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces(); 242 | foreach (NetworkInterface adapter in adapters) 243 | { 244 | 245 | IPInterfaceProperties adapterProperties = adapter.GetIPProperties(); 246 | IPAddressCollection dnsServers = adapterProperties.DnsAddresses; 247 | 248 | foreach (IPAddress dns in dnsServers) 249 | { 250 | Console.WriteLine("Discovered DNS: ", dns); 251 | 252 | yield return dns; 253 | } 254 | 255 | } 256 | } 257 | 258 | public void DumpHtml(TextWriter writer) 259 | { 260 | writer.WriteLine("DNS Server Status
"); 261 | writer.Write("Default Nameservers:"); 262 | foreach (IPAddress dns in _defaultDns) 263 | { 264 | writer.WriteLine(dns); 265 | } 266 | writer.WriteLine("DNS Server Status
"); 267 | } 268 | } 269 | } -------------------------------------------------------------------------------- /Dns/Extensions.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.IO; 11 | using System.Text; 12 | 13 | public static class Extensions 14 | { 15 | public static TextWriter CreateWriter(this Stream stream, Encoding encoding = null) 16 | { 17 | encoding = encoding ?? Encoding.UTF8; 18 | // write all data using UTF-8 19 | return new StreamWriter(stream, encoding); 20 | } 21 | 22 | public static ushort SwapEndian(this ushort val) 23 | { 24 | ushort value = (ushort) ((val << 8) | (val >> 8)); 25 | return value; 26 | } 27 | 28 | public static uint SwapEndian(this uint val) 29 | { 30 | uint value = (val << 24) | ((val << 8) & 0x00ff0000) | ((val >> 8) & 0x0000ff00) | (val >> 24); 31 | return value; 32 | } 33 | 34 | 35 | 36 | public static byte[] GetResourceBytes(this string str, char delimiter = '.') 37 | { 38 | if (str == null) 39 | { 40 | str = ""; 41 | } 42 | 43 | using (MemoryStream stream = new MemoryStream(str.Length + 2)) 44 | { 45 | string[] segments = str.Split(new char[] {'.'}); 46 | foreach (string segment in segments) 47 | { 48 | stream.WriteByte((byte)segment.Length); 49 | foreach (char currentChar in segment) 50 | { 51 | stream.WriteByte((byte)currentChar); 52 | } 53 | } 54 | // null delimiter 55 | stream.WriteByte(0x0); 56 | return stream.GetBuffer(); 57 | } 58 | } 59 | 60 | public static void WriteToStream(this string str, Stream stream) 61 | { 62 | if (!string.IsNullOrWhiteSpace(str)) 63 | { 64 | string[] segments = str.Split(new char[] { '.' }); 65 | foreach (string segment in segments) 66 | { 67 | stream.WriteByte((byte)segment.Length); 68 | foreach (char currentChar in segment) 69 | { 70 | stream.WriteByte((byte)currentChar); 71 | } 72 | } 73 | } 74 | 75 | // null delimiter 76 | stream.WriteByte(0x0); 77 | } 78 | 79 | 80 | public static byte[] GetBytes(this string str, Encoding encoding = null) 81 | { 82 | encoding = encoding ?? Encoding.ASCII; 83 | return encoding.GetBytes(str); 84 | } 85 | 86 | public static string IP(long ipLong) 87 | { 88 | StringBuilder b = new StringBuilder(); 89 | long tempLong, temp; 90 | 91 | tempLong = ipLong; 92 | temp = tempLong/(256*256*256); 93 | tempLong = tempLong - (temp*256*256*256); 94 | b.Append(Convert.ToString(temp)).Append("."); 95 | temp = tempLong/(256*256); 96 | tempLong = tempLong - (temp*256*256); 97 | b.Append(Convert.ToString(temp)).Append("."); 98 | temp = tempLong/256; 99 | tempLong = tempLong - (temp*256); 100 | b.Append(Convert.ToString(temp)).Append("."); 101 | temp = tempLong; 102 | tempLong = tempLong - temp; 103 | b.Append(Convert.ToString(temp)); 104 | 105 | return b.ToString().ToLower(); 106 | } 107 | 108 | public static void WriteToStream(this ushort value, Stream stream) 109 | { 110 | stream.WriteByte((byte)(value & 0xFF)); 111 | stream.WriteByte((byte)((value >> 8) & 0xFF)); 112 | } 113 | 114 | 115 | public static void WriteToStream(this uint value, Stream stream) 116 | { 117 | stream.WriteByte((byte)(value & 0xFF)); 118 | stream.WriteByte((byte)((value >> 8) & 0xFF)); 119 | stream.WriteByte((byte)((value >> 16) & 0xFF)); 120 | stream.WriteByte((byte)((value >> 24) & 0xFF)); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /Dns/HttpServer.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.IO; 11 | using System.Net; 12 | using System.Threading; 13 | using System.Threading.Tasks; 14 | using Dns.Contracts; 15 | 16 | internal delegate void OnHttpRequestHandler(HttpListenerContext context); 17 | internal delegate void OnHandledException(Exception ex); 18 | 19 | /// HTTP data receiver 20 | internal class HttpServer : IHtmlDump 21 | { 22 | private HttpListener _listener; 23 | private bool _running; 24 | 25 | private int _requestCounter = 0; 26 | private int _request200; 27 | private int _request300; 28 | private int _request400; 29 | private int _request500; 30 | private int _request600; 31 | 32 | private readonly string _machineName = Environment.MachineName; 33 | 34 | public event OnHttpRequestHandler OnProcessRequest; 35 | public event OnHttpRequestHandler OnHealthProbe; 36 | public event OnHandledException OnHandledException; 37 | 38 | /// Configure listener 39 | public void Initialize(params string[] prefixes) 40 | { 41 | _listener = new HttpListener(); 42 | foreach (string prefix in prefixes) 43 | { 44 | _listener.Prefixes.Add(prefix); 45 | } 46 | } 47 | 48 | /// Start listening 49 | public async void Start(CancellationToken ct) 50 | { 51 | ct.Register(this.Stop); 52 | 53 | while (true) 54 | { 55 | try 56 | { 57 | HttpListenerContext context = await this._listener.GetContextAsync(); 58 | var processRequest = Task.Run(() => this.ProcessRequest(context)); 59 | } 60 | catch (HttpListenerException ex) 61 | { 62 | if (this.OnHandledException != null) 63 | { 64 | this.OnHandledException(ex); 65 | } 66 | break; 67 | } 68 | catch (InvalidOperationException ex) 69 | { 70 | if (this.OnHandledException != null) 71 | { 72 | this.OnHandledException(ex); 73 | } 74 | break; 75 | } 76 | } 77 | } 78 | 79 | /// Stop listening 80 | public void Stop() 81 | { 82 | if (_running == true) 83 | { 84 | _running = false; 85 | } 86 | 87 | _listener.Stop(); 88 | } 89 | 90 | /// Process incoming request 91 | private void ProcessRequest(HttpListenerContext context) 92 | { 93 | 94 | // log 95 | // performance counters 96 | try 97 | { 98 | // special case health probes 99 | if (context.Request.RawUrl.Equals("/health/keepalive", StringComparison.InvariantCultureIgnoreCase)) 100 | { 101 | HealthProbe(context); 102 | } 103 | else 104 | { 105 | if (this.OnProcessRequest != null) 106 | { 107 | this.OnProcessRequest(context); 108 | } 109 | } 110 | } 111 | catch (Exception ex) 112 | { 113 | // TODO: log exception 114 | if (this.OnHandledException != null) 115 | { 116 | this.OnHandledException(ex); 117 | } 118 | context.Response.StatusCode = 500; 119 | } 120 | 121 | context.Response.OutputStream.Dispose(); 122 | 123 | int statusCode = context.Response.StatusCode; 124 | 125 | if ((200 <= statusCode) && (statusCode < 300)) _request200++; 126 | if ((300 <= statusCode) && (statusCode < 400)) _request300++; 127 | if ((400 <= statusCode) && (statusCode < 500)) _request400++; 128 | if ((500 <= statusCode) && (statusCode < 600)) _request500++; 129 | if ((600 <= statusCode) && (statusCode < 700)) _request600++; 130 | 131 | _requestCounter++; 132 | } 133 | 134 | /// Process health probe request 135 | private void HealthProbe(HttpListenerContext context) 136 | { 137 | 138 | if (this.OnHealthProbe != null) 139 | { 140 | this.OnHealthProbe(context); 141 | } 142 | else 143 | { 144 | context.Response.StatusCode = 200; 145 | context.Response.ContentEncoding = System.Text.Encoding.UTF8; 146 | context.Response.ContentType = "text/html"; 147 | using (TextWriter writer = context.Response.OutputStream.CreateWriter()) 148 | { 149 | 150 | } 151 | } 152 | } 153 | 154 | public void DumpHtml(TextWriter writer) 155 | { 156 | writer.WriteLine("Health Probe
"); 157 | writer.WriteLine("Machine: {0}
", this._machineName); 158 | writer.WriteLine("Count: {0}
", this._requestCounter); 159 | writer.WriteLine("200: {0}
", this._request200); 160 | writer.WriteLine("300: {0}
", this._request300); 161 | writer.WriteLine("400: {0}
", this._request400); 162 | writer.WriteLine("500: {0}
", this._request500); 163 | writer.WriteLine("600: {0}
", this._request600); 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /Dns/OpCode.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | public enum OpCode 10 | { 11 | QUERY = 0, 12 | IQUERY = 1, 13 | STATUS = 2, 14 | NOTIFY = 4, 15 | UPDATE = 5, 16 | } 17 | } -------------------------------------------------------------------------------- /Dns/Program.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.IO; 11 | using System.Linq; 12 | using System.Net; 13 | using System.Threading; 14 | using Dns.ZoneProvider.AP; 15 | 16 | using Ninject; 17 | using Microsoft.Extensions.Configuration; 18 | 19 | public class Program 20 | { 21 | 22 | private static IKernel container = new StandardKernel(); 23 | 24 | private static ZoneProvider.BaseZoneProvider _zoneProvider; // reloads Zones from machineinfo.csv changes 25 | private static SmartZoneResolver _zoneResolver; // resolver and delegated lookup for unsupported zones; 26 | private static DnsServer _dnsServer; // resolver and delegated lookup for unsupported zones; 27 | private static HttpServer _httpServer; 28 | 29 | /// 30 | /// DNS Server entrypoint 31 | /// 32 | /// Fully qualified configuration filename 33 | /// Cancellation Token Source 34 | public static void Run(string configFile, CancellationToken ct) 35 | { 36 | 37 | if (!File.Exists(configFile)) 38 | { 39 | throw new FileNotFoundException(null, configFile); 40 | } 41 | 42 | IConfiguration configuration = new ConfigurationBuilder() 43 | .AddJsonFile(configFile, true, true) 44 | .Build(); 45 | 46 | var appConfig = configuration.Get(); 47 | 48 | container.Bind().To(ByName(appConfig.Server.Zone.Provider)); 49 | var zoneProviderConfig = configuration.GetSection("zoneprovider"); 50 | _zoneProvider = container.Get(); 51 | _zoneProvider.Initialize(zoneProviderConfig, appConfig.Server.Zone.Name); 52 | 53 | _zoneResolver = new SmartZoneResolver(); 54 | _zoneResolver.SubscribeTo(_zoneProvider); 55 | 56 | _dnsServer = new DnsServer(appConfig.Server.DnsListener.Port); 57 | 58 | _httpServer = new HttpServer(); 59 | 60 | _dnsServer.Initialize(_zoneResolver); 61 | 62 | _zoneProvider.Start(ct); 63 | _dnsServer.Start(ct); 64 | 65 | if(appConfig.Server.WebServer.Enabled) 66 | { 67 | _httpServer.Initialize(string.Format("http://+:{0}/", appConfig.Server.WebServer.Port)); 68 | _httpServer.OnProcessRequest += _httpServer_OnProcessRequest; 69 | _httpServer.OnHealthProbe += _httpServer_OnHealthProbe; 70 | _httpServer.Start(ct); 71 | } 72 | 73 | ct.WaitHandle.WaitOne(); 74 | 75 | } 76 | 77 | static void _httpServer_OnHealthProbe(HttpListenerContext context) 78 | { 79 | } 80 | 81 | private static void _httpServer_OnProcessRequest(HttpListenerContext context) 82 | { 83 | string rawUrl = context.Request.RawUrl; 84 | if (rawUrl == "/dump/dnsresolver") 85 | { 86 | context.Response.Headers.Add("Content-Type","text/html"); 87 | using (TextWriter writer = context.Response.OutputStream.CreateWriter()) 88 | { 89 | _zoneResolver.DumpHtml(writer); 90 | } 91 | } 92 | else if (rawUrl == "/dump/httpserver") 93 | { 94 | context.Response.Headers.Add("Content-Type", "text/html"); 95 | using (TextWriter writer = context.Response.OutputStream.CreateWriter()) 96 | { 97 | _httpServer.DumpHtml(writer); 98 | } 99 | } 100 | else if (rawUrl == "/dump/dnsserver") 101 | { 102 | context.Response.Headers.Add("Content-Type", "text/html"); 103 | using (TextWriter writer = context.Response.OutputStream.CreateWriter()) 104 | { 105 | _dnsServer.DumpHtml(writer); 106 | } 107 | } 108 | else if (rawUrl == "/dump/zoneprovider") 109 | { 110 | context.Response.Headers.Add("Content-Type", "text/html"); 111 | using (TextWriter writer = context.Response.OutputStream.CreateWriter()) 112 | { 113 | _httpServer.DumpHtml(writer); 114 | } 115 | } 116 | } 117 | 118 | private static Type ByName(string name) 119 | { 120 | foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse()) 121 | { 122 | var tt = assembly.GetType(name); 123 | if (tt != null) 124 | { 125 | return tt; 126 | } 127 | } 128 | 129 | return null; 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /Dns/Question.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.IO; 11 | 12 | public class Question 13 | { 14 | public ResourceClass Class; 15 | public string Name; 16 | public ResourceType Type; 17 | 18 | public void WriteToStream(Stream stream) 19 | { 20 | byte[] name = this.Name.GetResourceBytes(); 21 | stream.Write(name, 0, name.Length); 22 | 23 | // Type 24 | stream.Write(BitConverter.GetBytes(((ushort) (this.Type)).SwapEndian()), 0, 2); 25 | 26 | // Class 27 | stream.Write(BitConverter.GetBytes(((ushort) this.Class).SwapEndian()), 0, 2); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Dns/QuestionList.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | 13 | public class QuestionList : List 14 | { 15 | public int LoadFrom(byte[] bytes, int offset, ushort count) 16 | { 17 | int currentOffset = offset; 18 | 19 | for (int index = 0; index < count; index++) 20 | { 21 | // TODO: move this code into the Question object 22 | 23 | Question question = new Question(); 24 | 25 | question.Name = DnsProtocol.ReadString(bytes, ref currentOffset); 26 | 27 | question.Type = (ResourceType) (BitConverter.ToUInt16(bytes, currentOffset).SwapEndian()); 28 | currentOffset += 2; 29 | 30 | question.Class = (ResourceClass) (BitConverter.ToUInt16(bytes, currentOffset).SwapEndian()); 31 | currentOffset += 2; 32 | 33 | this.Add(question); 34 | } 35 | 36 | int bytesRead = currentOffset - offset; 37 | return bytesRead; 38 | } 39 | 40 | public long WriteToStream(Stream stream) 41 | { 42 | long start = stream.Length; 43 | foreach (Question question in this) 44 | { 45 | question.WriteToStream(stream); 46 | } 47 | long end = stream.Length; 48 | return end - start; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Dns/RCode.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | public enum RCode 10 | { 11 | NOERROR = 0, 12 | FORMERR = 1, 13 | SERVFAIL = 2, 14 | NXDOMAIN = 3, 15 | NOTIMP = 4, 16 | REFUSED = 5, 17 | YXDOMAIN = 6, 18 | YXRRSET = 7, 19 | NXRRSET = 8, 20 | NOTAUTH = 9, 21 | NOTZONE = 10, 22 | BADVERS = 16, 23 | } 24 | } -------------------------------------------------------------------------------- /Dns/RData.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.IO; 11 | using System.Net; 12 | 13 | public abstract class RData 14 | { 15 | public abstract void Dump(); 16 | public abstract void WriteToStream(Stream stream); 17 | 18 | public abstract ushort Length { get; } 19 | 20 | } 21 | 22 | public class ANameRData : RData 23 | { 24 | public IPAddress Address 25 | { 26 | get; 27 | set; 28 | } 29 | 30 | public static ANameRData Parse(byte[] bytes, int offset, int size) 31 | { 32 | ANameRData aname = new ANameRData(); 33 | uint addressBytes = BitConverter.ToUInt32(bytes, offset); 34 | aname.Address = new IPAddress(addressBytes); 35 | return aname; 36 | } 37 | 38 | public override void WriteToStream(Stream stream) 39 | { 40 | byte[] bytes = this.Address.GetAddressBytes(); 41 | stream.Write(bytes, 0, bytes.Length); 42 | } 43 | 44 | public override ushort Length 45 | { 46 | get { return 4; } 47 | } 48 | 49 | public override void Dump() 50 | { 51 | Console.WriteLine("Address: {0}", this.Address.ToString()); 52 | } 53 | } 54 | 55 | public class CNameRData : RData 56 | { 57 | public string Name { get; set; } 58 | 59 | public override ushort Length 60 | { 61 | // dots replaced by bytes 62 | // + 1 segment prefix 63 | // + 1 null terminator 64 | get { return (ushort) (Name.Length + 2); } 65 | } 66 | 67 | public static CNameRData Parse(byte[] bytes, int offset, int size) 68 | { 69 | CNameRData cname = new CNameRData(); 70 | cname.Name = DnsProtocol.ReadString(bytes, ref offset); 71 | return cname; 72 | } 73 | 74 | public override void WriteToStream(Stream stream) 75 | { 76 | Name.WriteToStream(stream); 77 | } 78 | 79 | public override void Dump() 80 | { 81 | Console.WriteLine("CName: {0}", this.Name); 82 | } 83 | } 84 | 85 | public class DomainNamePointRData : RData 86 | { 87 | public string Name { get; set; } 88 | 89 | public static DomainNamePointRData Parse(byte[] bytes, int offset, int size) 90 | { 91 | DomainNamePointRData domainName = new DomainNamePointRData(); 92 | domainName.Name = DnsProtocol.ReadString(bytes, ref offset); 93 | return domainName; 94 | } 95 | 96 | public override void WriteToStream(Stream stream) 97 | { 98 | Name.WriteToStream(stream); 99 | } 100 | 101 | public override ushort Length 102 | { 103 | // dots replaced by bytes 104 | // + 1 segment prefix 105 | // + 1 null terminator 106 | get { return (ushort)(Name.Length + 2); } 107 | } 108 | 109 | public override void Dump() 110 | { 111 | Console.WriteLine("DName: {0}", this.Name); 112 | } 113 | } 114 | 115 | public class NameServerRData : RData 116 | { 117 | public string Name { get; set; } 118 | 119 | public static NameServerRData Parse(byte[] bytes, int offset, int size) 120 | { 121 | NameServerRData nsRdata = new NameServerRData(); 122 | nsRdata.Name = DnsProtocol.ReadString(bytes, ref offset); 123 | return nsRdata; 124 | } 125 | 126 | public override ushort Length 127 | { 128 | // dots replaced by bytes 129 | // + 1 segment prefix 130 | // + 1 null terminator 131 | get { return (ushort)(Name.Length + 2); } 132 | } 133 | 134 | public override void WriteToStream(Stream stream) 135 | { 136 | this.Name.WriteToStream(stream); 137 | } 138 | 139 | 140 | public override void Dump() 141 | { 142 | Console.WriteLine("NameServer: {0}", this.Name); 143 | } 144 | } 145 | 146 | public class StatementOfAuthorityRData : RData 147 | { 148 | 149 | public string PrimaryNameServer { get; set; } 150 | public string ResponsibleAuthoritativeMailbox { get; set; } 151 | public uint Serial { get; set; } 152 | public uint RefreshInterval { get; set; } 153 | public uint RetryInterval { get; set; } 154 | public uint ExpirationLimit { get; set; } 155 | public uint MinimumTTL { get; set; } 156 | 157 | public static StatementOfAuthorityRData Parse(byte[] bytes, int offset, int size) 158 | { 159 | StatementOfAuthorityRData soaRdata = new StatementOfAuthorityRData(); 160 | soaRdata.PrimaryNameServer = DnsProtocol.ReadString(bytes, ref offset); 161 | soaRdata.ResponsibleAuthoritativeMailbox = DnsProtocol.ReadString(bytes, ref offset); 162 | soaRdata.Serial = DnsProtocol.ReadUint(bytes, ref offset).SwapEndian(); 163 | soaRdata.RefreshInterval = DnsProtocol.ReadUint(bytes, ref offset).SwapEndian(); 164 | soaRdata.RetryInterval = DnsProtocol.ReadUint(bytes, ref offset).SwapEndian(); 165 | soaRdata.ExpirationLimit = DnsProtocol.ReadUint(bytes, ref offset).SwapEndian(); 166 | soaRdata.MinimumTTL = DnsProtocol.ReadUint(bytes, ref offset).SwapEndian(); 167 | return soaRdata; 168 | } 169 | 170 | public override ushort Length 171 | { 172 | // dots replaced by bytes 173 | // + 1 segment prefix 174 | // + 1 null terminator 175 | get { return (ushort) (PrimaryNameServer.Length + 2 + ResponsibleAuthoritativeMailbox.Length + 2 + 20); } 176 | } 177 | 178 | public override void WriteToStream(Stream stream) 179 | { 180 | this.PrimaryNameServer.WriteToStream(stream); 181 | this.ResponsibleAuthoritativeMailbox.WriteToStream(stream); 182 | this.Serial.SwapEndian().WriteToStream(stream); 183 | this.RefreshInterval.SwapEndian().WriteToStream(stream); 184 | this.RetryInterval.SwapEndian().WriteToStream(stream); 185 | this.ExpirationLimit.SwapEndian().WriteToStream(stream); 186 | this.MinimumTTL.SwapEndian().WriteToStream(stream); 187 | } 188 | 189 | public override void Dump() 190 | { 191 | Console.WriteLine("PrimaryNameServer: {0}", this.PrimaryNameServer); 192 | Console.WriteLine("ResponsibleAuthoritativeMailbox: {0}", this.ResponsibleAuthoritativeMailbox); 193 | Console.WriteLine("Serial: {0}", this.Serial); 194 | Console.WriteLine("RefreshInterval: {0}", this.RefreshInterval); 195 | Console.WriteLine("RetryInterval: {0}", this.RetryInterval); 196 | Console.WriteLine("ExpirationLimit: {0}", this.ExpirationLimit); 197 | Console.WriteLine("MinimumTTL: {0}", this.MinimumTTL); 198 | } 199 | } 200 | 201 | } -------------------------------------------------------------------------------- /Dns/ResourceClass.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | public enum ResourceClass : ushort 10 | { 11 | None = 0, 12 | IN = 1, 13 | CS = 2, 14 | CH = 3, 15 | HS = 4 16 | } 17 | } -------------------------------------------------------------------------------- /Dns/ResourceList.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | 13 | public class ResourceList : List 14 | { 15 | public int LoadFrom(byte[] bytes, int offset, ushort count) 16 | { 17 | int currentOffset = offset; 18 | 19 | for (int index = 0; index < count; index++) 20 | { 21 | // TODO: move this code into the Resource object 22 | 23 | ResourceRecord resourceRecord = new ResourceRecord(); 24 | //// extract the domain, question type, question class and Ttl 25 | 26 | resourceRecord.Name = DnsProtocol.ReadString(bytes, ref currentOffset); 27 | 28 | resourceRecord.Type = (ResourceType) (BitConverter.ToUInt16(bytes, currentOffset).SwapEndian()); 29 | currentOffset += sizeof (ushort); 30 | 31 | resourceRecord.Class = (ResourceClass) (BitConverter.ToUInt16(bytes, currentOffset).SwapEndian()); 32 | currentOffset += sizeof (ushort); 33 | 34 | resourceRecord.TTL = BitConverter.ToUInt32(bytes, currentOffset).SwapEndian(); 35 | currentOffset += sizeof (uint); 36 | 37 | resourceRecord.DataLength = BitConverter.ToUInt16(bytes, currentOffset).SwapEndian(); 38 | currentOffset += sizeof (ushort); 39 | 40 | if (resourceRecord.Class == ResourceClass.IN && resourceRecord.Type == ResourceType.A) 41 | { 42 | resourceRecord.RData = ANameRData.Parse(bytes, currentOffset, resourceRecord.DataLength); 43 | } 44 | else if (resourceRecord.Type == ResourceType.CNAME) 45 | { 46 | resourceRecord.RData = CNameRData.Parse(bytes, currentOffset, resourceRecord.DataLength); 47 | } 48 | 49 | // move past resource data record 50 | currentOffset = currentOffset + resourceRecord.DataLength; 51 | 52 | this.Add(resourceRecord); 53 | } 54 | 55 | int bytesRead = currentOffset - offset; 56 | return bytesRead; 57 | } 58 | 59 | public void WriteToStream(Stream stream) 60 | { 61 | foreach (var resource in this) 62 | { 63 | resource.WriteToStream(stream); 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Dns/ResourceRecord.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.IO; 11 | 12 | public class ResourceRecord 13 | { 14 | public string Name { get; set; } 15 | public uint TTL { get; set; } 16 | public ResourceClass Class { get; set; } 17 | public ResourceType Type { get; set; } 18 | public RData RData { get; set;} 19 | public ushort DataLength { get; set; } 20 | 21 | /// Serialize resource to stream according to RFC1034 format 22 | /// 23 | public void WriteToStream(Stream stream) 24 | { 25 | this.Name.WriteToStream(stream); 26 | ((ushort)(this.Type)).SwapEndian().WriteToStream(stream); 27 | ((ushort)(this.Class)).SwapEndian().WriteToStream(stream); 28 | this.TTL.SwapEndian().WriteToStream(stream); 29 | 30 | if(this.RData != null) 31 | { 32 | this.RData.Length.SwapEndian().WriteToStream(stream); 33 | this.RData.WriteToStream(stream); 34 | } 35 | else 36 | { 37 | // no RDATA write (ushort) DataLength=0 38 | stream.WriteByte(0x00); 39 | stream.WriteByte(0x00); 40 | } 41 | } 42 | 43 | public void Dump() 44 | { 45 | Console.WriteLine("ResourceName: {0}", this.Name); 46 | Console.WriteLine("ResourceType: {0}", this.Type); 47 | Console.WriteLine("ResourceClass: {0}", this.Class); 48 | Console.WriteLine("TimeToLive: {0}", this.TTL); 49 | Console.WriteLine("DataLength: {0}", this.DataLength); 50 | 51 | if (this.RData != null) 52 | { 53 | this.RData.Dump(); 54 | } 55 | } 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /Dns/ResourceType.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | public enum ResourceType : ushort 10 | { 11 | A = 0x0001, 12 | NS = 0x0002, 13 | MD = 0x0003, 14 | MF = 0x0004, 15 | CNAME = 0x0005, 16 | SOA = 0x0006, 17 | MB = 0x0007, 18 | MG = 0x0008, 19 | MR = 0x0009, 20 | NULL = 0x000a, 21 | WKS = 0x000b, 22 | PTR = 0x000c, 23 | HINFO = 0x000d, 24 | MINFO = 0x000e, 25 | MX = 0x000f, 26 | TEXT = 0x0010, 27 | RP = 0x0011, 28 | AFSDB = 0x0012, 29 | X25 = 0x0013, 30 | ISDN = 0x0014, 31 | RT = 0x0015, 32 | NSAP = 0x0016, 33 | NSAPPTR = 0x0017, 34 | SIG = 0x0018, 35 | KEY = 0x0019, 36 | PX = 0x001a, 37 | GPOS = 0x001b, 38 | AAAA = 0x001c, 39 | LOC = 0x001d, 40 | NXT = 0x001e, 41 | EID = 0x001f, 42 | NIMLOC = 0x0020, 43 | SRV = 0x0021, 44 | ATMA = 0x0022, 45 | NAPTR = 0x0023, 46 | KX = 0x0024, 47 | CERT = 0x0025, 48 | A6 = 0x0026, 49 | DNAME = 0x0027, 50 | SINK = 0x0028, 51 | OPT = 0x0029, 52 | DS = 0x002B, 53 | RRSIG = 0x002E, 54 | NSEC = 0x002F, 55 | DNSKEY = 0x0030, 56 | DHCID = 0x0031, 57 | UINFO = 0x0064, 58 | UID = 0x0065, 59 | GID = 0x0066, 60 | UNSPEC = 0x0067, 61 | ADDRS = 0x00f8, 62 | TKEY = 0x00f9, 63 | TSIG = 0x00fa, 64 | IXFR = 0x00fb, 65 | AXFR = 0x00fc, 66 | MAILB = 0x00fd, 67 | MAILA = 0x00fe, 68 | ALL = 0x00ff, 69 | ANY = 0x00ff, 70 | WINS = 0xff01, 71 | WINSR = 0xff02, 72 | NBSTAT = WINSR 73 | } 74 | } -------------------------------------------------------------------------------- /Dns/SmartAddressDispenser.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Net; 13 | using Dns.Contracts; 14 | 15 | /// Address Dispenser enables round-robin ordering for the specified zone record 16 | public class SmartAddressDispenser : IAddressDispenser 17 | { 18 | private ulong _sequence = 0; 19 | 20 | private readonly ZoneRecord _zoneRecord; 21 | private readonly ushort _maxAddressesReturned; 22 | 23 | public SmartAddressDispenser(ZoneRecord record, ushort maxAddressesReturned = 4) 24 | { 25 | _zoneRecord = record; 26 | _maxAddressesReturned = maxAddressesReturned; 27 | } 28 | 29 | string IAddressDispenser.HostName 30 | { 31 | get { return this._zoneRecord.Host; } 32 | } 33 | 34 | /// Returns round-robin rotated set of IP addresses 35 | /// Set of IP Addresses 36 | public IEnumerable GetAddresses() 37 | { 38 | IPAddress[] addresses = _zoneRecord.Addresses; 39 | 40 | if(addresses.Length == 0) 41 | { 42 | yield break; 43 | } 44 | 45 | // starting position in rollover list 46 | int start = (int) (_sequence % (ulong) addresses.Length); 47 | int offset = start; 48 | 49 | uint count = 0; 50 | while (true) 51 | { 52 | 53 | yield return addresses[offset]; 54 | offset++; 55 | 56 | // rollover to start of list 57 | if (offset == addresses.Length) 58 | { 59 | offset = 0; 60 | } 61 | 62 | // if back at starting position then exit 63 | if (offset == start) 64 | { 65 | break; 66 | } 67 | 68 | // manage max number of dns entries returned 69 | count++; 70 | if (count == _maxAddressesReturned) 71 | { 72 | break; 73 | } 74 | } 75 | // advance sequence 76 | _sequence++; 77 | } 78 | 79 | public void DumpHtml(TextWriter writer) 80 | { 81 | writer.WriteLine("Sequence:{0}", this._sequence); 82 | foreach (var address in this._zoneRecord.Addresses) 83 | { 84 | writer.WriteLine(address); 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Dns/SmartZoneResolver.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Linq; 13 | using System.Net; 14 | using System.Threading; 15 | using Dns.Contracts; 16 | 17 | public class SmartZoneResolver : IObserver, IDnsResolver 18 | { 19 | private long _hits; 20 | private long _misses; 21 | private long _queries; 22 | private IDisposable _subscription; 23 | private Zone _zone; 24 | private Dictionary _zoneMap; 25 | private DateTime _zoneReload = DateTime.MinValue; 26 | 27 | public string GetZoneName() 28 | { 29 | return this.Zone.Suffix; 30 | } 31 | 32 | public uint GetZoneSerial() 33 | { 34 | return this._zone.Serial; 35 | } 36 | 37 | public Zone Zone 38 | { 39 | get { return this._zone; } 40 | set 41 | { 42 | if(value == null) throw new ArgumentNullException("value"); 43 | 44 | this._zone = value; 45 | this._zoneReload = DateTime.Now; 46 | this._zoneMap = this._zone.ToDictionary(GenerateKey, zoneRecord => new SmartAddressDispenser(zoneRecord) as IAddressDispenser, StringComparer.CurrentCultureIgnoreCase); 47 | Console.WriteLine("Zone reloaded"); 48 | } 49 | } 50 | 51 | public DateTime LastZoneReload 52 | { 53 | get { return _zoneReload; } 54 | } 55 | 56 | void IObserver.OnCompleted() 57 | { 58 | throw new NotImplementedException(); 59 | } 60 | 61 | void IObserver.OnError(Exception error) 62 | { 63 | throw new NotImplementedException(); 64 | } 65 | 66 | void IObserver.OnNext(Zone value) 67 | { 68 | this.Zone = value; 69 | } 70 | 71 | public void DumpHtml(TextWriter writer) 72 | { 73 | writer.WriteLine("Type:{0}
", this.GetType().Name); 74 | writer.WriteLine("Queries:{0}
", this._queries); 75 | writer.WriteLine("Hits:{0}
", this._hits); 76 | writer.WriteLine("Misses:{0}
", this._misses); 77 | 78 | writer.WriteLine(""); 79 | writer.WriteLine(""); 80 | foreach (string key in _zoneMap.Keys) 81 | { 82 | writer.WriteLine(""); 87 | } 88 | writer.WriteLine("
KeyValue
"); 83 | writer.WriteLine(key); 84 | writer.WriteLine(""); 85 | _zoneMap[key].DumpHtml(writer); 86 | writer.WriteLine("
"); 89 | } 90 | 91 | public bool TryGetHostEntry(string hostName, ResourceClass resClass, ResourceType resType, out IPHostEntry entry) 92 | { 93 | if (hostName == null) throw new ArgumentNullException("hostName"); 94 | if (hostName.Length > 126) throw new ArgumentOutOfRangeException("hostName"); 95 | 96 | entry = null; 97 | 98 | Interlocked.Increment(ref this._queries); 99 | 100 | // fail fasts 101 | if (!this.IsZoneLoaded()) return false; 102 | if (!hostName.EndsWith(this._zone.Suffix)) return false; 103 | 104 | // lookup locally 105 | string key = GenerateKey(hostName, resClass, resType); 106 | IAddressDispenser dispenser; 107 | if (_zoneMap.TryGetValue(key, out dispenser)) 108 | { 109 | Interlocked.Increment(ref this._hits); 110 | entry = new IPHostEntry {AddressList = dispenser.GetAddresses().ToArray(), Aliases = new string[] {}, HostName = hostName}; 111 | return true; 112 | } 113 | 114 | Interlocked.Increment(ref this._misses); 115 | return false; 116 | } 117 | 118 | public bool IsZoneLoaded() 119 | { 120 | return _zone != null; 121 | } 122 | 123 | /// Subscribe to specified zone provider 124 | /// 125 | public void SubscribeTo(IObservable zoneProvider) 126 | { 127 | // release previous subscription 128 | if (this._subscription != null) 129 | { 130 | this._subscription.Dispose(); 131 | this._subscription = null; 132 | } 133 | 134 | this._subscription = zoneProvider.Subscribe(this); 135 | } 136 | 137 | private string GenerateKey(ZoneRecord record) 138 | { 139 | return GenerateKey(record.Host, record.Class, record.Type); 140 | } 141 | 142 | private string GenerateKey(string host, ResourceClass resClass, ResourceType resType) 143 | { 144 | return string.Format("{0}|{1}|{2}", host, resClass, resType); 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /Dns/SocketExtensions.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System.Net.Sockets; 10 | 11 | public static class SocketExtensions 12 | { 13 | public static SocketAwaitable ReceiveFromAsync(this Socket socket, SocketAwaitable awaitable) 14 | { 15 | awaitable.Reset(); 16 | if (!socket.ReceiveFromAsync(awaitable.m_eventArgs)) 17 | { 18 | awaitable.m_wasCompleted = true; 19 | } 20 | return awaitable; 21 | } 22 | 23 | public static SocketAwaitable SendToAsync(this Socket socket, SocketAwaitable awaitable) 24 | { 25 | awaitable.Reset(); 26 | if (!socket.SendToAsync(awaitable.m_eventArgs)) 27 | { 28 | awaitable.m_wasCompleted = true; 29 | } 30 | return awaitable; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Dns/UdpListener.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System; 10 | using System.Net; 11 | using System.Net.Sockets; 12 | using System.Runtime.CompilerServices; 13 | using System.Threading; 14 | using System.Threading.Tasks; 15 | 16 | public delegate void OnRequestHandler(byte[] buffer, EndPoint remoteEndPoint); 17 | 18 | public class UdpListener 19 | { 20 | public OnRequestHandler OnRequest; 21 | private Socket _listener; 22 | 23 | public void Initialize(ushort port = 53) 24 | { 25 | _listener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 26 | IPEndPoint ep = new IPEndPoint(IPAddress.Any, port); 27 | _listener.Bind(ep); 28 | } 29 | 30 | public async void Start() 31 | { 32 | while (true) 33 | { 34 | try 35 | { 36 | // Reusable SocketAsyncEventArgs and awaitable wrapper 37 | SocketAsyncEventArgs args = new SocketAsyncEventArgs(); 38 | args.SetBuffer(new byte[0x1000], 0, 0x1000); 39 | args.RemoteEndPoint = _listener.LocalEndPoint; 40 | SocketAwaitable awaitable = new SocketAwaitable(args); 41 | 42 | // Do processing, continually receiving from the socket 43 | while (true) 44 | { 45 | await _listener.ReceiveFromAsync(awaitable); 46 | int bytesRead = args.BytesTransferred; 47 | if (bytesRead <= 0) 48 | { 49 | break; 50 | } 51 | 52 | if (OnRequest != null) 53 | { 54 | var buffer = new byte[bytesRead]; 55 | Buffer.BlockCopy(args.Buffer, 0, buffer, 0, buffer.Length); 56 | var process = Task.Run(() => OnRequest(buffer, args.RemoteEndPoint)); 57 | } 58 | else 59 | { 60 | // defaults to console dump if no listener is bound 61 | var dump = Task.Run(() => ProcessReceiveFrom(args)); 62 | } 63 | } 64 | } 65 | catch (Exception ex) 66 | { 67 | Console.WriteLine(ex.ToString()); 68 | } 69 | // listener restarts if an exception occurs 70 | } 71 | } 72 | 73 | public void Stop() 74 | { 75 | _listener.Close(); 76 | } 77 | 78 | public async void SendToAsync(SocketAsyncEventArgs args) 79 | { 80 | SocketAwaitable awaitable = new SocketAwaitable(args); 81 | await _listener.SendToAsync(awaitable); 82 | } 83 | 84 | public void ProcessReceiveFrom(SocketAsyncEventArgs args) 85 | { 86 | Console.WriteLine(args.RemoteEndPoint.ToString()); 87 | Console.WriteLine(args.BytesTransferred); 88 | } 89 | } 90 | 91 | /// IO completion based socket await object 92 | public sealed class SocketAwaitable : INotifyCompletion 93 | { 94 | private static readonly Action SENTINEL = () => { }; 95 | 96 | internal Action m_continuation; 97 | internal SocketAsyncEventArgs m_eventArgs; 98 | internal bool m_wasCompleted; 99 | 100 | public SocketAwaitable(SocketAsyncEventArgs eventArgs) 101 | { 102 | if (eventArgs == null) 103 | { 104 | throw new ArgumentNullException("eventArgs"); 105 | } 106 | m_eventArgs = eventArgs; 107 | eventArgs.Completed += delegate 108 | { 109 | Action prev = m_continuation ?? Interlocked.CompareExchange(ref m_continuation, SENTINEL, null); 110 | if (prev != null) 111 | { 112 | prev(); 113 | } 114 | }; 115 | } 116 | 117 | public bool IsCompleted 118 | { 119 | get { return m_wasCompleted; } 120 | } 121 | 122 | public void OnCompleted(Action continuation) 123 | { 124 | if (m_continuation == SENTINEL || Interlocked.CompareExchange(ref m_continuation, continuation, null) == SENTINEL) 125 | { 126 | Task.Run(continuation); 127 | } 128 | } 129 | 130 | internal void Reset() 131 | { 132 | m_wasCompleted = false; 133 | m_continuation = null; 134 | } 135 | 136 | public SocketAwaitable GetAwaiter() 137 | { 138 | return this; 139 | } 140 | 141 | public void GetResult() 142 | { 143 | if (m_eventArgs.SocketError != SocketError.Success) 144 | { 145 | throw new SocketException((int) m_eventArgs.SocketError); 146 | } 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /Dns/Utility/BitPacker.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns.Utility 8 | { 9 | using System; 10 | 11 | public class BitPacker 12 | { 13 | private readonly byte[] _buffer; 14 | private int _bitOffset = 0; 15 | private readonly int[] _mask = new[] {1, 2, 4, 8, 16, 32, 64, 128}; 16 | 17 | public BitPacker(byte[] buffer) 18 | { 19 | this._buffer = buffer; 20 | } 21 | 22 | /// 23 | /// 24 | public byte GetByte(int count) 25 | { 26 | if (count > 8) throw new ArgumentOutOfRangeException("count"); 27 | 28 | int bit = this._bitOffset; 29 | 30 | if (((bit + count) / 8) > this._buffer.Length) throw new ArgumentOutOfRangeException("count"); 31 | 32 | int byteNumber; 33 | ushort bitOffset; 34 | 35 | this.GenerateInitialOffset(out byteNumber, out bitOffset); 36 | 37 | ushort span = BitConverter.ToUInt16(this._buffer, byteNumber); 38 | 39 | int mask = GetMask(count, bitOffset); 40 | int value = (span & mask) >> bitOffset; 41 | 42 | this._bitOffset += count; 43 | return (byte) value; 44 | } 45 | 46 | public enum Endian 47 | { 48 | HiLo, 49 | LoHi 50 | } 51 | 52 | public ushort GetUshort(int count = 16, Endian endian = Endian.LoHi) 53 | { 54 | if (count > 16) throw new ArgumentOutOfRangeException("count"); 55 | 56 | int bit = this._bitOffset; 57 | 58 | if (((bit + count) / 8) > this._buffer.Length) throw new ArgumentOutOfRangeException("count"); 59 | 60 | int byteNumber; 61 | ushort bitOffset; 62 | 63 | this.GenerateInitialOffset(out byteNumber, out bitOffset); 64 | 65 | uint span; 66 | if (byteNumber + 4 <= this._buffer.Length) 67 | { 68 | span = BitConverter.ToUInt32(this._buffer, byteNumber); 69 | } 70 | else 71 | { 72 | // buffer too small - clone bytes into an empty buffer 73 | byte[] copy = new byte[8]; 74 | Array.Copy(this._buffer, byteNumber, copy, byteNumber, this._buffer.Length - byteNumber); 75 | span = BitConverter.ToUInt32(copy, byteNumber); 76 | } 77 | 78 | int mask = GetMask(count, bitOffset); 79 | ushort value = (ushort) ((span & mask) >> bitOffset); 80 | 81 | if (endian == Endian.HiLo) 82 | { 83 | SwapEndian(ref value); 84 | } 85 | 86 | this._bitOffset += count; 87 | return value; 88 | } 89 | 90 | private static int GetMask(int count, ushort bitOffset) 91 | { 92 | int mask = 0; 93 | for (int index = bitOffset; index < bitOffset + count; index++) 94 | { 95 | mask = mask + (int) Math.Pow(2, index); 96 | } 97 | return mask; 98 | } 99 | 100 | public bool GetBoolean() 101 | { 102 | int byteNumber; 103 | ushort bitOffset; 104 | 105 | this.GenerateInitialOffset(out byteNumber, out bitOffset); 106 | int bitMask = this._mask[bitOffset]; 107 | 108 | bool value = (this._buffer[byteNumber] & bitMask) > 0; 109 | 110 | this._bitOffset++; 111 | return value; 112 | } 113 | 114 | public void Reset() 115 | { 116 | this._bitOffset = 0; 117 | } 118 | 119 | public int Write(byte value, uint count) 120 | { 121 | 122 | // generate bit 123 | return 0; 124 | } 125 | 126 | private void GenerateInitialOffset(out int index, out ushort offset) 127 | { 128 | index = this._bitOffset == 0 ? 0 : (this._bitOffset / 8); 129 | offset = (ushort)(this._bitOffset - (index * 8)); 130 | } 131 | 132 | public static void SwapEndian(ref ushort val) 133 | { 134 | val = (ushort)((val << 8) | (val >> 8)); 135 | } 136 | 137 | public static void SwapEndian(ref uint val) 138 | { 139 | val = (val<<24) | ((val<<8) & 0x00ff0000) | ((val>>8) & 0x0000ff00) | (val>>24); 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /Dns/Utility/CsvParser.cs: -------------------------------------------------------------------------------- 1 | // //------------------------------------------------------------------------------------------------- 2 | // // 3 | // // Copyright (c) Steve Butler. All rights reserved. 4 | // // 5 | // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns.Utility 8 | { 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Linq; 13 | 14 | /// Parses CSV files 15 | public class CsvParser 16 | { 17 | private static readonly char[] CSVDELIMITER = new[] {','}; 18 | private static readonly char[] COLONDELIMITER = new[] {':'}; 19 | 20 | private readonly string _filePath; 21 | 22 | private string _currentLine; 23 | private string[] _fields; 24 | 25 | private CsvParser() 26 | { 27 | } 28 | 29 | private CsvParser(string filePath) 30 | { 31 | this._filePath = filePath; 32 | } 33 | 34 | /// List of fields detected in CSV file 35 | public IEnumerable Fields 36 | { 37 | get { return this._fields; } 38 | } 39 | 40 | /// 41 | /// Returns enumerable collection of rows 42 | /// 43 | public IEnumerable Rows 44 | { 45 | get 46 | { 47 | using (FileStream stream = new FileStream(this._filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) 48 | using (StreamReader csvReader = new StreamReader(stream)) 49 | while (true) 50 | { 51 | if (csvReader.Peek() < 0) 52 | { 53 | yield break; 54 | } 55 | 56 | this._currentLine = csvReader.ReadLine(); 57 | if (this._currentLine == null) 58 | { 59 | yield break; 60 | } 61 | if(this._currentLine.Trim() == string.Empty) 62 | { 63 | continue; 64 | } 65 | if ("#;".Contains(this._currentLine[0])) 66 | { 67 | // is a comment 68 | if (this._currentLine.Length > 1 && this._currentLine.Substring(1).StartsWith("Fields")) 69 | { 70 | string[] fieldDeclaration = this._currentLine.Split(COLONDELIMITER); 71 | if (fieldDeclaration.Length != 2) 72 | { 73 | this._fields = null; 74 | } 75 | else 76 | { 77 | this._fields = fieldDeclaration[1].Trim().Split(CSVDELIMITER); 78 | } 79 | } 80 | } 81 | else 82 | { 83 | yield return new CsvRow(this._fields, this._currentLine.Split(CSVDELIMITER)); 84 | } 85 | } 86 | } 87 | } 88 | 89 | /// 90 | /// Create instance of CSV Parser 91 | /// 92 | /// Path of file to parse 93 | /// CSV Parser instance 94 | public static CsvParser Create(string filePath) 95 | { 96 | if (filePath == null) 97 | { 98 | throw new ArgumentNullException("filePath"); 99 | } 100 | 101 | if (!File.Exists(filePath)) 102 | { 103 | throw new FileNotFoundException("File Not Found", filePath); 104 | } 105 | 106 | CsvParser result = new CsvParser(filePath); 107 | return result; 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /Dns/Utility/CsvRow.cs: -------------------------------------------------------------------------------- 1 | // //------------------------------------------------------------------------------------------------- 2 | // // 3 | // // Copyright (c) Steve Butler. All rights reserved. 4 | // // 5 | // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns.Utility 8 | { 9 | using System.Collections.Generic; 10 | 11 | /// Represents row in comma separated value file 12 | public class CsvRow 13 | { 14 | private readonly string[] _fieldValues; 15 | private readonly Dictionary _fieldsByName = new Dictionary(); 16 | 17 | internal CsvRow(string[] fields, string[] fieldValues) 18 | { 19 | this._fieldValues = fieldValues; 20 | if ((fields != null) && (fields.Length == fieldValues.Length)) 21 | { 22 | 23 | for(int index = 0; index < fields.Length; index++) 24 | { 25 | this._fieldsByName[fields[index]] = fieldValues[index]; 26 | } 27 | } 28 | } 29 | 30 | /// Returns value for specified field ordinal 31 | /// Specifed field ordinal 32 | /// Value of field 33 | public string this[int index] 34 | { 35 | get { return this._fieldValues[index]; } 36 | } 37 | 38 | /// Returns value for specified field name 39 | /// Specified field name 40 | /// Value of field 41 | public string this[string name] 42 | { 43 | get 44 | { 45 | string fieldValue; 46 | if (this._fieldsByName.TryGetValue(name, out fieldValue)) 47 | { 48 | return fieldValue; 49 | } 50 | return null; 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Dns/Zone.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System.Collections.Generic; 10 | 11 | public class Zone : List 12 | { 13 | public string Suffix { get; set; } 14 | 15 | public uint Serial { get; set; } 16 | 17 | public void Initialize(IEnumerable nameRecords) 18 | { 19 | this.Clear(); 20 | this.AddRange(nameRecords); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Dns/ZoneProvider/AP/APZoneProvider.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns.ZoneProvider.AP 8 | { 9 | using System.IO; 10 | using System.Net; 11 | using System.Linq; 12 | using Dns.Utility; 13 | using Dns.ZoneProvider; 14 | 15 | using Microsoft.Extensions.Configuration; 16 | 17 | /// Source of Zone records 18 | public class APZoneProvider : FileWatcherZoneProvider 19 | { 20 | 21 | public override Zone GenerateZone() 22 | { 23 | if (!File.Exists(this.Filename)) 24 | { 25 | return null; 26 | } 27 | 28 | CsvParser parser = CsvParser.Create(this.Filename); 29 | var machines = parser.Rows.Select(row => new {MachineFunction = row["MachineFunction"], StaticIP = row["StaticIP"], MachineName = row["MachineName"]}).ToArray(); 30 | 31 | var zoneRecords = machines 32 | .GroupBy(machine => machine.MachineFunction + this.Zone, machine => IPAddress.Parse(machine.StaticIP)) 33 | .Select(group => new ZoneRecord {Host = group.Key, Count = group.Count(), Addresses = group.Select(address => address).ToArray()}) 34 | .ToArray(); 35 | 36 | Zone zone = new Zone(); 37 | zone.Suffix = this.Zone; 38 | zone.Serial = this._serial; 39 | zone.Initialize(zoneRecords); 40 | 41 | // increment serial number 42 | this._serial++; 43 | return zone; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Dns/ZoneProvider/BaseZoneProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Dns.ZoneProvider 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | using System.Threading; 7 | 8 | using Microsoft.Extensions.Configuration; 9 | 10 | public abstract class BaseZoneProvider : IObservable, IDisposable 11 | { 12 | internal uint _serial = 0; 13 | private string _zone; 14 | public string Zone 15 | { 16 | get { return _zone; } 17 | protected set { _zone = value; } 18 | } 19 | 20 | public abstract void Initialize(IConfiguration config, string zoneName); 21 | 22 | private readonly List> _observers = new List>(); 23 | 24 | public IDisposable Subscribe(IObserver observer) 25 | { 26 | this._observers.Add(observer); 27 | return new Subscription(this, observer); 28 | } 29 | 30 | private void Unsubscribe(IObserver observer) 31 | { 32 | this._observers.Remove(observer); 33 | } 34 | 35 | public abstract void Dispose(); 36 | 37 | /// Subscription memento for IObservable interface 38 | public class Subscription : IDisposable 39 | { 40 | private readonly IObserver _observer; 41 | private readonly BaseZoneProvider _provider; 42 | 43 | public Subscription(BaseZoneProvider provider, IObserver observer) 44 | { 45 | this._provider = provider; 46 | this._observer = observer; 47 | } 48 | 49 | void IDisposable.Dispose() 50 | { 51 | this._provider.Unsubscribe(this._observer); 52 | } 53 | } 54 | 55 | /// Publish zone to all subscribers 56 | /// 57 | public void Notify(Zone zone) 58 | { 59 | int remainingRetries = 3; 60 | 61 | while (remainingRetries > 0) 62 | { 63 | ParallelLoopResult result = Parallel.ForEach(this._observers, observer => observer.OnNext(zone)); 64 | if (result.IsCompleted) 65 | { 66 | break; 67 | } 68 | remainingRetries--; 69 | } 70 | } 71 | 72 | public abstract void Start(CancellationToken ct); 73 | 74 | } 75 | } -------------------------------------------------------------------------------- /Dns/ZoneProvider/Bind/BindZoneProvider.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns.ZoneProvider.Bind 8 | { 9 | using System; 10 | using Dns.ZoneProvider; 11 | 12 | public class BindZoneProvider : FileWatcherZoneProvider 13 | { 14 | public override Zone GenerateZone() 15 | { 16 | // RFC 1035 - https://tools.ietf.org/html/rfc1035 17 | // Forward scanning parser 18 | // while(not EOF) 19 | // State is in record 20 | // General Field list : Name Class Type [(Data 0..*)] EOR 21 | // $ORIGIN [name] 22 | // $TTL Timespan 23 | // [Name|@] IN SOA Name 24 | // [Name|@] IN NS Name 25 | // [Name|@] IN MX Priority Name 26 | // [Name|@] IN A IPv4 27 | // [Name|@] IN AAAA IPv6 28 | // [Name|@] IN CNAME name 29 | // endwhile 30 | 31 | throw new NotImplementedException(); 32 | } 33 | 34 | public override void Dispose() 35 | { 36 | throw new NotImplementedException(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Dns/ZoneProvider/FileWatcherProviderOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Dns.ZoneProvider 3 | { 4 | public class FileWatcherZoneProviderOptions 5 | { 6 | public string FileName { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Dns/ZoneProvider/FileWatcherZoneProvider.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns.ZoneProvider 8 | { 9 | using System; 10 | using System.IO; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | using Microsoft.Extensions.Configuration; 14 | 15 | public abstract class FileWatcherZoneProvider : BaseZoneProvider 16 | { 17 | public delegate void FileWatcherDelegate(object sender, FileSystemEventArgs e); 18 | 19 | public event FileWatcherDelegate OnCreated = delegate { }; 20 | public event FileWatcherDelegate OnDeleted = delegate { }; 21 | public event FileWatcherDelegate OnRenamed = delegate { }; 22 | public event FileWatcherDelegate OnChanged = delegate { }; 23 | public event FileWatcherDelegate OnSettlement = delegate {}; 24 | 25 | private FileSystemWatcher _fileWatcher; 26 | private TimeSpan _settlement = TimeSpan.FromSeconds(10); 27 | private Timer _timer; 28 | 29 | public abstract Zone GenerateZone(); 30 | 31 | /// Timespan between last file change and zone generation 32 | public TimeSpan FileSettlementPeriod 33 | { 34 | get { return this._settlement; } 35 | set { this._settlement = value; } 36 | } 37 | 38 | public string Filename { get; private set; } 39 | 40 | public override void Initialize(IConfiguration config, string zoneName) 41 | { 42 | var filewatcherConfig = config.Get(); 43 | 44 | var filename = filewatcherConfig.FileName; 45 | 46 | if (string.IsNullOrWhiteSpace(filename)) 47 | { 48 | throw new ArgumentException("Null or empty", "filename"); 49 | } 50 | 51 | filename = Environment.ExpandEnvironmentVariables(filename); 52 | filename = Path.GetFullPath(filename); 53 | 54 | if (!File.Exists(filename)) 55 | { 56 | throw new FileNotFoundException("filename not found", filename); 57 | } 58 | 59 | 60 | string directory = Path.GetDirectoryName(filename); 61 | string fileNameFilter = Path.GetFileName(filename); 62 | 63 | this.Filename = filename; 64 | this._fileWatcher = new FileSystemWatcher(directory, fileNameFilter); 65 | 66 | this._fileWatcher.Created += (s, e) => this.OnCreated(s, e); 67 | this._fileWatcher.Changed += (s, e) => this.OnChanged(s, e); 68 | this._fileWatcher.Renamed += (s, e) => this.OnRenamed(s, e); 69 | this._fileWatcher.Deleted += (s, e) => this.OnDeleted(s, e); 70 | 71 | this._timer = new Timer(this.OnTimer); 72 | 73 | this._fileWatcher.Created += this.FileChange; 74 | this._fileWatcher.Changed += this.FileChange; 75 | this._fileWatcher.Renamed += this.FileChange; 76 | this._fileWatcher.Deleted += this.FileChange; 77 | 78 | this.Zone = zoneName; 79 | } 80 | 81 | /// Start watching and generating zone files 82 | public override void Start(CancellationToken ct) 83 | { 84 | ct.Register(this.Stop); 85 | 86 | // fire first zone generation event on startup 87 | this._timer.Change(TimeSpan.FromSeconds(3), Timeout.InfiniteTimeSpan); 88 | this._fileWatcher.EnableRaisingEvents = true; 89 | } 90 | 91 | /// Handler for any file changes 92 | /// 93 | /// 94 | private void FileChange(object sender, FileSystemEventArgs e) 95 | { 96 | this._timer.Change(this._settlement, Timeout.InfiniteTimeSpan); 97 | } 98 | 99 | /// Stop watching 100 | private void Stop() 101 | { 102 | this._fileWatcher.EnableRaisingEvents = false; 103 | } 104 | 105 | /// Handler for settlement completion 106 | /// 107 | private void OnTimer(object state) 108 | { 109 | this._timer.Change(Timeout.Infinite, Timeout.Infinite); 110 | Task.Run(() => this.GenerateZone()).ContinueWith(t => this.Notify(t.Result)); 111 | } 112 | 113 | 114 | public override void Dispose() 115 | { 116 | if (this._fileWatcher != null) 117 | { 118 | this._fileWatcher.EnableRaisingEvents = false; 119 | this._fileWatcher.Dispose(); 120 | } 121 | 122 | if (this._timer != null) 123 | { 124 | this._timer.Change(Timeout.Infinite, Timeout.Infinite); 125 | this._timer.Dispose(); 126 | } 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /Dns/ZoneProvider/IPProbe/Host.cs: -------------------------------------------------------------------------------- 1 | namespace Dns.ZoneProvider.IPProbe 2 | { 3 | using System.Collections.Generic; 4 | 5 | internal class Host 6 | { 7 | internal string Name { get; set; } 8 | internal AvailabilityMode AvailabilityMode { get; set; } 9 | internal List AddressProbes = new List(); 10 | } 11 | } -------------------------------------------------------------------------------- /Dns/ZoneProvider/IPProbe/IPProbeProviderOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Dns.ZoneProvider.IPProbe 2 | { 3 | public class IPProbeProviderOptions 4 | { 5 | public ushort PollingIntervalSeconds { get; set; } 6 | public HostOptions[] Hosts { get; set; } 7 | } 8 | 9 | public class HostOptions 10 | { 11 | /// Host name 12 | public string Name { get; set; } 13 | 14 | /// Probe strategy 15 | public string Probe { get; set; } 16 | 17 | /// Host probe timeout 18 | public ushort Timeout { get; set; } 19 | 20 | public AvailabilityMode AvailabilityMode { get; set; } 21 | 22 | public string[] Ip { get; set; } 23 | } 24 | 25 | public enum AvailabilityMode 26 | { 27 | All, 28 | First, 29 | } 30 | } -------------------------------------------------------------------------------- /Dns/ZoneProvider/IPProbe/IPProbeZoneProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Dns.ZoneProvider.IPProbe 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using System.Net; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using Microsoft.Extensions.Configuration; 10 | 11 | 12 | /// 13 | /// IPProbeZoneProvider map via configuration a set of monitored IPs to host A records. 14 | /// Various monitoring strategies are implemented to detect IP health. 15 | /// Health IP addresses are added to the Zone. 16 | /// 17 | public class IPProbeZoneProvider : BaseZoneProvider 18 | { 19 | private IPProbeProviderOptions options; 20 | 21 | private State state { get; set; } 22 | private CancellationToken ct { get; set; } 23 | private Task runningTask { get; set; } 24 | 25 | /// Initialize ZoneProvider 26 | /// ZoneProvider Configuration Section 27 | /// Zone suffix 28 | public override void Initialize(IConfiguration config, string zoneName) 29 | { 30 | this.options = config.Get(); 31 | if (options == null) 32 | { 33 | throw new Exception("Error loading IPProbeProviderOptions"); 34 | } 35 | 36 | // load up initial state from options 37 | this.state = new State(options); 38 | this.Zone = zoneName; 39 | 40 | return; 41 | } 42 | 43 | public void ProbeLoop(CancellationToken ct) 44 | { 45 | Console.WriteLine("Probe loop started"); 46 | 47 | ParallelOptions options = new ParallelOptions(); 48 | options.CancellationToken = ct; 49 | options.MaxDegreeOfParallelism = 4; 50 | 51 | while (!ct.IsCancellationRequested) 52 | { 53 | var batchStartTime = DateTime.UtcNow; 54 | 55 | Parallel.ForEach(this.state.Targets, options, (probe) => 56 | { 57 | var startTime = DateTime.UtcNow; 58 | var result = probe.ProbeFunction(probe.Address, probe.TimeoutMilliseconds); 59 | var duration = DateTime.UtcNow - startTime; 60 | probe.AddResult(new ProbeResult { StartTime = startTime, Duration = duration, Available = result }); 61 | }); 62 | 63 | Task.Run(() => this.GetZone(state)).ContinueWith(t => this.Notify(t.Result)); 64 | 65 | var batchDuration = DateTime.UtcNow - batchStartTime; 66 | Console.WriteLine("Probe batch duration {0}", batchDuration); 67 | 68 | // wait remainder of Polling Interval 69 | var remainingWaitTimeout = (this.options.PollingIntervalSeconds * 1000) -(int)batchDuration.TotalMilliseconds; 70 | if(remainingWaitTimeout > 0) 71 | { 72 | ct.WaitHandle.WaitOne(remainingWaitTimeout); 73 | } 74 | } 75 | } 76 | 77 | public override void Dispose() 78 | { 79 | // cleanup 80 | } 81 | 82 | public override void Start(CancellationToken ct) 83 | { 84 | ct.Register(this.Stop); 85 | this.runningTask = Task.Run(()=>ProbeLoop(ct)); 86 | } 87 | 88 | private void Stop() 89 | { 90 | this.runningTask.Wait(); 91 | } 92 | 93 | internal IEnumerableGetZoneRecords(State state) 94 | { 95 | foreach(var host in state.Hosts) 96 | { 97 | var availableAddresses = host.AddressProbes 98 | .Where(addr => addr.IsAvailable) 99 | .Select(addr => addr.Address); 100 | 101 | if(host.AvailabilityMode == AvailabilityMode.First) 102 | { 103 | availableAddresses = availableAddresses.Take(1); 104 | } 105 | 106 | // materialize query 107 | var addresses = availableAddresses.ToArray(); 108 | 109 | if (addresses.Length == 0) 110 | { 111 | // no hosts with empty recordsets 112 | continue; 113 | } 114 | 115 | 116 | yield return new ZoneRecord 117 | { 118 | Host = host.Name + this.Zone, 119 | Addresses = addresses, 120 | Count = addresses.Length, 121 | Type = ResourceType.A, 122 | Class = ResourceClass.IN, 123 | }; 124 | } 125 | } 126 | 127 | internal Zone GetZone(State state) 128 | { 129 | var zoneRecords = GetZoneRecords(state); 130 | 131 | Zone zone = new Zone(); 132 | zone.Suffix = this.Zone; 133 | zone.Serial = _serial; 134 | zone.Initialize(zoneRecords); 135 | 136 | // increment serial number 137 | _serial++; 138 | return zone; 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /Dns/ZoneProvider/IPProbe/ProbeResult.cs: -------------------------------------------------------------------------------- 1 | namespace Dns.ZoneProvider.IPProbe 2 | { 3 | using System; 4 | 5 | internal class ProbeResult 6 | { 7 | internal DateTime StartTime; 8 | internal TimeSpan Duration; 9 | internal bool Available; 10 | } 11 | } -------------------------------------------------------------------------------- /Dns/ZoneProvider/IPProbe/State.cs: -------------------------------------------------------------------------------- 1 | namespace Dns.ZoneProvider.IPProbe 2 | { 3 | using System.Net; 4 | using System.Collections.Generic; 5 | 6 | internal class State 7 | { 8 | internal HashSet Targets = new HashSet(new Target.Comparer()); 9 | internal HashSet Hosts = new HashSet(); 10 | 11 | internal State(IPProbeProviderOptions options) 12 | { 13 | foreach (var host in options.Hosts) 14 | { 15 | var hostResult = new Host(); 16 | hostResult.Name = host.Name; 17 | hostResult.AvailabilityMode = host.AvailabilityMode; 18 | 19 | foreach (var address in host.Ip) 20 | { 21 | var addressProbe = new Target 22 | { 23 | Address = IPAddress.Parse(address), 24 | ProbeFunction = Strategy.Get(host.Probe), 25 | TimeoutMilliseconds = host.Timeout, 26 | }; 27 | 28 | Target preExisting; 29 | 30 | if (this.Targets.TryGetValue(addressProbe, out preExisting)) 31 | { 32 | hostResult.AddressProbes.Add(preExisting); 33 | } 34 | else 35 | { 36 | this.Targets.Add(addressProbe); 37 | hostResult.AddressProbes.Add(addressProbe); 38 | } 39 | } 40 | 41 | 42 | this.Hosts.Add(hostResult); 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Dns/ZoneProvider/IPProbe/Strategy.cs: -------------------------------------------------------------------------------- 1 | namespace Dns.ZoneProvider.IPProbe 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Net; 6 | 7 | using System.Net.NetworkInformation; 8 | 9 | 10 | 11 | public class Strategy 12 | { 13 | 14 | public delegate bool Probe(IPAddress addr, ushort timeout); 15 | 16 | // Probe Strategy Dictionary, maps configuration to implemented functions 17 | private static Dictionary probeFunctions = new Dictionary(); 18 | 19 | static Strategy() 20 | { 21 | // New probe strategies and enhancements can be added here 22 | probeFunctions["ping"] = Strategy.Ping; 23 | probeFunctions["noop"] = Strategy.NoOp; 24 | } 25 | 26 | public static Probe Get(string name) 27 | { 28 | return probeFunctions.GetValueOrDefault(name, Strategy.NoOp); 29 | } 30 | 31 | private static bool Ping(IPAddress address, ushort timeout) 32 | { 33 | Console.WriteLine("Ping: pinging {0}", address); 34 | Ping sender = new Ping(); 35 | PingOptions options = new PingOptions(64, true); 36 | var pingReply = sender.Send(address, timeout); 37 | return (pingReply.Status == IPStatus.Success); 38 | } 39 | 40 | private static bool NoOp(IPAddress address, ushort _) 41 | { 42 | return true; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Dns/ZoneProvider/IPProbe/Target.cs: -------------------------------------------------------------------------------- 1 | namespace Dns.ZoneProvider.IPProbe 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | 7 | internal class Target 8 | { 9 | internal IPAddress Address; 10 | internal Strategy.Probe ProbeFunction; 11 | internal ushort TimeoutMilliseconds; 12 | internal List Results = new List(); 13 | 14 | public override int GetHashCode() 15 | { 16 | return string.Format("{0}|{1}|{2}", this.Address, this.ProbeFunction, this.TimeoutMilliseconds).GetHashCode(); 17 | } 18 | 19 | internal bool IsAvailable 20 | { 21 | get 22 | { 23 | // Endpoint is available up-to last 3 results were successful 24 | return this.Results.TakeLast(3).All(r => r.Available); 25 | } 26 | } 27 | 28 | internal void AddResult(ProbeResult result) 29 | { 30 | this.Results.Add(result); 31 | if (this.Results.Count > 10) 32 | { 33 | this.Results.RemoveAt(0); 34 | } 35 | } 36 | 37 | 38 | internal class Comparer : IEqualityComparer 39 | { 40 | public bool Equals(Target x, Target y) 41 | { 42 | //Check whether the objects are the same object. 43 | if (x.Equals(y)) return true; 44 | 45 | return x.GetHashCode() == y.GetHashCode(); 46 | 47 | } 48 | 49 | public int GetHashCode(Target obj) 50 | { 51 | return obj.GetHashCode(); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Dns/ZoneRecord.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace Dns 8 | { 9 | using System.Net; 10 | 11 | public class ZoneRecord 12 | { 13 | public string Host; 14 | public ResourceClass Class = ResourceClass.IN; 15 | public ResourceType Type = ResourceType.A; 16 | public IPAddress[] Addresses; 17 | public int Count; 18 | } 19 | } -------------------------------------------------------------------------------- /Dns/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "zone": { 4 | "name": ".stephbu.org", 5 | "provider": "Dns.ZoneProvider.IPProbe.IPProbeZoneProvider" 6 | }, 7 | "dnslistener": { 8 | "port": 5335 9 | }, 10 | "webserver": { 11 | "port": 8080, 12 | "enabled": true 13 | } 14 | }, 15 | "zoneprovider": { 16 | "PollingIntervalSeconds": 15, 17 | "Hosts": [ 18 | { 19 | "Name": "foo", 20 | "Probe": "ping", 21 | "Timeout": 30, 22 | "AvailabilityMode": "first", 23 | "Ip": [ 24 | "192.168.1.1", 25 | "192.168.1.252", 26 | "192.168.1.253", 27 | "192.168.86.100" 28 | ] 29 | }, 30 | { 31 | "Name": "test", 32 | "Probe": "http", 33 | "Timeout": 100, 34 | "AvailabilityMode": "all", 35 | "Ip": [ 36 | "192.168.86.1", 37 | "192.168.1.1" 38 | ] 39 | } 40 | ] 41 | } 42 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # csharp-dns-server 2 | 3 | Fully functional DNS server written in C#. 4 | 5 | The project was conceived while working to reduce the cost of datacentre "stamps" while providing robust services within a datacentre, specifically to remove the need for an expensive load-balancer device by providing round-robin DNS services, and retrying connectivity instead. 6 | 7 | ## Licence 8 | This software is licenced under MIT terms that permits reuse within proprietary software provided all copies of the licensed software include a copy of the MIT License terms and the copyright notice. See [licence.txt](./licence.txt) 9 | 10 | ## Getting Started 11 | 12 | ``` 13 | // clone the repo 14 | >> cd $repo-root 15 | >> git clone https://github.com/stephbu/csharp-dns-server 16 | 17 | // check you can build the project 18 | >> cd $repo-root/csharp-dns-server 19 | >> dotnet build 20 | 21 | // check that the tests run 22 | >> dotnet test 23 | 24 | ``` 25 | 26 | ## Gotchas 27 | - if you're running on Windows 10 with Docker Tools installed, Docker uses the ICS SharedAccess service to provide DNS resolution for Docker containers - this listens on UDP:53, and will conflict with the DNS project. Either turn off the the service (```net stop SharedAccess```), or change the UDP port. 28 | 29 | ## Features 30 | 31 | As written, the server has the following features: 32 | 33 | - Pluggable Zone Resolver. Host one or more zones locally, and run your code to resolve names in that zone. Enables many complex scenarios such as: 34 | - round-robin load-balancing. Distribute load and provide failover with a datacentre without expensive hardware. 35 | - health-checks. While maintaining a list of machines in round-robin for a name, the code performs periodic healthchecks against the machines, if necessary removing machines that fail the health checks from rotation. 36 | - Delegates all other DNS lookup to host machines default DNS server(s) 37 | 38 | The DNS server has a built-in Web Server providing operational insight into the current server behaviour. 39 | - healthcheck for server status 40 | - counters 41 | - zone information 42 | 43 | ## Interesting Possible Uses 44 | Time-based constraints such as parental controls to block a site, e.g. Facebook. 45 | Logging of site usage e.g. company notifications 46 | 47 | ## Challenges 48 | 49 | ### Testing 50 | 51 | Two phases of testing was completed. 52 | 53 | 1) Verification that the bit-packing classes correctly added and removed bits in correct Endian order, complicated by network bitpacking in reverse order to Windows big-endian packing. 54 | 55 | 2) Protocol verification - that well known messages were correctly decoded and re-encoded using the bit-packing system. 56 | 57 | Much time was spent using Netmon to capture real DNS challenges and verify that the C# DNS server responded appropriately. 58 | 59 | ### DNS-Sec 60 | No effort made to handle or respond to DNS-Sec challenges. 61 | 62 | ## Contribution Guide 63 | Pull Requests, Bug Reports, and Feature Requests are most welcome. 64 | 65 | ### Contribution Workflow 66 | Suggested workflow for PRs is 67 | 68 | 1. Make a fork of csharp-dns-server/master in your own repository. 69 | 2. Create a branch in your own repo to entirely encapsulate all your proposed changes 70 | 3. Make your changes, add documentation if you need it, markdown text preferred. 71 | 4. Squash your commits into a single change [(Find out how to squash here)](http://stackoverflow.com/questions/616556/how-do-you-squash-commits-into-one-patch-with-git-format-patch) 72 | 5. Submit a PR, and put in comments anything that you think I'll need to help merge and evaluate the changes 73 | 74 | ### Licence Reminder 75 | All contributions must be licenced under the same MIT terms, do include a header file to that effect. 76 | 77 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | os: Visual Studio 2015 3 | install: 4 | - set PATH=C:\Program Files (x86)\MSBuild\15.0\Bin;%PATH% 5 | build: 6 | verbosity: normal -------------------------------------------------------------------------------- /csharp-dns-server.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dns", "dns\dns.csproj", "{6804468B-333A-4A7C-BFC1-FD4E498AB4A6}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dnstest", "dnstest\dnstest.csproj", "{126A266A-FF89-4516-BF36-37774EA35CD6}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dns-cli", "dns-cli\dns-cli.csproj", "{57BD1660-D9B3-4ABD-8F5F-9C89F4F1F8AE}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {126A266A-FF89-4516-BF36-37774EA35CD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {126A266A-FF89-4516-BF36-37774EA35CD6}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {126A266A-FF89-4516-BF36-37774EA35CD6}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {6804468B-333A-4A7C-BFC1-FD4E498AB4A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {6804468B-333A-4A7C-BFC1-FD4E498AB4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {6804468B-333A-4A7C-BFC1-FD4E498AB4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {6804468B-333A-4A7C-BFC1-FD4E498AB4A6}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {6804468B-333A-4A7C-BFC1-FD4E498AB4A6}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 24 | {57BD1660-D9B3-4ABD-8F5F-9C89F4F1F8AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {57BD1660-D9B3-4ABD-8F5F-9C89F4F1F8AE}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {57BD1660-D9B3-4ABD-8F5F-9C89F4F1F8AE}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {57BD1660-D9B3-4ABD-8F5F-9C89F4F1F8AE}.Release|Any CPU.Build.0 = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(MonoDevelopProperties) = preSolution 30 | Policies = $0 31 | $0.DotNetNamingPolicy = $1 32 | $1.DirectoryNamespaceAssociation = PrefixedHierarchical 33 | $0.VersionControlPolicy = $2 34 | EndGlobalSection 35 | EndGlobal 36 | -------------------------------------------------------------------------------- /dns-cli/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace DnsCli 5 | { 6 | /// Stub program that enables DNS Server to run from the command line 7 | 8 | class Program 9 | { 10 | private static CancellationTokenSource cts = new CancellationTokenSource(); 11 | private static ManualResetEvent _exitTimeout = new ManualResetEvent(false); 12 | 13 | public static void Main(string[] args) 14 | { 15 | Console.CancelKeyPress += Console_CancelKeyPress; 16 | 17 | Console.WriteLine("DNS Server - Console Mode"); 18 | 19 | if(args.Length == 0) 20 | { 21 | args = new string[] { "./appsettings.json" }; 22 | } 23 | 24 | Dns.Program.Run(args[0], cts.Token); 25 | 26 | _exitTimeout.Set(); 27 | 28 | } 29 | 30 | private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) 31 | { 32 | Console.WriteLine("\r\nShutting Down"); 33 | cts.Cancel(); 34 | _exitTimeout.WaitOne(5000); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /dns-cli/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "zone": { 4 | "name": ".stephbu.org", 5 | "provider": "Dns.ZoneProvider.IPProbe.IPProbeZoneProvider" 6 | }, 7 | "dnslistener": { 8 | "port": 5335 9 | }, 10 | "webserver": { 11 | "port": 8080, 12 | "enabled": true 13 | } 14 | }, 15 | "zoneprovider": { 16 | "PollingIntervalSeconds": 15, 17 | "Hosts": [ 18 | { 19 | "Name": "foo", 20 | "Probe": "ping", 21 | "Timeout": 30, 22 | "AvailabilityMode": "first", 23 | "Ip": [ 24 | "192.168.1.1", 25 | "192.168.1.252", 26 | "192.168.1.253", 27 | "192.168.86.100" 28 | ] 29 | }, 30 | { 31 | "Name": "test", 32 | "Probe": "http", 33 | "Timeout": 100, 34 | "AvailabilityMode": "all", 35 | "Ip": [ 36 | "192.168.86.1", 37 | "192.168.1.1" 38 | ] 39 | } 40 | ] 41 | } 42 | } -------------------------------------------------------------------------------- /dns-cli/dns-cli.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | DnsCli 7 | DnsCli.Program 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Always 19 | 20 | 21 | Always 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /dnstest/BitPackerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace DnsTest 5 | { 6 | using Dns.Utility; 7 | 8 | public class BitPackerTests 9 | { 10 | [Fact] 11 | public void Test1() 12 | { 13 | byte[] bytes = BitConverter.GetBytes(0xAA); 14 | 15 | BitPacker packer = new BitPacker(bytes); 16 | Assert.False(packer.GetBoolean()); 17 | Assert.True(packer.GetBoolean()); 18 | Assert.False(packer.GetBoolean()); 19 | Assert.True(packer.GetBoolean()); 20 | Assert.False(packer.GetBoolean()); 21 | Assert.True(packer.GetBoolean()); 22 | Assert.False(packer.GetBoolean()); 23 | Assert.True(packer.GetBoolean()); 24 | 25 | bytes = BitConverter.GetBytes(0x0A); 26 | packer = new BitPacker(bytes); 27 | Assert.False(packer.GetBoolean()); 28 | Assert.True(packer.GetBoolean()); 29 | Assert.False(packer.GetBoolean()); 30 | Assert.True(packer.GetBoolean()); 31 | Assert.False(packer.GetBoolean()); 32 | Assert.False(packer.GetBoolean()); 33 | Assert.False(packer.GetBoolean()); 34 | Assert.False(packer.GetBoolean()); 35 | } 36 | 37 | [Fact] 38 | public void Test2() 39 | { 40 | byte[] bytes; 41 | BitPacker packer; 42 | 43 | bytes = BitConverter.GetBytes(0xAFFF); 44 | packer = new BitPacker(bytes); 45 | 46 | Assert.True(packer.GetBoolean()); 47 | Assert.True(packer.GetBoolean()); 48 | Assert.Equal(15, packer.GetByte(4)); 49 | Assert.True(packer.GetBoolean()); 50 | Assert.Equal(95, packer.GetByte(7)); 51 | Assert.False(packer.GetBoolean()); 52 | Assert.True(packer.GetBoolean()); 53 | } 54 | 55 | [Fact] 56 | public void Test3() 57 | { 58 | byte[] bytes; 59 | BitPacker packer; 60 | 61 | bytes = BitConverter.GetBytes(0xAFFF); 62 | packer = new BitPacker(bytes); 63 | 64 | Assert.Equal(15, packer.GetByte(4)); 65 | Assert.Equal(15, packer.GetByte(4)); 66 | Assert.Equal(0xAF,packer.GetUshort(8)); 67 | 68 | bytes = BitConverter.GetBytes(0x0CD000); 69 | packer = new BitPacker(bytes); 70 | 71 | Assert.Equal(0x00, packer.GetByte(8)); 72 | Assert.Equal(0x0CD0, packer.GetUshort(16)); 73 | 74 | bytes = BitConverter.GetBytes(0x000F << 1) ; 75 | packer = new BitPacker(bytes); 76 | Assert.False(packer.GetBoolean()); 77 | Assert.Equal(0xF, packer.GetUshort(8)); 78 | 79 | bytes = BitConverter.GetBytes(0xAABB); 80 | packer = new BitPacker(bytes); 81 | Assert.Equal(0xAABB, packer.GetUshort(16, BitPacker.Endian.LoHi)); 82 | 83 | packer.Reset(); 84 | Assert.Equal(0xBBAA, packer.GetUshort(16, BitPacker.Endian.HiLo)); 85 | 86 | packer.Reset(); 87 | Assert.Equal(0xBBAA, packer.GetUshort(16, BitPacker.Endian.HiLo)); 88 | 89 | bytes = BitConverter.GetBytes(0x0100); 90 | packer = new BitPacker(bytes); 91 | Assert.Equal(0x0001, packer.GetUshort(16, BitPacker.Endian.HiLo)); 92 | } 93 | 94 | [Fact] 95 | public void TestEndian() 96 | { 97 | uint intValue = 0xAABBCCDD; 98 | BitPacker.SwapEndian(ref intValue); 99 | Assert.Equal(0xDDCCBBAA, intValue); 100 | 101 | ushort ushortValue = 0xAABB; 102 | BitPacker.SwapEndian(ref ushortValue); 103 | Assert.Equal(0xBBAA, ushortValue); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /dnstest/ConfigTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.Configuration.Json; 5 | 6 | using Dns.Config; 7 | 8 | namespace DnsTest 9 | { 10 | public class ConfigTests 11 | { 12 | public ConfigTests() 13 | { 14 | } 15 | 16 | [Fact] 17 | public void LoadConfig() 18 | { 19 | var jsonSource = new JsonConfigurationSource 20 | { 21 | Path = "./Data/appsettings.json" 22 | }; 23 | var jsonConfig = new JsonConfigurationProvider(jsonSource); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dnstest/Data/BindZoneFiles/bindzonetest1.txt: -------------------------------------------------------------------------------- 1 | $TTL 14400 2 | $ORIGIN stephbu.org. 3 | 4 | ; Specify the primary nameserver ns1.example.com in SOA 5 | @ 14400 IN SOA ns1.stephbu.org. stephbu.org. ( 6 | 2008092902 ; Serial in YYYYMMDDXX (XX is increment) 7 | 10800; refresh seconds 8 | 3600; retry 9 | 604800; expire 10 | 38400; minimum 11 | ); 12 | ; Website IP Address specified in A record 13 | 14 | IN A 11.11.11.11 15 | 16 | ; TWO nameserver names 17 | 18 | IN NS ns1.example.com. 19 | IN NS ns2.example.com. 20 | 21 | ; Nameservers and their corresponding IPs 22 | 23 | ns1 IN A 11.11.11.11 24 | ns2 IN A 22.22.22.22 25 | 26 | ; Specify here any Aliases using CNAME record 27 | 28 | www IN CNAME stephbu.org. 29 | ftp IN CNAME stephbu.org. 30 | 31 | ; Set Mail Exchanger record with priority 32 | 33 | mail IN MX 10 stephbu.org. 34 | 35 | 36 | -------------------------------------------------------------------------------- /dnstest/Data/Config/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "zone": "localdomain.com", 4 | "listener": { 5 | "port": 53 6 | }, 7 | "webserver": { 8 | "port": 8080, 9 | "enabled": false 10 | }, 11 | "resolver": { 12 | "name": "Dns.ZoneProvider.IPProbe.IPProbeZoneProvider" 13 | } 14 | }, 15 | "providerconfig" : {} 16 | } -------------------------------------------------------------------------------- /dnstest/DnsCacheTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Linq; 4 | using Xunit; 5 | 6 | namespace DnsTest 7 | { 8 | using Dns; 9 | 10 | public class DnsCacheTests 11 | { 12 | [Fact] 13 | public void Test1() { 14 | Dns.Contracts.IDnsCache cache = new Dns.DnsCache(); 15 | var invalidKeyResult = cache.Get("invalidTestKey"); 16 | Xunit.Assert.Null(invalidKeyResult); 17 | } 18 | 19 | [Fact] 20 | public void Test2() { 21 | Dns.Contracts.IDnsCache cache = new Dns.DnsCache(); 22 | 23 | string key = "sampleCacheKey"; 24 | byte[] data = Encoding.ASCII.GetBytes("test"); 25 | Int32 ttl = 10; 26 | 27 | cache.Set(key, data, ttl); 28 | var result = cache.Get(key); 29 | 30 | Xunit.Assert.True(data.SequenceEqual(result)); 31 | 32 | var invalidKeyResult = cache.Get("invalidTestKey"); 33 | Xunit.Assert.Null(invalidKeyResult); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /dnstest/DnsProtocolTest.cs: -------------------------------------------------------------------------------- 1 | // // //------------------------------------------------------------------------------------------------- 2 | // // // 3 | // // // Copyright (c) Steve Butler. All rights reserved. 4 | // // // 5 | // // //------------------------------------------------------------------------------------------------- 6 | 7 | namespace DnsTest 8 | { 9 | using System.IO; 10 | using System.Linq; 11 | using System.Net; 12 | using Dns; 13 | using Xunit; 14 | 15 | public class DnsProtocolTest 16 | { 17 | [Fact] 18 | public void DnsQuery() 19 | { 20 | byte[] sampleQuery = new byte[] {0xD3, 0x03, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x64, 0x64, 0x63, 0x64, 0x73, 0x30, 0x31, 0x07, 0x72, 0x65, 0x64, 0x6D, 0x6F, 0x6E, 0x64, 0x04, 0x63, 0x6F, 0x72, 0x70, 0x09, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x03, 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x01, 0x00, 0x01}; 21 | DnsMessage query; 22 | Assert.True(DnsMessage.TryParse(sampleQuery, out query)); 23 | query.Dump(); 24 | } 25 | 26 | [Fact] 27 | public void DnsQuery2() 28 | { 29 | byte[] sampleQuery = new byte[] {0x00, 0x03, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77, 0x03, 0x6D, 0x73, 0x6E, 0x03, 0x63, 0x6F, 0x6D, 0x07, 0x72, 0x65, 0x64, 0x6D, 0x6F, 0x6E, 0x64, 0x04, 0x63, 0x6F, 0x72, 0x70, 0x09, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x03, 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x1C, 0x00, 0x01}; 30 | DnsMessage query; 31 | Assert.True(DnsMessage.TryParse(sampleQuery, out query)); 32 | 33 | // Header Checks 34 | Assert.Equal(0x3, query.QueryIdentifier); 35 | Assert.False(query.QR); 36 | Assert.Equal(0x0000, query.Opcode); 37 | Assert.False(query.AA); 38 | Assert.False(query.TC); 39 | Assert.True(query.RD); 40 | Assert.False(query.RA); 41 | Assert.False(query.Zero); 42 | Assert.False(query.AuthenticatingData); 43 | Assert.False(query.CheckingDisabled); 44 | Assert.Equal(0x0000, query.RCode); 45 | Assert.Equal(0x0001, query.QuestionCount); 46 | Assert.Equal(0x0000, query.AnswerCount); 47 | Assert.Equal(0x0000, query.NameServerCount); 48 | Assert.Equal(0x0000, query.AdditionalCount); 49 | 50 | // Question Checks 51 | Assert.Equal(query.QuestionCount, query.Questions.Count()); 52 | 53 | // Q1 54 | Assert.Equal("www.msn.com.redmond.corp.microsoft.com", query.Questions[0].Name); 55 | Assert.Equal(ResourceType.AAAA, query.Questions[0].Type); 56 | Assert.Equal(ResourceClass.IN, query.Questions[0].Class); 57 | 58 | // dump results 59 | query.Dump(); 60 | } 61 | 62 | [Fact] 63 | public void DnsResponse1() 64 | { 65 | byte[] sampleQuery = new byte[] {0x44, 0xFD, 0x81, 0x80, 0x00, 0x01, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77, 0x10, 0x67, 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x2D, 0x61, 0x6E, 0x61, 0x6C, 0x79, 0x74, 0x69, 0x63, 0x73, 0x03, 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x01, 0x00, 0x01, 0xC0, 0x0C, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x89, 0x89, 0x00, 0x20, 0x14, 0x77, 0x77, 0x77, 0x2D, 0x67, 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x2D, 0x61, 0x6E, 0x61, 0x6C, 0x79, 0x74, 0x69, 0x63, 0x73, 0x01, 0x6C, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0xC0, 0x21, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x25, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x21, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x28, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x29, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x20, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x2E, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x26, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x24, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x27, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x22, 0xC0, 0x36, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x04, 0xAD, 0xC2, 0x21, 0x23}; 66 | DnsMessage query; 67 | Assert.True(DnsMessage.TryParse(sampleQuery, out query)); 68 | query.Dump(); 69 | } 70 | 71 | // Response Contains Compression information 72 | [Fact] 73 | public void DnsResponse2() 74 | { 75 | byte[] sampleQuery = new byte[] {0x00, 0x04, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77, 0x03, 0x6D, 0x73, 0x6E, 0x03, 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x01, 0x00, 0x01, 0xC0, 0x0C, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x02, 0x35, 0x00, 0x1E, 0x02, 0x75, 0x73, 0x03, 0x63, 0x6F, 0x31, 0x03, 0x63, 0x62, 0x33, 0x06, 0x67, 0x6C, 0x62, 0x64, 0x6E, 0x73, 0x09, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0xC0, 0x14, 0xC0, 0x29, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x53, 0x00, 0x04, 0x83, 0xFD, 0x0D, 0x8C}; 76 | DnsMessage query; 77 | Assert.True(DnsMessage.TryParse(sampleQuery, out query)); 78 | 79 | // Header Checks 80 | Assert.Equal(0x4, query.QueryIdentifier); 81 | Assert.True(query.QR); 82 | Assert.Equal(0x0000, query.Opcode); 83 | Assert.False(query.AA); 84 | Assert.False(query.TC); 85 | Assert.True(query.RD); 86 | Assert.True(query.RA); 87 | Assert.False(query.Zero); 88 | Assert.False(query.AuthenticatingData); 89 | Assert.False(query.CheckingDisabled); 90 | Assert.Equal(0x0000, query.RCode); 91 | Assert.Equal(0x0001, query.QuestionCount); 92 | Assert.Equal(0x0002, query.AnswerCount); 93 | Assert.Equal(0x0000, query.NameServerCount); 94 | Assert.Equal(0x0000, query.AdditionalCount); 95 | 96 | // Question Checks 97 | Assert.Equal(query.QuestionCount, query.Questions.Count()); 98 | 99 | // Q1 100 | Assert.Equal("www.msn.com", query.Questions[0].Name); 101 | Assert.Equal(ResourceType.A, query.Questions[0].Type); 102 | Assert.Equal(ResourceClass.IN, query.Questions[0].Class); 103 | 104 | // dump results 105 | query.Dump(); 106 | } 107 | 108 | // Response Contains Compression information 109 | [Fact] 110 | public void DnsResponse3() 111 | { 112 | byte[] sampleQuery = new byte[] {0xDD, 0x15, 0x81, 0x80, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x70, 0x69, 0x04, 0x62, 0x69, 0x6E, 0x67, 0x03, 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x01, 0x00, 0x01, 0xC0, 0x0C, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x83, 0x00, 0x14, 0x04, 0x61, 0x31, 0x33, 0x34, 0x02, 0x6C, 0x6D, 0x06, 0x61, 0x6B, 0x61, 0x6D, 0x61, 0x69, 0x03, 0x6E, 0x65, 0x74, 0x00, 0xC0, 0x2A, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x04, 0xCF, 0x6D, 0x49, 0x91, 0xC0, 0x2A, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x04, 0xCF, 0x6D, 0x49, 0x51}; 113 | DnsMessage query; 114 | Assert.True(DnsMessage.TryParse(sampleQuery, out query)); 115 | 116 | // Header Checks 117 | Assert.Equal(0xDD15, query.QueryIdentifier); 118 | Assert.True(query.QR); 119 | Assert.Equal(0x0000, query.Opcode); 120 | Assert.False(query.AA); 121 | Assert.False(query.TC); 122 | Assert.True(query.RD); 123 | Assert.True(query.RA); 124 | Assert.False(query.Zero); 125 | Assert.False(query.AuthenticatingData); 126 | Assert.False(query.CheckingDisabled); 127 | Assert.Equal(0x0000, query.RCode); 128 | Assert.Equal(0x0001, query.QuestionCount); 129 | Assert.Equal(0x0003, query.AnswerCount); 130 | Assert.Equal(0x0000, query.NameServerCount); 131 | Assert.Equal(0x0000, query.AdditionalCount); 132 | 133 | // Question Checks 134 | Assert.Equal(query.QuestionCount, query.Questions.Count()); 135 | 136 | // Q1 137 | Assert.Equal("api.bing.com", query.Questions[0].Name); 138 | Assert.Equal(ResourceType.A, query.Questions[0].Type); 139 | Assert.Equal(ResourceClass.IN, query.Questions[0].Class); 140 | 141 | // Answer Checks 142 | Assert.Equal(query.AnswerCount, query.Answers.Count()); 143 | 144 | // A1 145 | Assert.Equal("api.bing.com", query.Answers[0].Name); 146 | Assert.Equal(ResourceType.CNAME, query.Answers[0].Type); 147 | Assert.Equal(ResourceClass.IN, query.Answers[0].Class); 148 | Assert.True(query.Answers[0].TTL == 0x83); 149 | Assert.Equal(0x14, query.Answers[0].DataLength); 150 | Assert.Equal(typeof (CNameRData), query.Answers[0].RData.GetType()); 151 | 152 | // dump results 153 | query.Dump(); 154 | } 155 | 156 | // Response Contains Compression information 157 | [Fact] 158 | public void DnsQuery3() 159 | { 160 | byte[] sampleQuery = new byte[] {0xFB, 0x65, 0x84, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2D, 0x75, 0x73, 0x0C, 0x69, 0x6D, 0x72, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x77, 0x69, 0x64, 0x65, 0x03, 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x1C, 0x00, 0x01}; 161 | DnsMessage query; 162 | Assert.True(DnsMessage.TryParse(sampleQuery, out query)); 163 | 164 | // Header Checks 165 | Assert.Equal(0xFB65, query.QueryIdentifier); 166 | Assert.True(query.QR); 167 | Assert.Equal(0x0000, query.Opcode); 168 | Assert.True(query.AA); 169 | Assert.False(query.TC); 170 | Assert.False(query.RD); 171 | Assert.False(query.RA); 172 | Assert.False(query.Zero); 173 | Assert.False(query.AuthenticatingData); 174 | Assert.False(query.CheckingDisabled); 175 | Assert.Equal(0x0000, query.RCode); 176 | Assert.Equal(0x0001, query.QuestionCount); 177 | Assert.Equal(0x0000, query.AnswerCount); 178 | Assert.Equal(0x0000, query.NameServerCount); 179 | Assert.Equal(0x0000, query.AdditionalCount); 180 | 181 | // Question Checks 182 | Assert.Equal(query.QuestionCount, query.Questions.Count()); 183 | 184 | // Q1 185 | Assert.Equal("secure-us.imrworldwide.com", query.Questions[0].Name); 186 | Assert.Equal(ResourceType.AAAA, query.Questions[0].Type); 187 | Assert.Equal(ResourceClass.IN, query.Questions[0].Class); 188 | 189 | // dump results 190 | query.Dump(); 191 | } 192 | 193 | [Fact] 194 | public void TransitiveQueryTest() 195 | { 196 | DnsMessage message = new DnsMessage(); 197 | message.QueryIdentifier = 0xFEED; 198 | message.QR = false; 199 | message.Opcode = (byte) OpCode.QUERY; 200 | message.AA = false; 201 | message.TC = false; 202 | message.RD = true; 203 | message.RA = false; 204 | message.Zero = false; 205 | message.AuthenticatingData = false; 206 | message.CheckingDisabled = false; 207 | message.RCode = 0x0000; 208 | message.QuestionCount = 1; 209 | message.AnswerCount = 0; 210 | message.NameServerCount = 0; 211 | message.AdditionalCount = 0; 212 | message.Questions = new QuestionList(); 213 | message.Questions.Add(new Question {Name = "www.msn.com", Class = ResourceClass.IN, Type = ResourceType.A}); 214 | 215 | DnsMessage outMessage; 216 | using (MemoryStream stream = new MemoryStream()) 217 | { 218 | message.WriteToStream(stream); 219 | Assert.True(DnsMessage.TryParse(stream.GetBuffer(), out outMessage)); 220 | } 221 | 222 | Assert.Equal(0xFEED, outMessage.QueryIdentifier); 223 | Assert.False(outMessage.QR); 224 | Assert.Equal((byte) OpCode.QUERY, outMessage.Opcode); 225 | Assert.False(outMessage.AA); 226 | Assert.False(outMessage.TC); 227 | Assert.True(outMessage.RD); 228 | Assert.False(outMessage.RA); 229 | Assert.False(outMessage.Zero); 230 | Assert.False(outMessage.AuthenticatingData); 231 | Assert.False(outMessage.CheckingDisabled); 232 | Assert.Equal(0x0000, outMessage.RCode); 233 | Assert.Equal(0x0001, outMessage.QuestionCount); 234 | Assert.Equal(0x0000, outMessage.AnswerCount); 235 | Assert.Equal(0x0000, outMessage.NameServerCount); 236 | Assert.Equal(0x0000, outMessage.AdditionalCount); 237 | 238 | // Question Checks 239 | Assert.Equal(outMessage.QuestionCount, outMessage.Questions.Count()); 240 | 241 | // Q1 242 | Assert.Equal("www.msn.com", outMessage.Questions[0].Name); 243 | Assert.Equal(ResourceType.A, outMessage.Questions[0].Type); 244 | Assert.Equal(ResourceClass.IN, outMessage.Questions[0].Class); 245 | } 246 | 247 | [Fact] 248 | public void TransitiveQueryTest2() 249 | { 250 | DnsMessage message = new DnsMessage(); 251 | message.QueryIdentifier = 0xFEED; 252 | message.QR = false; 253 | message.Opcode = (byte) OpCode.QUERY; 254 | message.AA = false; 255 | message.TC = false; 256 | message.RD = true; 257 | message.RA = false; 258 | message.Zero = false; 259 | message.AuthenticatingData = false; 260 | message.CheckingDisabled = false; 261 | message.RCode = 0x0000; 262 | message.QuestionCount = 1; 263 | message.AnswerCount = 2; 264 | message.NameServerCount = 0; 265 | message.AdditionalCount = 0; 266 | message.Questions = new QuestionList(); 267 | message.Questions.Add(new Question {Name = "www.msn.com", Class = ResourceClass.IN, Type = ResourceType.A}); 268 | message.Answers.Add(new ResourceRecord {Name = "8.8.8.8", Class = ResourceClass.IN, Type = ResourceType.NS, TTL = 468, DataLength = 0, RData = null}); 269 | RData data = new ANameRData {Address = IPAddress.Parse("8.8.8.9")}; 270 | message.Answers.Add(new ResourceRecord {Name = "8.8.8.9", Class = ResourceClass.IN, Type = ResourceType.NS, TTL = 468, RData = data, DataLength = (ushort) data.Length}); 271 | 272 | DnsMessage outMessage; 273 | using (MemoryStream stream = new MemoryStream()) 274 | { 275 | message.WriteToStream(stream); 276 | Assert.True(DnsMessage.TryParse(stream.GetBuffer(), out outMessage)); 277 | } 278 | 279 | Assert.Equal(0xFEED, outMessage.QueryIdentifier); 280 | Assert.False(outMessage.QR); 281 | Assert.Equal((byte) OpCode.QUERY, outMessage.Opcode); 282 | Assert.False(outMessage.AA); 283 | Assert.False(outMessage.TC); 284 | Assert.True(outMessage.RD); 285 | Assert.False(outMessage.RA); 286 | Assert.False(outMessage.Zero); 287 | Assert.False(outMessage.AuthenticatingData); 288 | Assert.False(outMessage.CheckingDisabled); 289 | Assert.Equal(0x0000, outMessage.RCode); 290 | Assert.Equal(0x0001, outMessage.QuestionCount); 291 | Assert.Equal(0x0002, outMessage.AnswerCount); 292 | Assert.Equal(0x0000, outMessage.NameServerCount); 293 | Assert.Equal(0x0000, outMessage.AdditionalCount); 294 | 295 | // Question Checks 296 | Assert.Equal(outMessage.QuestionCount, outMessage.Questions.Count()); 297 | 298 | // Q1 299 | Assert.Equal("www.msn.com", outMessage.Questions[0].Name); 300 | Assert.Equal(ResourceType.A, outMessage.Questions[0].Type); 301 | Assert.Equal(ResourceClass.IN, outMessage.Questions[0].Class); 302 | 303 | Assert.Equal(outMessage.AnswerCount, outMessage.Answers.Count()); 304 | Assert.Equal(outMessage.AnswerCount, outMessage.Answers.Count()); 305 | Assert.Equal("8.8.8.8", outMessage.Answers[0].Name); 306 | Assert.Equal("8.8.8.9", outMessage.Answers[1].Name); 307 | } 308 | 309 | [Fact] 310 | public void Opcode() 311 | { 312 | DnsMessage message = new DnsMessage(); 313 | message.QR = true; 314 | Assert.Equal(0x8000, message.Flags); 315 | message.Opcode = (byte) OpCode.UPDATE; 316 | Assert.Equal((byte) OpCode.UPDATE, message.Opcode); 317 | Assert.Equal(0xa800, message.Flags); 318 | } 319 | } 320 | } -------------------------------------------------------------------------------- /dnstest/dnstest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | DnsTest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /licence.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Steve Butler 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 4 | files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 5 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 6 | Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 11 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 13 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | --------------------------------------------------------------------------------