Use Generic Host model. Refactor server implementations.

This commit is contained in:
femsci 2023-05-24 00:38:13 +02:00
parent dbffe9e4cc
commit 51909d18f4
Signed by: femsci
GPG key ID: 08F7911F0E650C67
28 changed files with 501 additions and 134 deletions

View file

@ -4,8 +4,8 @@
<ProjectReference Include="..\IPMeow.Lib\IPMeow.Lib.csproj" /> <ProjectReference Include="..\IPMeow.Lib\IPMeow.Lib.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -1,5 +1,8 @@
using IPMeow.Dhcp.Server.Dhcp4; using IPMeow.Dhcp.Server.Dhcp4;
using IPMeow.Lib.Host; using IPMeow.Dhcp.Server.Dhcp6;
using IPMeow.Dhcp.Standalone;
using IPMeow.Lib.Request;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
namespace IPMeow.Dhcp namespace IPMeow.Dhcp
@ -8,7 +11,13 @@ namespace IPMeow.Dhcp
{ {
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
var app = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args).Build(); var app = Host.CreateDefaultBuilder(args).ConfigureServices(s =>
{
s.AddScoped<HttpClient>();
s.AddSingleton<IAddressProvider<AddressRequestContext>, DecoupledIPAddressProvider>();
s.AddHostedService<Dhcp4Server>();
s.AddHostedService<Dhcp6Server>();
}).Build();
Console.WriteLine("Listening on DHCP..."); Console.WriteLine("Listening on DHCP...");

View file

@ -1,6 +1,7 @@
using System.Net; using System.Net;
using System.Text; using System.Text;
using IPMeow.Lib; using IPMeow.Lib;
using IPMeow.Lib.Address;
namespace IPMeow.Dhcp.Server.Dhcp4; namespace IPMeow.Dhcp.Server.Dhcp4;
@ -20,7 +21,12 @@ public class DhcpPacket
public required byte[] ClientHWAddress { get; init; } public required byte[] ClientHWAddress { get; init; }
public required string ServerHostName { get; init; } public required string ServerHostName { get; init; }
public required string BootFileName { get; init; } public required string BootFileName { get; init; }
public bool? IsValidated { get; set; } public bool IsValidated { get; set; }
public override string ToString()
{
return $"{OpType}, htype: {HType}, hlen: {HLen}, hops: {Hops}\nXid: {Xid}, Seconds: {Seconds}, Flags: {Flags:x}\nci: {ClientAddress}\nyi: {YourAddress}, ns: {NextServerAddress}, ra: {RelayAgentAddress}\nhw: {MacAddress.Parse(GetMacAddress())}\nhost: {ServerHostName}\nboot: {BootFileName}";
}
public byte[] GetClientHwAddress() public byte[] GetClientHwAddress()
{ {
@ -36,6 +42,8 @@ public class DhcpPacket
return buf; return buf;
} }
public byte[] GetMacAddress() => ClientHWAddress[0..6];
public byte[] GetSnameBytes() public byte[] GetSnameBytes()
{ {
var buf = new byte[64]; var buf = new byte[64];

View file

@ -1,6 +1,6 @@
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using IPMeow.Lib.Address; using IPMeow.Lib.Request;
namespace IPMeow.Dhcp.Server.Dhcp4; namespace IPMeow.Dhcp.Server.Dhcp4;
@ -8,27 +8,20 @@ public class Dhcp4Server : IDhcpServer, IDisposable
{ {
public const int clientPort = 68, serverPort = 67; public const int clientPort = 68, serverPort = 67;
public Dhcp4Server(IAddressProvider addressProvider) public Dhcp4Server(IAddressProvider<AddressRequestContext> addressProvider)
{ {
_addrProvider = addressProvider; _addrProvider = addressProvider;
_udp = new UdpClient(serverPort, AddressFamily.InterNetwork); _udp = new UdpClient(serverPort, AddressFamily.InterNetwork);
} }
private readonly IAddressProvider _addrProvider; private readonly IAddressProvider<AddressRequestContext> _addrProvider;
public event EventHandler<DhcpRequestEvent> DhcpRequestedEvent; public event EventHandler<DhcpRequestEventArgs> DhcpRequestedEvent = delegate { };
public async Task Run() protected async Task Receive(CancellationToken token)
{ {
while (true) var datagram = await _udp.ReceiveAsync(token);
{ Console.WriteLine($"New v4 request: {datagram.Buffer.Length}");
await Receive();
}
}
protected async Task Receive()
{
var datagram = await _udp.ReceiveAsync();
if (datagram.Buffer.Length < 236 || datagram.Buffer.Length > 576) if (datagram.Buffer.Length < 236 || datagram.Buffer.Length > 576)
{ {
@ -36,19 +29,23 @@ public class Dhcp4Server : IDhcpServer, IDisposable
} }
var packet = DhcpPacket.Deserialize(datagram.Buffer); var packet = DhcpPacket.Deserialize(datagram.Buffer);
packet.IsValidated = ValidateIncoming(packet);
if (!packet.IsValidated.Value) if (!ValidateIncoming(packet))
{ {
return; return;
} }
packet.IsValidated = true;
Console.WriteLine("Solicitation: "); Console.WriteLine("Solicitation: ");
Console.WriteLine(packet.ToString()); Console.WriteLine(packet.ToString());
//TODO
} }
public bool ValidateIncoming(DhcpPacket packet) public bool ValidateIncoming(DhcpPacket packet)
{ {
Console.WriteLine(packet.ToString());
if (packet.OpType != DhcpOpType.BOOTREQUEST) if (packet.OpType != DhcpOpType.BOOTREQUEST)
{ {
return false; return false;
@ -72,7 +69,31 @@ public class Dhcp4Server : IDhcpServer, IDisposable
public void Dispose() public void Dispose()
{ {
throw new NotImplementedException(); GC.SuppressFinalize(this);
_udp.Dispose();
}
async Task Run(CancellationToken token)
{
while (true)
{
if (token.IsCancellationRequested)
{
return;
}
await Receive(token);
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
new Thread(async () => await Run(cancellationToken)).Start();
Console.WriteLine("V4 started.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
} }
private readonly UdpClient _udp; private readonly UdpClient _udp;

View file

@ -15,8 +15,18 @@ public enum DhcpOptionCode : short
ElapsedTime = 8, ElapsedTime = 8,
ServerUnicast = 12, ServerUnicast = 12,
StatusCode = 13, StatusCode = 13,
VendorClass = 16,
VendorOptions = 17,
IfaceId = 18,
DnsServers = 23,
DomainList = 24,
SntpServers = 31,
SubscriberId = 38,
ClientFqdn = 39,
V6Lost = 51,
NtpServer = 56,
Addrsel = 84,
AddrselTable = 85,
} }
public interface IDhcpOption public interface IDhcpOption
@ -37,7 +47,7 @@ public class ClientIdentifierOption : IDhcpOption
public object GetData() public object GetData()
{ {
return new Duid(Data); return Duid.Parse(Data);
} }
} }
@ -51,7 +61,7 @@ public class ServerIdentifierOption : IDhcpOption
public object GetData() public object GetData()
{ {
return new Duid(Data); return Duid.Parse(Data);
} }
} }
@ -155,3 +165,101 @@ public class StatusCodeOption : IDhcpOption
return new DhcpStatus(code, message); return new DhcpStatus(code, message);
} }
} }
public class VendorClassOption : IDhcpOption
{
public DhcpOptionCode Code => DhcpOptionCode.VendorClass;
public required byte[] Data { get; set; }
public Type DataType => typeof(byte[]);
public object GetData()
{
return Data;
}
}
public class InterfaceIdOption : IDhcpOption
{
public DhcpOptionCode Code => DhcpOptionCode.VendorClass;
public required byte[] Data { get; set; }
public Type DataType => typeof(byte[]);
public object GetData()
{
return Data;
}
}
public class SubscriberIdOption : IDhcpOption
{
public DhcpOptionCode Code => DhcpOptionCode.VendorClass;
public required byte[] Data { get; set; }
public Type DataType => typeof(byte[]);
public object GetData()
{
return Data;
}
}
public class V6LostOption : IDhcpOption
{
public DhcpOptionCode Code => DhcpOptionCode.VendorClass;
public required byte[] Data { get; set; }
public Type DataType => typeof(byte[]);
public object GetData()
{
return Data;
}
}
public class ClientFqdnOption : IDhcpOption
{
public DhcpOptionCode Code => DhcpOptionCode.VendorClass;
public required byte[] Data { get; set; }
public Type DataType => typeof(byte[]);
public object GetData()
{
return Data;
}
}
public class DnsServersOption : IDhcpOption
{
public DhcpOptionCode Code => DhcpOptionCode.VendorClass;
public required byte[] Data { get; set; }
public Type DataType => typeof(byte[]);
public object GetData()
{
return Data;
}
}
public class NtpServerOption : IDhcpOption
{
public DhcpOptionCode Code => DhcpOptionCode.VendorClass;
public required byte[] Data { get; set; }
public Type DataType => typeof(byte[]);
public object GetData()
{
return Data;
}
}

View file

@ -16,14 +16,69 @@ public class DhcpPacket
{ {
Data = data Data = data
}, },
DhcpOptionCode.ServerId => throw new NotImplementedException(), DhcpOptionCode.ServerId => new ServerIdentifierOption()
DhcpOptionCode.IANonTempAssoc => throw new NotImplementedException(), {
DhcpOptionCode.IATempAssoc => throw new NotImplementedException(), Data = data
DhcpOptionCode.IAAddr => throw new NotImplementedException(), },
DhcpOptionCode.OptionRequest => throw new NotImplementedException(), DhcpOptionCode.IANonTempAssoc => new NonTempIdentityAssociationOption()
{
Data = data
},
DhcpOptionCode.IATempAssoc => new TempIdentityAssociationOption()
{
Data = data
},
DhcpOptionCode.IAAddr => new IAAddressOption()
{
Data = data
},
DhcpOptionCode.OptionRequest => new OptionRequestOption()
{
Data = data
},
DhcpOptionCode.Preference => throw new NotImplementedException(), DhcpOptionCode.Preference => throw new NotImplementedException(),
DhcpOptionCode.ElapsedTime => throw new NotImplementedException(), DhcpOptionCode.ElapsedTime => new ElapsedTimeOption()
DhcpOptionCode.StatusCode => throw new NotImplementedException(), {
Data = data
},
DhcpOptionCode.StatusCode => new StatusCodeOption()
{
Data = data
},
DhcpOptionCode.ServerUnicast => new ServerUnicastOption()
{
Data = data
},
DhcpOptionCode.VendorClass => new VendorClassOption()
{
Data = data
},
DhcpOptionCode.IfaceId => new InterfaceIdOption()
{
Data = data
},
DhcpOptionCode.DnsServers => new DnsServersOption()
{
Data = data
},
DhcpOptionCode.SubscriberId => new SubscriberIdOption()
{
Data = data
},
DhcpOptionCode.ClientFqdn => new ClientFqdnOption()
{
Data = data
},
DhcpOptionCode.V6Lost => new V6LostOption()
{
Data = data
},
DhcpOptionCode.NtpServer => new NtpServerOption()
{
Data = data
},
DhcpOptionCode.Addrsel => throw new NotImplementedException(),
DhcpOptionCode.AddrselTable => throw new NotImplementedException(),
_ => throw new InvalidOperationException($"Invalid option type {type}") _ => throw new InvalidOperationException($"Invalid option type {type}")
}; };
} }
@ -36,13 +91,22 @@ public class DhcpPacket
DhcpMessageType MessageType = (DhcpMessageType)header[0]; DhcpMessageType MessageType = (DhcpMessageType)header[0];
uint Xid = BitConverter.ToUInt32(header, 1); header[0] = 0;
uint Xid = BitConverter.ToUInt32(header);
Console.WriteLine("Preview:\n{0}", string.Join(' ', data.Select(b => b.ToString("x2"))));
Console.WriteLine($"v6: {Xid}, {enu.Position}, {MessageType}, {data.Length}");
List<IDhcpOption> options = new();
while (enu.Position < data.Length) while (enu.Position < data.Length)
{ {
short optionCode = enu.TakeInt16(); DhcpOptionCode optionCode = (DhcpOptionCode)enu.TakeInt16();
short optionLen = enu.TakeInt16(); short optionLen = enu.TakeInt16();
Console.WriteLine($"D: {data.Length}, {enu.Position}, {optionCode}, {optionLen}");
if (enu.Position + optionLen > data.Length) if (enu.Position + optionLen > data.Length)
{ {
throw new DataMisalignedException("Invalid option length."); throw new DataMisalignedException("Invalid option length.");
@ -50,13 +114,17 @@ public class DhcpPacket
byte[] optionData = enu.TakeBytes(optionLen); byte[] optionData = enu.TakeBytes(optionLen);
Console.WriteLine(string.Join(' ', optionData.Select(b => b.ToString("x2"))));
IDhcpOption option = GetOption(optionCode, optionData);
options.Add(option);
} }
return new() return new()
{ {
MessageType = MessageType, MessageType = MessageType,
Xid = Xid Xid = Xid,
Options = options
}; };
} }
} }

View file

@ -1,6 +1,6 @@
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using IPMeow.Lib.Address; using IPMeow.Lib.Request;
namespace IPMeow.Dhcp.Server.Dhcp6; namespace IPMeow.Dhcp.Server.Dhcp6;
@ -8,17 +8,18 @@ public class Dhcp6Server : IDhcpServer, IDisposable
{ {
public const int clientPort = 546, serverPort = 547; public const int clientPort = 546, serverPort = 547;
public Dhcp6Server(IAddressProvider addressProvider) public Dhcp6Server(IAddressProvider<AddressRequestContext> addressProvider)
{ {
_addrProvider = addressProvider; _addrProvider = addressProvider;
_listener = new(new IPEndPoint(IPAddress.Parse("ff02::1:2"), serverPort)); _listener = new(serverPort, AddressFamily.InterNetworkV6);
_listener.JoinMulticastGroup(IPAddress.Parse("ff02::1:2"));
_sender = new(clientPort, AddressFamily.InterNetworkV6); _sender = new(clientPort, AddressFamily.InterNetworkV6);
} }
private readonly IAddressProvider _addrProvider; private readonly IAddressProvider<AddressRequestContext> _addrProvider;
private readonly UdpClient _listener, _sender; private readonly UdpClient _listener, _sender;
public event EventHandler<DhcpRequestEvent> DhcpRequestedEvent; public event EventHandler<DhcpRequestEventArgs> DhcpRequestedEvent = delegate { };
public void Dispose() public void Dispose()
{ {
@ -27,24 +28,44 @@ public class Dhcp6Server : IDhcpServer, IDisposable
_sender.Dispose(); _sender.Dispose();
} }
public async Task Run() protected async Task Receive(CancellationToken token)
{ {
while (true) var datagram = await _listener.ReceiveAsync(token);
{
await Receive();
}
}
protected async Task Receive() Console.WriteLine($"From v6: {datagram.RemoteEndPoint}, len: {datagram.Buffer.Length}");
{
var datagram = await _listener.ReceiveAsync();
var packet = DhcpPacket.Deserialize(datagram.Buffer); var packet = DhcpPacket.Deserialize(datagram.Buffer);
Console.WriteLine($"New v6 packet: {packet.Xid}, len: {datagram.Buffer.Length}");
if (packet.MessageType is DhcpMessageType.Invalid or DhcpMessageType.Solicit or DhcpMessageType.Confirm or DhcpMessageType.Rebind) if (packet.MessageType is DhcpMessageType.Invalid or DhcpMessageType.Solicit or DhcpMessageType.Confirm or DhcpMessageType.Rebind)
{ {
return; return;
} }
//TODO
}
public async Task Run(CancellationToken token)
{
while (true)
{
if (token.IsCancellationRequested)
{
return;
}
await Receive(token);
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
new Thread(async () => await Run(cancellationToken)).Start();
Console.WriteLine("V6 started.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
} }
} }

View file

@ -2,6 +2,33 @@ using System.Text;
namespace IPMeow.Dhcp.Server.Dhcp6; namespace IPMeow.Dhcp.Server.Dhcp6;
public enum DhcpStatusCode : short
{
Success = 0,
UnspecifiedFail = 1,
NoAddrAvailable = 2,
NoBinding = 3,
NotOnLink = 4,
UseMulticast = 5,
NoPrefixAvailable = 6,
UnknownQueryType = 7,
MalformedQuery = 8,
NotConfigured = 9,
NotAllowed = 10,
QueryTerminated = 11,
DataMissing = 12,
CatchUpComplete = 13,
NotSupported = 14,
TlsConnRefused = 15,
AddrInUse = 16,
ConfigConflict = 17,
MissingBindingInfo = 18,
OutdatedBindingInfo = 19,
ServerShuttingDown = 20,
DnsUpdateNotSupported = 21,
ExcessiveTimeSkew = 22
}
public struct DhcpStatus public struct DhcpStatus
{ {
public DhcpStatus(ushort code, string? message) public DhcpStatus(ushort code, string? message)

View file

@ -1,3 +1,5 @@
using IPMeow.Lib.Request;
namespace IPMeow.Dhcp.Server.Dhcp6; namespace IPMeow.Dhcp.Server.Dhcp6;
public enum DuidType : short public enum DuidType : short
@ -8,12 +10,36 @@ public enum DuidType : short
Uuid = 4 Uuid = 4
} }
public readonly struct Duid public readonly struct Duid : IRequestClientIdentifier
{ {
public Duid(byte[] data) private Duid(DuidType type, byte[] data)
{ {
Type = (DuidType)BitConverter.ToInt16(data.AsSpan()[0..2]); Type = type;
Data = data[2..]; Data = data;
}
private static readonly Duid Invalid = new(0, Array.Empty<byte>());
public static bool TryParse(byte[] data, out Duid duid)
{
short dType = BitConverter.ToInt16(data.AsSpan()[0..2]);
if (!Enum.IsDefined(typeof(DuidType), dType))
{
duid = Invalid;
return false;
}
byte[] duidData = data[2..];
duid = new((DuidType)dType, duidData);
return true;
}
public static Duid Parse(byte[] data)
{
return TryParse(data, out Duid duid) ? duid : throw new InvalidDataException("Invalid data supplied.");
} }
public DuidType Type { get; } public DuidType Type { get; }

View file

@ -1,13 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks;
using IPMeow.Lib.Address; using IPMeow.Lib.Address;
namespace IPMeow.Dhcp.Server; namespace IPMeow.Dhcp.Server;
public class DhcpRequestEvent public class DhcpRequestEventArgs : EventArgs
{ {
public bool IsV6 { get; init; } public bool IsV6 { get; init; }
public IPAddress? RequestOrigin { get; init; } public IPAddress? RequestOrigin { get; init; }

View file

@ -1,8 +1,8 @@
using Microsoft.Extensions.Hosting;
namespace IPMeow.Dhcp.Server; namespace IPMeow.Dhcp.Server;
public interface IDhcpServer public interface IDhcpServer : IHostedService
{ {
Task Run(); event EventHandler<DhcpRequestEventArgs> DhcpRequestedEvent;
event EventHandler<DhcpRequestEvent> DhcpRequestedEvent;
} }

View file

@ -1,50 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using IPMeow.Lib.Request;
using IPMeow.Lib.Address;
namespace IPMeow.Dhcp.Standalone; namespace IPMeow.Dhcp.Standalone;
public class DecoupledIPv6AddressProvider : IPv6AddressProvider
public class DecoupledIPAddressProvider : IAddressProvider<AddressRequestContext>
{ {
public DecoupledIPv6AddressProvider(HttpClient http) public DecoupledIPAddressProvider(HttpClient http)
{ {
_http = http; _http = http;
} }
private readonly HttpClient _http; private readonly HttpClient _http;
public IPv6Address GetAddress() public IPAddress GetAddress(AddressRequestContext ctx)
{
_http.GetAsync("/api/request/6/free");
throw new NotImplementedException();
}
public IPv6Address GetAddress(MacAddress mac)
{ {
throw new NotImplementedException(); _http.GetAsync($"/api/request/{(ctx.IsV6 ? "6" : "4")}/free");
}
}
public class DecoupledIPv4AddressProvider : IPv4AddressProvider
{
public DecoupledIPv4AddressProvider(HttpClient http)
{
_http = http;
}
private readonly HttpClient _http;
public IPv4Address GetAddress()
{
_http.GetAsync("/api/request/4/free");
throw new NotImplementedException();
}
public IPv4Address GetAddress(MacAddress mac)
{
_http.GetAsync($"/api/request/4/mac/{mac}");
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }

View file

@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace IPMeow.Lib.Address;
public interface IAddressProvider
{
public IPAddress GetAddress();
public IPAddress GetAddress(MacAddress mac);
}
public interface IPv4AddressProvider
{
public IPv4Address GetAddress();
public IPv4Address GetAddress(MacAddress mac);
}
public interface IPv6AddressProvider
{
public IPv6Address GetAddress();
public IPv6Address GetAddress(MacAddress mac);
}

View file

@ -22,7 +22,7 @@ public readonly struct MacAddress
return mac.Value; return mac.Value;
} }
throw new ArgumentException("Invalid address byte length. Expected 6 octets."); throw new ArgumentException($"Invalid address byte length. Expected 6 octets. Got {addr.Length} octets.");
} }
public static MacAddress Parse(string addr) public static MacAddress Parse(string addr)
@ -32,7 +32,7 @@ public readonly struct MacAddress
return mac.Value; return mac.Value;
} }
throw new FormatException("Invalid address format."); throw new FormatException($"Invalid address format: {addr}.");
} }
public static bool TryParse(byte[] addr, [NotNullWhen(true)] out MacAddress? mac) public static bool TryParse(byte[] addr, [NotNullWhen(true)] out MacAddress? mac)

View file

@ -1,3 +1,5 @@
using System.Buffers.Binary;
namespace IPMeow.Lib; namespace IPMeow.Lib;
public class BitEnumerator public class BitEnumerator
@ -26,7 +28,7 @@ public class BitEnumerator
public short TakeInt16() public short TakeInt16()
{ {
short num = BitConverter.ToInt16(_data.AsSpan()[_position..(_position + 2)]); short num = BinaryPrimitives.ReadInt16BigEndian(_data.AsSpan()[_position..(_position + 2)]);
_position += 2; _position += 2;
@ -35,7 +37,7 @@ public class BitEnumerator
public int TakeInt32() public int TakeInt32()
{ {
int num = BitConverter.ToInt32(_data.AsSpan()[_position..(_position + 4)]); int num = BinaryPrimitives.ReadInt32BigEndian(_data.AsSpan()[_position..(_position + 4)]);
_position += 4; _position += 4;
@ -44,7 +46,7 @@ public class BitEnumerator
public long TakeInt64() public long TakeInt64()
{ {
long num = BitConverter.ToInt64(_data.AsSpan()[_position..(_position + 8)]); long num = BinaryPrimitives.ReadInt64BigEndian(_data.AsSpan()[_position..(_position + 8)]);
_position += 8; _position += 8;
@ -53,7 +55,7 @@ public class BitEnumerator
public ushort TakeUInt16() public ushort TakeUInt16()
{ {
ushort num = BitConverter.ToUInt16(_data.AsSpan()[_position..(_position + 2)]); ushort num = BinaryPrimitives.ReadUInt16BigEndian(_data.AsSpan()[_position..(_position + 2)]);
_position += 2; _position += 2;
@ -62,7 +64,7 @@ public class BitEnumerator
public uint TakeUInt32() public uint TakeUInt32()
{ {
uint num = BitConverter.ToUInt32(_data.AsSpan()[_position..(_position + 4)]); uint num = BinaryPrimitives.ReadUInt32BigEndian(_data.AsSpan()[_position..(_position + 4)]);
_position += 4; _position += 4;
@ -71,7 +73,7 @@ public class BitEnumerator
public ulong TakeUInt64() public ulong TakeUInt64()
{ {
ulong num = BitConverter.ToUInt64(_data.AsSpan()[_position..(_position + 8)]); ulong num = BinaryPrimitives.ReadUInt64BigEndian(_data.AsSpan()[_position..(_position + 8)]);
_position += 8; _position += 8;

View file

@ -8,7 +8,17 @@ public class Host : IBindable
{ {
[Key] [Key]
public required string Id { get; set; } public required string Id { get; set; }
public DateTime? LastPing { get; set; } [MaxLength(16)]
public byte[] Duid { get; set; } = default!;
public HostType HostType { get; set; }
public required IPAddressCollection Addresses { get; set; } public required IPAddressCollection Addresses { get; set; }
public required MacAddress Mac { get; set; } public required MacAddress Mac { get; set; }
} }
public enum HostType : byte
{
Physical = 0b1,
Virtual = 0b10,
Container = 0b100,
Other = 0b1000000,
}

View file

@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace IPMeow.Lib.Host;
public class HostInterface
{
[Key]
public required string Id { get; set; }
public byte[] IAID { get; set; } = default!;
public DateTime LastPinged { get; set; }
public DateTime LastAssignment { get; set; }
public string HostId { get; set; } = default!;
public virtual Host Host { get; set; } = default!;
}

View file

@ -0,0 +1,9 @@
using IPMeow.Lib.Address;
namespace IPMeow.Lib.Request;
public class AddressRequestContext
{
public bool IsV6 { get; set; }
public MacAddress? MacAddress { get; set; }
}

View file

@ -0,0 +1,8 @@
using System.Net;
namespace IPMeow.Lib.Request;
public interface IAddressProvider<T> where T : AddressRequestContext
{
public IPAddress GetAddress(T ctx);
}

View file

@ -0,0 +1,6 @@
namespace IPMeow.Lib.Request;
public interface IRequestClientIdentifier
{
}

View file

@ -0,0 +1,21 @@
using IPMeow.Lib.Request;
using Microsoft.AspNetCore.Mvc;
namespace IPMeow.Server.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AddressController : ControllerBase
{
[HttpPost("request")]
public async Task<IActionResult> RequestAddress(AddressRequestContext ctx)
{
throw new NotImplementedException();
}
[HttpGet]
public async Task<IActionResult> ConfirmRequest()
{
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,26 @@
using IPMeow.Lib.Host;
using IPMeow.Server.Data.Model;
using Microsoft.EntityFrameworkCore;
namespace IPMeow.Server.Data;
public class IpamContext : DbContext
{
public DbSet<Lib.Host.Host> Hosts => Set<Lib.Host.Host>();
public DbSet<HostInterface> HostInterfaces => Set<HostInterface>();
public DbSet<AddressAssignment> Assignments => Set<AddressAssignment>();
protected override void OnConfiguring(DbContextOptionsBuilder opt)
{
opt.LogTo(t => Console.WriteLine(t));
opt.EnableDetailedErrors();
opt.EnableSensitiveDataLogging();
base.OnConfiguring(opt);
}
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<AddressAssignment>().HasKey(ass => new { Address = ass.Address.GetAddressBytes(), HostId = ass.HostInterfaceId });
base.OnModelCreating(mb);
}
}

View file

@ -0,0 +1,13 @@
using System.Net;
using IPMeow.Lib.Host;
namespace IPMeow.Server.Data.Model;
public class AddressAssignment
{
public required string HostInterfaceId { get; set; }
public required IPAddress Address { get; set; }
public DateTime LastAssigned { get; set; }
public virtual HostInterface HostInterface { get; set; } = default!;
}

View file

@ -0,0 +1,9 @@
using System.Net;
namespace IPMeow.Server.Data.Model;
public class AddressBlock
{
public required IPAddress Address { get; set; }
public required int Prefix { get; set; }
}

View file

@ -0,0 +1,6 @@
namespace IPMeow.Server.Data.Model;
public class AddressPolicy
{
public bool IsTemporary { get; set; }
}

View file

@ -1,5 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<ProjectReference Include="..\IPMeow.Lib\IPMeow.Lib.csproj" />
<ProjectReference Include="..\IPMeow.Dhcp\IPMeow.Dhcp.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>

View file

@ -1,4 +1,9 @@
using IPMeow.Server.Data;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<IpamContext>();
var app = builder.Build(); var app = builder.Build();
app.Run(); app.Run();

View file

@ -1,4 +1,3 @@
using IPMeow.Lib;
using IPMeow.Lib.Address; using IPMeow.Lib.Address;
namespace IPMeow.Test; namespace IPMeow.Test;