diff --git a/src/pdns-dhcp/Kea/KeaDhcpLease.cs b/src/pdns-dhcp/Kea/KeaDhcpLease.cs index a96bba8..2f08f59 100644 --- a/src/pdns-dhcp/Kea/KeaDhcpLease.cs +++ b/src/pdns-dhcp/Kea/KeaDhcpLease.cs @@ -3,6 +3,8 @@ using System.Globalization; using DotNext.Buffers; +using Microsoft.Toolkit.HighPerformance.Buffers; + namespace pdns_dhcp.Kea; public static class KeaDhcpLease @@ -21,7 +23,7 @@ public static class KeaDhcpLease static string SlowPath(int esc_pos, in ReadOnlySpan text) { SpanReader reader = new(text); - ArrayBufferWriter writer = new(text.Length); + using ArrayPoolBufferWriter writer = new(text.Length); while (reader.RemainingCount > 0) { writer.Write(reader.Read(esc_pos)); diff --git a/src/pdns-dhcp/Kea/KeaDhcpLeaseWatcher.cs b/src/pdns-dhcp/Kea/KeaDhcpLeaseWatcher.cs index d13091c..3e7b428 100644 --- a/src/pdns-dhcp/Kea/KeaDhcpLeaseWatcher.cs +++ b/src/pdns-dhcp/Kea/KeaDhcpLeaseWatcher.cs @@ -35,7 +35,7 @@ public sealed class KeaDhcpLeaseWatcher : IHostedService public KeaDhcpLeaseWatcher(KeaDhcpServerOptions options, T handler) { - Options = options; + Options = options = options with { Leases = PathEx.ExpandPath(options.Leases) }; _handler = handler; var leases = options.Leases.AsSpan(); diff --git a/src/pdns-dhcp/PowerDns/Methods.cs b/src/pdns-dhcp/PowerDns/Methods.cs index a9e882e..d6843f8 100644 --- a/src/pdns-dhcp/PowerDns/Methods.cs +++ b/src/pdns-dhcp/PowerDns/Methods.cs @@ -3,10 +3,7 @@ using System.Text.Json.Serialization; namespace pdns_dhcp.PowerDns; -public interface IMethod -{ - public abstract static string Method { get; } -} +public interface IMethod; [JsonPolymorphic(TypeDiscriminatorPropertyName = "method")] [JsonDerivedType(typeof(InitializeMethod), "initialize")] @@ -24,12 +21,6 @@ public abstract class Method(TParam parameters) : Method w public TParam Parameters => parameters; } -public class InitializeMethod : Method, IMethod -{ - public static string Method => "Initialize"; -} +public class InitializeMethod : Method, IMethod; -public class LookupMethod : Method, IMethod -{ - public static string Method => "Lookup"; -} +public class LookupMethod : Method, IMethod; diff --git a/src/pdns-dhcp/PowerDns/PowerDnsStreamClient.cs b/src/pdns-dhcp/PowerDns/PowerDnsStreamClient.cs index d38d782..41efea6 100644 --- a/src/pdns-dhcp/PowerDns/PowerDnsStreamClient.cs +++ b/src/pdns-dhcp/PowerDns/PowerDnsStreamClient.cs @@ -1,7 +1,5 @@ using System.Text.Json; -using Stl.Async; - namespace pdns_dhcp.PowerDns; public class PowerDnsStreamClient : IDisposable @@ -17,32 +15,63 @@ public class PowerDnsStreamClient : IDisposable ~PowerDnsStreamClient() { - Dispose(); + DisposeCore(); } public void Dispose() { - using (_cts) - using (_stream) + _cts.Cancel(); + if (Interlocked.Exchange(ref _task, null!) is { } task) { - _cts.Cancel(); - _task.GetAwaiter().GetResult(); + task + .ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing) + .GetAwaiter().GetResult(); } + + DisposeCore(); + GC.SuppressFinalize(this); } public void Start(CancellationToken stoppingToken) { - using var other = Interlocked.Exchange(ref _cts, CancellationTokenSource.CreateLinkedTokenSource(stoppingToken)); - _task = Run(_cts.Token); - other.Cancel(); + var cts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); + using var old = Interlocked.Exchange(ref _cts, cts); + old.Cancel(); + _task = Run(cts.Token); + } + + private void DisposeCore() + { + _cts.Dispose(); + _stream.Dispose(); } private async Task Run(CancellationToken stoppingToken) { - while (!stoppingToken.IsCancellationRequested) + try { - await JsonSerializer.DeserializeAsync(_stream, cancellationToken: stoppingToken); + while (!stoppingToken.IsCancellationRequested) + { + switch (await JsonSerializer.DeserializeAsync(_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(); + } } } } diff --git a/src/pdns-dhcp/Program.cs b/src/pdns-dhcp/Program.cs index 3ea6830..a79f932 100644 --- a/src/pdns-dhcp/Program.cs +++ b/src/pdns-dhcp/Program.cs @@ -1,6 +1,6 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using pdns_dhcp.Kea; using pdns_dhcp.Options; @@ -9,7 +9,7 @@ using pdns_dhcp.Services; using Stl.Interception; -var builder = Host.CreateApplicationBuilder(args); +var builder = WebApplication.CreateBuilder(args); builder.Services.Configure(builder.Configuration.GetRequiredSection("Dhcp")); builder.Services.Configure(builder.Configuration.GetRequiredSection("PowerDns")); diff --git a/src/pdns-dhcp/Services/PowerDnsBackend.cs b/src/pdns-dhcp/Services/PowerDnsBackend.cs index 8f6f7ea..fed8a3b 100644 --- a/src/pdns-dhcp/Services/PowerDnsBackend.cs +++ b/src/pdns-dhcp/Services/PowerDnsBackend.cs @@ -16,8 +16,17 @@ public class PowerDnsBackend : BackgroundService public PowerDnsBackend(IOptions options, IPowerDnsFactory socketFactory) { _factory = socketFactory; - _socket = new(SocketType.Stream, ProtocolType.Unknown); - _socket.Bind(new UnixDomainSocketEndPoint(options.Value.Listener.Socket)); + _socket = new(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + 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) @@ -29,4 +38,16 @@ public class PowerDnsBackend : BackgroundService .Start(stoppingToken); } } + + public override void Dispose() + { + base.Dispose(); + DisposeCore(); + GC.SuppressFinalize(this); + } + + private void DisposeCore() + { + _socket.Dispose(); + } } diff --git a/src/pdns-dhcp/System/IO/Paths.cs b/src/pdns-dhcp/System/IO/Paths.cs new file mode 100644 index 0000000..9ea36be --- /dev/null +++ b/src/pdns-dhcp/System/IO/Paths.cs @@ -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(new(path.AsMemory())); + if (!reader.TryReadTo(out ReadOnlySpan read, '$')) + { + return path; + } + + using ArrayPoolBufferWriter 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(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 +} diff --git a/src/pdns-dhcp/appsettings.Development.json b/src/pdns-dhcp/appsettings.Development.json index a2c1054..c0cab42 100644 --- a/src/pdns-dhcp/appsettings.Development.json +++ b/src/pdns-dhcp/appsettings.Development.json @@ -8,5 +8,10 @@ "Leases": "../../ext/kea/dhcp6.leases" } } + }, + "PowerDns": { + "Listener": { + "Socket": "${XDG_RUNTIME_DIR}/pdns-dhcp/pdns.sock" + } } } \ No newline at end of file diff --git a/src/pdns-dhcp/pdns-dhcp.csproj b/src/pdns-dhcp/pdns-dhcp.csproj index 3130929..478689f 100644 --- a/src/pdns-dhcp/pdns-dhcp.csproj +++ b/src/pdns-dhcp/pdns-dhcp.csproj @@ -9,6 +9,10 @@ true + + + +