using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using SimpleBase; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("Richard Schneider")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("© 2018-2019 Richard Schneider")] [assembly: AssemblyDescription("DNS data model with serializer/deserializer for the wire and master file format.")] [assembly: AssemblyFileVersion("2.0.1")] [assembly: AssemblyInformationalVersion("2.0.1+701463d2091e6d98d4cc4490abb0e0ead8ae2985")] [assembly: AssemblyProduct("Makaretu.Dns")] [assembly: AssemblyTitle("Makaretu.Dns")] [assembly: AssemblyVersion("2.0.1.0")] namespace Makaretu.Dns { public class AAAARecord : AddressRecord { public AAAARecord() { base.Type = DnsType.AAAA; } } public abstract class AddressRecord : ResourceRecord { public IPAddress Address { get; set; } public AddressRecord() { base.TTL = ResourceRecord.DefaultHostTTL; } public static AddressRecord Create(DomainName name, IPAddress address) { if (address.AddressFamily == AddressFamily.InterNetwork) { return new ARecord { Name = name, Address = address }; } return new AAAARecord { Name = name, Address = address }; } public override void ReadData(WireReader reader, int length) { Address = reader.ReadIPAddress(length); } public override void ReadData(PresentationReader reader) { Address = reader.ReadIPAddress(); } public override void WriteData(WireWriter writer) { writer.WriteIPAddress(Address); } public override void WriteData(PresentationWriter writer) { writer.WriteIPAddress(Address, appendSpace: false); } } public class AFSDBRecord : ResourceRecord { public ushort Subtype { get; set; } public DomainName Target { get; set; } public AFSDBRecord() { base.Type = DnsType.AFSDB; } public override void ReadData(WireReader reader, int length) { Subtype = reader.ReadUInt16(); Target = reader.ReadDomainName(); } public override void ReadData(PresentationReader reader) { Subtype = reader.ReadUInt16(); Target = reader.ReadDomainName(); } public override void WriteData(WireWriter writer) { writer.WriteUInt16(Subtype); writer.WriteDomainName(Target); } public override void WriteData(PresentationWriter writer) { writer.WriteUInt16(Subtype); writer.WriteDomainName(Target, appendSpace: false); } } public class ARecord : AddressRecord { public ARecord() { base.Type = DnsType.A; } } public class CNAMERecord : ResourceRecord { public DomainName Target { get; set; } public CNAMERecord() { base.Type = DnsType.CNAME; } public override void ReadData(WireReader reader, int length) { Target = reader.ReadDomainName(); } public override void ReadData(PresentationReader reader) { Target = reader.ReadDomainName(); } public override void WriteData(WireWriter writer) { writer.WriteDomainName(Target); } public override void WriteData(PresentationWriter writer) { writer.WriteDomainName(Target, appendSpace: false); } } public static class DigestRegistry { public static Dictionary> Digests; static DigestRegistry() { Digests = new Dictionary>(); Digests.Add(DigestType.Sha1, () => SHA1.Create()); Digests.Add(DigestType.Sha256, () => SHA256.Create()); Digests.Add(DigestType.Sha384, () => SHA384.Create()); Digests.Add(DigestType.Sha512, () => SHA512.Create()); } public static HashAlgorithm Create(DigestType digestType) { if (Digests.TryGetValue(digestType, out var value)) { return value(); } throw new NotImplementedException($"Digest type '{digestType}' is not implemented."); } public static HashAlgorithm Create(SecurityAlgorithm algorithm) { return Create(SecurityAlgorithmRegistry.GetMetadata(algorithm).HashAlgorithm); } } public enum DigestType : byte { Sha1 = 1, Sha256, GostR34_11_94, Sha384, Sha512 } public class DNAMERecord : ResourceRecord { public DomainName Target { get; set; } public DNAMERecord() { base.Type = DnsType.DNAME; } public override void ReadData(WireReader reader, int length) { Target = reader.ReadDomainName(); } public override void ReadData(PresentationReader reader) { Target = reader.ReadDomainName(); } public override void WriteData(WireWriter writer) { writer.WriteDomainName(Target, uncompressed: true); } public override void WriteData(PresentationWriter writer) { writer.WriteDomainName(Target, appendSpace: false); } } public enum DnsClass : ushort { IN = 1, CS = 2, CH = 3, HS = 4, None = 254, ANY = 255 } [Flags] public enum DNSKEYFlags : ushort { None = 0, SecureEntryPoint = 1, ZoneKey = 0x100 } public class DNSKEYRecord : ResourceRecord { public DNSKEYFlags Flags { get; set; } public byte Protocol { get; set; } = 3; public SecurityAlgorithm Algorithm { get; set; } public byte[] PublicKey { get; set; } public DNSKEYRecord() { base.Type = DnsType.DNSKEY; } public DNSKEYRecord(RSA key, SecurityAlgorithm algorithm) : this() { switch (algorithm) { default: throw new ArgumentException($"Security algorithm '{algorithm}' is not allowed for a RSA key."); case SecurityAlgorithm.RSAMD5: case SecurityAlgorithm.RSASHA1: case SecurityAlgorithm.RSASHA1NSEC3SHA1: case SecurityAlgorithm.RSASHA256: case SecurityAlgorithm.RSASHA512: { Algorithm = algorithm; using MemoryStream memoryStream = new MemoryStream(); RSAParameters rSAParameters = key.ExportParameters(includePrivateParameters: false); memoryStream.WriteByte((byte)rSAParameters.Exponent.Length); memoryStream.Write(rSAParameters.Exponent, 0, rSAParameters.Exponent.Length); memoryStream.Write(rSAParameters.Modulus, 0, rSAParameters.Modulus.Length); PublicKey = memoryStream.ToArray(); break; } } } public DNSKEYRecord(ECDsa key) : this() { ECParameters p = key.ExportParameters(false); p.Validate(); if (!p.Curve.IsNamed) { throw new ArgumentException("Only named ECDSA curves are allowed."); } Algorithm = (from alg in SecurityAlgorithmRegistry.Algorithms where alg.Value.OtherNames.Contains(p.Curve.Oid.FriendlyName) select alg.Key).FirstOrDefault(); if (Algorithm == SecurityAlgorithm.DELETE) { throw new ArgumentException("ECDSA curve '" + p.Curve.Oid.FriendlyName + " is not known'."); } using MemoryStream memoryStream = new MemoryStream(); memoryStream.Write(p.Q.X, 0, p.Q.X.Length); memoryStream.Write(p.Q.Y, 0, p.Q.Y.Length); PublicKey = memoryStream.ToArray(); } public ushort KeyTag() { byte[] data = GetData(); int num = data.Length; int num2 = 0; for (int i = 0; i < num; i++) { num2 += (((i & 1) == 1) ? data[i] : (data[i] << 8)); } num2 += (num2 >> 16) & 0xFFFF; return (ushort)(num2 & 0xFFFF); } public override void ReadData(WireReader reader, int length) { int num = reader.Position + length; Flags = (DNSKEYFlags)reader.ReadUInt16(); Protocol = reader.ReadByte(); Algorithm = (SecurityAlgorithm)reader.ReadByte(); PublicKey = reader.ReadBytes(num - reader.Position); } public override void WriteData(WireWriter writer) { writer.WriteUInt16((ushort)Flags); writer.WriteByte(Protocol); writer.WriteByte((byte)Algorithm); writer.WriteBytes(PublicKey); } public override void ReadData(PresentationReader reader) { Flags = (DNSKEYFlags)reader.ReadUInt16(); Protocol = reader.ReadByte(); Algorithm = (SecurityAlgorithm)reader.ReadByte(); PublicKey = reader.ReadBase64String(); } public override void WriteData(PresentationWriter writer) { writer.WriteUInt16((ushort)Flags); writer.WriteByte(Protocol); writer.WriteByte((byte)Algorithm); writer.WriteBase64String(PublicKey, appendSpace: false); } } public abstract class DnsObject : IWireSerialiser, ICloneable { public DateTime CreationTime { get; set; } = DateTime.Now; public int Length() { WireWriter wireWriter = new WireWriter(Stream.Null); Write(wireWriter); return wireWriter.Position; } public virtual object Clone() { using MemoryStream memoryStream = new MemoryStream(); Write(memoryStream); memoryStream.Position = 0L; DnsObject obj = (DnsObject)Read(memoryStream); obj.CreationTime = CreationTime; return obj; } public T Clone() where T : DnsObject { return (T)Clone(); } public IWireSerialiser Read(byte[] buffer) { return Read(buffer, 0, buffer.Length); } public IWireSerialiser Read(byte[] buffer, int offset, int count) { using MemoryStream stream = new MemoryStream(buffer, offset, count, writable: false); return Read(new WireReader(stream)); } public IWireSerialiser Read(Stream stream) { return Read(new WireReader(stream)); } public abstract IWireSerialiser Read(WireReader reader); public byte[] ToByteArray() { using MemoryStream memoryStream = new MemoryStream(); Write(memoryStream); return memoryStream.ToArray(); } public void Write(Stream stream) { Write(new WireWriter(stream)); } public abstract void Write(WireWriter writer); } public enum DnsType : ushort { A = 1, NS = 2, [Obsolete("Use MX")] MD = 3, [Obsolete("Use MX")] MF = 4, CNAME = 5, SOA = 6, MB = 7, MG = 8, MR = 9, NULL = 10, WKS = 11, PTR = 12, HINFO = 13, MINFO = 14, MX = 15, TXT = 16, RP = 17, AFSDB = 18, AAAA = 28, SRV = 33, DNAME = 39, OPT = 41, DS = 43, RRSIG = 46, NSEC = 47, DNSKEY = 48, NSEC3 = 50, NSEC3PARAM = 51, TKEY = 249, TSIG = 250, AXFR = 252, MAILB = 253, [Obsolete("Use MX")] MAILA = 254, ANY = 255, URI = 256, CAA = 257 } public class DomainName : IEquatable { private const string dot = "."; private const char dotChar = '.'; private const string escapedDot = "\\."; private const string backslash = "\\"; private const char backslashChar = '\\'; private const string escapedBackslash = "\\092"; public static DomainName Root = new DomainName(string.Empty); private List labels = new List(); public IReadOnlyList Labels => labels; public DomainName(string name) { Parse(name); } public DomainName(params string[] labels) { this.labels.AddRange(labels); } public static DomainName Join(params DomainName[] names) { DomainName domainName = new DomainName(); foreach (DomainName domainName2 in names) { domainName.labels.AddRange(domainName2.Labels); } return domainName; } public override string ToString() { return string.Join(".", Labels.Select(EscapeLabel)); } private string EscapeLabel(string label) { StringBuilder stringBuilder = new StringBuilder(); foreach (char c in label) { if (c == '\\') { stringBuilder.Append("\\092"); continue; } switch (c) { case '.': stringBuilder.Append("\\."); continue; case '!': case '"': case '#': case '$': case '%': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case '-': case '/': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ':': case ';': case '<': case '=': case '>': case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': stringBuilder.Append(c); continue; } stringBuilder.Append('\\'); int num = c; stringBuilder.Append(num.ToString("000", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } public DomainName ToCanonical() { return new DomainName(Labels.Select((string l) => l.ToLowerInvariant()).ToArray()); } public bool BelongsTo(DomainName domain) { if (!(this == domain)) { return IsSubdomainOf(domain); } return true; } public bool IsSubdomainOf(DomainName domain) { if (domain == null) { return false; } if (labels.Count <= domain.labels.Count) { return false; } int num = labels.Count - 1; int num2 = domain.labels.Count - 1; while (0 <= num2) { if (!LabelsEqual(labels[num], domain.labels[num2])) { return false; } num--; num2--; } return true; } public DomainName Parent() { if (labels.Count == 0) { return null; } return new DomainName(labels.Skip(1).ToArray()); } private void Parse(string name) { labels.Clear(); StringBuilder stringBuilder = new StringBuilder(); int length = name.Length; for (int i = 0; i < length; i++) { char c = name[i]; switch (c) { case '\\': { c = name[++i]; if (!char.IsDigit(c)) { stringBuilder.Append(c); break; } int num = c - 48; num = num * 10 + (name[++i] - 48); num = num * 10 + (name[++i] - 48); stringBuilder.Append((char)num); break; } case '.': labels.Add(stringBuilder.ToString()); stringBuilder.Clear(); break; default: stringBuilder.Append(c); break; } } if (stringBuilder.Length > 0) { labels.Add(stringBuilder.ToString()); } } public override int GetHashCode() { return ToString().ToLowerInvariant().GetHashCode(); } public override bool Equals(object obj) { DomainName domainName = obj as DomainName; if (!(domainName == null)) { return Equals(domainName); } return false; } public bool Equals(DomainName that) { if ((object)that == null) { return false; } int count = labels.Count; if (count != that.labels.Count) { return false; } for (int i = 0; i < count; i++) { if (!LabelsEqual(labels[i], that.labels[i])) { return false; } } return true; } public static bool operator ==(DomainName a, DomainName b) { if ((object)a == b) { return true; } if ((object)a == null) { return false; } if ((object)b == null) { return false; } return a.Equals(b); } public static bool operator !=(DomainName a, DomainName b) { return !(a == b); } public static implicit operator DomainName(string s) { return new DomainName(s); } public static bool LabelsEqual(string a, string b) { return StringComparer.InvariantCultureIgnoreCase.Compare(a, b) == 0; } } public class DSRecord : ResourceRecord { public ushort KeyTag { get; set; } public SecurityAlgorithm Algorithm { get; set; } public DigestType HashAlgorithm { get; set; } public byte[] Digest { get; set; } public DSRecord() { base.Type = DnsType.DS; } public DSRecord(DNSKEYRecord key, bool force = false) : this() { if (!force) { if ((key.Flags & DNSKEYFlags.ZoneKey) == 0) { throw new ArgumentException("ZoneKey must be set.", "key"); } if ((key.Flags & DNSKEYFlags.SecureEntryPoint) == 0) { throw new ArgumentException("SecureEntryPoint must be set.", "key"); } } byte[] digest; using (MemoryStream memoryStream = new MemoryStream()) { using HashAlgorithm hashAlgorithm = DigestRegistry.Create(key.Algorithm); WireWriter wireWriter = new WireWriter(memoryStream) { CanonicalForm = true }; wireWriter.WriteDomainName(key.Name); key.WriteData(wireWriter); memoryStream.Position = 0L; digest = hashAlgorithm.ComputeHash(memoryStream); } Algorithm = key.Algorithm; base.Class = key.Class; KeyTag = key.KeyTag(); base.Name = key.Name; base.TTL = key.TTL; Digest = digest; HashAlgorithm = DigestType.Sha1; } public override void ReadData(WireReader reader, int length) { int num = reader.Position + length; KeyTag = reader.ReadUInt16(); Algorithm = (SecurityAlgorithm)reader.ReadByte(); HashAlgorithm = (DigestType)reader.ReadByte(); Digest = reader.ReadBytes(num - reader.Position); } public override void WriteData(WireWriter writer) { writer.WriteUInt16(KeyTag); writer.WriteByte((byte)Algorithm); writer.WriteByte((byte)HashAlgorithm); writer.WriteBytes(Digest); } public override void ReadData(PresentationReader reader) { KeyTag = reader.ReadUInt16(); Algorithm = (SecurityAlgorithm)reader.ReadByte(); HashAlgorithm = (DigestType)reader.ReadByte(); StringBuilder stringBuilder = new StringBuilder(); while (!reader.IsEndOfLine()) { stringBuilder.Append(reader.ReadString()); } Digest = Base16.Decode(stringBuilder.ToString()); } public override void WriteData(PresentationWriter writer) { writer.WriteUInt16(KeyTag); writer.WriteByte((byte)Algorithm); writer.WriteByte((byte)HashAlgorithm); writer.WriteBase16String(Digest, appendSpace: false); } } public class EdnsDAUOption : EdnsOption { public List Algorithms { get; set; } public EdnsDAUOption() { base.Type = EdnsOptionType.DAU; Algorithms = new List(); } public static EdnsDAUOption Create() { EdnsDAUOption ednsDAUOption = new EdnsDAUOption(); ednsDAUOption.Algorithms.AddRange(SecurityAlgorithmRegistry.Algorithms.Keys); return ednsDAUOption; } public override void ReadData(WireReader reader, int length) { Algorithms.Clear(); while (length > 0) { Algorithms.Add((SecurityAlgorithm)reader.ReadByte()); length--; } } public override void WriteData(WireWriter writer) { foreach (SecurityAlgorithm algorithm in Algorithms) { writer.WriteByte((byte)algorithm); } } public override string ToString() { return "; DAU = " + string.Join(", ", Algorithms); } } public class EdnsDHUOption : EdnsOption { public List Algorithms { get; set; } public EdnsDHUOption() { base.Type = EdnsOptionType.DHU; Algorithms = new List(); } public static EdnsDHUOption Create() { EdnsDHUOption ednsDHUOption = new EdnsDHUOption(); ednsDHUOption.Algorithms.AddRange(DigestRegistry.Digests.Keys); return ednsDHUOption; } public override void ReadData(WireReader reader, int length) { Algorithms.Clear(); while (length > 0) { Algorithms.Add((DigestType)reader.ReadByte()); length--; } } public override void WriteData(WireWriter writer) { foreach (DigestType algorithm in Algorithms) { writer.WriteByte((byte)algorithm); } } public override string ToString() { return "; DHU = " + string.Join(", ", Algorithms); } } public class EdnsKeepaliveOption : EdnsOption { public TimeSpan? Timeout { get; set; } public EdnsKeepaliveOption() { base.Type = EdnsOptionType.Keepalive; } public override void ReadData(WireReader reader, int length) { switch (length) { case 0: Timeout = null; break; case 2: Timeout = TimeSpan.FromMilliseconds(reader.ReadUInt16() * 100); break; default: throw new InvalidDataException($"Invalid EdnsKeepAlive length of '{length}'."); } } public override void WriteData(WireWriter writer) { if (Timeout.HasValue) { writer.WriteUInt16((ushort)(Timeout.Value.TotalMilliseconds / 100.0)); } } public override string ToString() { return $"; Keepalive = {Timeout}"; } } public class EdnsN3UOption : EdnsOption { public List Algorithms { get; set; } public EdnsN3UOption() { base.Type = EdnsOptionType.N3U; Algorithms = new List(); } public static EdnsN3UOption Create() { EdnsN3UOption ednsN3UOption = new EdnsN3UOption(); ednsN3UOption.Algorithms.AddRange(DigestRegistry.Digests.Keys); return ednsN3UOption; } public override void ReadData(WireReader reader, int length) { Algorithms.Clear(); while (length > 0) { Algorithms.Add((DigestType)reader.ReadByte()); length--; } } public override void WriteData(WireWriter writer) { foreach (DigestType algorithm in Algorithms) { writer.WriteByte((byte)algorithm); } } public override string ToString() { return "; N3U = " + string.Join(", ", Algorithms); } } public class EdnsNSIDOption : EdnsOption { public byte[] Id { get; set; } public EdnsNSIDOption() { base.Type = EdnsOptionType.NSID; } public override void ReadData(WireReader reader, int length) { Id = reader.ReadBytes(length); } public override void WriteData(WireWriter writer) { writer.WriteBytes(Id); } } public abstract class EdnsOption { public EdnsOptionType Type { get; set; } public abstract void ReadData(WireReader reader, int length); public abstract void WriteData(WireWriter writer); } public static class EdnsOptionRegistry { public static Dictionary> Options; static EdnsOptionRegistry() { Options = new Dictionary>(); Register(); Register(); Register(); Register(); Register(); Register(); } public static void Register() where T : EdnsOption, new() { T val = new T(); Options.Add(val.Type, () => new T()); } } public class EdnsPaddingOption : EdnsOption { public byte[] Padding { get; set; } public EdnsPaddingOption() { base.Type = EdnsOptionType.Padding; } public override void ReadData(WireReader reader, int length) { Padding = reader.ReadBytes(length); } public override void WriteData(WireWriter writer) { writer.WriteBytes(Padding); } public override string ToString() { return "; Padding = " + ((Padding == null) ? "null" : Padding.Length.ToString()); } } public enum EdnsOptionType : ushort { NSID = 3, DAU = 5, DHU = 6, N3U = 7, ClientSubnet = 8, Expire = 9, Cookie = 10, Keepalive = 11, Padding = 12, Chain = 13, KeyTag = 14, ExperimentalMin = 65001, ExperimentalMax = 65534, FutureExpansion = ushort.MaxValue } public class HINFORecord : ResourceRecord { public string Cpu { get; set; } public string OS { get; set; } public HINFORecord() { base.Type = DnsType.HINFO; base.TTL = ResourceRecord.DefaultHostTTL; } public override void ReadData(WireReader reader, int length) { Cpu = reader.ReadString(); OS = reader.ReadString(); } public override void ReadData(PresentationReader reader) { Cpu = reader.ReadString(); OS = reader.ReadString(); } public override void WriteData(WireWriter writer) { writer.WriteString(Cpu); writer.WriteString(OS); } public override void WriteData(PresentationWriter writer) { writer.WriteString(Cpu); writer.WriteString(OS, appendSpace: false); } } public static class IPAddressExtensions { public static string GetArpaName(this IPAddress ip) { byte[] addressBytes = ip.GetAddressBytes(); Array.Reverse((Array)addressBytes); if (ip.AddressFamily == AddressFamily.InterNetworkV6) { return string.Concat(addressBytes.SelectMany((byte b) => new int[2] { b & 0xF, (b >> 4) & 0xF }).Aggregate(new StringBuilder(), (StringBuilder s, int b) => s.Append(b.ToString("x")).Append(".")), "ip6.arpa"); } if (ip.AddressFamily == AddressFamily.InterNetwork) { return string.Join(".", addressBytes) + ".in-addr.arpa"; } throw new ArgumentException($"Unsupported address family '{ip.AddressFamily}'.", "ip"); } } public interface IPresentationSerialiser { ResourceRecord Read(PresentationReader reader); void Write(PresentationWriter writer); } public interface IResolver { Task ResolveAsync(Message request, CancellationToken cancel = default(CancellationToken)); } public interface IWireSerialiser { IWireSerialiser Read(WireReader reader); void Write(WireWriter writer); } public enum KeyExchangeMode : ushort { ServerAssignment = 1, DiffieHellman, GssApi, ResolverAssignment, KeyDeletion } public class Message : DnsObject { private byte opcode4; public const int MaxLength = 9000; public const int MinLength = 12; public ushort Id { get; set; } public bool QR { get; set; } public bool IsQuery => !QR; public bool IsResponse => QR; public MessageOperation Opcode { get { OPTRecord oPTRecord = AdditionalRecords.OfType().FirstOrDefault(); if (oPTRecord == null) { return (MessageOperation)opcode4; } return (MessageOperation)((oPTRecord.Opcode8 << 4) | opcode4); } set { OPTRecord oPTRecord = AdditionalRecords.OfType().FirstOrDefault(); if ((value & (MessageOperation)4080) == 0) { opcode4 = (byte)value; if (oPTRecord != null) { oPTRecord.Opcode8 = 0; } return; } if (oPTRecord == null) { oPTRecord = new OPTRecord(); AdditionalRecords.Add(oPTRecord); } opcode4 = (byte)(value & (MessageOperation)15); oPTRecord.Opcode8 = (byte)(((int)value >> 4) & 0xFF); } } public bool AA { get; set; } public bool TC { get; set; } public bool RD { get; set; } public bool RA { get; set; } public int Z { get; set; } public bool AD { get; set; } public bool CD { get; set; } public bool DO { get { return AdditionalRecords.OfType().FirstOrDefault()?.DO ?? false; } set { OPTRecord oPTRecord = AdditionalRecords.OfType().FirstOrDefault(); if (oPTRecord == null) { oPTRecord = new OPTRecord(); AdditionalRecords.Add(oPTRecord); } oPTRecord.DO = value; } } public MessageStatus Status { get; set; } public List Questions { get; } = new List(); public List Answers { get; set; } = new List(); public List AuthorityRecords { get; set; } = new List(); public List AdditionalRecords { get; set; } = new List(); public Message CreateResponse() { Message message = new Message(); message.Id = Id; message.Opcode = Opcode; message.QR = true; message.Questions.AddRange(Questions); return message; } public void Truncate(int length) { while (Length() > length) { if (AdditionalRecords.Count > 0) { AdditionalRecords.RemoveAt(AdditionalRecords.Count - 1); continue; } if (AuthorityRecords.Count > 0) { AuthorityRecords.RemoveAt(AuthorityRecords.Count - 1); continue; } TC = true; break; } } public Message UseDnsSecurity() { DO = true; return this; } public override IWireSerialiser Read(WireReader reader) { Id = reader.ReadUInt16(); ushort num = reader.ReadUInt16(); QR = (num & 0x8000) == 32768; AA = (num & 0x400) == 1024; TC = (num & 0x200) == 512; RD = (num & 0x100) == 256; RA = (num & 0x80) == 128; opcode4 = (byte)((num & 0x7800) >> 11); Z = (num & 0x40) >> 6; AD = (num & 0x20) == 32; CD = (num & 0x10) == 16; Status = (MessageStatus)(num & 0xF); ushort num2 = reader.ReadUInt16(); ushort num3 = reader.ReadUInt16(); ushort num4 = reader.ReadUInt16(); ushort num5 = reader.ReadUInt16(); for (int i = 0; i < num2; i++) { Question item = (Question)new Question().Read(reader); Questions.Add(item); } for (int j = 0; j < num3; j++) { ResourceRecord item2 = (ResourceRecord)new ResourceRecord().Read(reader); Answers.Add(item2); } for (int k = 0; k < num4; k++) { ResourceRecord item3 = (ResourceRecord)new ResourceRecord().Read(reader); AuthorityRecords.Add(item3); } for (int l = 0; l < num5; l++) { ResourceRecord item4 = (ResourceRecord)new ResourceRecord().Read(reader); AdditionalRecords.Add(item4); } return this; } public override void Write(WireWriter writer) { writer.WriteUInt16(Id); int num = (Convert.ToInt32(QR) << 15) | ((opcode4 & 0xF) << 11) | (Convert.ToInt32(AA) << 10) | (Convert.ToInt32(TC) << 9) | (Convert.ToInt32(RD) << 8) | (Convert.ToInt32(RA) << 7) | ((Z & 1) << 6) | (Convert.ToInt32(AD) << 5) | (Convert.ToInt32(CD) << 4) | (int)(Status & (MessageStatus)15); writer.WriteUInt16((ushort)num); writer.WriteUInt16((ushort)Questions.Count); writer.WriteUInt16((ushort)Answers.Count); writer.WriteUInt16((ushort)AuthorityRecords.Count); writer.WriteUInt16((ushort)AdditionalRecords.Count); foreach (Question question in Questions) { question.Write(writer); } foreach (ResourceRecord answer in Answers) { answer.Write(writer); } foreach (ResourceRecord authorityRecord in AuthorityRecords) { authorityRecord.Write(writer); } foreach (ResourceRecord additionalRecord in AdditionalRecords) { additionalRecord.Write(writer); } } public override string ToString() { using StringWriter stringWriter = new StringWriter(); stringWriter.Write(";; Header:"); if (QR) { stringWriter.Write(" QR"); } if (AA) { stringWriter.Write(" AA"); } if (TC) { stringWriter.Write(" TC"); } if (RD) { stringWriter.Write(" RD"); } if (AD) { stringWriter.Write(" AD"); } if (CD) { stringWriter.Write(" CD"); } stringWriter.Write(" RCODE="); stringWriter.Write(Status); stringWriter.WriteLine(); stringWriter.WriteLine(); stringWriter.WriteLine(";; Question"); if (Questions.Count == 0) { stringWriter.WriteLine(";; (empty)"); } else { foreach (Question question in Questions) { stringWriter.WriteLine(question.ToString()); } } Stringify(stringWriter, "Answer", Answers); Stringify(stringWriter, "Authority", AuthorityRecords); Stringify(stringWriter, "Additional", AdditionalRecords); return stringWriter.ToString(); } private void Stringify(StringWriter s, string title, List records) { s.WriteLine(); s.Write(";; "); s.WriteLine(title); if (records.Count == 0) { s.WriteLine(";; (empty)"); return; } foreach (ResourceRecord record in records) { s.WriteLine(record.ToString()); } } } public enum MessageOperation : ushort { Query = 0, InverseQuery = 1, Status = 2, Notify = 4, Update = 5 } public enum MessageStatus : byte { NoError = 0, FormatError = 1, ServerFailure = 2, NameError = 3, NotImplemented = 4, Refused = 5, YXDomain = 6, YXRRSet = 7, NXRRSet = 8, NotAuthoritative = 9, NotZone = 10, BadVersion = 16, BadSignature = 16, BadKey = 17, BadTime = 18, BADMODE = 19, BADNAME = 20, BADALG = 21 } public class MXRecord : ResourceRecord { public ushort Preference { get; set; } public DomainName Exchange { get; set; } public MXRecord() { base.Type = DnsType.MX; } public override void ReadData(WireReader reader, int length) { Preference = reader.ReadUInt16(); Exchange = reader.ReadDomainName(); } public override void ReadData(PresentationReader reader) { Preference = reader.ReadUInt16(); Exchange = reader.ReadDomainName(); } public override void WriteData(WireWriter writer) { writer.WriteUInt16(Preference); writer.WriteDomainName(Exchange); } public override void WriteData(PresentationWriter writer) { writer.WriteUInt16(Preference); writer.WriteDomainName(Exchange, appendSpace: false); } } [Flags] public enum NSEC3Flags : byte { OptOut = 1 } public class NSEC3PARAMRecord : ResourceRecord { public DigestType HashAlgorithm { get; set; } public byte Flags { get; set; } public ushort Iterations { get; set; } public byte[] Salt { get; set; } public NSEC3PARAMRecord() { base.Type = DnsType.NSEC3PARAM; } public override void ReadData(WireReader reader, int length) { _ = reader.Position; HashAlgorithm = (DigestType)reader.ReadByte(); Flags = reader.ReadByte(); Iterations = reader.ReadUInt16(); Salt = reader.ReadByteLengthPrefixedBytes(); } public override void WriteData(WireWriter writer) { writer.WriteByte((byte)HashAlgorithm); writer.WriteByte(Flags); writer.WriteUInt16(Iterations); writer.WriteByteLengthPrefixedBytes(Salt); } public override void ReadData(PresentationReader reader) { HashAlgorithm = (DigestType)reader.ReadByte(); Flags = reader.ReadByte(); Iterations = reader.ReadUInt16(); string text = reader.ReadString(); if (text != "-") { Salt = Base16.Decode(text); } } public override void WriteData(PresentationWriter writer) { writer.WriteByte((byte)HashAlgorithm); writer.WriteByte(Flags); writer.WriteUInt16(Iterations); if (Salt == null || Salt.Length == 0) { writer.WriteString("-"); } else { writer.WriteBase16String(Salt, appendSpace: false); } } } public class NSEC3Record : ResourceRecord { public DigestType HashAlgorithm { get; set; } public NSEC3Flags Flags { get; set; } public ushort Iterations { get; set; } public byte[] Salt { get; set; } public byte[] NextHashedOwnerName { get; set; } public List Types { get; set; } = new List(); public NSEC3Record() { base.Type = DnsType.NSEC3; } public override void ReadData(WireReader reader, int length) { int num = reader.Position + length; HashAlgorithm = (DigestType)reader.ReadByte(); Flags = (NSEC3Flags)reader.ReadByte(); Iterations = reader.ReadUInt16(); Salt = reader.ReadByteLengthPrefixedBytes(); NextHashedOwnerName = reader.ReadByteLengthPrefixedBytes(); while (reader.Position < num) { Types.AddRange(from t in reader.ReadBitmap() select (DnsType)t); } } public override void WriteData(WireWriter writer) { writer.WriteByte((byte)HashAlgorithm); writer.WriteByte((byte)Flags); writer.WriteUInt16(Iterations); writer.WriteByteLengthPrefixedBytes(Salt); writer.WriteByteLengthPrefixedBytes(NextHashedOwnerName); writer.WriteBitmap(Types.Select((DnsType t) => (ushort)t)); } public override void ReadData(PresentationReader reader) { HashAlgorithm = (DigestType)reader.ReadByte(); Flags = (NSEC3Flags)reader.ReadByte(); Iterations = reader.ReadUInt16(); string text = reader.ReadString(); if (text != "-") { Salt = Base16.Decode(text); } NextHashedOwnerName = Base32.ExtendedHex.Decode(reader.ReadString()); while (!reader.IsEndOfLine()) { Types.Add(reader.ReadDnsType()); } } public override void WriteData(PresentationWriter writer) { writer.WriteByte((byte)HashAlgorithm); writer.WriteByte((byte)Flags); writer.WriteUInt16(Iterations); if (Salt == null || Salt.Length == 0) { writer.WriteString("-"); } else { writer.WriteBase16String(Salt); } writer.WriteString(Base32.ExtendedHex.Encode(NextHashedOwnerName, false).ToLowerInvariant()); bool flag = false; foreach (DnsType type in Types) { if (flag) { writer.WriteSpace(); } writer.WriteDnsType(type, appendSpace: false); flag = true; } } } public class NSECRecord : ResourceRecord { public DomainName NextOwnerName { get; set; } = DomainName.Root; public List Types { get; set; } = new List(); public NSECRecord() { base.Type = DnsType.NSEC; } public override void ReadData(WireReader reader, int length) { int num = reader.Position + length; NextOwnerName = reader.ReadDomainName(); while (reader.Position < num) { Types.AddRange(from t in reader.ReadBitmap() select (DnsType)t); } } public override void WriteData(WireWriter writer) { writer.WriteDomainName(NextOwnerName, uncompressed: true); writer.WriteBitmap(Types.Select((DnsType t) => (ushort)t)); } public override void ReadData(PresentationReader reader) { NextOwnerName = reader.ReadDomainName(); while (!reader.IsEndOfLine()) { Types.Add(reader.ReadDnsType()); } } public override void WriteData(PresentationWriter writer) { writer.WriteDomainName(NextOwnerName); bool flag = false; foreach (DnsType type in Types) { if (flag) { writer.WriteSpace(); } writer.WriteDnsType(type, appendSpace: false); flag = true; } } } public class NSRecord : ResourceRecord { public DomainName Authority { get; set; } public NSRecord() { base.Type = DnsType.NS; } public override void ReadData(WireReader reader, int length) { Authority = reader.ReadDomainName(); } public override void ReadData(PresentationReader reader) { Authority = reader.ReadDomainName(); } public override void WriteData(WireWriter writer) { writer.WriteDomainName(Authority); } public override void WriteData(PresentationWriter writer) { writer.WriteDomainName(Authority, appendSpace: false); } } public class NULLRecord : ResourceRecord { public byte[] Data { get; set; } public NULLRecord() { base.Type = DnsType.NULL; } public override void ReadData(WireReader reader, int length) { Data = reader.ReadBytes(length); } public override void ReadData(PresentationReader reader) { Data = reader.ReadResourceData(); } public override void WriteData(WireWriter writer) { writer.WriteBytes(Data); } } public class OPTRecord : ResourceRecord { public ushort RequestorPayloadSize { get { return (ushort)base.Class; } set { base.Class = (DnsClass)value; } } public byte Opcode8 { get { return (byte)((base.TTL.Ticks / 10000000 >> 24) & 0xFF); } set { base.TTL = TimeSpan.FromTicks((long)(((ulong)((base.TTL.Ticks / 10000000) & -4278190081L) | ((ulong)value << 24)) * 10000000)); } } public byte Version { get { return (byte)((base.TTL.Ticks / 10000000 >> 16) & 0xFF); } set { base.TTL = TimeSpan.FromTicks((long)(((ulong)((base.TTL.Ticks / 10000000) & -16711681) | ((ulong)value << 16)) * 10000000)); } } public bool DO { get { return base.TTL.Ticks / 10000000 == 32768; } set { base.TTL = TimeSpan.FromTicks((((base.TTL.Ticks / 10000000) & -32769) | (Convert.ToInt64(value) << 15)) * 10000000); } } public List Options { get; set; } = new List(); public OPTRecord() { base.Type = DnsType.OPT; base.Name = DomainName.Root; RequestorPayloadSize = 1280; base.TTL = TimeSpan.Zero; } public override void ReadData(WireReader reader, int length) { int num = reader.Position + length; while (reader.Position < num) { EdnsOptionType ednsOptionType = (EdnsOptionType)reader.ReadUInt16(); int length2 = reader.ReadUInt16(); Func value; EdnsOption ednsOption = ((!EdnsOptionRegistry.Options.TryGetValue(ednsOptionType, out value)) ? new UnknownEdnsOption { Type = ednsOptionType } : value()); Options.Add(ednsOption); ednsOption.ReadData(reader, length2); } } public override void WriteData(WireWriter writer) { foreach (EdnsOption option in Options) { writer.WriteUInt16((ushort)option.Type); writer.PushLengthPrefixedScope(); option.WriteData(writer); writer.PopLengthPrefixedScope(); } } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"; EDNS: version: {Version}, udp {RequestorPayloadSize}"); foreach (EdnsOption option in Options) { stringBuilder.AppendLine(option.ToString()); } return stringBuilder.ToString(); } } public class PresentationReader { private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private TextReader text; private TimeSpan? defaultTTL; private DomainName defaultDomainName; private int parenLevel; private int previousChar = 10; private bool eolSeen; private bool tokenStartsNewLine; public int Position; public DomainName Origin { get; set; } = DomainName.Root; public PresentationReader(TextReader text) { this.text = text; } public byte ReadByte() { return byte.Parse(ReadToken(), CultureInfo.InvariantCulture); } public ushort ReadUInt16() { return ushort.Parse(ReadToken(), CultureInfo.InvariantCulture); } public uint ReadUInt32() { return uint.Parse(ReadToken(), CultureInfo.InvariantCulture); } public DomainName ReadDomainName() { return MakeAbsoluteDomainName(ReadToken(ignoreEscape: true)); } private DomainName MakeAbsoluteDomainName(string name) { if (name.EndsWith(".")) { return new DomainName(name.Substring(0, name.Length - 1)); } return DomainName.Join(new DomainName(name), Origin); } public string ReadString() { return ReadToken(); } public byte[] ReadBase64String() { StringBuilder stringBuilder = new StringBuilder(); while (!IsEndOfLine()) { stringBuilder.Append(ReadToken()); } return Convert.FromBase64String(stringBuilder.ToString()); } public TimeSpan ReadTimeSpan16() { return TimeSpan.FromSeconds((int)ReadUInt16()); } public TimeSpan ReadTimeSpan32() { return TimeSpan.FromSeconds(ReadUInt32()); } public IPAddress ReadIPAddress(int length = 4) { return IPAddress.Parse(ReadToken()); } public DnsType ReadDnsType() { string text = ReadToken(); if (text.StartsWith("TYPE")) { return (DnsType)ushort.Parse(text.Substring(4), CultureInfo.InvariantCulture); } return (DnsType)Enum.Parse(typeof(DnsType), text); } public DateTime ReadDateTime() { string text = ReadToken(); if (text.Length == 14) { return DateTime.ParseExact(text, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); } DateTime unixEpoch = UnixEpoch; return unixEpoch.AddSeconds(ulong.Parse(text, CultureInfo.InvariantCulture)); } public byte[] ReadResourceData() { string text = ReadToken(); if (text != "#") { throw new FormatException("Expected RDATA leadin '\\#', not '" + text + "'."); } uint num = ReadUInt32(); if (num == 0) { return new byte[0]; } StringBuilder stringBuilder = new StringBuilder(); while (stringBuilder.Length < num * 2) { string text2 = ReadToken(); if (text2.Length == 0) { break; } if (text2.Length % 2 != 0) { throw new FormatException("The hex word ('" + text2 + "') must have an even number of digits."); } stringBuilder.Append(text2); } if (stringBuilder.Length != num * 2) { throw new FormatException("Wrong number of RDATA hex digits."); } try { return Base16.Decode(stringBuilder.ToString()); } catch (InvalidOperationException ex) { throw new FormatException(ex.Message); } } public ResourceRecord ReadResourceRecord() { DomainName domainName = defaultDomainName; DnsClass dnsClass = DnsClass.IN; TimeSpan? timeSpan = defaultTTL; DnsType? dnsType = null; while (!dnsType.HasValue) { string text = ReadToken(ignoreEscape: true); if (text == "") { return null; } DnsType result; if (tokenStartsNewLine) { switch (text) { case "$ORIGIN": Origin = ReadDomainName(); break; case "$TTL": timeSpan = (defaultTTL = ReadTimeSpan32()); break; case "@": domainName = (defaultDomainName = Origin); break; default: domainName = (defaultDomainName = MakeAbsoluteDomainName(text)); break; } } else if (text.All((char c) => char.IsDigit(c))) { timeSpan = TimeSpan.FromSeconds(uint.Parse(text)); } else if (text.StartsWith("TYPE")) { dnsType = (DnsType)ushort.Parse(text.Substring(4), CultureInfo.InvariantCulture); } else if (text.ToLowerInvariant() != "any" && Enum.TryParse(text, out result)) { dnsType = result; } else if (text.StartsWith("CLASS")) { dnsClass = (DnsClass)ushort.Parse(text.Substring(5), CultureInfo.InvariantCulture); } else { if (!Enum.TryParse(text, out var result2)) { throw new InvalidDataException("Unknown token '" + text + "', expected a Class, Type or TTL."); } dnsClass = result2; } } if (domainName == null) { throw new InvalidDataException("Missing resource record name."); } ResourceRecord resourceRecord = ResourceRegistry.Create(dnsType.Value); resourceRecord.Name = domainName; resourceRecord.Type = dnsType.Value; resourceRecord.Class = dnsClass; if (timeSpan.HasValue) { resourceRecord.TTL = timeSpan.Value; } resourceRecord.ReadData(this); return resourceRecord; } public bool IsEndOfLine() { int num; while (parenLevel > 0) { for (; (num = text.Peek()) >= 0; num = text.Read(), previousChar = num) { switch (num) { case 9: case 10: case 13: case 32: continue; case 41: break; default: return false; } parenLevel--; num = text.Read(); previousChar = num; break; } } if (eolSeen) { return true; } while ((num = text.Peek()) >= 0) { switch (num) { case 9: case 32: break; default: return num == 59; case 10: case 13: return true; } num = text.Read(); previousChar = num; } return true; } private string ReadToken(bool ignoreEscape = false) { StringBuilder stringBuilder = new StringBuilder(); bool flag = true; bool flag2 = false; bool flag3 = false; eolSeen = false; int num; while ((num = text.Read()) >= 0) { if (flag3) { if (num == 13 || num == 10) { flag3 = false; flag = true; } previousChar = num; continue; } if (num == 92) { if (ignoreEscape) { if (stringBuilder.Length == 0) { tokenStartsNewLine = previousChar == 13 || previousChar == 10; } stringBuilder.Append((char)num); previousChar = num; num = text.Read(); if (0 <= num) { stringBuilder.Append((char)num); previousChar = num; } continue; } previousChar = num; int i = 0; int num2 = 0; for (; i <= 3; i++) { num = text.Peek(); if (48 > num || num > 57) { break; } text.Read(); num2 = num2 * 10 + (num - 48); if (num2 > 255) { throw new FormatException("Invalid value."); } } num = ((i > 0) ? num2 : text.Read()); stringBuilder.Append((char)num); flag = false; previousChar = (ushort)num; continue; } if (flag2) { if (num == 34) { flag2 = false; break; } stringBuilder.Append((char)num); previousChar = num; continue; } switch (num) { case 34: flag2 = true; previousChar = num; continue; case 40: parenLevel++; num = 32; break; } if (num == 41) { parenLevel--; num = 32; } if (flag) { if (char.IsWhiteSpace((char)num)) { previousChar = num; continue; } flag = false; } if (char.IsWhiteSpace((char)num)) { previousChar = num; eolSeen = num == 13 || num == 10; break; } if (num == 59) { flag3 = true; previousChar = num; continue; } if (stringBuilder.Length == 0) { tokenStartsNewLine = previousChar == 13 || previousChar == 10; } stringBuilder.Append((char)num); previousChar = num; } return stringBuilder.ToString(); } } public class PresentationWriter { private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private TextWriter text; public PresentationWriter(TextWriter text) { this.text = text; } public void WriteSpace() { text.Write(' '); } public void WriteEndOfLine() { text.Write("\r\n"); } public void WriteByte(byte value, bool appendSpace = true) { text.Write(value); if (appendSpace) { WriteSpace(); } } public void WriteUInt16(ushort value, bool appendSpace = true) { text.Write(value); if (appendSpace) { WriteSpace(); } } public void WriteUInt32(uint value, bool appendSpace = true) { text.Write(value); if (appendSpace) { WriteSpace(); } } public void WriteString(string value, bool appendSpace = true) { bool flag = false; if (value == null) { value = string.Empty; } if (value == string.Empty) { flag = true; } value = value.Replace("\\", "\\\\").Replace("\"", "\\\""); if (Enumerable.Contains(value, ' ')) { flag = true; } if (flag) { text.Write('"'); } text.Write(value); if (flag) { text.Write('"'); } if (appendSpace) { WriteSpace(); } } public void WriteStringUnencoded(string value, bool appendSpace = true) { text.Write(value); if (appendSpace) { WriteSpace(); } } public void WriteDomainName(DomainName value, bool appendSpace = true) { WriteStringUnencoded(value.ToString(), appendSpace); } public void WriteBase16String(byte[] value, bool appendSpace = true) { WriteString(Base16.EncodeLower(value), appendSpace); } public void WriteBase64String(byte[] value, bool appendSpace = true) { WriteString(Convert.ToBase64String(value), appendSpace); } public void WriteTimeSpan16(TimeSpan value, bool appendSpace = true) { WriteUInt16((ushort)value.TotalSeconds, appendSpace); } public void WriteTimeSpan32(TimeSpan value, bool appendSpace = true) { WriteUInt32((uint)value.TotalSeconds, appendSpace); } public void WriteDateTime(DateTime value, bool appendSpace = true) { WriteString(value.ToUniversalTime().ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture), appendSpace); } public void WriteIPAddress(IPAddress value, bool appendSpace = true) { WriteString(value.ToString(), appendSpace); } public void WriteDnsType(DnsType value, bool appendSpace = true) { if (!Enum.IsDefined(typeof(DnsType), value)) { text.Write("TYPE"); } text.Write(value); if (appendSpace) { WriteSpace(); } } public void WriteDnsClass(DnsClass value, bool appendSpace = true) { if (!Enum.IsDefined(typeof(DnsClass), value)) { text.Write("CLASS"); } text.Write(value); if (appendSpace) { WriteSpace(); } } } public class PTRRecord : ResourceRecord { public DomainName DomainName { get; set; } public PTRRecord() { base.Type = DnsType.PTR; } public override void ReadData(WireReader reader, int length) { DomainName = reader.ReadDomainName(); } public override void ReadData(PresentationReader reader) { DomainName = reader.ReadDomainName(); } public override void WriteData(WireWriter writer) { writer.WriteDomainName(DomainName); } public override void WriteData(PresentationWriter writer) { writer.WriteDomainName(DomainName, appendSpace: false); } } public class Question : DnsObject { public DomainName Name { get; set; } public DnsType Type { get; set; } public DnsClass Class { get; set; } = DnsClass.IN; public override IWireSerialiser Read(WireReader reader) { Name = reader.ReadDomainName(); Type = (DnsType)reader.ReadUInt16(); Class = (DnsClass)reader.ReadUInt16(); return this; } public override void Write(WireWriter writer) { writer.WriteDomainName(Name); writer.WriteUInt16((ushort)Type); writer.WriteUInt16((ushort)Class); } public override string ToString() { using StringWriter stringWriter = new StringWriter(); PresentationWriter presentationWriter = new PresentationWriter(stringWriter); presentationWriter.WriteDomainName(Name); presentationWriter.WriteDnsClass(Class); presentationWriter.WriteDnsType(Type, appendSpace: false); return stringWriter.ToString(); } } public class ResourceRecord : DnsObject, IPresentationSerialiser { public static TimeSpan DefaultTTL = TimeSpan.FromDays(1.0); public static TimeSpan DefaultHostTTL = TimeSpan.FromDays(1.0); public DomainName Name { get; set; } public string CanonicalName => Name.ToCanonical().ToString(); public DnsType Type { get; set; } public DnsClass Class { get; set; } = DnsClass.IN; public TimeSpan TTL { get; set; } = DefaultTTL; public bool IsExpired(DateTime? from = null) { DateTime dateTime = from ?? DateTime.Now; return base.CreationTime + TTL <= dateTime; } public int GetDataLength() { using MemoryStream memoryStream = new MemoryStream(); WireWriter writer = new WireWriter(memoryStream); WriteData(writer); return (int)memoryStream.Length; } public byte[] GetData() { using MemoryStream memoryStream = new MemoryStream(); WireWriter writer = new WireWriter(memoryStream); WriteData(writer); return memoryStream.ToArray(); } public override IWireSerialiser Read(WireReader reader) { Name = reader.ReadDomainName(); Type = (DnsType)reader.ReadUInt16(); Class = (DnsClass)reader.ReadUInt16(); TTL = reader.ReadTimeSpan32(); int num = reader.ReadUInt16(); ResourceRecord resourceRecord = ResourceRegistry.Create(Type); resourceRecord.Name = Name; resourceRecord.Type = Type; resourceRecord.Class = Class; resourceRecord.TTL = TTL; int num2 = reader.Position + num; resourceRecord.ReadData(reader, num); if (reader.Position != num2) { throw new InvalidDataException("Found extra data while decoding RDATA."); } return resourceRecord; } public virtual void ReadData(WireReader reader, int length) { } public override void Write(WireWriter writer) { writer.WriteDomainName(Name); writer.WriteUInt16((ushort)Type); writer.WriteUInt16((ushort)Class); writer.WriteTimeSpan32(TTL); writer.PushLengthPrefixedScope(); WriteData(writer); writer.PopLengthPrefixedScope(); } public virtual void WriteData(WireWriter writer) { } public override bool Equals(object obj) { ResourceRecord resourceRecord = obj as ResourceRecord; if (resourceRecord == null) { return false; } if (Name != resourceRecord.Name) { return false; } if (Class != resourceRecord.Class) { return false; } if (Type != resourceRecord.Type) { return false; } return GetData().SequenceEqual(resourceRecord.GetData()); } public static bool operator ==(ResourceRecord a, ResourceRecord b) { if ((object)a == b) { return true; } if ((object)a == null) { return false; } if ((object)b == null) { return false; } return a.Equals(b); } public static bool operator !=(ResourceRecord a, ResourceRecord b) { if ((object)a == b) { return false; } if ((object)a == null) { return true; } if ((object)b == null) { return true; } return !a.Equals(b); } public override int GetHashCode() { return Name?.GetHashCode() ?? (0 ^ Class.GetHashCode() ^ Type.GetHashCode() ^ GetData().Aggregate(0, (int r, byte b) => r ^ b.GetHashCode())); } public override string ToString() { using StringWriter stringWriter = new StringWriter(); Write(new PresentationWriter(stringWriter)); StringBuilder stringBuilder = stringWriter.GetStringBuilder(); while (stringBuilder.Length > 0 && char.IsWhiteSpace(stringBuilder[stringBuilder.Length - 1])) { int length = stringBuilder.Length - 1; stringBuilder.Length = length; } return stringBuilder.ToString(); } public void Write(PresentationWriter writer) { writer.WriteDomainName(Name); if (TTL != DefaultTTL) { writer.WriteTimeSpan32(TTL); } writer.WriteDnsClass(Class); writer.WriteDnsType(Type); WriteData(writer); writer.WriteEndOfLine(); } public virtual void WriteData(PresentationWriter writer) { byte[] data = GetData(); bool flag = data.Length != 0; writer.WriteStringUnencoded("\\#"); writer.WriteUInt32((uint)data.Length, flag); if (flag) { writer.WriteBase16String(data, appendSpace: false); } } public ResourceRecord Read(string text) { return Read(new PresentationReader(new StringReader(text))); } public ResourceRecord Read(PresentationReader reader) { return reader.ReadResourceRecord(); } public virtual void ReadData(PresentationReader reader) { } } public static class ResourceRegistry { public static Dictionary> Records; static ResourceRegistry() { Records = new Dictionary>(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); Register(); } public static void Register() where T : ResourceRecord, new() { T val = new T(); if (val.Type == (DnsType)0) { throw new ArgumentException("The RR TYPE is not defined.", "TYPE"); } Records.Add(val.Type, () => new T()); } public static ResourceRecord Create(DnsType type) { if (Records.TryGetValue(type, out var value)) { return value(); } return new UnknownRecord(); } } public class RPRecord : ResourceRecord { public DomainName Mailbox { get; set; } = DomainName.Root; public DomainName TextName { get; set; } = DomainName.Root; public RPRecord() { base.Type = DnsType.RP; } public override void ReadData(WireReader reader, int length) { Mailbox = reader.ReadDomainName(); TextName = reader.ReadDomainName(); } public override void ReadData(PresentationReader reader) { Mailbox = reader.ReadDomainName(); TextName = reader.ReadDomainName(); } public override void WriteData(WireWriter writer) { writer.WriteDomainName(Mailbox); writer.WriteDomainName(TextName); } public override void WriteData(PresentationWriter writer) { writer.WriteDomainName(Mailbox); writer.WriteDomainName(TextName, appendSpace: false); } } public class RRSIGRecord : ResourceRecord { public DnsType TypeCovered { get; set; } public SecurityAlgorithm Algorithm { get; set; } public byte Labels { get; set; } public TimeSpan OriginalTTL { get; set; } public DateTime SignatureExpiration { get; set; } public DateTime SignatureInception { get; set; } public ushort KeyTag { get; set; } public DomainName SignerName { get; set; } public byte[] Signature { get; set; } public RRSIGRecord() { base.Type = DnsType.RRSIG; } public override void ReadData(WireReader reader, int length) { int num = reader.Position + length; TypeCovered = (DnsType)reader.ReadUInt16(); Algorithm = (SecurityAlgorithm)reader.ReadByte(); Labels = reader.ReadByte(); OriginalTTL = reader.ReadTimeSpan32(); SignatureExpiration = reader.ReadDateTime32(); SignatureInception = reader.ReadDateTime32(); KeyTag = reader.ReadUInt16(); SignerName = reader.ReadDomainName(); Signature = reader.ReadBytes(num - reader.Position); } public override void WriteData(WireWriter writer) { writer.WriteUInt16((ushort)TypeCovered); writer.WriteByte((byte)Algorithm); writer.WriteByte(Labels); writer.WriteTimeSpan32(OriginalTTL); writer.WriteDateTime32(SignatureExpiration); writer.WriteDateTime32(SignatureInception); writer.WriteUInt16(KeyTag); writer.WriteDomainName(SignerName, uncompressed: true); writer.WriteBytes(Signature); } public override void ReadData(PresentationReader reader) { TypeCovered = reader.ReadDnsType(); Algorithm = (SecurityAlgorithm)reader.ReadByte(); Labels = reader.ReadByte(); OriginalTTL = reader.ReadTimeSpan32(); SignatureExpiration = reader.ReadDateTime(); SignatureInception = reader.ReadDateTime(); KeyTag = reader.ReadUInt16(); SignerName = reader.ReadDomainName(); Signature = reader.ReadBase64String(); } public override void WriteData(PresentationWriter writer) { writer.WriteDnsType(TypeCovered); writer.WriteByte((byte)Algorithm); writer.WriteByte(Labels); writer.WriteTimeSpan32(OriginalTTL); writer.WriteDateTime(SignatureExpiration); writer.WriteDateTime(SignatureInception); writer.WriteUInt16(KeyTag); writer.WriteDomainName(SignerName); writer.WriteBase64String(Signature, appendSpace: false); } } public enum SecurityAlgorithm : byte { DELETE = 0, RSAMD5 = 1, DH = 2, DSA = 3, RSASHA1 = 5, DSANSEC3SHA1 = 6, RSASHA1NSEC3SHA1 = 7, RSASHA256 = 8, RSASHA512 = 10, ECCGOST = 12, ECDSAP256SHA256 = 13, ECDSAP384SHA384 = 14, ED25519 = 15, ED448 = 16, INDIRECT = 252, PRIVATEDNS = 253, PRIVATEOID = 254 } public static class SecurityAlgorithmRegistry { public class Metadata { public DigestType HashAlgorithm { get; set; } public string[] OtherNames { get; set; } = new string[0]; } public static Dictionary Algorithms; static SecurityAlgorithmRegistry() { Algorithms = new Dictionary(); Algorithms.Add(SecurityAlgorithm.RSASHA1, new Metadata { HashAlgorithm = DigestType.Sha1 }); Algorithms.Add(SecurityAlgorithm.RSASHA256, new Metadata { HashAlgorithm = DigestType.Sha256 }); Algorithms.Add(SecurityAlgorithm.RSASHA512, new Metadata { HashAlgorithm = DigestType.Sha512 }); Algorithms.Add(SecurityAlgorithm.DSA, new Metadata { HashAlgorithm = DigestType.Sha1 }); Algorithms.Add(SecurityAlgorithm.ECDSAP256SHA256, new Metadata { HashAlgorithm = DigestType.Sha256, OtherNames = new string[2] { "nistP256", "ECDSA_P256" } }); Algorithms.Add(SecurityAlgorithm.ECDSAP384SHA384, new Metadata { HashAlgorithm = DigestType.Sha384, OtherNames = new string[2] { "nistP384", "ECDSA_P384" } }); Algorithms.Add(SecurityAlgorithm.RSASHA1NSEC3SHA1, Algorithms[SecurityAlgorithm.RSASHA1]); Algorithms.Add(SecurityAlgorithm.DSANSEC3SHA1, Algorithms[SecurityAlgorithm.DSA]); } public static Metadata GetMetadata(SecurityAlgorithm algorithm) { if (Algorithms.TryGetValue(algorithm, out var value)) { return value; } throw new NotImplementedException($"The security algorithm '{algorithm}' is not defined."); } } public class SOARecord : ResourceRecord { public DomainName PrimaryName { get; set; } public DomainName Mailbox { get; set; } public uint SerialNumber { get; set; } public TimeSpan Refresh { get; set; } public TimeSpan Retry { get; set; } public TimeSpan Expire { get; set; } public TimeSpan Minimum { get; set; } public SOARecord() { base.Type = DnsType.SOA; base.TTL = TimeSpan.FromSeconds(0.0); } public override void ReadData(WireReader reader, int length) { PrimaryName = reader.ReadDomainName(); Mailbox = reader.ReadDomainName(); SerialNumber = reader.ReadUInt32(); Refresh = reader.ReadTimeSpan32(); Retry = reader.ReadTimeSpan32(); Expire = reader.ReadTimeSpan32(); Minimum = reader.ReadTimeSpan32(); } public override void ReadData(PresentationReader reader) { PrimaryName = reader.ReadDomainName(); Mailbox = reader.ReadDomainName(); SerialNumber = reader.ReadUInt32(); Refresh = reader.ReadTimeSpan32(); Retry = reader.ReadTimeSpan32(); Expire = reader.ReadTimeSpan32(); Minimum = reader.ReadTimeSpan32(); } public override void WriteData(WireWriter writer) { writer.WriteDomainName(PrimaryName); writer.WriteDomainName(Mailbox); writer.WriteUInt32(SerialNumber); writer.WriteTimeSpan32(Refresh); writer.WriteTimeSpan32(Retry); writer.WriteTimeSpan32(Expire); writer.WriteTimeSpan32(Minimum); } public override void WriteData(PresentationWriter writer) { writer.WriteDomainName(PrimaryName); writer.WriteDomainName(Mailbox); writer.WriteUInt32(SerialNumber); writer.WriteTimeSpan32(Refresh); writer.WriteTimeSpan32(Retry); writer.WriteTimeSpan32(Expire); writer.WriteTimeSpan32(Minimum, appendSpace: false); } } public class SRVRecord : ResourceRecord { public ushort Priority { get; set; } public ushort Weight { get; set; } public ushort Port { get; set; } public DomainName Target { get; set; } public SRVRecord() { base.Type = DnsType.SRV; } public override void ReadData(WireReader reader, int length) { Priority = reader.ReadUInt16(); Weight = reader.ReadUInt16(); Port = reader.ReadUInt16(); Target = reader.ReadDomainName(); } public override void ReadData(PresentationReader reader) { Priority = reader.ReadUInt16(); Weight = reader.ReadUInt16(); Port = reader.ReadUInt16(); Target = reader.ReadDomainName(); } public override void WriteData(WireWriter writer) { writer.WriteUInt16(Priority); writer.WriteUInt16(Weight); writer.WriteUInt16(Port); writer.WriteDomainName(Target); } public override void WriteData(PresentationWriter writer) { writer.WriteUInt16(Priority); writer.WriteUInt16(Weight); writer.WriteUInt16(Port); writer.WriteDomainName(Target, appendSpace: false); } } public class TKEYRecord : ResourceRecord { private static readonly byte[] NoData = new byte[0]; public DomainName Algorithm { get; set; } public DateTime Inception { get; set; } public DateTime Expiration { get; set; } public KeyExchangeMode Mode { get; set; } public MessageStatus Error { get; set; } public byte[] Key { get; set; } public byte[] OtherData { get; set; } public TKEYRecord() { base.Type = DnsType.TKEY; base.Class = DnsClass.ANY; base.TTL = TimeSpan.Zero; _ = DateTime.UtcNow; OtherData = NoData; } public override void ReadData(WireReader reader, int length) { Algorithm = reader.ReadDomainName(); Inception = reader.ReadDateTime32(); Expiration = reader.ReadDateTime32(); Mode = (KeyExchangeMode)reader.ReadUInt16(); Error = (MessageStatus)reader.ReadUInt16(); Key = reader.ReadUInt16LengthPrefixedBytes(); OtherData = reader.ReadUInt16LengthPrefixedBytes(); } public override void WriteData(WireWriter writer) { writer.WriteDomainName(Algorithm); writer.WriteDateTime32(Inception); writer.WriteDateTime32(Expiration); writer.WriteUInt16((ushort)Mode); writer.WriteUInt16((ushort)Error); writer.WriteUint16LengthPrefixedBytes(Key); writer.WriteUint16LengthPrefixedBytes(OtherData); } public override void ReadData(PresentationReader reader) { Algorithm = reader.ReadDomainName(); Inception = reader.ReadDateTime(); Expiration = reader.ReadDateTime(); Mode = (KeyExchangeMode)reader.ReadUInt16(); Error = (MessageStatus)reader.ReadUInt16(); Key = Convert.FromBase64String(reader.ReadString()); OtherData = Convert.FromBase64String(reader.ReadString()); } public override void WriteData(PresentationWriter writer) { writer.WriteDomainName(Algorithm); writer.WriteDateTime(Inception); writer.WriteDateTime(Expiration); writer.WriteUInt16((ushort)Mode); writer.WriteUInt16((ushort)Error); writer.WriteBase64String(Key); writer.WriteBase64String(OtherData ?? NoData, appendSpace: false); } } public class TSIGRecord : ResourceRecord { private static readonly byte[] NoData = new byte[0]; public const string HMACMD5 = "HMAC-MD5.SIG-ALG.REG.INT"; public const string GSSTSIG = "gss-tsig"; public const string HMACSHA1 = "hmac-sha1"; public const string HMACSHA224 = "hmac-sha224"; public const string HMACSHA256 = "hmac-sha256"; public const string HMACSHA384 = "hmac-sha384"; public const string HMACSHA512 = "hmac-sha512"; public DomainName Algorithm { get; set; } public DateTime TimeSigned { get; set; } public byte[] MAC { get; set; } public TimeSpan Fudge { get; set; } public ushort OriginalMessageId { get; set; } public MessageStatus Error { get; set; } public byte[] OtherData { get; set; } public TSIGRecord() { base.Type = DnsType.TSIG; base.Class = DnsClass.ANY; base.TTL = TimeSpan.Zero; DateTime utcNow = DateTime.UtcNow; TimeSigned = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, utcNow.Hour, utcNow.Minute, utcNow.Second, utcNow.Kind); Fudge = TimeSpan.FromSeconds(300.0); OtherData = NoData; } public override void ReadData(WireReader reader, int length) { Algorithm = reader.ReadDomainName(); TimeSigned = reader.ReadDateTime48(); Fudge = reader.ReadTimeSpan16(); MAC = reader.ReadUInt16LengthPrefixedBytes(); OriginalMessageId = reader.ReadUInt16(); Error = (MessageStatus)reader.ReadUInt16(); OtherData = reader.ReadUInt16LengthPrefixedBytes(); } public override void WriteData(WireWriter writer) { writer.WriteDomainName(Algorithm); writer.WriteDateTime48(TimeSigned); writer.WriteTimeSpan16(Fudge); writer.WriteUint16LengthPrefixedBytes(MAC); writer.WriteUInt16(OriginalMessageId); writer.WriteUInt16((ushort)Error); writer.WriteUint16LengthPrefixedBytes(OtherData); } public override void ReadData(PresentationReader reader) { Algorithm = reader.ReadDomainName(); TimeSigned = reader.ReadDateTime(); Fudge = reader.ReadTimeSpan16(); MAC = Convert.FromBase64String(reader.ReadString()); OriginalMessageId = reader.ReadUInt16(); Error = (MessageStatus)reader.ReadUInt16(); OtherData = Convert.FromBase64String(reader.ReadString()); } public override void WriteData(PresentationWriter writer) { writer.WriteDomainName(Algorithm); writer.WriteDateTime(TimeSigned); writer.WriteTimeSpan16(Fudge); writer.WriteBase64String(MAC); writer.WriteUInt16(OriginalMessageId); writer.WriteUInt16((ushort)Error); writer.WriteBase64String(OtherData ?? NoData, appendSpace: false); } } public class TXTRecord : ResourceRecord { public List Strings { get; set; } = new List(); public TXTRecord() { base.Type = DnsType.TXT; } public override void ReadData(WireReader reader, int length) { while (length > 0) { string text = reader.ReadString(); Strings.Add(text); length -= Encoding.UTF8.GetByteCount(text) + 1; } } public override void ReadData(PresentationReader reader) { while (!reader.IsEndOfLine()) { Strings.Add(reader.ReadString()); } } public override void WriteData(WireWriter writer) { foreach (string @string in Strings) { writer.WriteString(@string); } } public override void WriteData(PresentationWriter writer) { bool flag = false; foreach (string @string in Strings) { if (flag) { writer.WriteSpace(); } writer.WriteString(@string, appendSpace: false); flag = true; } } } public class UnknownEdnsOption : EdnsOption { public byte[] Data { get; set; } public override void ReadData(WireReader reader, int length) { Data = reader.ReadBytes(length); } public override void WriteData(WireWriter writer) { writer.WriteBytes(Data); } public override string ToString() { return $"; Type = {base.Type}; Data = {Convert.ToBase64String(Data)}"; } } public class UnknownRecord : ResourceRecord { public byte[] Data { get; set; } public override void ReadData(WireReader reader, int length) { Data = reader.ReadBytes(length); } public override void ReadData(PresentationReader reader) { Data = reader.ReadResourceData(); } public override void WriteData(WireWriter writer) { writer.WriteBytes(Data); } } public class UpdateMessage : DnsObject { public ushort Id { get; set; } public bool QR { get; set; } public bool IsUpdate => !QR; public bool IsResponse => QR; public MessageOperation Opcode { get; set; } = MessageOperation.Update; public int Z { get; set; } public MessageStatus Status { get; set; } public Question Zone { get; set; } = new Question { Class = DnsClass.IN, Type = DnsType.SOA }; public UpdatePrerequisiteList Prerequisites { get; } = new UpdatePrerequisiteList(); public UpdateResourceList Updates { get; } = new UpdateResourceList(); public List AdditionalResources { get; } = new List(); public UpdateMessage CreateResponse() { return new UpdateMessage { Id = Id, Opcode = Opcode, QR = true }; } public override IWireSerialiser Read(WireReader reader) { Id = reader.ReadUInt16(); ushort num = reader.ReadUInt16(); QR = (num & 0x8000) == 32768; Opcode = (MessageOperation)((num & 0x7800) >> 11); Z = (num & 0x7F0) >> 4; Status = (MessageStatus)(num & 0xF); ushort num2 = reader.ReadUInt16(); ushort num3 = reader.ReadUInt16(); ushort num4 = reader.ReadUInt16(); ushort num5 = reader.ReadUInt16(); for (int i = 0; i < num2; i++) { Zone = (Question)new Question().Read(reader); } for (int j = 0; j < num3; j++) { ResourceRecord item = (ResourceRecord)new ResourceRecord().Read(reader); Prerequisites.Add(item); } for (int k = 0; k < num4; k++) { ResourceRecord item2 = (ResourceRecord)new ResourceRecord().Read(reader); Updates.Add(item2); } for (int l = 0; l < num5; l++) { ResourceRecord item3 = (ResourceRecord)new ResourceRecord().Read(reader); AdditionalResources.Add(item3); } return this; } public override void Write(WireWriter writer) { writer.WriteUInt16(Id); int num = (int)((uint)(Convert.ToInt32(QR) << 15) | ((uint)(Opcode & (MessageOperation)15) << 11) | (uint)((Z & 0x7F) << 4)) | (int)(Status & (MessageStatus)15); writer.WriteUInt16((ushort)num); writer.WriteUInt16(1); writer.WriteUInt16((ushort)Prerequisites.Count); writer.WriteUInt16((ushort)Updates.Count); writer.WriteUInt16((ushort)AdditionalResources.Count); Zone.Write(writer); foreach (ResourceRecord prerequisite in Prerequisites) { prerequisite.Write(writer); } foreach (ResourceRecord update in Updates) { update.Write(writer); } foreach (ResourceRecord additionalResource in AdditionalResources) { additionalResource.Write(writer); } } } public class UpdatePrerequisiteList : List { public UpdatePrerequisiteList MustExist(DomainName name, DnsType type) { ResourceRecord item = new ResourceRecord { Name = name, Type = type, Class = DnsClass.ANY, TTL = TimeSpan.Zero }; Add(item); return this; } public UpdatePrerequisiteList MustExist(DomainName name) { return MustExist(name, DnsType.ANY); } public UpdatePrerequisiteList MustExist(DomainName name) where T : ResourceRecord, new() { return MustExist(name, new T().Type); } public UpdatePrerequisiteList MustExist(ResourceRecord resource) { resource.TTL = TimeSpan.Zero; Add(resource); return this; } public UpdatePrerequisiteList MustNotExist(DomainName name, DnsType type) { ResourceRecord item = new ResourceRecord { Name = name, Type = type, Class = DnsClass.None, TTL = TimeSpan.Zero }; Add(item); return this; } public UpdatePrerequisiteList MustNotExist(DomainName name) { return MustNotExist(name, DnsType.ANY); } public UpdatePrerequisiteList MustNotExist(DomainName name) where T : ResourceRecord, new() { return MustNotExist(name, new T().Type); } } public class UpdateResourceList : List { public UpdateResourceList AddResource(ResourceRecord resource) { Add(resource); return this; } public UpdateResourceList DeleteResource(ResourceRecord resource) { resource.Class = DnsClass.None; resource.TTL = TimeSpan.Zero; Add(resource); return this; } public UpdateResourceList DeleteResource(DomainName name) { ResourceRecord item = new ResourceRecord { Name = name, Class = DnsClass.ANY, Type = DnsType.ANY, TTL = TimeSpan.Zero }; Add(item); return this; } public UpdateResourceList DeleteResource(DomainName name, DnsType type) { ResourceRecord item = new ResourceRecord { Name = name, Class = DnsClass.ANY, Type = type, TTL = TimeSpan.Zero }; Add(item); return this; } public UpdateResourceList DeleteResource(DomainName name) where T : ResourceRecord, new() { return DeleteResource(name, new T().Type); } } public class WireReader { private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private Stream stream; private readonly Dictionary> names = new Dictionary>(); public int Position; public WireReader(Stream stream) { this.stream = stream; } public byte ReadByte() { int num = stream.ReadByte(); if (num < 0) { throw new EndOfStreamException(); } Position++; return (byte)num; } public byte[] ReadBytes(int length) { byte[] array = new byte[length]; int num = 0; while (length > 0) { int num2 = stream.Read(array, num, length); if (num2 == 0) { throw new EndOfStreamException(); } num += num2; length -= num2; Position += num2; } return array; } public byte[] ReadByteLengthPrefixedBytes() { int length = ReadByte(); return ReadBytes(length); } public byte[] ReadUInt16LengthPrefixedBytes() { int length = ReadUInt16(); return ReadBytes(length); } public ushort ReadUInt16() { return (ushort)((ReadByte() << 8) | ReadByte()); } public uint ReadUInt32() { return (uint)((((((ReadByte() << 8) | ReadByte()) << 8) | ReadByte()) << 8) | ReadByte()); } public ulong ReadUInt48() { return ((((((((((ulong)ReadByte() << 8) | ReadByte()) << 8) | ReadByte()) << 8) | ReadByte()) << 8) | ReadByte()) << 8) | ReadByte(); } public DomainName ReadDomainName() { return new DomainName(ReadLabels().ToArray()); } private List ReadLabels() { int position = Position; byte b = ReadByte(); if ((b & 0xC0) == 192) { int key = ((b ^ 0xC0) << 8) | ReadByte(); List list = names[key]; names[position] = list; return list; } List list2 = new List(); if (b == 0) { return list2; } byte[] bytes = ReadBytes(b); list2.Add(Encoding.UTF8.GetString(bytes, 0, b)); list2.AddRange(ReadLabels()); names[position] = list2; return list2; } public string ReadString() { byte[] array = ReadByteLengthPrefixedBytes(); if (array.Any((byte c) => c > 127)) { throw new InvalidDataException("Only ASCII characters are allowed."); } return Encoding.ASCII.GetString(array); } public TimeSpan ReadTimeSpan16() { return TimeSpan.FromSeconds((int)ReadUInt16()); } public TimeSpan ReadTimeSpan32() { return TimeSpan.FromSeconds(ReadUInt32()); } public IPAddress ReadIPAddress(int length = 4) { return new IPAddress(ReadBytes(length)); } public List ReadBitmap() { List list = new List(); byte num = ReadByte(); byte b = ReadByte(); int num2 = num * 256; int num3 = 0; while (num3 < b) { byte b2 = ReadByte(); for (int i = 0; i < 8; i++) { if ((b2 & (1 << Math.Abs(i - 7))) != 0) { list.Add((ushort)(num2 + i)); } } num3++; num2 += 8; } return list; } public DateTime ReadDateTime32() { uint num = ReadUInt32(); DateTime unixEpoch = UnixEpoch; return unixEpoch.AddSeconds(num); } public DateTime ReadDateTime48() { ulong num = ReadUInt48(); DateTime unixEpoch = UnixEpoch; return unixEpoch.AddSeconds(num); } } public class WireWriter { private const int maxPointer = 16383; private const ulong uint48MaxValue = 281474976710655uL; private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private Stream stream; private Dictionary pointers = new Dictionary(); private Stack scopes = new Stack(); public int Position; public bool CanonicalForm { get; set; } public WireWriter(Stream stream) { this.stream = stream; } public void PushLengthPrefixedScope() { scopes.Push(stream); stream = new MemoryStream(); Position += 2; } public ushort PopLengthPrefixedScope() { Stream obj = stream; ushort num = (ushort)obj.Position; stream = scopes.Pop(); WriteUInt16(num); Position -= 2; obj.Position = 0L; obj.CopyTo(stream); return num; } public void WriteByte(byte value) { stream.WriteByte(value); Position++; } public void WriteBytes(byte[] bytes) { if (bytes != null) { stream.Write(bytes, 0, bytes.Length); Position += bytes.Length; } } public void WriteByteLengthPrefixedBytes(byte[] bytes) { int num = ((bytes != null) ? bytes.Length : 0); if (num > 255) { throw new ArgumentException($"Length can not exceed {byte.MaxValue}.", "bytes"); } WriteByte((byte)num); WriteBytes(bytes); } public void WriteUint16LengthPrefixedBytes(byte[] bytes) { int num = ((bytes != null) ? bytes.Length : 0); if (num > 65535) { throw new ArgumentException($"Bytes length can not exceed {ushort.MaxValue}."); } WriteUInt16((ushort)num); WriteBytes(bytes); } public void WriteUInt16(ushort value) { stream.WriteByte((byte)(value >> 8)); stream.WriteByte((byte)value); Position += 2; } public void WriteUInt32(uint value) { stream.WriteByte((byte)(value >> 24)); stream.WriteByte((byte)(value >> 16)); stream.WriteByte((byte)(value >> 8)); stream.WriteByte((byte)value); Position += 4; } public void WriteUInt48(ulong value) { if (value > 281474976710655L) { throw new ArgumentException("Value is greater than 48 bits."); } stream.WriteByte((byte)(value >> 40)); stream.WriteByte((byte)(value >> 32)); stream.WriteByte((byte)(value >> 24)); stream.WriteByte((byte)(value >> 16)); stream.WriteByte((byte)(value >> 8)); stream.WriteByte((byte)value); Position += 6; } public void WriteDomainName(string name, bool uncompressed = false) { if (string.IsNullOrEmpty(name)) { stream.WriteByte(0); Position++; } else { WriteDomainName(new DomainName(name), uncompressed); } } public void WriteDomainName(DomainName name, bool uncompressed = false) { if (name == null) { stream.WriteByte(0); Position++; return; } if (CanonicalForm) { uncompressed = true; name = name.ToCanonical(); } string[] array = name.Labels.ToArray(); int num = array.Length; for (int i = 0; i < num; i++) { string text = array[i]; if (text.Length > 63) { throw new ArgumentException("Label '" + text + "' cannot exceed 63 octets."); } string key = string.Join(".", array, i, array.Length - i); if (!uncompressed && pointers.TryGetValue(key, out var value)) { WriteUInt16((ushort)(0xC000 | value)); return; } if (Position <= 16383) { pointers[key] = Position; } byte[] bytes = Encoding.UTF8.GetBytes(text); WriteByteLengthPrefixedBytes(bytes); } stream.WriteByte(0); Position++; } public void WriteString(string value) { if (value.Any((char c) => c > '\u007f')) { throw new ArgumentException("Only ASCII characters are allowed."); } byte[] bytes = Encoding.ASCII.GetBytes(value); WriteByteLengthPrefixedBytes(bytes); } public void WriteTimeSpan16(TimeSpan value) { WriteUInt16((ushort)value.TotalSeconds); } public void WriteTimeSpan32(TimeSpan value) { WriteUInt32((uint)value.TotalSeconds); } public void WriteDateTime32(DateTime value) { double totalSeconds = (value.ToUniversalTime() - UnixEpoch).TotalSeconds; WriteUInt32(Convert.ToUInt32(totalSeconds)); } public void WriteDateTime48(DateTime value) { double totalSeconds = (value.ToUniversalTime() - UnixEpoch).TotalSeconds; WriteUInt48(Convert.ToUInt64(totalSeconds)); } public void WriteIPAddress(IPAddress value) { WriteBytes(value.GetAddressBytes()); } public void WriteBitmap(IEnumerable values) { var array = (from w in values.Select(delegate(ushort v) { var anon2 = new { Window = v / 256, Mask = new BitArray(256) }; anon2.Mask[v & 0xFF] = true; return anon2; }) group w by w.Window into g select new { Window = g.Key, Mask = g.Select(w => w.Mask).Aggregate((BitArray a, BitArray b) => a.Or(b)) } into w orderby w.Window select w).ToArray(); foreach (var anon in array) { List list = ToBytes(anon.Mask, MSB: true).ToList(); int num2 = list.Count - 1; while (num2 > 0 && list[num2] == 0) { list.RemoveAt(num2); num2--; } stream.WriteByte((byte)anon.Window); stream.WriteByte((byte)list.Count); Position += 2; WriteBytes(list.ToArray()); } } private static IEnumerable ToBytes(BitArray bits, bool MSB = false) { int num = 7; int num2 = 0; foreach (bool bit in bits) { if (bit) { num2 |= (MSB ? (1 << num) : (1 << 7 - num)); } if (num == 0) { yield return (byte)num2; num = 8; num2 = 0; } num--; } if (num < 7) { yield return (byte)num2; } } } } namespace Makaretu.Dns.Resolving { public class CachedNameServer : NameServer { public void Prune(DateTime? now = null) { now = now ?? DateTime.Now; foreach (Node item in base.Catalog.Values.Where((Node node) => !node.Authoritative)) { foreach (ResourceRecord item2 in item.Resources.Where((ResourceRecord r) => r.IsExpired(now))) { item.Resources.Remove(item2); } } } public CancellationTokenSource PruneContinuously(TimeSpan interval) { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); CancellationToken token = cancellationTokenSource.Token; Task.Run(async delegate { while (!token.IsCancellationRequested) { Prune(); await Task.Delay(interval, token); } }, token); return cancellationTokenSource; } public void Add(Message response) { foreach (ResourceRecord item in from r in response.Answers.Concat(response.AdditionalRecords) where r.TTL > TimeSpan.Zero select r) { base.Catalog.Add(item); } } } public class Catalog : ConcurrentDictionary { public Node IncludeZone(PresentationReader reader) { List list = new List(); while (true) { ResourceRecord resourceRecord = reader.ReadResourceRecord(); if (resourceRecord == null) { break; } list.Add(resourceRecord); } if (list.Count == 0) { throw new InvalidDataException("No resources."); } if (list[0].Type != DnsType.SOA) { throw new InvalidDataException("First resource record must be a SOA."); } SOARecord soa = (SOARecord)list[0]; if (list.Any((ResourceRecord r) => !r.Name.BelongsTo(soa.Name))) { throw new InvalidDataException("All resource records must belong to the zone."); } foreach (Node item in list.GroupBy((ResourceRecord r) => r.Name, (DomainName key, IEnumerable results) => new Node { Name = key, Authoritative = true, Resources = new ConcurrentSet(results) })) { if (!TryAdd(item.Name, item)) { throw new InvalidDataException($"'{item.Name}' already exists."); } } return base[soa.Name]; } public void RemoveZone(DomainName name) { foreach (DomainName item in base.Keys.Where((DomainName k) => k.BelongsTo(name))) { TryRemove(item, out var _); } } public Node Add(ResourceRecord resource, bool authoritative = false) { Node node = AddOrUpdate(resource.Name, (DomainName k) => new Node { Name = k, Authoritative = authoritative }, (DomainName k, Node n) => n); if (!node.Resources.Add(resource)) { node.Resources.Remove(resource); node.Resources.Add(resource); } return node; } public Node IncludeRootHints() { using (Stream stream = typeof(Catalog).GetTypeInfo().Assembly.GetManifestResourceStream("Makaretu.Dns.Resolving.RootHints")) { PresentationReader presentationReader = new PresentationReader(new StreamReader(stream)); ResourceRecord resource; while (null != (resource = presentationReader.ReadResourceRecord())) { Add(resource); } } Node node = base[new DomainName("")]; node.Authoritative = true; return node; } public void Include(PresentationReader reader, bool authoritative = false) { while (true) { ResourceRecord resourceRecord = reader.ReadResourceRecord(); if (!(resourceRecord == null)) { Add(resourceRecord, authoritative); continue; } break; } } public IEnumerable NodesInCanonicalOrder() { return base.Values.OrderBy((Node node) => new DomainName(node.Name.ToCanonical().Labels.Reverse().ToArray()).ToString()); } public void IncludeReverseLookupRecords() { foreach (AddressRecord item in base.Values.Where((Node node) => node.Authoritative).SelectMany((Node node) => node.Resources.OfType())) { PTRRecord resource = new PTRRecord { Class = item.Class, Name = new DomainName(item.Address.GetArpaName()), DomainName = item.Name, TTL = item.TTL }; Add(resource, authoritative: true); } } } [DebuggerDisplay("Count = {Count}")] public sealed class ConcurrentSet : ICollection, IEnumerable, IEnumerable { public struct KeyEnumerator { private readonly IEnumerator> _kvpEnumerator; public T Current => _kvpEnumerator.Current.Key; internal KeyEnumerator(IEnumerable> data) { _kvpEnumerator = data.GetEnumerator(); } public bool MoveNext() { return _kvpEnumerator.MoveNext(); } public void Reset() { _kvpEnumerator.Reset(); } } private const int DefaultConcurrencyLevel = 2; private const int DefaultCapacity = 31; private readonly ConcurrentDictionary _dictionary; public int Count => _dictionary.Count; public bool IsEmpty => _dictionary.IsEmpty; public bool IsReadOnly => false; public ConcurrentSet() { _dictionary = new ConcurrentDictionary(2, 31); } public ConcurrentSet(IEqualityComparer equalityComparer) { _dictionary = new ConcurrentDictionary(2, 31, equalityComparer); } public ConcurrentSet(IEnumerable values) : this() { AddRange(values); } public bool Contains(T value) { return _dictionary.ContainsKey(value); } public bool Add(T value) { return _dictionary.TryAdd(value, 0); } public void AddRange(IEnumerable values) { if (values == null) { return; } foreach (T value in values) { Add(value); } } public bool Remove(T value) { byte value2; return _dictionary.TryRemove(value, out value2); } public void Clear() { _dictionary.Clear(); } public KeyEnumerator GetEnumerator() { return new KeyEnumerator(_dictionary); } private IEnumerator GetEnumeratorImpl() { foreach (KeyValuePair item in _dictionary) { yield return item.Key; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumeratorImpl(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumeratorImpl(); } void ICollection.Add(T item) { Add(item); } public void CopyTo(T[] array, int arrayIndex) { _dictionary.Keys.CopyTo(array, arrayIndex); } } public class NameServer : IResolver { public Catalog Catalog { get; set; } public bool AnswerAllQuestions { get; set; } public async Task ResolveAsync(Message request, CancellationToken cancel = default(CancellationToken)) { Message response = request.CreateResponse(); foreach (Question question in request.Questions) { await ResolveAsync(question, response, cancel); if (response.Answers.Count > 0 && !AnswerAllQuestions) { break; } } if (response.Answers.Count > 0) { response.Status = MessageStatus.NoError; } if (response.Answers.Count > 1) { response.Answers = response.Answers.Distinct().ToList(); } if (response.AuthorityRecords.Count > 1) { response.AuthorityRecords = response.AuthorityRecords.Distinct().ToList(); } if (response.AdditionalRecords.Count > 0) { response.AdditionalRecords = response.AdditionalRecords.Where((ResourceRecord a) => !response.Answers.Contains(a)).ToList(); } return await AddSecurityExtensionsAsync(request, response); } public async Task ResolveAsync(Question question, Message response = null, CancellationToken cancel = default(CancellationToken)) { response = response ?? new Message { QR = true }; bool num = await FindAnswerAsync(question, response, cancel); SOARecord soa = FindAuthority(question.Name); if (!num && response.Status == MessageStatus.NoError) { response.Status = MessageStatus.NameError; } if (num && soa != null) { Message res = new Message(); Question question2 = new Question { Name = soa.Name, Class = soa.Class, Type = DnsType.NS }; await FindAnswerAsync(question2, res, cancel); response.AuthorityRecords.AddRange(res.Answers.OfType()); } if (response.Status == MessageStatus.NameError && soa != null) { response.AuthorityRecords.Add(soa); } AddAdditionalRecords(response); return response; } protected Task FindAnswerAsync(Question question, Message response, CancellationToken cancel) { if (!Catalog.TryGetValue(question.Name, out var node)) { return Task.FromResult(result: false); } response.AA |= node.Authoritative && question.Class != DnsClass.ANY; ResourceRecord[] array = (from r in node.Resources where question.Class == DnsClass.ANY || r.Class == question.Class where question.Type == DnsType.ANY || r.Type == question.Type where node.Authoritative || !r.IsExpired(question.CreationTime) select r).ToArray(); if (array.Length != 0) { response.Answers.AddRange(array); return Task.FromResult(result: true); } CNAMERecord cNAMERecord = node.Resources.OfType().FirstOrDefault(); if (cNAMERecord != null) { response.Answers.Add(cNAMERecord); question = question.Clone(); question.Name = cNAMERecord.Target; return FindAnswerAsync(question, response, cancel); } return Task.FromResult(result: false); } private SOARecord FindAuthority(DomainName domainName) { DomainName domainName2 = domainName; while (domainName2 != null) { if (Catalog.TryGetValue(domainName2, out var value)) { SOARecord sOARecord = value.Resources.OfType().FirstOrDefault(); if (sOARecord != null) { return sOARecord; } } domainName2 = domainName2.Parent(); } return null; } private void AddAdditionalRecords(Message response) { Message message = new Message(); IEnumerable enumerable = response.Answers.Concat(response.AdditionalRecords).Concat(response.AuthorityRecords); Question question = new Question(); foreach (ResourceRecord item in enumerable) { switch (item.Type) { case DnsType.A: question.Class = item.Class; question.Name = item.Name; question.Type = DnsType.AAAA; _ = FindAnswerAsync(question, message, default(CancellationToken)).Result; break; case DnsType.AAAA: question.Class = item.Class; question.Name = item.Name; question.Type = DnsType.A; _ = FindAnswerAsync(question, message, default(CancellationToken)).Result; break; case DnsType.NS: FindAddresses(((NSRecord)item).Authority, item.Class, message); break; case DnsType.PTR: { PTRRecord pTRRecord = (PTRRecord)item; question.Class = item.Class; question.Name = pTRRecord.DomainName; question.Type = DnsType.ANY; _ = FindAnswerAsync(question, message, default(CancellationToken)).Result; break; } case DnsType.SOA: FindAddresses(((SOARecord)item).PrimaryName, item.Class, message); break; case DnsType.SRV: question.Class = item.Class; question.Name = item.Name; question.Type = DnsType.TXT; _ = FindAnswerAsync(question, message, default(CancellationToken)).Result; FindAddresses(((SRVRecord)item).Target, item.Class, message); break; } } message.Answers = (from a in message.Answers where !response.Answers.Contains(a) where !response.AdditionalRecords.Contains(a) select a).Distinct().ToList(); response.AdditionalRecords.AddRange(message.Answers); if (message.Answers.Count > 0) { AddAdditionalRecords(response); } } private void FindAddresses(DomainName name, DnsClass klass, Message response) { Question question = new Question { Name = name, Class = klass, Type = DnsType.A }; _ = FindAnswerAsync(question, response, default(CancellationToken)).Result; question.Type = DnsType.AAAA; _ = FindAnswerAsync(question, response, default(CancellationToken)).Result; } private async Task AddSecurityExtensionsAsync(Message request, Message response) { if (!request.DO) { return response; } response.DO = true; await AddSecurityResourcesAsync(response.Answers); await AddSecurityResourcesAsync(response.AuthorityRecords); await AddSecurityResourcesAsync(response.AdditionalRecords); return response; } private async Task AddSecurityResourcesAsync(List rrset) { IEnumerable enumerable = from r in rrset where r.CanonicalName != string.Empty group r by new { r.CanonicalName, r.Type, r.Class } into g select g.First(); foreach (ResourceRecord need in enumerable) { Message signatures = new Message(); Question question = new Question { Name = need.Name, Class = need.Class, Type = DnsType.RRSIG }; if (await FindAnswerAsync(question, signatures, CancellationToken.None)) { rrset.AddRange(from r in signatures.Answers.OfType() where r.TypeCovered == need.Type select r); } } } } public class Node { public DomainName Name { get; set; } = DomainName.Root; public ConcurrentSet Resources { get; set; } = new ConcurrentSet(); public bool Authoritative { get; set; } public override string ToString() { return Name.ToString(); } } }