/// <summary> /// Creates a new instance of <see cref="LookupClient"/> with one or more <see cref="IPAddress"/> and port combination /// stored in <see cref="IPEndPoint"/>(s). /// </summary> /// <param name="nameServers">The <see cref="IPEndPoint"/>(s) to be used by this <see cref="LookupClient"/> instance.</param> /// <example> /// In this example, we instantiate a new <see cref="IPEndPoint"/> using an <see cref="IPAddress"/> and custom port which is different than the default port <c>53</c>. /// <code> /// <![CDATA[ /// // Using localhost and port 8600 to connect to a Consul agent. /// var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8600); /// var client = new LookupClient(endpoint); /// ]]> /// </code> /// <para> /// The <see cref="NameServer"/> class also contains pre defined <see cref="IPEndPoint"/>s for the public google DNS servers, which can be used as follows: /// <code> /// <![CDATA[ /// var client = new LookupClient(NameServer.GooglePublicDns, NameServer.GooglePublicDnsIPv6); /// ]]> /// </code> /// </para> /// </example> public LookupClient(params IPEndPoint[] nameServers) { if (nameServers == null || nameServers.Length == 0) { throw new ArgumentException("At least one name server must be configured.", nameof(nameServers)); } // TODO validate ip endpoints NameServers = nameServers.Select(p => new NameServer(p)).ToArray(); _endpoints = new ConcurrentQueue <NameServer>(NameServers); _messageHandler = new DnsUdpMessageHandler(true); _tcpFallbackHandler = new DnsTcpMessageHandler(); }
private async Task <IDnsQueryResponse> ResolveQueryAsync(DnsMessageHandler handler, DnsRequestMessage request, CancellationToken cancellationToken, Audit continueAudit = null) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var audit = continueAudit ?? new Audit(); var servers = GetNextServers(); foreach (var serverInfo in servers) { var tries = 0; do { tries++; try { cancellationToken.ThrowIfCancellationRequested(); if (EnableAuditTrail) { audit.StartTimer(); } DnsResponseMessage response; Action onCancel = () => { }; Task <DnsResponseMessage> resultTask = handler.QueryAsync(serverInfo.Endpoint, request, cancellationToken, (cancel) => { onCancel = cancel; }); if (Timeout != s_infiniteTimeout || (cancellationToken != CancellationToken.None && cancellationToken.CanBeCanceled)) { var cts = new CancellationTokenSource(Timeout); CancellationTokenSource linkedCts = null; if (cancellationToken != CancellationToken.None) { linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken); } using (cts) using (linkedCts) { response = await resultTask.WithCancellation((linkedCts ?? cts).Token, onCancel).ConfigureAwait(false); } } else { response = await resultTask.ConfigureAwait(false); } if (response.Header.ResultTruncated && UseTcpFallback && !handler.GetType().Equals(typeof(DnsTcpMessageHandler))) { if (EnableAuditTrail) { audit.AuditTruncatedRetryTcp(); } return(await ResolveQueryAsync(_tcpFallbackHandler, request, cancellationToken, audit).ConfigureAwait(false)); } if (EnableAuditTrail) { audit.AuditResolveServers(_endpoints.Count); audit.AuditResponseHeader(response.Header); } if (response.Header.ResponseCode != DnsResponseCode.NoError) { if (EnableAuditTrail) { audit.AuditResponseError(response.Header.ResponseCode); } if (ThrowDnsErrors) { throw new DnsResponseException(response.Header.ResponseCode); } } HandleOptRecords(audit, serverInfo, response); DnsQueryResponse queryResponse = response.AsQueryResponse(serverInfo.Clone()); if (EnableAuditTrail) { audit.AuditResponse(queryResponse); audit.AuditEnd(queryResponse); queryResponse.AuditTrail = audit.Build(); } Interlocked.Increment(ref StaticLog.ResolveQueryCount); Interlocked.Add(ref StaticLog.ResolveQueryTries, tries); return(queryResponse); } catch (DnsResponseException ex) { audit.AuditException(ex); ex.AuditTrail = audit.Build(); throw; } catch (SocketException ex) when(ex.SocketErrorCode == SocketError.AddressFamilyNotSupported) { // this socket error might indicate the server endpoint is actually bad and should be ignored in future queries. DisableServer(serverInfo); break; } catch (Exception ex) when(ex is TimeoutException || handler.IsTransientException(ex)) { // our timeout got eventually triggered by the a task cancelation token, throw OCE instead... if (cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(cancellationToken); } DisableServer(serverInfo); } catch (Exception ex) { DisableServer(serverInfo); var handleEx = ex; var agg = ex as AggregateException; if (agg != null) { if (agg.InnerExceptions.Any(e => e is TimeoutException || handler.IsTransientException(e))) { continue; } handleEx = agg.InnerException; } if (handleEx is OperationCanceledException || handleEx is TaskCanceledException) { if (cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(cancellationToken); } continue; } audit.AuditException(ex); throw new DnsResponseException("Unhandled exception", ex) { AuditTrail = audit.Build() }; } } while (tries <= Retries && !cancellationToken.IsCancellationRequested && serverInfo.Enabled); } throw new DnsResponseException( DnsResponseCode.ConnectionTimeout, $"No connection could be established to any of the following name servers: {string.Join(", ", NameServers)}.") { AuditTrail = audit.Build() }; }
private IDnsQueryResponse ResolveQuery(DnsMessageHandler handler, DnsRequestMessage request, Audit continueAudit = null) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var audit = continueAudit ?? new Audit(); var servers = GetNextServers(); foreach (var serverInfo in servers) { var tries = 0; do { tries++; try { if (EnableAuditTrail) { audit.StartTimer(); } DnsResponseMessage response = handler.Query(serverInfo.Endpoint, request, Timeout); if (response.Header.ResultTruncated && UseTcpFallback && !handler.GetType().Equals(typeof(DnsTcpMessageHandler))) { if (EnableAuditTrail) { audit.AuditTruncatedRetryTcp(); } return(ResolveQuery(_tcpFallbackHandler, request, audit)); } if (EnableAuditTrail) { audit.AuditResolveServers(_endpoints.Count); audit.AuditResponseHeader(response.Header); } if (response.Header.ResponseCode != DnsResponseCode.NoError) { if (EnableAuditTrail) { audit.AuditResponseError(response.Header.ResponseCode); } if (ThrowDnsErrors) { throw new DnsResponseException(response.Header.ResponseCode); } } HandleOptRecords(audit, serverInfo, response); DnsQueryResponse queryResponse = response.AsQueryResponse(serverInfo.Clone()); if (EnableAuditTrail) { audit.AuditResponse(queryResponse); audit.AuditEnd(queryResponse); queryResponse.AuditTrail = audit.Build(); } Interlocked.Increment(ref StaticLog.ResolveQueryCount); Interlocked.Add(ref StaticLog.ResolveQueryTries, tries); return(queryResponse); } catch (DnsResponseException ex) { audit.AuditException(ex); ex.AuditTrail = audit.Build(); throw; } catch (SocketException ex) when(ex.SocketErrorCode == SocketError.AddressFamilyNotSupported) { // this socket error might indicate the server endpoint is actually bad and should be ignored in future queries. DisableServer(serverInfo); break; } catch (Exception ex) when(ex is TimeoutException || handler.IsTransientException(ex)) { DisableServer(serverInfo); } catch (Exception ex) { DisableServer(serverInfo); if (ex is OperationCanceledException || ex is TaskCanceledException) { // timeout continue; } audit.AuditException(ex); throw new DnsResponseException(DnsResponseCode.Unassigned, "Unhandled exception", ex) { AuditTrail = audit.Build() }; } } while (tries <= Retries && serverInfo.Enabled); } throw new DnsResponseException(DnsResponseCode.ConnectionTimeout, $"No connection could be established to any of the following name servers: {string.Join(", ", NameServers)}.") { AuditTrail = audit.Build() }; }