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" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
</ItemGroup>
<PropertyGroup>

View file

@ -1,5 +1,8 @@
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;
namespace IPMeow.Dhcp
@ -8,7 +11,13 @@ namespace IPMeow.Dhcp
{
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...");

View file

@ -1,6 +1,7 @@
using System.Net;
using System.Text;
using IPMeow.Lib;
using IPMeow.Lib.Address;
namespace IPMeow.Dhcp.Server.Dhcp4;
@ -20,7 +21,12 @@ public class DhcpPacket
public required byte[] ClientHWAddress { get; init; }
public required string ServerHostName { 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()
{
@ -36,6 +42,8 @@ public class DhcpPacket
return buf;
}
public byte[] GetMacAddress() => ClientHWAddress[0..6];
public byte[] GetSnameBytes()
{
var buf = new byte[64];

View file

@ -1,6 +1,6 @@
using System.Net;
using System.Net.Sockets;
using IPMeow.Lib.Address;
using IPMeow.Lib.Request;
namespace IPMeow.Dhcp.Server.Dhcp4;
@ -8,27 +8,20 @@ public class Dhcp4Server : IDhcpServer, IDisposable
{
public const int clientPort = 68, serverPort = 67;
public Dhcp4Server(IAddressProvider addressProvider)
public Dhcp4Server(IAddressProvider<AddressRequestContext> addressProvider)
{
_addrProvider = addressProvider;
_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)
{
await Receive();
}
}
protected async Task Receive()
{
var datagram = await _udp.ReceiveAsync();
var datagram = await _udp.ReceiveAsync(token);
Console.WriteLine($"New v4 request: {datagram.Buffer.Length}");
if (datagram.Buffer.Length < 236 || datagram.Buffer.Length > 576)
{
@ -36,19 +29,23 @@ public class Dhcp4Server : IDhcpServer, IDisposable
}
var packet = DhcpPacket.Deserialize(datagram.Buffer);
packet.IsValidated = ValidateIncoming(packet);
if (!packet.IsValidated.Value)
if (!ValidateIncoming(packet))
{
return;
}
packet.IsValidated = true;
Console.WriteLine("Solicitation: ");
Console.WriteLine(packet.ToString());
//TODO
}
public bool ValidateIncoming(DhcpPacket packet)
{
Console.WriteLine(packet.ToString());
if (packet.OpType != DhcpOpType.BOOTREQUEST)
{
return false;
@ -72,7 +69,31 @@ public class Dhcp4Server : IDhcpServer, IDisposable
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;

View file

@ -15,8 +15,18 @@ public enum DhcpOptionCode : short
ElapsedTime = 8,
ServerUnicast = 12,
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
@ -37,7 +47,7 @@ public class ClientIdentifierOption : IDhcpOption
public object GetData()
{
return new Duid(Data);
return Duid.Parse(Data);
}
}
@ -51,7 +61,7 @@ public class ServerIdentifierOption : IDhcpOption
public object GetData()
{
return new Duid(Data);
return Duid.Parse(Data);
}
}
@ -155,3 +165,101 @@ public class StatusCodeOption : IDhcpOption
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
},
DhcpOptionCode.ServerId => throw new NotImplementedException(),
DhcpOptionCode.IANonTempAssoc => throw new NotImplementedException(),
DhcpOptionCode.IATempAssoc => throw new NotImplementedException(),
DhcpOptionCode.IAAddr => throw new NotImplementedException(),
DhcpOptionCode.OptionRequest => throw new NotImplementedException(),
DhcpOptionCode.ServerId => new ServerIdentifierOption()
{
Data = data
},
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.ElapsedTime => throw new NotImplementedException(),
DhcpOptionCode.StatusCode => throw new NotImplementedException(),
DhcpOptionCode.ElapsedTime => new ElapsedTimeOption()
{
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}")
};
}
@ -36,13 +91,22 @@ public class DhcpPacket
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)
{
short optionCode = enu.TakeInt16();
DhcpOptionCode optionCode = (DhcpOptionCode)enu.TakeInt16();
short optionLen = enu.TakeInt16();
Console.WriteLine($"D: {data.Length}, {enu.Position}, {optionCode}, {optionLen}");
if (enu.Position + optionLen > data.Length)
{
throw new DataMisalignedException("Invalid option length.");
@ -50,13 +114,17 @@ public class DhcpPacket
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()
{
MessageType = MessageType,
Xid = Xid
Xid = Xid,
Options = options
};
}
}

View file

@ -1,6 +1,6 @@
using System.Net;
using System.Net.Sockets;
using IPMeow.Lib.Address;
using IPMeow.Lib.Request;
namespace IPMeow.Dhcp.Server.Dhcp6;
@ -8,17 +8,18 @@ public class Dhcp6Server : IDhcpServer, IDisposable
{
public const int clientPort = 546, serverPort = 547;
public Dhcp6Server(IAddressProvider addressProvider)
public Dhcp6Server(IAddressProvider<AddressRequestContext> 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);
}
private readonly IAddressProvider _addrProvider;
private readonly IAddressProvider<AddressRequestContext> _addrProvider;
private readonly UdpClient _listener, _sender;
public event EventHandler<DhcpRequestEvent> DhcpRequestedEvent;
public event EventHandler<DhcpRequestEventArgs> DhcpRequestedEvent = delegate { };
public void Dispose()
{
@ -27,24 +28,44 @@ public class Dhcp6Server : IDhcpServer, IDisposable
_sender.Dispose();
}
public async Task Run()
protected async Task Receive(CancellationToken token)
{
while (true)
{
await Receive();
}
}
var datagram = await _listener.ReceiveAsync(token);
protected async Task Receive()
{
var datagram = await _listener.ReceiveAsync();
Console.WriteLine($"From v6: {datagram.RemoteEndPoint}, len: {datagram.Buffer.Length}");
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)
{
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;
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 DhcpStatus(ushort code, string? message)

View file

@ -1,3 +1,5 @@
using IPMeow.Lib.Request;
namespace IPMeow.Dhcp.Server.Dhcp6;
public enum DuidType : short
@ -8,12 +10,36 @@ public enum DuidType : short
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]);
Data = data[2..];
Type = type;
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; }

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,17 @@ public class Host : IBindable
{
[Key]
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 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">
<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>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>

View file

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

View file

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