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