diff --git a/src/pdns-dhcp/Dhcp/DhcpLeaseQueue.cs b/src/pdns-dhcp/Dhcp/DhcpLeaseQueue.cs new file mode 100644 index 0000000..0d72356 --- /dev/null +++ b/src/pdns-dhcp/Dhcp/DhcpLeaseQueue.cs @@ -0,0 +1,20 @@ +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Threading.Channels; + +namespace pdns_dhcp.Dhcp; + +public class DhcpLeaseQueue +{ + private readonly Channel _pipe = Channel.CreateUnbounded(); +} + +public readonly record struct DhcpLeaseChange(IPAddress Address, string FQDN, DhcpLeaseIdentifier Identifier, TimeSpan Lifetime) +{ + public AddressFamily LeaseType { get; } = Address.AddressFamily; +} + +public record DhcpLeaseIdentifier; +public record DhcpLeaseClientIdentifier(string ClientId) : DhcpLeaseIdentifier; +public record DhcpLeaseHWAddrIdentifier(PhysicalAddress HWAddr) : DhcpLeaseIdentifier; diff --git a/src/pdns-dhcp/Dns/DnsRepository.cs b/src/pdns-dhcp/Dns/DnsRepository.cs new file mode 100644 index 0000000..c76f955 --- /dev/null +++ b/src/pdns-dhcp/Dns/DnsRepository.cs @@ -0,0 +1,154 @@ +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +using DotNext.Threading; + +using pdns_dhcp.Dhcp; + +using Timeout = System.Threading.Timeout; + +namespace pdns_dhcp.Dns; + +public class DnsRepository +{ + private static ReadOnlySpan Lifetimes => [600, 3600]; + + private readonly ReaderWriterLockSlim _recordLock = new(); + private readonly List _records = []; + + public List Find(Predicate query) + { + bool enteredLock = false; + try + { + enteredLock = _recordLock.TryEnterReadLock(Timeout.Infinite); + return _records.FindAll(query); + } + finally + { + if (enteredLock) + { + _recordLock.ExitReadLock(); + } + } + } + + public Task> FindAsync(Predicate query, CancellationToken cancellationToken = default) + { + return Task.Factory.StartNew(state => Find((Predicate)state!), query, + cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + } + + public async ValueTask Record(DhcpLeaseChange leaseChange) + { + // just lock that thing. + using (await _recordLock.AcquireLockAsync(CancellationToken.None).ConfigureAwait(false)) + { + RecordContinuation(leaseChange); + } + + void RecordContinuation(DhcpLeaseChange leaseChange) + { + var search = Matches(leaseChange); + bool lockEntered = false; + + try + { + lockEntered = _recordLock.TryEnterWriteLock(Timeout.Infinite); + DnsRecordIdentifier identifier = leaseChange.Identifier switch + { + DhcpLeaseClientIdentifier clientId => new DnsRecordClientIdentifier(clientId.ClientId), + DhcpLeaseHWAddrIdentifier hwAddr => new DnsRecordHWAddrIdentifier(hwAddr.HWAddr), + _ => throw new ArgumentException(nameof(leaseChange.Identifier)) + }; + + TimeSpan lifetime = leaseChange.Lifetime.TotalSeconds switch + { + <= 1800 => TimeSpan.FromSeconds(Lifetimes[0]), + >= 10800 => TimeSpan.FromSeconds(Lifetimes[1]), + { } seconds => TimeSpan.FromSeconds(seconds / 3) + }; + + var record = new DnsRecord(leaseChange.Address, leaseChange.FQDN, identifier, lifetime); + if (search.First is { } node) + { + search.RemoveFirst(); + _records[node.Value] = record; + } + else + { + _records.Add(record); + } + + while (search.Last is { } replace) + { + search.RemoveLast(); + var last = _records.Count - 1; + if (replace.Value < last) + { + _records[replace.Value] = _records[last]; + } + + _records.RemoveAt(last); + } + } + finally + { + if (lockEntered) + { + _recordLock.ExitWriteLock(); + } + } + } + + LinkedList Matches(DhcpLeaseChange query) + { + LinkedList list = []; + + for (int i = 0; i < _records.Count; i++) + { + var record = _records[i]; + if (record.RecordType != query.LeaseType) + { + continue; + } + + switch ((record.Identifier, query.Identifier)) + { + case (DnsRecordClientIdentifier recordClientId, DhcpLeaseClientIdentifier queryClientId) + when StringComparer.InvariantCultureIgnoreCase.Equals(recordClientId.ClientId, queryClientId.ClientId): + + case (DnsRecordHWAddrIdentifier recordHWAddr, DhcpLeaseHWAddrIdentifier queryHWAddr) + when EqualityComparer.Default.Equals(recordHWAddr.HWAddr, queryHWAddr.HWAddr): + + list.AddLast(i); + continue; + } + + if (EqualityComparer.Default.Equals(record.Address, query.Address)) + { + list.AddLast(i); + } + // Opt-In to disallow duplicate FQDN? + //else if (StringComparer.InvariantCultureIgnoreCase.Equals(record.FQDN, query.FQDN)) + //{ + // list.AddLast(i); + //} + } + + return list; + } + } +} + +// TODO Remove duplication +public record DnsRecordIdentifier; +public record DnsRecordClientIdentifier(string ClientId) : DnsRecordIdentifier; +public record DnsRecordHWAddrIdentifier(PhysicalAddress HWAddr) : DnsRecordIdentifier; +// /TODO + +public record DnsRecord(IPAddress Address, string FQDN, DnsRecordIdentifier Identifier, TimeSpan Lifetime) +{ + public AddressFamily RecordType { get; } = Address.AddressFamily; +} diff --git a/src/pdns-dhcp/PowerDns/PowerDnsHandler.cs b/src/pdns-dhcp/PowerDns/PowerDnsHandler.cs index 861506b..953630e 100644 --- a/src/pdns-dhcp/PowerDns/PowerDnsHandler.cs +++ b/src/pdns-dhcp/PowerDns/PowerDnsHandler.cs @@ -5,10 +5,19 @@ using Microsoft.AspNetCore.Connections; using Microsoft.Toolkit.HighPerformance; using Microsoft.Toolkit.HighPerformance.Buffers; +using pdns_dhcp.Dns; + namespace pdns_dhcp.PowerDns; public class PowerDnsHandler : ConnectionHandler { + private readonly DnsRepository _repository; + + public PowerDnsHandler(DnsRepository repository) + { + _repository = repository; + } + public override async Task OnConnectedAsync(ConnectionContext connection) { var input = connection.Transport.Input; diff --git a/src/pdns-dhcp/Services/DhcpLeaseWatcher.cs b/src/pdns-dhcp/Services/DhcpLeaseWatcher.cs index 5ea282a..b5b7172 100644 --- a/src/pdns-dhcp/Services/DhcpLeaseWatcher.cs +++ b/src/pdns-dhcp/Services/DhcpLeaseWatcher.cs @@ -52,7 +52,7 @@ public class DhcpLeaseWatcher : IHostedService var waitTask = Task.WhenAll(tasks); TaskCompletionSource taskCompletionSource = new(); - using var registration = cancellationToken.Register(s => (s as TaskCompletionSource)!.SetCanceled(), taskCompletionSource); + using var registration = cancellationToken.Register(s => ((TaskCompletionSource)s!).SetCanceled(), taskCompletionSource); await Task.WhenAny(waitTask, taskCompletionSource.Task).ConfigureAwait(continueOnCapturedContext: false); } } diff --git a/src/pdns-dhcp/pdns-dhcp.csproj b/src/pdns-dhcp/pdns-dhcp.csproj index 41c9376..7de941b 100644 --- a/src/pdns-dhcp/pdns-dhcp.csproj +++ b/src/pdns-dhcp/pdns-dhcp.csproj @@ -14,7 +14,7 @@ - +