diff --git a/samples/MiniDig/Properties/launchSettings.json b/samples/MiniDig/Properties/launchSettings.json index 33ee869b..d4e390dd 100644 --- a/samples/MiniDig/Properties/launchSettings.json +++ b/samples/MiniDig/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "DigApp": { "commandName": "Project", - "commandLineArgs": "perf -s 192.168.178.21 localhost -c 5 -r 10" + "commandLineArgs": "-s 8.8.4.4 microsoft.com any" }, "MiniDig": { "commandName": "Project" diff --git a/src/DnsClient/DnsDatagramWriter.cs b/src/DnsClient/DnsDatagramWriter.cs index 17a33d78..1906f528 100644 --- a/src/DnsClient/DnsDatagramWriter.cs +++ b/src/DnsClient/DnsDatagramWriter.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Net; using System.Text; @@ -8,7 +9,7 @@ namespace DnsClient internal class DnsDatagramWriter : IDisposable { // queries can only be 255 octets + some header bytes, so that size is pretty safe... - private const int MaxBufferSize = 1024; + public const int BufferSize = 1024; private const byte DotByte = 46; @@ -16,11 +17,11 @@ internal class DnsDatagramWriter : IDisposable private ArraySegment _buffer; - public byte[] Data + public ArraySegment Data { get { - return new ArraySegment(_buffer.Array, 0, Index).ToArray(); + return new ArraySegment(_buffer.Array, 0, Index); } } @@ -28,8 +29,15 @@ public byte[] Data public DnsDatagramWriter() { - _pooledBytes = new PooledBytes(MaxBufferSize); - _buffer = new ArraySegment(_pooledBytes.Buffer, 0, MaxBufferSize); + _pooledBytes = new PooledBytes(BufferSize); + _buffer = new ArraySegment(_pooledBytes.Buffer, 0, BufferSize); + } + + public DnsDatagramWriter(ArraySegment useBuffer) + { + Debug.Assert(useBuffer.Count >= BufferSize); + + _buffer = useBuffer; } public void WriteHostName(string queryName) @@ -91,7 +99,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - _pooledBytes.Dispose(); + _pooledBytes?.Dispose(); } } diff --git a/src/DnsClient/DnsMessageHandler.cs b/src/DnsClient/DnsMessageHandler.cs index 7efa1e30..977aea40 100644 --- a/src/DnsClient/DnsMessageHandler.cs +++ b/src/DnsClient/DnsMessageHandler.cs @@ -14,67 +14,60 @@ internal abstract class DnsMessageHandler public abstract bool IsTransientException(T exception) where T : Exception; - public virtual byte[] GetRequestData(DnsRequestMessage request) + public virtual void GetRequestData(DnsRequestMessage request, DnsDatagramWriter writer) { var question = request.Question; /* - 1 1 1 1 1 1 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | ID | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |QR| Opcode |AA|TC|RD|RA| Z | RCODE | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | QDCOUNT | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | ANCOUNT | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | NSCOUNT | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | ARCOUNT | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - * */ + 1 1 1 1 1 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | ID | + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |QR| Opcode |AA|TC|RD|RA| Z | RCODE | + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | QDCOUNT | + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | ANCOUNT | + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | NSCOUNT | + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | ARCOUNT | + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + * */ // 4 more bytes for the type and class - using (var writer = new DnsDatagramWriter()) - { - writer.WriteInt16NetworkOrder((short)request.Header.Id); - writer.WriteUInt16NetworkOrder(request.Header.RawFlags); - writer.WriteInt16NetworkOrder(1); // we support single question only... (as most DNS servers anyways). - writer.WriteInt16NetworkOrder(0); - writer.WriteInt16NetworkOrder(0); - writer.WriteInt16NetworkOrder(1); // one additional for the Opt record. - - writer.WriteHostName(question.QueryName); - //writer.WriteBytes(questionData.Array, questionData.Count); - writer.WriteUInt16NetworkOrder((ushort)question.QuestionType); - writer.WriteUInt16NetworkOrder((ushort)question.QuestionClass); - - /* - +------------+--------------+------------------------------+ - | Field Name | Field Type | Description | - +------------+--------------+------------------------------+ - | NAME | domain name | MUST be 0 (root domain) | - | TYPE | u_int16_t | OPT (41) | - | CLASS | u_int16_t | requestor's UDP payload size | - | TTL | u_int32_t | extended RCODE and flags | - | RDLEN | u_int16_t | length of all RDATA | - | RDATA | octet stream | {attribute,value} pairs | - +------------+--------------+------------------------------+ - * */ - var opt = new OptRecord(); - //var nameBytes = opt.DomainName.GetBytes(); - writer.WriteHostName(""); - //writer.WriteBytes(nameBytes.Array, nameBytes.Count); - writer.WriteUInt16NetworkOrder((ushort)opt.RecordType); - writer.WriteUInt16NetworkOrder((ushort)opt.RecordClass); - writer.WriteUInt32NetworkOrder((ushort)opt.TimeToLive); - writer.WriteUInt16NetworkOrder(0); - - return writer.Data; - - // dispose the writer here to return puled byte array... otherwise we don't know when to dispose and might be risky - } + + writer.WriteInt16NetworkOrder((short)request.Header.Id); + writer.WriteUInt16NetworkOrder(request.Header.RawFlags); + writer.WriteInt16NetworkOrder(1); // we support single question only... (as most DNS servers anyways). + writer.WriteInt16NetworkOrder(0); + writer.WriteInt16NetworkOrder(0); + writer.WriteInt16NetworkOrder(1); // one additional for the Opt record. + + writer.WriteHostName(question.QueryName); + writer.WriteUInt16NetworkOrder((ushort)question.QuestionType); + writer.WriteUInt16NetworkOrder((ushort)question.QuestionClass); + + /* + +------------+--------------+------------------------------+ + | Field Name | Field Type | Description | + +------------+--------------+------------------------------+ + | NAME | domain name | MUST be 0 (root domain) | + | TYPE | u_int16_t | OPT (41) | + | CLASS | u_int16_t | requestor's UDP payload size | + | TTL | u_int32_t | extended RCODE and flags | + | RDLEN | u_int16_t | length of all RDATA | + | RDATA | octet stream | {attribute,value} pairs | + +------------+--------------+------------------------------+ + * */ + + var opt = new OptRecord(); + + writer.WriteHostName(""); + writer.WriteUInt16NetworkOrder((ushort)opt.RecordType); + writer.WriteUInt16NetworkOrder((ushort)opt.RecordClass); + writer.WriteUInt32NetworkOrder((ushort)opt.TimeToLive); + writer.WriteUInt16NetworkOrder(0); } public virtual DnsResponseMessage GetResponseMessage(ArraySegment responseData) diff --git a/src/DnsClient/DnsName.cs b/src/DnsClient/DnsName.cs index 4b17857b..8961f3cf 100644 --- a/src/DnsClient/DnsName.cs +++ b/src/DnsClient/DnsName.cs @@ -447,7 +447,7 @@ public byte[] GetBytes() using (var writer = new DnsDatagramWriter()) { WriteBytes(writer); - return writer.Data; + return writer.Data.ToArray(); } } diff --git a/src/DnsClient/DnsTcpMessageHandler.cs b/src/DnsClient/DnsTcpMessageHandler.cs index 12644340..4109a16f 100644 --- a/src/DnsClient/DnsTcpMessageHandler.cs +++ b/src/DnsClient/DnsTcpMessageHandler.cs @@ -17,7 +17,7 @@ public override bool IsTransientException(T exception) public override DnsResponseMessage Query(IPEndPoint endpoint, DnsRequestMessage request) { - throw new NotImplementedException(); + return QueryAsync(endpoint, request, CancellationToken.None).Result; } public override async Task QueryAsync( @@ -32,21 +32,23 @@ public override async Task QueryAsync( await client.ConnectAsync(server.Address, server.Port).ConfigureAwait(false); using (var stream = client.GetStream()) { - var data = GetRequestData(request); - int dataLength = data.Length; - - //var sendLength = new byte[2]; - //sendLength[0] = (byte)((data.Length >> 8) & 0xff); - //sendLength[1] = (byte)(data.Length & 0xff); + // use a pooled buffer to writer the data + the length of the data later into the frist two bytes + using (var memory = new PooledBytes(DnsDatagramWriter.BufferSize + 2)) + using (var writer = new DnsDatagramWriter(new ArraySegment(memory.Buffer, 2, memory.Buffer.Length - 2))) + { + GetRequestData(request, writer); + int dataLength = writer.Index; + memory.Buffer[0] = (byte)((dataLength >> 8) & 0xff); + memory.Buffer[1] = (byte)(dataLength & 0xff); - var sendData = new byte[dataLength + 2]; - sendData[0] = (byte)((dataLength >> 8) & 0xff); - sendData[1] = (byte)(dataLength & 0xff); - Array.Copy(data, 0, sendData, 2, dataLength); - - await stream.WriteAsync(sendData, 0, sendData.Length, cancellationToken).ConfigureAwait(false); + //var sendData = new byte[dataLength + 2]; + //sendData[0] = (byte)((dataLength >> 8) & 0xff); + //sendData[1] = (byte)(dataLength & 0xff); + //Array.Copy(data, 0, sendData, 2, dataLength); - await stream.FlushAsync(cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(memory.Buffer, 0, dataLength + 2, cancellationToken).ConfigureAwait(false); + await stream.FlushAsync(cancellationToken).ConfigureAwait(false); + } int length = stream.ReadByte() << 8 | stream.ReadByte(); if (length <= 0) diff --git a/src/DnsClient/DnsUdpMessageHandler.cs b/src/DnsClient/DnsUdpMessageHandler.cs index 9773599f..86bacde5 100644 --- a/src/DnsClient/DnsUdpMessageHandler.cs +++ b/src/DnsClient/DnsUdpMessageHandler.cs @@ -10,20 +10,13 @@ namespace DnsClient { internal class DnsUdpMessageHandler : DnsMessageHandler { - private const int MaxSize = 4096 * 4; + private const int MaxSize = 4096; private static ConcurrentQueue _clients = new ConcurrentQueue(); private readonly bool _enableClientQueue; public DnsUdpMessageHandler(bool enableClientQueue) { _enableClientQueue = enableClientQueue; - if (_enableClientQueue && _clients.Count == 0) - { - for (var i = 0; i < 10; i++) - { - _clients.Enqueue(new UdpClient()); - } - } } public override bool IsTransientException(T exception) @@ -39,18 +32,19 @@ public override DnsResponseMessage Query( UdpClient udpClient = GetNextUdpClient(); try { - var data = GetRequestData(request); - udpClient.Client.SendTo(data, server); - - using (var memory = new PooledBytes(MaxSize)) + using (var writer = new DnsDatagramWriter()) { - var received = udpClient.Client.Receive(memory.Buffer, 0, 4096, SocketFlags.None); - while (udpClient.Available > 0) - { - received = udpClient.Client.Receive(memory.Buffer, received, 4096, SocketFlags.None); - } + GetRequestData(request, writer); + udpClient.Client.SendTo(writer.Data.Array, writer.Data.Offset, writer.Data.Count, SocketFlags.None, server); + } + + var readSize = udpClient.Available > MaxSize ? udpClient.Available : MaxSize; - var response = GetResponseMessage(new ArraySegment(memory.Buffer, 0, memory.Buffer.Length)); + using (var memory = new PooledBytes(readSize)) + { + var received = udpClient.Client.Receive(memory.Buffer, 0, readSize, SocketFlags.None); + + var response = GetResponseMessage(new ArraySegment(memory.Buffer, 0, received)); if (request.Header.Id != response.Header.Id) { throw new DnsResponseException("Header id missmatch."); @@ -68,16 +62,19 @@ public override DnsResponseMessage Query( { if (!_enableClientQueue) { + try + { #if PORTABLE - udpClient.Dispose(); + udpClient.Dispose(); #else - udpClient.Close(); + udpClient.Close(); #endif + } + catch { } } } } - ////private static readonly byte[] fixData = new byte[] { 25, 158, 133, 0, 0, 1, 0, 1, 0, 0, 0, 1, 9, 108, 111, 99, 97, 108, 104, 111, 115, 116, 0, 0, 1, 0, 1, 192, 12, 0, 1, 0, 1, 0, 9, 58, 128, 0, 4, 127, 0, 0, 1, 0, 0, 41, 16, 0, 0, 0, 0, 0, 0, 0 }; public override async Task QueryAsync( IPEndPoint server, DnsRequestMessage request, @@ -88,34 +85,52 @@ public override async Task QueryAsync( UdpClient udpClient = GetNextUdpClient(); try { - var data = GetRequestData(request); - await udpClient.SendAsync(data, data.Length, server).ConfigureAwait(false); - - var result = await udpClient.ReceiveAsync().ConfigureAwait(false); - - var response = GetResponseMessage(new ArraySegment(result.Buffer, 0, result.Buffer.Length)); - - if (request.Header.Id != response.Header.Id) + using (var writer = new DnsDatagramWriter()) { - throw new DnsResponseException("Header id missmatch."); + GetRequestData(request, writer); + await udpClient.SendAsync(writer.Data.Array, writer.Data.Count, server).ConfigureAwait(false); } - if (_enableClientQueue) + var readSize = udpClient.Available > MaxSize ? udpClient.Available : MaxSize; + + using (var memory = new PooledBytes(readSize)) { - _clients.Enqueue(udpClient); - } +#if PORTABLE + int received = await udpClient.Client.ReceiveAsync(new ArraySegment(memory.Buffer), SocketFlags.None).ConfigureAwait(false); + + var response = GetResponseMessage(new ArraySegment(memory.Buffer, 0, received)); + +#else + var result = await udpClient.ReceiveAsync().ConfigureAwait(false); + + var response = GetResponseMessage(new ArraySegment(result.Buffer, 0, result.Buffer.Length)); +#endif + if (request.Header.Id != response.Header.Id) + { + throw new DnsResponseException("Header id missmatch."); + } + + if (_enableClientQueue) + { + _clients.Enqueue(udpClient); + } - return response; + return response; + } } finally { if (!_enableClientQueue) { + try + { #if PORTABLE - udpClient.Dispose(); + udpClient.Dispose(); #else - udpClient.Close(); + udpClient.Close(); #endif + } + catch { } } } } diff --git a/src/DnsClient/LookupClient.cs b/src/DnsClient/LookupClient.cs index ab894e79..e71284d8 100644 --- a/src/DnsClient/LookupClient.cs +++ b/src/DnsClient/LookupClient.cs @@ -355,7 +355,7 @@ private async Task QueryAsync(DnsQuestion question, Cancellati var item = _cache.Get(cacheKey); if (item == null) { - item = await ResolveQueryAsync(handler, request, cancellationToken); + item = await ResolveQueryAsync(handler, request, cancellationToken).ConfigureAwait(false); _cache.Add(cacheKey, item); } @@ -363,7 +363,7 @@ private async Task QueryAsync(DnsQuestion question, Cancellati } else { - return await ResolveQueryAsync(handler, request, cancellationToken); + return await ResolveQueryAsync(handler, request, cancellationToken).ConfigureAwait(false); } } @@ -395,18 +395,17 @@ private async Task ResolveQueryAsync(DnsMessageHandler handler DnsResponseMessage response; var resultTask = handler.QueryAsync(serverInfo.Endpoint, request, cancellationToken); - //todo: fix this task shit + if (Timeout != s_infiniteTimeout) { using (var cts = new CancellationTokenSource(Timeout)) - //using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken)) { - response = await resultTask.WithCancellation(cts.Token); + response = await resultTask.WithCancellation(cts.Token).ConfigureAwait(false); } } else { - response = await resultTask; + response = await resultTask.ConfigureAwait(false); } if (response.Header.ResultTruncated && UseTcpFallback && !handler.GetType().Equals(typeof(DnsTcpMessageHandler))) diff --git a/src/DnsClient/PooledBytes.cs b/src/DnsClient/PooledBytes.cs index 48c129f9..0c55860f 100644 --- a/src/DnsClient/PooledBytes.cs +++ b/src/DnsClient/PooledBytes.cs @@ -7,7 +7,7 @@ namespace DnsClient { internal class PooledBytes : IDisposable { - private static readonly ArrayPool _pool = ArrayPool.Create(4096, 500); + private static readonly ArrayPool _pool = ArrayPool.Create(4096 * 2, 200); private readonly byte[] _buffer; diff --git a/src/DnsClient/ResponseCache.cs b/src/DnsClient/ResponseCache.cs index eb107fee..8355317b 100644 --- a/src/DnsClient/ResponseCache.cs +++ b/src/DnsClient/ResponseCache.cs @@ -108,6 +108,7 @@ private static void DoCleanup(ResponseCache cache) private void StartCleanup() { + // TickCount jump every 25days to int.MinValue, adjusting... var currentTicks = Environment.TickCount & int.MaxValue; if (_lastCleanup + CleanupInterval < 0 || currentTicks + CleanupInterval < 0) _lastCleanup = 0; if (!_cleanupRunning && _lastCleanup + CleanupInterval < currentTicks) diff --git a/src/DnsClient/TaskExtensions.cs b/src/DnsClient/TaskExtensions.cs index 005f77a3..73fb4373 100644 --- a/src/DnsClient/TaskExtensions.cs +++ b/src/DnsClient/TaskExtensions.cs @@ -27,43 +27,34 @@ internal static class TaskExtensions // } //} - //public static async Task TimeoutAfter(this Task task, TimeSpan timeout, CancellationToken parentToken) - //{ - // using (var cts = new CancellationTokenSource()) - // using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, parentToken)) - // { - // var linkedToken = linkedSource.Token; - // int millis = timeout.TotalMilliseconds > int.MaxValue ? int.MaxValue : (int)timeout.TotalMilliseconds; + // too slow + ////public static async Task TimeoutAfter(this Task task, TimeSpan timeout) + ////{ + //// int millis = timeout.TotalMilliseconds > int.MaxValue ? int.MaxValue : (int)timeout.TotalMilliseconds; - // if (task == await Task.WhenAny(task, Task.Delay(millis, linkedToken)).ConfigureAwait(false)) - // { - // linkedSource.Cancel(); - // return await task.ConfigureAwait(false); - // } - // else - // { - // if (parentToken.IsCancellationRequested) - // { - // throw new TaskCanceledException(); - // } - - // throw new TimeoutException(); - // } - // } - //} + //// if (task == await Task.WhenAny(task, Task.Delay(millis)).ConfigureAwait(false)) + //// { + //// return task.Result; + //// } + //// else + //// { + //// throw new TimeoutException(); + //// } + ////} public static async Task WithCancellation(this Task task, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource(); + using (cancellationToken.Register(s => ((TaskCompletionSource)s).TrySetResult(true), tcs)) { - if (task != await Task.WhenAny(task, tcs.Task)) + if (task != await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)) { throw new OperationCanceledException(cancellationToken); } } - return await task; + return await task.ConfigureAwait(false); } } } \ No newline at end of file diff --git a/test/Benchmark/Benchmark.csproj b/test/Benchmark/Benchmark.csproj index 127f9d9d..0de0dad3 100644 --- a/test/Benchmark/Benchmark.csproj +++ b/test/Benchmark/Benchmark.csproj @@ -52,7 +52,7 @@ False - ..\..\src\DnsClient\bin\Debug\net45\DnsClient.dll + ..\..\src\DnsClient\bin\Release\net45\DnsClient.dll diff --git a/test/Benchmark/Program.cs b/test/Benchmark/Program.cs index 4e490bea..899855ac 100644 --- a/test/Benchmark/Program.cs +++ b/test/Benchmark/Program.cs @@ -26,7 +26,7 @@ private static void Main(string[] args) _client.UseCache = false; _client.EnableAuditTrail = false; - double seconds = 15; + double seconds = 10; int runtime = 1000 * (int)seconds; int clients = 40; diff --git a/test/DnsClient.Tests/DnsMessageHandlerTest.cs b/test/DnsClient.Tests/DnsMessageHandlerTest.cs index 4b4b1d22..8bf358ff 100644 --- a/test/DnsClient.Tests/DnsMessageHandlerTest.cs +++ b/test/DnsClient.Tests/DnsMessageHandlerTest.cs @@ -66,7 +66,7 @@ private static byte[] GetResponseBytes(DnsQueryResponse message, byte[] answerDa //writer.Extend(answerData.Length); // the following data->length writer.WriteBytes(answerData, answerData.Length); - return writer.Data; + return writer.Data.ToArray(); } } } diff --git a/test/FullFrameworkOwinApp/FullFrameworkOwinApp.csproj b/test/FullFrameworkOwinApp/FullFrameworkOwinApp.csproj index f26b420b..56777048 100644 --- a/test/FullFrameworkOwinApp/FullFrameworkOwinApp.csproj +++ b/test/FullFrameworkOwinApp/FullFrameworkOwinApp.csproj @@ -44,8 +44,8 @@ - ..\..\packages\DnsClient.1.0.2-beta-1056\lib\net45\DnsClient.dll - True + False + ..\..\src\DnsClient\bin\Debug\net45\DnsClient.dll ..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll diff --git a/test/FullFrameworkOwinApp/Startup.cs b/test/FullFrameworkOwinApp/Startup.cs index 1394b7e2..98884a10 100644 --- a/test/FullFrameworkOwinApp/Startup.cs +++ b/test/FullFrameworkOwinApp/Startup.cs @@ -35,6 +35,7 @@ public void Configuration(IAppBuilder app) private static async Task GetService(string query) { var dnsClient = new LookupClient(); + dnsClient.UseTcpOnly = true; var dnsResult = await dnsClient.QueryAsync(query, QueryType.ANY).ConfigureAwait(false); var aRecord = dnsResult.Answers.ARecords().FirstOrDefault();