using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
namespace DnsClient
{
///
/// The contract for the LookupClient.
///
/// The interfaces for the query methods and the lookup client properties are separated so that one can
/// inject or expose only the without exposing the configuration options.
///
///
public interface ILookupClient : IDnsQuery
{
///
/// Gets the list of configured default name servers.
///
IReadOnlyCollection NameServers { get; }
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
// all settings will be moved into DnsQueryOptions/LookupClientOptions
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
TimeSpan? MinimumCacheTimeout { get; set; }
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
bool EnableAuditTrail { get; set; }
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
bool UseCache { get; set; }
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
bool Recursion { get; set; }
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
int Retries { get; set; }
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
bool ThrowDnsErrors { get; set; }
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
bool UseRandomNameServer { get; set; }
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
bool ContinueOnDnsError { get; set; }
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
TimeSpan Timeout { get; set; }
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
bool UseTcpFallback { get; set; }
[Obsolete("This property will be removed from LookupClient in the next version. Use LookupClientOptions to initialize LookupClient instead.")]
bool UseTcpOnly { get; set; }
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}
///
/// The options used to override the defaults of per query.
///
public class DnsQueryOptions
{
private static readonly TimeSpan s_defaultTimeout = TimeSpan.FromSeconds(5);
private static readonly TimeSpan s_infiniteTimeout = System.Threading.Timeout.InfiniteTimeSpan;
private static readonly TimeSpan s_maxTimeout = TimeSpan.FromMilliseconds(int.MaxValue);
private TimeSpan _timeout = s_defaultTimeout;
// TODO: evalualte if defaulting resolveNameServers to false here and in the query plan, fall back to configured nameservers of the client
///
/// Creates a new instance of without name servers.
///
///
/// If no nameservers are configured, a query will fallback to the nameservers already configured on the instance.
///
/// If set to true,
/// will be used to get a list of nameservers.
public DnsQueryOptions(bool resolveNameServers = false)
: this(resolveNameServers ? NameServer.ResolveNameServers() : null)
{
AutoResolvedNameServers = resolveNameServers;
}
///
/// Creates a new instance of with one name server.
/// or can be used as well thanks to implicit conversion.
///
/// The name servers.
public DnsQueryOptions(NameServer nameServer)
: this(new[] { nameServer })
{
}
///
/// Creates a new instance of .
///
/// A collection of name servers.
public DnsQueryOptions(IReadOnlyCollection nameServers)
{
if (nameServers != null && nameServers.Count > 0)
{
NameServers = nameServers.ToList();
}
}
///
/// Creates a new instance of .
///
/// A collection of name servers.
public DnsQueryOptions(params NameServer[] nameServers)
{
if (nameServers != null && nameServers.Length > 0)
{
NameServers = nameServers.ToList();
}
}
///
/// Creates a new instance of .
///
/// A collection of name servers.
public DnsQueryOptions(params IPEndPoint[] nameServers)
: this(nameServers.Select(p => (NameServer)p).ToArray())
{
}
///
/// Creates a new instance of .
///
/// A collection of name servers.
public DnsQueryOptions(params IPAddress[] nameServers)
: this(nameServers.Select(p => (NameServer)p).ToArray())
{
}
///
/// Gets or sets a flag indicating whether each will contain a full documentation of the response(s).
/// Default is False.
///
///
public bool EnableAuditTrail { get; set; } = false;
///
/// Gets a flag indicating whether the name server collection was manually defined or automatically resolved
///
public bool AutoResolvedNameServers { get; }
///
/// Gets or sets a flag indicating whether DNS queries should use response caching or not.
/// The cache duration is calculated by the resource record of the response. Usually, the lowest TTL is used.
/// Default is True.
///
///
/// In case the DNS Server returns records with a TTL of zero. The response cannot be cached.
///
public bool UseCache { get; set; } = true;
///
/// Gets or sets a list of name servers which should be used to query.
///
public IList NameServers { get; set; } = new List();
///
/// Gets or sets a flag indicating whether DNS queries should instruct the DNS server to do recursive lookups, or not.
/// Default is True.
///
/// The flag indicating if recursion should be used or not.
public bool Recursion { get; set; } = true;
///
/// Gets or sets the number of tries to get a response from one name server before trying the next one.
/// Only transient errors, like network or connection errors will be retried.
/// Default is 5.
///
/// If all configured error out after retries, an exception will be thrown at the end.
///
///
/// The number of retries.
public int Retries { get; set; } = 5;
///
/// Gets or sets a flag indicating whether the should throw a
/// in case the query result has a other than .
/// Default is False.
///
///
///
/// If set to False, the query will return a result with an
/// which contains more information.
///
///
/// If set to True, any query method of will throw an if
/// the response header indicates an error.
///
///
/// If both, and are set to True,
/// will continue to query all configured .
/// If none of the servers yield a valid response, a will be thrown
/// with the error of the last response.
///
///
///
///
public bool ThrowDnsErrors { get; set; } = false;
///
/// Gets or sets a flag indicating whether the can cycle through all
/// configured on each consecutive request, basically using a random server, or not.
/// Default is True.
/// If only one is configured, this setting is not used.
///
///
///
/// If False, configured endpoint will be used in random order.
/// If True, the order will be preserved.
///
///
/// Even if is set to True, the endpoint might still get
/// disabled and might not being used for some time if it errors out, e.g. no connection can be established.
///
///
public bool UseRandomNameServer { get; set; } = true;
///
/// Gets or sets a flag indicating whether to query the next configured in case the response of the last query
/// returned a other than .
/// Default is True.
///
///
/// If True, lookup client will continue until a server returns a valid result, or,
/// if no yield a valid result, the last response with the error will be returned.
/// In case no server yields a valid result and is also enabled, an exception
/// will be thrown containing the error of the last response.
///
///
public bool ContinueOnDnsError { get; set; } = true;
///
/// Gets or sets the request timeout in milliseconds. is used for limiting the connection and request time for one operation.
/// Timeout must be greater than zero and less than .
/// If (or -1) is used, no timeout will be applied.
/// Default is 5 seconds.
///
///
/// If a very short timeout is configured, queries will more likely result in s.
///
/// Important to note, s will be retried, if are not disabled (set to 0).
/// This should help in case one or more configured DNS servers are not reachable or under load for example.
///
///
public TimeSpan Timeout
{
get { return _timeout; }
set
{
if ((value <= TimeSpan.Zero || value > s_maxTimeout) && value != s_infiniteTimeout)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_timeout = value;
}
}
///
/// Gets or sets a flag indicating whether Tcp should be used in case a Udp response is truncated.
/// Default is True.
///
/// If False, truncated results will potentially yield no or incomplete answers.
///
///
public bool UseTcpFallback { get; set; } = true;
///
/// Gets or sets a flag indicating whether Udp should not be used at all.
/// Default is False.
///
/// Enable this only if Udp cannot be used because of your firewall rules for example.
/// Also, zone transfers (see ) must use TCP only.
///
///
public bool UseTcpOnly { get; set; } = false;
///
/// Converts the query options into readonly settings.
///
/// The options.
public static implicit operator DnsQuerySettings(DnsQueryOptions fromOptions)
{
if (fromOptions == null)
{
return null;
}
return new DnsQuerySettings(fromOptions);
}
}
///
/// The options used to configure defaults in and to optionally use specific settings per query.
///
public class LookupClientOptions : DnsQueryOptions
{
private static readonly TimeSpan s_infiniteTimeout = System.Threading.Timeout.InfiniteTimeSpan;
// max is 24 days
private static readonly TimeSpan s_maxTimeout = TimeSpan.FromMilliseconds(int.MaxValue);
private TimeSpan? _minimumCacheTimeout;
///
/// Creates a new instance of without name servers.
///
public LookupClientOptions(bool resolveNameServers = true)
: base(resolveNameServers)
{
}
///
/// Creates a new instance of with one name server.
/// or can be used as well thanks to implicit conversion.
///
/// The name servers.
public LookupClientOptions(NameServer nameServer)
: base(nameServer)
{
}
///
/// Creates a new instance of .
///
/// A collection of name servers.
public LookupClientOptions(params NameServer[] nameServers)
: base(nameServers)
{
}
///
/// Creates a new instance of .
///
/// A collection of name servers.
public LookupClientOptions(params IPEndPoint[] nameServers)
: base(nameServers)
{
}
///
/// Creates a new instance of .
///
/// A collection of name servers.
public LookupClientOptions(params IPAddress[] nameServers)
: base(nameServers)
{
}
///
/// Creates a new instance of .
///
/// A collection of name servers.
public LookupClientOptions(IReadOnlyCollection nameServers)
: base(nameServers)
{
}
///
/// Gets or sets a which can override the TTL of a resource record in case the
/// TTL of the record is lower than this minimum value.
/// Default is Null.
///
/// This is useful in cases where the server retruns records with zero TTL.
///
///
///
/// This setting gets igonred in case is set to False.
/// The maximum value is 24 days or .
///
public TimeSpan? MinimumCacheTimeout
{
get { return _minimumCacheTimeout; }
set
{
if (value.HasValue &&
(value < TimeSpan.Zero || value > s_maxTimeout) && value != s_infiniteTimeout)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_minimumCacheTimeout = value;
}
}
///
/// Converts the options into readonly settings.
///
/// The options.
public static implicit operator LookupClientSettings(LookupClientOptions fromOptions)
{
if (fromOptions == null)
{
return null;
}
return new LookupClientSettings(fromOptions);
}
}
// TODO: revisit if we need this AND LookupClientSettings, might not be needed for per query options
///
/// The readonly version of used to customize settings per query.
///
public class DnsQuerySettings : IEquatable
{
private NameServer[] _endpoints;
///
/// Gets a flag indicating whether each will contain a full documentation of the response(s).
/// Default is False.
///
///
public bool EnableAuditTrail { get; }
///
/// Gets a flag indicating whether DNS queries should use response caching or not.
/// The cache duration is calculated by the resource record of the response. Usually, the lowest TTL is used.
/// Default is True.
///
///
/// In case the DNS Server returns records with a TTL of zero. The response cannot be cached.
///
public bool UseCache { get; }
///
/// Gets a collection of name servers which should be used to query.
///
public IReadOnlyCollection NameServers => _endpoints;
///
/// Gets a flag indicating whether DNS queries should instruct the DNS server to do recursive lookups, or not.
/// Default is True.
///
/// The flag indicating if recursion should be used or not.
public bool Recursion { get; }
///
/// Gets the number of tries to get a response from one name server before trying the next one.
/// Only transient errors, like network or connection errors will be retried.
/// Default is 5.
///
/// If all configured error out after retries, an exception will be thrown at the end.
///
///
/// The number of retries.
public int Retries { get; }
///
/// Gets a flag indicating whether the should throw a
/// in case the query result has a other than .
/// Default is False.
///
///
///
/// If set to False, the query will return a result with an
/// which contains more information.
///
///
/// If set to True, any query method of will throw an if
/// the response header indicates an error.
///
///
/// If both, and are set to True,
/// will continue to query all configured .
/// If none of the servers yield a valid response, a will be thrown
/// with the error of the last response.
///
///
///
///
public bool ThrowDnsErrors { get; }
///
/// Gets a flag indicating whether the can cycle through all
/// configured on each consecutive request, basically using a random server, or not.
/// Default is True.
/// If only one is configured, this setting is not used.
///
///
///
/// If False, configured endpoint will be used in random order.
/// If True, the order will be preserved.
///
///
/// Even if is set to True, the endpoint might still get
/// disabled and might not being used for some time if it errors out, e.g. no connection can be established.
///
///
public bool UseRandomNameServer { get; }
///
/// Gets a flag indicating whether to query the next configured in case the response of the last query
/// returned a other than .
/// Default is True.
///
///
/// If True, lookup client will continue until a server returns a valid result, or,
/// if no yield a valid result, the last response with the error will be returned.
/// In case no server yields a valid result and is also enabled, an exception
/// will be thrown containing the error of the last response.
///
///
public bool ContinueOnDnsError { get; }
///
/// Gets the request timeout in milliseconds. is used for limiting the connection and request time for one operation.
/// Timeout must be greater than zero and less than .
/// If (or -1) is used, no timeout will be applied.
/// Default is 5 seconds.
///
///
/// If a very short timeout is configured, queries will more likely result in s.
///
/// Important to note, s will be retried, if are not disabled (set to 0).
/// This should help in case one or more configured DNS servers are not reachable or under load for example.
///
///
public TimeSpan Timeout { get; }
///
/// Gets a flag indicating whether Tcp should be used in case a Udp response is truncated.
/// Default is True.
///
/// If False, truncated results will potentially yield no or incomplete answers.
///
///
public bool UseTcpFallback { get; }
///
/// Gets a flag indicating whether Udp should not be used at all.
/// Default is False.
///
/// Enable this only if Udp cannot be used because of your firewall rules for example.
/// Also, zone transfers (see ) must use TCP only.
///
///
public bool UseTcpOnly { get; }
///
/// Gets a flag indicating whether the name server collection was manually defined or automatically resolved
///
public bool AutoResolvedNameServers { get; }
///
/// Creates a new instance of .
///
public DnsQuerySettings(DnsQueryOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_endpoints = options.NameServers.ToArray();
ContinueOnDnsError = options.ContinueOnDnsError;
EnableAuditTrail = options.EnableAuditTrail;
Recursion = options.Recursion;
Retries = options.Retries;
ThrowDnsErrors = options.ThrowDnsErrors;
Timeout = options.Timeout;
UseCache = options.UseCache;
UseRandomNameServer = options.UseRandomNameServer;
UseTcpFallback = options.UseTcpFallback;
UseTcpOnly = options.UseTcpOnly;
AutoResolvedNameServers = options.AutoResolvedNameServers;
}
///
/// Creates a new instance of .
///
public DnsQuerySettings(DnsQueryOptions options, IReadOnlyCollection overrideServers)
: this(options)
{
_endpoints = overrideServers?.ToArray() ?? throw new ArgumentNullException(nameof(overrideServers));
}
internal IReadOnlyCollection ShuffleNameServers()
{
if (NameServers.Count > 1)
{
var servers = _endpoints.Where(p => p.Enabled).ToArray();
// if all servers are disabled, retry all of them
if (servers.Length == 0)
{
return _endpoints;
}
// shuffle servers only if we do not have to preserve the order
if (UseRandomNameServer)
{
var q = new Queue(_endpoints);
var server = q.Dequeue();
q.Enqueue(server);
_endpoints = q.ToArray();
}
return servers;
}
else
{
return NameServers;
}
}
internal DnsQuerySettings WithServers(
IReadOnlyCollection nameServers)
{
return new DnsQuerySettings(new DnsQueryOptions(nameServers)
{
ContinueOnDnsError = ContinueOnDnsError,
EnableAuditTrail = EnableAuditTrail,
Recursion = Recursion,
Retries = Retries,
ThrowDnsErrors = ThrowDnsErrors,
Timeout = Timeout,
UseCache = UseCache,
UseRandomNameServer = UseRandomNameServer,
UseTcpFallback = UseTcpFallback,
UseTcpOnly = UseTcpOnly
});
}
///
public override bool Equals(object obj)
{
return Equals(obj as DnsQuerySettings);
}
///
public bool Equals(DnsQuerySettings other)
{
return other != null &&
NameServers.SequenceEqual(other.NameServers) &&
EnableAuditTrail == other.EnableAuditTrail &&
UseCache == other.UseCache &&
Recursion == other.Recursion &&
Retries == other.Retries &&
ThrowDnsErrors == other.ThrowDnsErrors &&
UseRandomNameServer == other.UseRandomNameServer &&
ContinueOnDnsError == other.ContinueOnDnsError &&
Timeout.Equals(other.Timeout) &&
UseTcpFallback == other.UseTcpFallback &&
UseTcpOnly == other.UseTcpOnly &&
AutoResolvedNameServers == other.AutoResolvedNameServers;
}
///
public override int GetHashCode()
{
var hashCode = -1775804580;
hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_endpoints);
hashCode = hashCode * -1521134295 + EnableAuditTrail.GetHashCode();
hashCode = hashCode * -1521134295 + UseCache.GetHashCode();
hashCode = hashCode * -1521134295 + Recursion.GetHashCode();
hashCode = hashCode * -1521134295 + Retries.GetHashCode();
hashCode = hashCode * -1521134295 + ThrowDnsErrors.GetHashCode();
hashCode = hashCode * -1521134295 + UseRandomNameServer.GetHashCode();
hashCode = hashCode * -1521134295 + ContinueOnDnsError.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Timeout);
hashCode = hashCode * -1521134295 + UseTcpFallback.GetHashCode();
hashCode = hashCode * -1521134295 + UseTcpOnly.GetHashCode();
hashCode = hashCode * -1521134295 + AutoResolvedNameServers.GetHashCode();
return hashCode;
}
}
///
/// The readonly version of used as default settings in .
///
public class LookupClientSettings : DnsQuerySettings, IEquatable
{
///
/// Creates a new instance of .
///
public LookupClientSettings(LookupClientOptions options) : base(options)
{
MinimumCacheTimeout = options.MinimumCacheTimeout;
}
///
/// Gets a which can override the TTL of a resource record in case the
/// TTL of the record is lower than this minimum value.
/// Default is Null.
///
/// This is useful in cases where the server retruns records with zero TTL.
///
///
///
/// This setting gets igonred in case is set to False.
/// The maximum value is 24 days or .
///
public TimeSpan? MinimumCacheTimeout { get; }
///
public override bool Equals(object obj)
{
return Equals(obj as LookupClientSettings);
}
///
public bool Equals(LookupClientSettings other)
{
return other != null &&
base.Equals(other) &&
EqualityComparer.Default.Equals(MinimumCacheTimeout, other.MinimumCacheTimeout);
}
///
public override int GetHashCode()
{
var hashCode = 1049610412;
hashCode = hashCode * -1521134295 + base.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(MinimumCacheTimeout);
return hashCode;
}
internal LookupClientSettings Copy(
IReadOnlyCollection nameServers,
TimeSpan? minimumCacheTimeout,
bool? continueOnDnsError = null,
bool? enableAuditTrail = null,
bool? recursion = null,
int? retries = null,
bool? throwDnsErrors = null,
TimeSpan? timeout = null,
bool? useCache = null,
bool? useRandomNameServer = null,
bool? useTcpFallback = null,
bool? useTcpOnly = null)
{
// auto resolved flag might get lost here. But this stuff gets deleted anyways.
return new LookupClientSettings(new LookupClientOptions(nameServers)
{
MinimumCacheTimeout = minimumCacheTimeout,
ContinueOnDnsError = continueOnDnsError ?? ContinueOnDnsError,
EnableAuditTrail = enableAuditTrail ?? EnableAuditTrail,
Recursion = recursion ?? Recursion,
Retries = retries ?? Retries,
ThrowDnsErrors = throwDnsErrors ?? ThrowDnsErrors,
Timeout = timeout ?? Timeout,
UseCache = useCache ?? UseCache,
UseRandomNameServer = useRandomNameServer ?? UseRandomNameServer,
UseTcpFallback = useTcpFallback ?? UseTcpFallback,
UseTcpOnly = useTcpOnly ?? UseTcpOnly
});
}
// TODO: remove if LookupClient settings can be made readonly
internal LookupClientSettings WithContinueOnDnsError(bool value)
{
return Copy(NameServers, MinimumCacheTimeout, continueOnDnsError: value);
}
// TODO: remove if LookupClient settings can be made readonly
internal LookupClientSettings WithEnableAuditTrail(bool value)
{
return Copy(NameServers, MinimumCacheTimeout, enableAuditTrail: value);
}
// TODO: remove if LookupClient settings can be made readonly
internal LookupClientSettings WithMinimumCacheTimeout(TimeSpan? value)
{
return Copy(NameServers, minimumCacheTimeout: value);
}
// TODO: remove if LookupClient settings can be made readonly
internal LookupClientSettings WithRecursion(bool value)
{
return Copy(NameServers, MinimumCacheTimeout, recursion: value);
}
// TODO: remove if LookupClient settings can be made readonly
internal LookupClientSettings WithRetries(int value)
{
return Copy(NameServers, MinimumCacheTimeout, retries: value);
}
// TODO: remove if LookupClient settings can be made readonly
internal LookupClientSettings WithThrowDnsErrors(bool value)
{
return Copy(NameServers, MinimumCacheTimeout, throwDnsErrors: value);
}
// TODO: remove if LookupClient settings can be made readonly
internal LookupClientSettings WithTimeout(TimeSpan value)
{
return Copy(NameServers, MinimumCacheTimeout, timeout: value);
}
// TODO: remove if LookupClient settings can be made readonly
internal LookupClientSettings WithUseCache(bool value)
{
return Copy(NameServers, MinimumCacheTimeout, useCache: value);
}
// TODO: remove if LookupClient settings can be made readonly
internal LookupClientSettings WithUseTcpFallback(bool value)
{
return Copy(NameServers, MinimumCacheTimeout, useTcpFallback: value);
}
// TODO: remove if LookupClient settings can be made readonly
internal LookupClientSettings WithUseTcpOnly(bool value)
{
return Copy(NameServers, MinimumCacheTimeout, useTcpOnly: value);
}
}
}