Migrate existing code
This commit is contained in:
parent
384ff4a6f3
commit
9b49f880a2
30 changed files with 1789 additions and 5 deletions
36
Services/Dhcp/DhcpLeaseQueue.cs
Normal file
36
Services/Dhcp/DhcpLeaseQueue.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace DotNetDDI.Services.Dhcp;
|
||||
|
||||
public class DhcpLeaseQueue
|
||||
{
|
||||
private readonly Channel<DhcpLeaseChange> _pipe;
|
||||
private readonly ChannelReader<DhcpLeaseChange> _reader;
|
||||
private readonly ChannelWriter<DhcpLeaseChange> _writer;
|
||||
|
||||
public ref readonly ChannelReader<DhcpLeaseChange> Reader => ref _reader;
|
||||
|
||||
public DhcpLeaseQueue()
|
||||
{
|
||||
_pipe = Channel.CreateUnbounded<DhcpLeaseChange>();
|
||||
_reader = _pipe.Reader;
|
||||
_writer = _pipe.Writer;
|
||||
}
|
||||
|
||||
public ValueTask Write(DhcpLeaseChange change, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _writer.WriteAsync(change, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
44
Services/Dhcp/DhcpQueueWorker.cs
Normal file
44
Services/Dhcp/DhcpQueueWorker.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System.Threading.Channels;
|
||||
|
||||
using DotNetDDI.Services.Dns;
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace DotNetDDI.Services.Dhcp;
|
||||
|
||||
public class DhcpQueueWorker : BackgroundService
|
||||
{
|
||||
private readonly ChannelReader<DhcpLeaseChange> _channelReader;
|
||||
private readonly DnsRepository _repository;
|
||||
|
||||
public DhcpQueueWorker(DhcpLeaseQueue queue, DnsRepository repository)
|
||||
{
|
||||
_channelReader = queue.Reader;
|
||||
_repository = repository;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (await _channelReader.WaitToReadAsync(stoppingToken).ConfigureAwait(false))
|
||||
{
|
||||
while (_channelReader.TryRead(out var lease))
|
||||
{
|
||||
DnsRecordIdentifier identifier = lease.Identifier switch
|
||||
{
|
||||
DhcpLeaseClientIdentifier clientId => new DnsRecordClientIdentifier(clientId.ClientId),
|
||||
DhcpLeaseHWAddrIdentifier hwAddr => new DnsRecordHWAddrIdentifier(hwAddr.HWAddr),
|
||||
_ => throw new ArgumentException(nameof(lease.Identifier))
|
||||
};
|
||||
|
||||
TimeSpan lifetime = lease.Lifetime.TotalSeconds switch
|
||||
{
|
||||
<= 1800 => TimeSpan.FromSeconds(600),
|
||||
>= 10800 => TimeSpan.FromSeconds(3600),
|
||||
{ } seconds => TimeSpan.FromSeconds(seconds / 3)
|
||||
};
|
||||
|
||||
await _repository.Record(new DnsRecord(lease.Address, lease.FQDN, identifier, lifetime), stoppingToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Services/Dhcp/DhcpWatcher.cs
Normal file
50
Services/Dhcp/DhcpWatcher.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System.Collections.Immutable;
|
||||
|
||||
using DotNetDDI.Options;
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace DotNetDDI.Services.Dhcp;
|
||||
|
||||
public class DhcpWatcher : IHostedService
|
||||
{
|
||||
private readonly ImmutableArray<IHostedService> _services;
|
||||
|
||||
public DhcpWatcher(IOptions<DhcpOptions> options, IDhcpWatcherFactory factory)
|
||||
{
|
||||
var dhcpOptions = options.Value;
|
||||
var services = ImmutableArray.CreateBuilder<IHostedService>();
|
||||
if (dhcpOptions.Kea is { } keaOptions)
|
||||
{
|
||||
services.Add(factory.KeaService(keaOptions));
|
||||
}
|
||||
|
||||
_services = services.DrainToImmutable();
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
Task[] tasks = new Task[_services.Length];
|
||||
for (int i = 0; i < tasks.Length; i++)
|
||||
{
|
||||
tasks[i] = _services[i].StartAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
Task[] tasks = new Task[_services.Length];
|
||||
for (int i = 0; i < tasks.Length; i++)
|
||||
{
|
||||
tasks[i] = _services[i].StopAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var waitTask = Task.WhenAll(tasks);
|
||||
TaskCompletionSource taskCompletionSource = new();
|
||||
using var registration = cancellationToken.Register(s => ((TaskCompletionSource)s!).SetCanceled(), taskCompletionSource);
|
||||
await Task.WhenAny(waitTask, taskCompletionSource.Task).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
26
Services/Dhcp/DhcpWatcherFactoryServices.cs
Normal file
26
Services/Dhcp/DhcpWatcherFactoryServices.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using DotNetDDI.Integrations.Kea;
|
||||
using DotNetDDI.Options;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DotNetDDI.Services.Dhcp;
|
||||
|
||||
public static class DhcpWatcherFactoryServices
|
||||
{
|
||||
public static IServiceCollection AddDhcpWatcherFactory(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<IDhcpWatcherFactory, DhcpWatcherFactory>();
|
||||
return services;
|
||||
}
|
||||
|
||||
private class DhcpWatcherFactory(IServiceProvider services) : IDhcpWatcherFactory
|
||||
{
|
||||
private ObjectFactory<KeaService>? _cachedKeaService;
|
||||
|
||||
KeaService IDhcpWatcherFactory.KeaService(KeaDhcpOptions options)
|
||||
{
|
||||
_cachedKeaService ??= ActivatorUtilities.CreateFactory<KeaService>([typeof(KeaDhcpOptions)]);
|
||||
return _cachedKeaService(services, [options]);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Services/Dhcp/IDhcpWatcherFactory.cs
Normal file
9
Services/Dhcp/IDhcpWatcherFactory.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using DotNetDDI.Integrations.Kea;
|
||||
using DotNetDDI.Options;
|
||||
|
||||
namespace DotNetDDI.Services.Dhcp;
|
||||
|
||||
public interface IDhcpWatcherFactory
|
||||
{
|
||||
KeaService KeaService(KeaDhcpOptions options);
|
||||
}
|
||||
157
Services/Dns/DnsRepository.cs
Normal file
157
Services/Dns/DnsRepository.cs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
|
||||
using DotNetDDI.Options;
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Timeout = System.Threading.Timeout;
|
||||
|
||||
namespace DotNetDDI.Services.Dns;
|
||||
|
||||
public class DnsRepository
|
||||
{
|
||||
private static ReadOnlySpan<int> Lifetimes => [600, 3600];
|
||||
|
||||
private readonly PowerDnsOptions _options;
|
||||
private readonly ReaderWriterLockSlim _recordLock = new();
|
||||
private readonly List<DnsRecord> _records = [];
|
||||
private readonly SemaphoreSlim _syncLock = new(1, 1);
|
||||
|
||||
public DnsRepository(IOptions<PowerDnsOptions> options)
|
||||
{
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public List<DnsRecord> Find(Predicate<DnsRecord> query)
|
||||
{
|
||||
bool enteredLock = false;
|
||||
try
|
||||
{
|
||||
enteredLock = _recordLock.TryEnterReadLock(Timeout.Infinite);
|
||||
return _records.FindAll(query);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (enteredLock)
|
||||
{
|
||||
_recordLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<DnsRecord>> FindAsync(Predicate<DnsRecord> query, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.Factory.StartNew(state => Find((Predicate<DnsRecord>)state!), query,
|
||||
cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
public async ValueTask Record(DnsRecord record, CancellationToken cancellationToken = default)
|
||||
{
|
||||
bool entered = false;
|
||||
try
|
||||
{
|
||||
entered = await _syncLock.WaitAsync(Timeout.Infinite, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
|
||||
RecordContinuation(record);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (entered)
|
||||
{
|
||||
_syncLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
void RecordContinuation(DnsRecord record)
|
||||
{
|
||||
var search = Matches(record);
|
||||
bool entered = false;
|
||||
try
|
||||
{
|
||||
entered = _recordLock.TryEnterWriteLock(Timeout.Infinite);
|
||||
|
||||
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 (entered)
|
||||
{
|
||||
_recordLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LinkedList<int> Matches(DnsRecord query)
|
||||
{
|
||||
LinkedList<int> list = [];
|
||||
|
||||
for (int i = 0; i < _records.Count; i++)
|
||||
{
|
||||
var record = _records[i];
|
||||
if (record.RecordType != query.RecordType)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ((record.Identifier, query.Identifier))
|
||||
{
|
||||
case (
|
||||
DnsRecordClientIdentifier { ClientId: { } recordClientId },
|
||||
DnsRecordClientIdentifier { ClientId: { } queryClientId }
|
||||
) when StringComparer.InvariantCultureIgnoreCase.Equals(recordClientId, queryClientId):
|
||||
|
||||
case (
|
||||
DnsRecordHWAddrIdentifier { HWAddr: { } recordHWAddr },
|
||||
DnsRecordHWAddrIdentifier { HWAddr: { } queryHWAddr }
|
||||
) when EqualityComparer<PhysicalAddress>.Default.Equals(recordHWAddr, queryHWAddr):
|
||||
|
||||
list.AddLast(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (EqualityComparer<IPAddress>.Default.Equals(record.Address, query.Address))
|
||||
{
|
||||
list.AddLast(i);
|
||||
}
|
||||
else if (_options.UniqueHostnames && StringComparer.OrdinalIgnoreCase.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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue