432 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |