PowerDNS Socket (to be changed from custom-implementation to Kestrel with ConnectionHandler)

This commit is contained in:
Jöran Malek 2023-12-14 00:32:19 +01:00
parent 58be1ee178
commit 7f9dc36473
9 changed files with 196 additions and 31 deletions

View file

@ -3,6 +3,8 @@ using System.Globalization;
using DotNext.Buffers; using DotNext.Buffers;
using Microsoft.Toolkit.HighPerformance.Buffers;
namespace pdns_dhcp.Kea; namespace pdns_dhcp.Kea;
public static class KeaDhcpLease public static class KeaDhcpLease
@ -21,7 +23,7 @@ public static class KeaDhcpLease
static string SlowPath(int esc_pos, in ReadOnlySpan<char> text) static string SlowPath(int esc_pos, in ReadOnlySpan<char> text)
{ {
SpanReader<char> reader = new(text); SpanReader<char> reader = new(text);
ArrayBufferWriter<char> writer = new(text.Length); using ArrayPoolBufferWriter<char> writer = new(text.Length);
while (reader.RemainingCount > 0) while (reader.RemainingCount > 0)
{ {
writer.Write(reader.Read(esc_pos)); writer.Write(reader.Read(esc_pos));

View file

@ -35,7 +35,7 @@ public sealed class KeaDhcpLeaseWatcher<T> : IHostedService
public KeaDhcpLeaseWatcher(KeaDhcpServerOptions options, T handler) public KeaDhcpLeaseWatcher(KeaDhcpServerOptions options, T handler)
{ {
Options = options; Options = options = options with { Leases = PathEx.ExpandPath(options.Leases) };
_handler = handler; _handler = handler;
var leases = options.Leases.AsSpan(); var leases = options.Leases.AsSpan();

View file

@ -3,10 +3,7 @@ using System.Text.Json.Serialization;
namespace pdns_dhcp.PowerDns; namespace pdns_dhcp.PowerDns;
public interface IMethod public interface IMethod;
{
public abstract static string Method { get; }
}
[JsonPolymorphic(TypeDiscriminatorPropertyName = "method")] [JsonPolymorphic(TypeDiscriminatorPropertyName = "method")]
[JsonDerivedType(typeof(InitializeMethod), "initialize")] [JsonDerivedType(typeof(InitializeMethod), "initialize")]
@ -24,12 +21,6 @@ public abstract class Method<TSelf, TParam>(TParam parameters) : Method<TSelf> w
public TParam Parameters => parameters; public TParam Parameters => parameters;
} }
public class InitializeMethod : Method<InitializeMethod>, IMethod public class InitializeMethod : Method<InitializeMethod>, IMethod;
{
public static string Method => "Initialize";
}
public class LookupMethod : Method<LookupMethod>, IMethod public class LookupMethod : Method<LookupMethod>, IMethod;
{
public static string Method => "Lookup";
}

View file

@ -1,7 +1,5 @@
using System.Text.Json; using System.Text.Json;
using Stl.Async;
namespace pdns_dhcp.PowerDns; namespace pdns_dhcp.PowerDns;
public class PowerDnsStreamClient : IDisposable public class PowerDnsStreamClient : IDisposable
@ -17,32 +15,63 @@ public class PowerDnsStreamClient : IDisposable
~PowerDnsStreamClient() ~PowerDnsStreamClient()
{ {
Dispose(); DisposeCore();
} }
public void Dispose() public void Dispose()
{ {
using (_cts) _cts.Cancel();
using (_stream) if (Interlocked.Exchange(ref _task, null!) is { } task)
{ {
_cts.Cancel(); task
_task.GetAwaiter().GetResult(); .ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing)
.GetAwaiter().GetResult();
} }
DisposeCore();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
public void Start(CancellationToken stoppingToken) public void Start(CancellationToken stoppingToken)
{ {
using var other = Interlocked.Exchange(ref _cts, CancellationTokenSource.CreateLinkedTokenSource(stoppingToken)); var cts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
_task = Run(_cts.Token); using var old = Interlocked.Exchange(ref _cts, cts);
other.Cancel(); old.Cancel();
_task = Run(cts.Token);
}
private void DisposeCore()
{
_cts.Dispose();
_stream.Dispose();
} }
private async Task Run(CancellationToken stoppingToken) private async Task Run(CancellationToken stoppingToken)
{ {
while (!stoppingToken.IsCancellationRequested) try
{ {
await JsonSerializer.DeserializeAsync<Method>(_stream, cancellationToken: stoppingToken); while (!stoppingToken.IsCancellationRequested)
{
switch (await JsonSerializer.DeserializeAsync<Method>(_stream, cancellationToken: stoppingToken).ConfigureAwait(false))
{
case InitializeMethod init:
break;
case LookupMethod lookup:
break;
default:
break;
}
}
}
finally
{
if (Interlocked.Exchange(ref _task, null!) is not null)
{
Dispose();
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
using Microsoft.Extensions.Configuration; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using pdns_dhcp.Kea; using pdns_dhcp.Kea;
using pdns_dhcp.Options; using pdns_dhcp.Options;
@ -9,7 +9,7 @@ using pdns_dhcp.Services;
using Stl.Interception; using Stl.Interception;
var builder = Host.CreateApplicationBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<DhcpOptions>(builder.Configuration.GetRequiredSection("Dhcp")); builder.Services.Configure<DhcpOptions>(builder.Configuration.GetRequiredSection("Dhcp"));
builder.Services.Configure<PowerDnsOptions>(builder.Configuration.GetRequiredSection("PowerDns")); builder.Services.Configure<PowerDnsOptions>(builder.Configuration.GetRequiredSection("PowerDns"));

View file

@ -16,8 +16,17 @@ public class PowerDnsBackend : BackgroundService
public PowerDnsBackend(IOptions<PowerDnsOptions> options, IPowerDnsFactory socketFactory) public PowerDnsBackend(IOptions<PowerDnsOptions> options, IPowerDnsFactory socketFactory)
{ {
_factory = socketFactory; _factory = socketFactory;
_socket = new(SocketType.Stream, ProtocolType.Unknown); _socket = new(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
_socket.Bind(new UnixDomainSocketEndPoint(options.Value.Listener.Socket)); var path = PathEx.ExpandPath(options.Value.Listener.Socket);
FileInfo file = new(path);
file.Directory!.Create();
file.Delete();
_socket.Bind(new UnixDomainSocketEndPoint(path));
}
~PowerDnsBackend()
{
DisposeCore();
} }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
@ -29,4 +38,16 @@ public class PowerDnsBackend : BackgroundService
.Start(stoppingToken); .Start(stoppingToken);
} }
} }
public override void Dispose()
{
base.Dispose();
DisposeCore();
GC.SuppressFinalize(this);
}
private void DisposeCore()
{
_socket.Dispose();
}
} }

View file

@ -0,0 +1,113 @@
#if !(LINUX || MACOS || WINDOWS)
#define TARGET_ANY
#endif
#if TARGET_ANY || LINUX
#define TARGET_LINUX
#endif
#if TARGET_ANY || MACOS
#define TARGET_MACOS
#endif
#if TARGET_ANY || WINDOWS
#define TARGET_WINDOWS
#endif
using System.Buffers;
using Microsoft.Toolkit.HighPerformance.Buffers;
namespace System.IO;
public static class PathEx
{
public static string ExpandPath(string path)
{
#if TARGET_ANY
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
#endif
#if TARGET_LINUX || TARGET_MACOS
return ExpandPathUnixImpl(path);
#endif
#if TARGET_ANY
}
else if (OperatingSystem.IsWindows())
{
#endif
#if TARGET_WINDOWS
return ExpandPathWindowsImpl(path);
#endif
#if TARGET_ANY
}
else
{
throw new PlatformNotSupportedException();
}
#endif
}
#if TARGET_LINUX || TARGET_MACOS
private static string ExpandPathUnixImpl(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return path;
}
var reader = new SequenceReader<char>(new(path.AsMemory()));
if (!reader.TryReadTo(out ReadOnlySpan<char> read, '$'))
{
return path;
}
using ArrayPoolBufferWriter<char> result = new();
while (true)
{
result.Write(read);
int skip = 0;
if (reader.UnreadSpan[0] == '{' && reader.UnreadSpan.IndexOf('}') is not -1 and int s)
{
read = reader.UnreadSpan[1..s];
skip = s + 1;
}
else
{
var propertyReader = new SequenceReader<char>(reader.UnreadSequence);
while (!propertyReader.End && propertyReader.UnreadSpan[0] is char c && (c == '_' || char.IsAsciiLetterOrDigit(c)))
{
propertyReader.Advance(1);
}
read = reader.UnreadSpan[..(int)propertyReader.Consumed];
skip = read.Length;
}
if (skip != 0 && Environment.GetEnvironmentVariable(read.ToString()) is { } env)
{
result.Write(env);
reader.Advance(skip);
}
else
{
result.Write(['$']);
}
if (!reader.TryReadTo(out read, '$'))
{
break;
}
}
result.Write(reader.UnreadSpan);
return result.WrittenSpan.ToString();
}
#endif
#if TARGET_WINDOWS
private static string ExpandPathWindowsImpl(string path)
{
return Environment.ExpandEnvironmentVariables(path);
}
#endif
}

View file

@ -8,5 +8,10 @@
"Leases": "../../ext/kea/dhcp6.leases" "Leases": "../../ext/kea/dhcp6.leases"
} }
} }
},
"PowerDns": {
"Listener": {
"Socket": "${XDG_RUNTIME_DIR}/pdns-dhcp/pdns.sock"
}
} }
} }

View file

@ -9,6 +9,10 @@
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator> <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DotNext" Version="4.15.2" /> <PackageReference Include="DotNext" Version="4.15.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />