using Swan.Formatters; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Runtime.InteropServices; namespace Swan.Net.Dns { /// /// DnsClient public methods. /// internal partial class DnsClient { public abstract class DnsResourceRecordBase : IDnsResourceRecord { private readonly IDnsResourceRecord _record; protected DnsResourceRecordBase(IDnsResourceRecord record) => this._record = record; public DnsDomain Name => this._record.Name; public DnsRecordType Type => this._record.Type; public DnsRecordClass Class => this._record.Class; public TimeSpan TimeToLive => this._record.TimeToLive; public Int32 DataLength => this._record.DataLength; public Byte[] Data => this._record.Data; public Int32 Size => this._record.Size; protected virtual String[] IncludedProperties => new[] { nameof(this.Name), nameof(this.Type), nameof(this.Class), nameof(this.TimeToLive), nameof(this.DataLength) }; public Byte[] ToArray() => this._record.ToArray(); public override String ToString() => Json.SerializeOnly(this, true, this.IncludedProperties); } public class DnsResourceRecord : IDnsResourceRecord { public DnsResourceRecord(DnsDomain domain, Byte[] data, DnsRecordType type, DnsRecordClass klass = DnsRecordClass.IN, TimeSpan ttl = default) { this.Name = domain; this.Type = type; this.Class = klass; this.TimeToLive = ttl; this.Data = data; } public DnsDomain Name { get; } public DnsRecordType Type { get; } public DnsRecordClass Class { get; } public TimeSpan TimeToLive { get; } public Int32 DataLength => this.Data.Length; public Byte[] Data { get; } public Int32 Size => this.Name.Size + Tail.SIZE + this.Data.Length; public static DnsResourceRecord FromArray(Byte[] message, Int32 offset, out Int32 endOffset) { DnsDomain domain = DnsDomain.FromArray(message, offset, out offset); Tail tail = message.ToStruct(offset, Tail.SIZE); Byte[] data = new Byte[tail.DataLength]; offset += Tail.SIZE; Array.Copy(message, offset, data, 0, data.Length); endOffset = offset + data.Length; return new DnsResourceRecord(domain, data, tail.Type, tail.Class, tail.TimeToLive); } public Byte[] ToArray() => new MemoryStream(this.Size).Append(this.Name.ToArray()).Append(new Tail() { Type = Type, Class = Class, TimeToLive = TimeToLive, DataLength = this.Data.Length, }.ToBytes()).Append(this.Data).ToArray(); public override String ToString() => Json.SerializeOnly(this, true, nameof(this.Name), nameof(this.Type), nameof(this.Class), nameof(this.TimeToLive), nameof(this.DataLength)); [StructEndianness(Endianness.Big)] [StructLayout(LayoutKind.Sequential, Pack = 2)] private struct Tail { public const Int32 SIZE = 10; private UInt16 type; private UInt16 klass; private UInt32 ttl; private UInt16 dataLength; public DnsRecordType Type { get => (DnsRecordType)this.type; set => this.type = (UInt16)value; } public DnsRecordClass Class { get => (DnsRecordClass)this.klass; set => this.klass = (UInt16)value; } public TimeSpan TimeToLive { get => TimeSpan.FromSeconds(this.ttl); set => this.ttl = (UInt32)value.TotalSeconds; } public Int32 DataLength { get => this.dataLength; set => this.dataLength = (UInt16)value; } } } public class DnsPointerResourceRecord : DnsResourceRecordBase { public DnsPointerResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset) : base(record) => this.PointerDomainName = DnsDomain.FromArray(message, dataOffset); public DnsDomain PointerDomainName { get; } protected override String[] IncludedProperties { get { List temp = new List(base.IncludedProperties) { nameof(this.PointerDomainName) }; return temp.ToArray(); } } } public class DnsIPAddressResourceRecord : DnsResourceRecordBase { public DnsIPAddressResourceRecord(IDnsResourceRecord record) : base(record) => this.IPAddress = new IPAddress(this.Data); public IPAddress IPAddress { get; } protected override String[] IncludedProperties => new List(base.IncludedProperties) { nameof(this.IPAddress) }.ToArray(); } public class DnsNameServerResourceRecord : DnsResourceRecordBase { public DnsNameServerResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset) : base(record) => this.NSDomainName = DnsDomain.FromArray(message, dataOffset); public DnsDomain NSDomainName { get; } protected override String[] IncludedProperties => new List(base.IncludedProperties) { nameof(this.NSDomainName) }.ToArray(); } public class DnsCanonicalNameResourceRecord : DnsResourceRecordBase { public DnsCanonicalNameResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset) : base(record) => this.CanonicalDomainName = DnsDomain.FromArray(message, dataOffset); public DnsDomain CanonicalDomainName { get; } protected override String[] IncludedProperties => new List(base.IncludedProperties) { nameof(this.CanonicalDomainName) }.ToArray(); } public class DnsMailExchangeResourceRecord : DnsResourceRecordBase { private const Int32 PreferenceSize = 2; public DnsMailExchangeResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset) : base(record) { Byte[] preference = new Byte[PreferenceSize]; Array.Copy(message, dataOffset, preference, 0, preference.Length); if(BitConverter.IsLittleEndian) { Array.Reverse(preference); } dataOffset += PreferenceSize; this.Preference = BitConverter.ToUInt16(preference, 0); this.ExchangeDomainName = DnsDomain.FromArray(message, dataOffset); } public Int32 Preference { get; } public DnsDomain ExchangeDomainName { get; } protected override String[] IncludedProperties => new List(base.IncludedProperties) { nameof(this.Preference), nameof(this.ExchangeDomainName), }.ToArray(); } public class DnsStartOfAuthorityResourceRecord : DnsResourceRecordBase { public DnsStartOfAuthorityResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset) : base(record) { this.MasterDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset); this.ResponsibleDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset); Options tail = message.ToStruct(dataOffset, Options.SIZE); this.SerialNumber = tail.SerialNumber; this.RefreshInterval = tail.RefreshInterval; this.RetryInterval = tail.RetryInterval; this.ExpireInterval = tail.ExpireInterval; this.MinimumTimeToLive = tail.MinimumTimeToLive; } public DnsStartOfAuthorityResourceRecord(DnsDomain domain, DnsDomain master, DnsDomain responsible, Int64 serial, TimeSpan refresh, TimeSpan retry, TimeSpan expire, TimeSpan minTtl, TimeSpan ttl = default) : base(Create(domain, master, responsible, serial, refresh, retry, expire, minTtl, ttl)) { this.MasterDomainName = master; this.ResponsibleDomainName = responsible; this.SerialNumber = serial; this.RefreshInterval = refresh; this.RetryInterval = retry; this.ExpireInterval = expire; this.MinimumTimeToLive = minTtl; } public DnsDomain MasterDomainName { get; } public DnsDomain ResponsibleDomainName { get; } public Int64 SerialNumber { get; } public TimeSpan RefreshInterval { get; } public TimeSpan RetryInterval { get; } public TimeSpan ExpireInterval { get; } public TimeSpan MinimumTimeToLive { get; } protected override String[] IncludedProperties => new List(base.IncludedProperties) { nameof(this.MasterDomainName), nameof(this.ResponsibleDomainName), nameof(this.SerialNumber), }.ToArray(); private static IDnsResourceRecord Create(DnsDomain domain, DnsDomain master, DnsDomain responsible, Int64 serial, TimeSpan refresh, TimeSpan retry, TimeSpan expire, TimeSpan minTtl, TimeSpan ttl) { MemoryStream data = new MemoryStream(Options.SIZE + master.Size + responsible.Size); Options tail = new Options { SerialNumber = serial, RefreshInterval = refresh, RetryInterval = retry, ExpireInterval = expire, MinimumTimeToLive = minTtl, }; _ = data.Append(master.ToArray()).Append(responsible.ToArray()).Append(tail.ToBytes()); return new DnsResourceRecord(domain, data.ToArray(), DnsRecordType.SOA, DnsRecordClass.IN, ttl); } [StructEndianness(Endianness.Big)] [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct Options { public const Int32 SIZE = 20; private UInt32 serialNumber; private UInt32 refreshInterval; private UInt32 retryInterval; private UInt32 expireInterval; private UInt32 ttl; public Int64 SerialNumber { get => this.serialNumber; set => this.serialNumber = (UInt32)value; } public TimeSpan RefreshInterval { get => TimeSpan.FromSeconds(this.refreshInterval); set => this.refreshInterval = (UInt32)value.TotalSeconds; } public TimeSpan RetryInterval { get => TimeSpan.FromSeconds(this.retryInterval); set => this.retryInterval = (UInt32)value.TotalSeconds; } public TimeSpan ExpireInterval { get => TimeSpan.FromSeconds(this.expireInterval); set => this.expireInterval = (UInt32)value.TotalSeconds; } public TimeSpan MinimumTimeToLive { get => TimeSpan.FromSeconds(this.ttl); set => this.ttl = (UInt32)value.TotalSeconds; } } } private static class DnsResourceRecordFactory { public static IList GetAllFromArray(Byte[] message, Int32 offset, Int32 count, out Int32 endOffset) { List result = new List(count); for(Int32 i = 0; i < count; i++) { result.Add(GetFromArray(message, offset, out offset)); } endOffset = offset; return result; } private static IDnsResourceRecord GetFromArray(Byte[] message, Int32 offset, out Int32 endOffset) { DnsResourceRecord record = DnsResourceRecord.FromArray(message, offset, out endOffset); Int32 dataOffset = endOffset - record.DataLength; return record.Type switch { DnsRecordType.A => (new DnsIPAddressResourceRecord(record)), DnsRecordType.AAAA => new DnsIPAddressResourceRecord(record), DnsRecordType.NS => new DnsNameServerResourceRecord(record, message, dataOffset), DnsRecordType.CNAME => new DnsCanonicalNameResourceRecord(record, message, dataOffset), DnsRecordType.SOA => new DnsStartOfAuthorityResourceRecord(record, message, dataOffset), DnsRecordType.PTR => new DnsPointerResourceRecord(record, message, dataOffset), DnsRecordType.MX => new DnsMailExchangeResourceRecord(record, message, dataOffset), _ => record }; } } } }