-
Notifications
You must be signed in to change notification settings - Fork 760
Managed implementation of DNS resolver #6104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
2c217a3
704e33c
aff6d17
15db8a1
6bda235
6fc61e0
2b6d91e
2137954
c204b01
2fc7de4
9b4de29
3ad5a75
40f8f9b
e385605
3cf0a9d
6016e41
f8c189d
d929889
00e41d7
7267fb7
c1b76d3
86490cc
92b2905
fdf1904
61814e7
82a8108
fa627fd
4ec79ca
123375d
6311616
c57a8c5
83ca958
03e6a99
46418fe
4075e4f
56cd169
a19e8a2
5d5edae
3c387e9
c6e3d63
e66229a
da018f7
2697412
214cc9b
0d891cb
46cf262
35a5809
fcf18cf
df785d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…on server.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -265,46 +265,62 @@ internal struct SendQueryResult | |
|
|
||
| async ValueTask<SendQueryResult> SendQueryWithRetriesAsync(string name, QueryType queryType, CancellationToken cancellationToken) | ||
| { | ||
| SendQueryResult result = default; | ||
| SendQueryResult? result = default; | ||
|
|
||
| for (int index = 0; index < _options.Servers.Length; index++) | ||
| { | ||
| IPEndPoint serverEndPoint = _options.Servers[index]; | ||
|
|
||
| for (int attempt = 0; attempt < _options.Attempts; attempt++) | ||
| for (int attempt = 1; attempt <= _options.Attempts; attempt++) | ||
| { | ||
|
|
||
| try | ||
| { | ||
| result = await SendQueryToServerWithTimeoutAsync(serverEndPoint, name, queryType, index == _options.Servers.Length - 1, attempt, cancellationToken).ConfigureAwait(false); | ||
| } | ||
| catch (SocketException ex) | ||
| { | ||
| Log.NetworkError(_logger, queryType, name, serverEndPoint, attempt, ex); | ||
| result = new SendQueryResult { Error = SendQueryError.NetworkError }; | ||
| continue; // retry or skip to the next server | ||
| } | ||
| catch (Exception ex) when (!cancellationToken.IsCancellationRequested) | ||
| { | ||
| Log.QueryError(_logger, queryType, name, serverEndPoint, attempt, ex); | ||
| continue; // retry or skip to the next server | ||
| } | ||
|
|
||
| switch (result.Error) | ||
| Debug.Assert(result.HasValue); | ||
|
|
||
| switch (result.Value.Error) | ||
| { | ||
| case SendQueryError.NoError: | ||
| return result; | ||
| return result.Value; | ||
| case SendQueryError.Timeout: | ||
| // TODO: should we retry on timeout or skip to the next server? | ||
| Log.Timeout(_logger, queryType, name, serverEndPoint, attempt); | ||
| break; | ||
| case SendQueryError.ServerError: | ||
| Log.ErrorResponseCode(_logger, queryType, name, serverEndPoint, result.Response.Header.ResponseCode); | ||
| Log.ErrorResponseCode(_logger, queryType, name, serverEndPoint, result.Value.Response.Header.ResponseCode); | ||
| break; | ||
| case SendQueryError.NoData: | ||
| Log.NoData(_logger, queryType, name, serverEndPoint, attempt); | ||
| break; | ||
| case SendQueryError.MalformedResponse: | ||
| Log.MalformedResponse(_logger, queryType, name, serverEndPoint, attempt); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // return the last error received | ||
| // TODO: will this always have nondefault value? | ||
| return result; | ||
| // we should have an error result by now, except when we threw an exception due to internal bug | ||
| // (handled here), or cancellation (handled by the caller). | ||
| // if (!result.HasValue) | ||
| // { | ||
| // result = new SendQueryResult { Error = SendQueryError.InternalError }; | ||
| // } | ||
|
|
||
| return result!.Value; | ||
| } | ||
|
|
||
| internal async ValueTask<SendQueryResult> SendQueryToServerWithTimeoutAsync(IPEndPoint serverEndPoint, string name, QueryType queryType, bool isLastServer, int attempt, CancellationToken cancellationToken) | ||
|
|
@@ -343,6 +359,7 @@ private async ValueTask<SendQueryResult> SendQueryToServerAsync(IPEndPoint serve | |
| { | ||
| Log.Query(_logger, queryType, name, serverEndPoint, attempt); | ||
|
|
||
| SendQueryError sendError = SendQueryError.NoError; | ||
| DateTime queryStartedTime = _timeProvider.GetUtcNow().DateTime; | ||
| (DnsDataReader responseReader, DnsMessageHeader header) = await SendDnsQueryCoreUdpAsync(serverEndPoint, name, queryType, cancellationToken).ConfigureAwait(false); | ||
|
|
||
|
|
@@ -353,7 +370,13 @@ private async ValueTask<SendQueryResult> SendQueryToServerAsync(IPEndPoint serve | |
| Log.ResultTruncated(_logger, queryType, name, serverEndPoint, 0); | ||
| responseReader.Dispose(); | ||
| // TCP fallback | ||
| (responseReader, header) = await SendDnsQueryCoreTcpAsync(serverEndPoint, name, queryType, cancellationToken).ConfigureAwait(false); | ||
| (responseReader, header, sendError) = await SendDnsQueryCoreTcpAsync(serverEndPoint, name, queryType, cancellationToken).ConfigureAwait(false); | ||
| } | ||
|
|
||
| if (sendError != SendQueryError.NoError) | ||
| { | ||
| // we failed to get back any response | ||
| return new SendQueryResult { Error = sendError }; | ||
| } | ||
|
|
||
| if (header.QueryCount != 1 || | ||
|
|
@@ -364,11 +387,15 @@ private async ValueTask<SendQueryResult> SendQueryToServerAsync(IPEndPoint serve | |
| return new SendQueryResult | ||
| { | ||
| Response = new DnsResponse(Array.Empty<byte>(), header, queryStartedTime, queryStartedTime, null!, null!, null!), | ||
| Error = SendQueryError.ServerError | ||
| Error = SendQueryError.MalformedResponse | ||
| }; | ||
| } | ||
|
|
||
| if (header.ResponseCode != QueryResponseCode.NoError) | ||
| // we are interested in returned RRs only in case of NOERROR response code, | ||
| // if this is not a successful response and we have attempts remaining, | ||
| // we skip parsing the response and retry. | ||
| // TODO: test server failover behavior | ||
| if (header.ResponseCode != QueryResponseCode.NoError && (!isLastServer || attempt != _options.Attempts)) | ||
| { | ||
| return new SendQueryResult | ||
| { | ||
|
|
@@ -377,12 +404,6 @@ private async ValueTask<SendQueryResult> SendQueryToServerAsync(IPEndPoint serve | |
| }; | ||
| } | ||
|
|
||
| if (header.ResponseCode != QueryResponseCode.NoError && !isLastServer) | ||
| { | ||
| // we exhausted attempts on this server, try the next one | ||
| return default; | ||
| } | ||
|
|
||
| int ttl = int.MaxValue; | ||
| List<DnsResourceRecord> answers = ReadRecords(header.AnswerCount, ref ttl, ref responseReader); | ||
| List<DnsResourceRecord> authorities = ReadRecords(header.AuthorityCount, ref ttl, ref responseReader); | ||
|
|
@@ -506,9 +527,7 @@ internal static SendQueryError ValidateResponse(in DnsResponse response) | |
| (ushort transactionId, int length) = EncodeQuestion(memory, name, queryType); | ||
|
|
||
| using var socket = new Socket(serverEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); | ||
| await socket.ConnectAsync(serverEndPoint, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| await socket.SendAsync(memory.Slice(0, length), SocketFlags.None, cancellationToken).ConfigureAwait(false); | ||
| await socket.SendToAsync(memory.Slice(0, length), SocketFlags.None, serverEndPoint, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| DnsDataReader responseReader; | ||
| DnsMessageHeader header; | ||
|
|
@@ -527,8 +546,7 @@ internal static SendQueryError ValidateResponse(in DnsResponse response) | |
| header.TransactionId != transactionId || | ||
| !header.IsResponse) | ||
| { | ||
| // the message is not a response for our query. | ||
| // don't dispose reader, we will reuse the buffer | ||
| // header mismatch, this is not a response to our query | ||
| continue; | ||
rzikm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
|
|
@@ -546,7 +564,7 @@ internal static SendQueryError ValidateResponse(in DnsResponse response) | |
| } | ||
| } | ||
|
|
||
| internal static async ValueTask<(DnsDataReader reader, DnsMessageHeader header)> SendDnsQueryCoreTcpAsync(IPEndPoint serverEndPoint, string name, QueryType queryType, CancellationToken cancellationToken) | ||
| internal static async ValueTask<(DnsDataReader reader, DnsMessageHeader header, SendQueryError error)> SendDnsQueryCoreTcpAsync(IPEndPoint serverEndPoint, string name, QueryType queryType, CancellationToken cancellationToken) | ||
| { | ||
| var buffer = ArrayPool<byte>.Shared.Rent(8 * 1024); | ||
| try | ||
|
|
@@ -566,12 +584,20 @@ internal static SendQueryError ValidateResponse(in DnsResponse response) | |
| int read = await socket.ReceiveAsync(buffer.AsMemory(bytesRead), SocketFlags.None, cancellationToken).ConfigureAwait(false); | ||
BrennanConroy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| bytesRead += read; | ||
|
|
||
| if (read == 0) | ||
| { | ||
| // connection closed before receiving complete response message | ||
| return (default, default, SendQueryError.MalformedResponse); | ||
| } | ||
|
|
||
| if (responseLength < 0 && bytesRead >= 2) | ||
| { | ||
| responseLength = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(0, 2)); | ||
|
|
||
| if (responseLength > buffer.Length) | ||
| { | ||
| // even though this is user-controlled pre-allocation, it is limited to | ||
| // 64 kB, so it should be fine. | ||
| var largerBuffer = ArrayPool<byte>.Shared.Rent(responseLength); | ||
|
||
| Array.Copy(buffer, largerBuffer, bytesRead); | ||
| ArrayPool<byte>.Shared.Return(buffer); | ||
|
|
@@ -585,12 +611,13 @@ internal static SendQueryError ValidateResponse(in DnsResponse response) | |
| header.TransactionId != transactionId || | ||
| !header.IsResponse) | ||
| { | ||
| throw new InvalidOperationException("Invalid response: Header mismatch"); | ||
| // header mismatch on TCP fallback | ||
| return (default, default, SendQueryError.MalformedResponse); | ||
| } | ||
|
|
||
| // transfer ownership of buffer to the caller | ||
| buffer = null!; | ||
BrennanConroy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return (responseReader, header); | ||
| return (responseReader, header, SendQueryError.NoError); | ||
| } | ||
| finally | ||
| { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.