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); } } }