RaspberryIO/Unosquare.Swan/Networking/DnsClient.ResourceRecords.cs
2019-12-03 18:44:25 +01:00

432 lines
14 KiB
C#

using Unosquare.Swan.Attributes;
using Unosquare.Swan.Formatters;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
namespace Unosquare.Swan.Networking {
/// <summary>
/// DnsClient public methods.
/// </summary>
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 IList<DnsResourceRecord> GetAllFromArray(
Byte[] message,
Int32 offset,
Int32 count,
out Int32 endOffset) {
IList<DnsResourceRecord> records = new List<DnsResourceRecord>(count);
for(Int32 i = 0; i < count; i++) {
records.Add(FromArray(message, offset, out offset));
}
endOffset = offset;
return records;
}
public static DnsResourceRecord FromArray(Byte[] message, Int32 offset, out Int32 endOffset) {
DnsDomain domain = DnsDomain.FromArray(message, offset, out offset);
Tail tail = message.ToStruct<Tail>(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<String> temp = new List<String>(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 {
get {
List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.IPAddress) };
return temp.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 {
get {
List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.NSDomainName) };
return temp.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<String>(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<String>(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<Options>(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<String>(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<IDnsResourceRecord> GetAllFromArray(
Byte[] message,
Int32 offset,
Int32 count,
out Int32 endOffset) {
List<IDnsResourceRecord> result = new List<IDnsResourceRecord>(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;
switch(record.Type) {
case DnsRecordType.A:
case DnsRecordType.AAAA:
return new DnsIPAddressResourceRecord(record);
case DnsRecordType.NS:
return new DnsNameServerResourceRecord(record, message, dataOffset);
case DnsRecordType.CNAME:
return new DnsCanonicalNameResourceRecord(record, message, dataOffset);
case DnsRecordType.SOA:
return new DnsStartOfAuthorityResourceRecord(record, message, dataOffset);
case DnsRecordType.PTR:
return new DnsPointerResourceRecord(record, message, dataOffset);
case DnsRecordType.MX:
return new DnsMailExchangeResourceRecord(record, message, dataOffset);
default:
return record;
}
}
}
}
}