Skip to content

Commit

Permalink
Adds DnsCaptureRawClient.
Browse files Browse the repository at this point in the history
  • Loading branch information
alanedwardes committed Feb 4, 2024
1 parent 2221ff2 commit bc80f15
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 4 deletions.
11 changes: 8 additions & 3 deletions misc/Ae.Dns.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,14 @@ async Task ReportStats(CancellationToken token)
dnsClient = new DnsAppMetricsClient(metrics, dnsClient);

// Create a "raw" client which deals with buffers directly
var rawClient = ActivatorUtilities.CreateInstance<DnsRawClient>(provider, dnsClient);
IDnsRawClient rawClient = ActivatorUtilities.CreateInstance<DnsRawClient>(provider, dnsClient);

IDnsServer tcpServer = ActivatorUtilities.CreateInstance<DnsTcpServer>(provider, rawClient);
IDnsServer udpServer = ActivatorUtilities.CreateInstance<DnsUdpServer>(provider, rawClient);
// Create a dormant capture client for debugging purposes
DnsCaptureRawClient captureRawClient = ActivatorUtilities.CreateInstance<DnsCaptureRawClient>(provider, rawClient);

// Create two servers, TCP and UDP to serve answers
IDnsServer tcpServer = ActivatorUtilities.CreateInstance<DnsTcpServer>(provider, captureRawClient);
IDnsServer udpServer = ActivatorUtilities.CreateInstance<DnsUdpServer>(provider, captureRawClient);

// Add a very basic stats panel
var builder = Host.CreateDefaultBuilder()
Expand All @@ -230,6 +234,7 @@ async Task ReportStats(CancellationToken token)
})
.ConfigureServices(x =>
{
x.AddSingleton(captureRawClient);
x.AddSingleton(mainCache);
x.AddSingleton<IDnsMiddlewareConfig>(new DnsMiddlewareConfig());
x.AddSingleton(dnsClient);
Expand Down
49 changes: 49 additions & 0 deletions misc/Ae.Dns.Console/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,55 @@ async Task GroupToTable(IEnumerable<IGrouping<string?, DnsQuery>> groups, params
await context.Response.WriteAsync("</table>");
}

if (context.Request.Path.StartsWithSegments("/captures"))
{
var captureClient = context.RequestServices.GetRequiredService<DnsCaptureRawClient>();

if (context.Request.Path.StartsWithSegments("/captures/on"))
{
captureClient.IsEnabled = true;
context.Response.Redirect("/captures");
return;
}

if (context.Request.Path.StartsWithSegments("/captures/off"))
{
captureClient.IsEnabled = false;
context.Response.Redirect("/captures");
return;
}

if (context.Request.Path.StartsWithSegments("/captures/clear"))
{
captureClient.Captures.Clear();
context.Response.Redirect("/captures");
return;
}

context.Response.StatusCode = StatusCodes.Status200OK;
context.Response.ContentType = "text/html; charset=utf-8";

await context.Response.WriteAsync($"<h1>Captures</h1>");
await context.Response.WriteAsync($"<p>Captures are {(captureClient.IsEnabled ? "on" : "off")}, captured = {captureClient.Captures.Count()}</p>");
await context.Response.WriteAsync($"<p>Controls: <a href=\"/captures/{(captureClient.IsEnabled ? "off" : "on")}\">{(captureClient.IsEnabled ? "Stop" : "Start")} Capturing</a> <a href=\"/captures/clear\">Clear Captures</a></p>");

await context.Response.WriteAsync($"<ul>");

foreach (var capture in captureClient.Captures)
{
await context.Response.WriteAsync($"<li>");
await context.Response.WriteAsync($"<b>Served by {capture.Request.ServerName} from {capture.Request.SourceEndpoint}</b>");
await context.Response.WriteAsync($"<pre>{capture.Response.Query}</pre>");
await context.Response.WriteAsync($"<pre>Query bytes: {DnsByteExtensions.ToDebugString(capture.Query)}</pre>");
await context.Response.WriteAsync($"<pre>{capture.Response.Answer}</pre>");
await context.Response.WriteAsync($"<pre>Answer bytes: {DnsByteExtensions.ToDebugString(capture.Answer)}</pre>");
await context.Response.WriteAsync($"</li>");

}

await context.Response.WriteAsync($"</ul>");
}

if (context.Request.Path == "/")
{
var dnsClient = context.RequestServices.GetRequiredService<IDnsClient>();
Expand Down
99 changes: 99 additions & 0 deletions src/Ae.Dns.Client/DnsCaptureRawClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using Ae.Dns.Protocol;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace Ae.Dns.Client
{
/// <summary>
/// Defines a client which can be set to capture DNS packets.
/// </summary>
public sealed class DnsCaptureRawClient : IDnsRawClient
{
private readonly IDnsRawClient _innerClient;

/// <summary>
/// The raw bytes for the capture.
/// </summary>
public sealed class Capture
{
/// <summary>
/// The incoming request.
/// </summary>
public DnsRawClientRequest Request { get; set; }
/// <summary>
/// The raw query.
/// </summary>
public ReadOnlyMemory<byte> Query { get; set; }
/// <summary>
/// The raw answer.
/// </summary>
public ReadOnlyMemory<byte> Answer { get; set; }
/// <summary>
/// The outgoing response.
/// </summary>
public DnsRawClientResponse Response { get; set; }
}

/// <summary>
/// Whether packet capture is enabled.
/// </summary>
public bool IsEnabled { get; set; }

/// <summary>
/// An optional filter for the packet capture.
/// </summary>
public Func<DnsRawClientRequest, DnsRawClientResponse, bool> CaptureFilter { get; set; } = (request, response) => true;

/// <summary>
/// The raw captures.
/// </summary>
public ConcurrentBag<Capture> Captures { get; } = new ConcurrentBag<Capture>();

/// <summary>
/// Construct a new (inactive) packet capturing client, delegating to the specified <see cref="IDnsRawClient"/>.
/// </summary>
/// <param name="innerClient"></param>
public DnsCaptureRawClient(IDnsRawClient innerClient)
{
_innerClient = innerClient;
}

/// <inheritdoc/>
public void Dispose() => _innerClient.Dispose();

/// <inheritdoc/>
public async Task<DnsRawClientResponse> Query(Memory<byte> buffer, DnsRawClientRequest request, CancellationToken token = default)
{
if (!IsEnabled)
{
return await _innerClient.Query(buffer, request, token);
}

// Unfortunately we must copy the query before
// we know whether we need it, otherwise it will
// overwritten in the buffer by the answer.
var queryBuffer = new byte[request.QueryLength];
buffer.Slice(0, request.QueryLength).CopyTo(queryBuffer);

var response = await _innerClient.Query(buffer, request, token);

if (CaptureFilter(request, response))
{
var answerBuffer = new byte[response.AnswerLength];
buffer.Slice(0, response.AnswerLength).CopyTo(answerBuffer);

Captures.Add(new Capture
{
Request = request,
Query = queryBuffer,
Answer = answerBuffer,
Response = response
});
}

return response;
}
}
}
3 changes: 2 additions & 1 deletion src/Ae.Dns.Protocol/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
[assembly: InternalsVisibleTo("Ae.Dns.Server.Http")]
[assembly: InternalsVisibleTo("Ae.Dns.Tests")]
[assembly: InternalsVisibleTo("Ae.Dns.Benchmarks")]
[assembly: InternalsVisibleTo("Ae.Dns.Crawler")]
[assembly: InternalsVisibleTo("Ae.Dns.Crawler")]
[assembly: InternalsVisibleTo("Ae.Dns.Console")]

0 comments on commit bc80f15

Please sign in to comment.