public override async Task <DnsDatagram> QueryAsync(DnsDatagram request, int timeout, int retries, CancellationToken cancellationToken) { _lastQueried = DateTime.UtcNow; async Task <HttpRequestMessage> GetHttpRequest() { Uri queryUri; if (_proxy == null) { if (_server.IsIPEndPointStale) { await _server.RecursiveResolveIPAddressAsync(); } queryUri = new Uri(_server.DnsOverHttpEndPoint.Scheme + "://" + _server.IPEndPoint.ToString() + _server.DnsOverHttpEndPoint.PathAndQuery); } else { if (_server.IPEndPoint == null) { queryUri = _server.DnsOverHttpEndPoint; } else { queryUri = new Uri(_server.DnsOverHttpEndPoint.Scheme + "://" + _server.IPEndPoint.ToString() + _server.DnsOverHttpEndPoint.PathAndQuery); } } return(new HttpRequestMessage(HttpMethod.Get, queryUri.AbsoluteUri + "?name=" + request.Question[0].Name + "&type=" + Convert.ToString((int)request.Question[0].Type))); } //DoH JSON format request Stopwatch stopwatch = new Stopwatch(); int retry = 0; while (retry < retries) //retry loop { retry++; if (cancellationToken.IsCancellationRequested) { return(await Task.FromCanceled <DnsDatagram>(cancellationToken)); //task cancelled } stopwatch.Start(); Task <HttpResponseMessage> task = _httpClient.SendAsync(await GetHttpRequest(), cancellationToken); using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource()) { using (CancellationTokenRegistration ctr = cancellationToken.Register(delegate() { timeoutCancellationTokenSource.Cancel(); })) { if (await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)) != task) { continue; //request timed out; retry } } timeoutCancellationTokenSource.Cancel(); //to stop delay task } HttpResponseMessage httpResponse = await task; stopwatch.Stop(); httpResponse.EnsureSuccessStatusCode(); string responseJson = await httpResponse.Content.ReadAsStringAsync(cancellationToken); //parse response DnsDatagram response = DnsDatagram.ReadFromJson(JsonConvert.DeserializeObject(responseJson), responseJson.Length); response.SetIdentifier(request.Identifier); response.SetMetadata(_server, _protocol, stopwatch.Elapsed.TotalMilliseconds); if (response.Question.Count != request.Question.Count) { throw new DnsClientResponseValidationException("Invalid response was received: question count mismatch."); } for (int i = 0; i < response.Question.Count; i++) { if (request.Question[i].ZoneCut == null) { if (!response.Question[i].Name.Equals(request.Question[i].Name, StringComparison.Ordinal)) { throw new DnsClientResponseValidationException("Invalid response was received: QNAME mismatch."); } if (response.Question[i].Type != request.Question[i].Type) { throw new DnsClientResponseValidationException("Invalid response was received: QTYPE mismatch."); } } else { if (!response.Question[i].Name.Equals(request.Question[i].MinimizedName, StringComparison.Ordinal)) { throw new DnsClientResponseValidationException("Invalid response was received: QNAME mismatch."); } if (response.Question[i].Type != request.Question[i].MinimizedType) { throw new DnsClientResponseValidationException("Invalid response was received: QTYPE mismatch."); } } if (response.Question[i].Class != request.Question[i].Class) { throw new DnsClientResponseValidationException("Invalid response was received: QCLASS mismatch."); } } return(response); } throw new DnsClientException("DnsClient failed to resolve the request: request timed out."); }