Refactor DHCP to implement DHCP and DHCPv6. Implement most DHCPv6 options.
This commit is contained in:
parent
22cd2dbf3a
commit
5ae056ad64
15 changed files with 642 additions and 41 deletions
|
@ -1,10 +1,11 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\IPMeow.Lib\IPMeow.Lib.csproj" />
|
<ProjectReference Include="..\IPMeow.Lib\IPMeow.Lib.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
16
src/IPMeow.Dhcp/Program.cs
Normal file
16
src/IPMeow.Dhcp/Program.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using IPMeow.Dhcp.Server;
|
||||||
|
using IPMeow.Dhcp.Server.Dhcp4;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Listening on DHCP...");
|
||||||
|
var dhcp = new DhcpServer();
|
||||||
|
|
||||||
|
await dhcp.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/IPMeow.Dhcp/Server/AddressAssociation.cs
Normal file
15
src/IPMeow.Dhcp/Server/AddressAssociation.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server;
|
||||||
|
|
||||||
|
public readonly struct AddressAssociation
|
||||||
|
{
|
||||||
|
public IPAddress Address { get; }
|
||||||
|
public int PreferredLifetime { get; }
|
||||||
|
public int ValidLifetime { get; }
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
namespace IPMeow.Dhcp.Server;
|
namespace IPMeow.Dhcp.Server.Dhcp4;
|
||||||
|
|
||||||
public enum DhcpMessageType
|
public enum DhcpMessageType
|
||||||
{
|
{
|
135
src/IPMeow.Dhcp/Server/Dhcp4/DhcpPacket.cs
Normal file
135
src/IPMeow.Dhcp/Server/Dhcp4/DhcpPacket.cs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using IPMeow.Lib;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server.Dhcp4;
|
||||||
|
|
||||||
|
public class DhcpPacket
|
||||||
|
{
|
||||||
|
public required DhcpOpType OpType { get; init; }
|
||||||
|
public required byte HType { get; init; }
|
||||||
|
public required byte HLen { get; init; }
|
||||||
|
public required byte Hops { get; init; }
|
||||||
|
public required uint Xid { get; init; }
|
||||||
|
public required short Seconds { get; init; }
|
||||||
|
public required short Flags { get; init; }
|
||||||
|
public required IPAddress ClientAddress { get; init; }
|
||||||
|
public required IPAddress YourAddress { get; init; }
|
||||||
|
public required IPAddress NextServerAddress { get; init; }
|
||||||
|
public required IPAddress RelayAgentAddress { get; init; }
|
||||||
|
public required byte[] ClientHWAddress { get; init; }
|
||||||
|
public required string ServerHostName { get; init; }
|
||||||
|
public required string BootFileName { get; init; }
|
||||||
|
public bool? IsValidated { get; set; }
|
||||||
|
|
||||||
|
public byte[] GetClientHwAddress()
|
||||||
|
{
|
||||||
|
int padLen = 16 - ClientHWAddress.Length;
|
||||||
|
|
||||||
|
if (padLen == 16)
|
||||||
|
{
|
||||||
|
return ClientHWAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf = new byte[16];
|
||||||
|
ClientHWAddress.CopyTo(buf, 0);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetSnameBytes()
|
||||||
|
{
|
||||||
|
var buf = new byte[64];
|
||||||
|
if (!string.IsNullOrWhiteSpace(ServerHostName))
|
||||||
|
{
|
||||||
|
Encoding.UTF8.GetBytes(ServerHostName).CopyTo(buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetBootFileBytes()
|
||||||
|
{
|
||||||
|
var buf = new byte[128];
|
||||||
|
if (!string.IsNullOrWhiteSpace(BootFileName))
|
||||||
|
{
|
||||||
|
Encoding.UTF8.GetBytes(BootFileName).CopyTo(buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DhcpPacket Deserialize(byte[] data)
|
||||||
|
{
|
||||||
|
BitEnumerator enu = new(data);
|
||||||
|
|
||||||
|
if (data.Length < 236 || data.Length > 576)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid datagram.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var OpType = (DhcpOpType)enu.TakeByte();
|
||||||
|
|
||||||
|
var HType = enu.TakeByte();
|
||||||
|
var HLen = enu.TakeByte();
|
||||||
|
var Hops = enu.TakeByte();
|
||||||
|
var Xid = enu.TakeUInt32();
|
||||||
|
var Seconds = enu.TakeInt16();
|
||||||
|
var Flags = enu.TakeInt16();
|
||||||
|
var ClientAddress = new IPAddress(enu.TakeBytes(4));
|
||||||
|
var YourAddress = new IPAddress(enu.TakeBytes(4));
|
||||||
|
var NextServerAddress = new IPAddress(enu.TakeBytes(4));
|
||||||
|
var RelayAgentAddress = new IPAddress(enu.TakeBytes(4));
|
||||||
|
byte[] chaddr = enu.TakeBytes(16);
|
||||||
|
byte[] sname = enu.TakeBytes(64);
|
||||||
|
byte[] file = enu.TakeBytes(128);
|
||||||
|
|
||||||
|
var BootFileName = Encoding.UTF8.GetString(file.TakeWhile(x => x != 0).ToArray());
|
||||||
|
var ServerHostName = Encoding.UTF8.GetString(sname.TakeWhile(x => x != 0).ToArray());
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
OpType = OpType,
|
||||||
|
HType = HType,
|
||||||
|
HLen = HLen,
|
||||||
|
Hops = Hops,
|
||||||
|
Xid = Xid,
|
||||||
|
Seconds = Seconds,
|
||||||
|
Flags = Flags,
|
||||||
|
ClientAddress = ClientAddress,
|
||||||
|
YourAddress = YourAddress,
|
||||||
|
NextServerAddress = NextServerAddress,
|
||||||
|
RelayAgentAddress = RelayAgentAddress,
|
||||||
|
ClientHWAddress = chaddr,
|
||||||
|
ServerHostName = ServerHostName,
|
||||||
|
BootFileName = BootFileName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Serialize()
|
||||||
|
{
|
||||||
|
using MemoryStream buffer = new();
|
||||||
|
BinaryWriter writer = new(buffer);
|
||||||
|
|
||||||
|
writer.Write((byte)OpType);
|
||||||
|
writer.Write(HType);
|
||||||
|
writer.Write(HLen);
|
||||||
|
writer.Write(Hops);
|
||||||
|
writer.Write(Xid);
|
||||||
|
writer.Write(Seconds);
|
||||||
|
writer.Write(Flags);
|
||||||
|
writer.Write(ClientAddress.GetAddressBytes());
|
||||||
|
writer.Write(YourAddress.GetAddressBytes());
|
||||||
|
writer.Write(NextServerAddress.GetAddressBytes());
|
||||||
|
writer.Write(RelayAgentAddress.GetAddressBytes());
|
||||||
|
writer.Write(GetClientHwAddress());
|
||||||
|
writer.Write(GetSnameBytes());
|
||||||
|
writer.Write(GetBootFileBytes());
|
||||||
|
|
||||||
|
return buffer.GetBuffer();
|
||||||
|
}
|
||||||
|
}
|
75
src/IPMeow.Dhcp/Server/Dhcp4/DhcpServer.cs
Normal file
75
src/IPMeow.Dhcp/Server/Dhcp4/DhcpServer.cs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server.Dhcp4;
|
||||||
|
|
||||||
|
public class DhcpServer
|
||||||
|
{
|
||||||
|
public const int clientPort = 68, serverPort = 67;
|
||||||
|
|
||||||
|
public DhcpServer()
|
||||||
|
{
|
||||||
|
_udp = new UdpClient(serverPort, AddressFamily.InterNetwork);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Run()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await Receive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task Receive()
|
||||||
|
{
|
||||||
|
var datagram = await _udp.ReceiveAsync();
|
||||||
|
|
||||||
|
if (datagram.Buffer.Length < 236 || datagram.Buffer.Length > 576)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var packet = DhcpPacket.Deserialize(datagram.Buffer);
|
||||||
|
packet.IsValidated = ValidateIncoming(packet);
|
||||||
|
|
||||||
|
if (!packet.IsValidated.Value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Console.WriteLine("Solicitation: ");
|
||||||
|
Console.WriteLine(packet.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ValidateIncoming(DhcpPacket packet)
|
||||||
|
{
|
||||||
|
if (packet.OpType != DhcpOpType.BOOTREQUEST)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Discard unsupported
|
||||||
|
bool supported = packet.HLen == 6 && packet.HType == 1;
|
||||||
|
|
||||||
|
if (!supported)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.NextServerAddress != IPAddress.Any)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly UdpClient _udp;
|
||||||
|
}
|
24
src/IPMeow.Dhcp/Server/Dhcp6/DhcpFields.cs
Normal file
24
src/IPMeow.Dhcp/Server/Dhcp6/DhcpFields.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server.Dhcp6;
|
||||||
|
|
||||||
|
public enum DhcpMessageType : byte
|
||||||
|
{
|
||||||
|
Invalid = 0,
|
||||||
|
Solicit = 1,
|
||||||
|
Advertise = 2,
|
||||||
|
Request = 3,
|
||||||
|
Confirm = 4,
|
||||||
|
Renew = 5,
|
||||||
|
Rebind = 6,
|
||||||
|
Reply = 7,
|
||||||
|
Release = 8,
|
||||||
|
Decline = 9,
|
||||||
|
Reconfigure = 10,
|
||||||
|
InfoRequest = 11,
|
||||||
|
RelayForw = 12,
|
||||||
|
RelayRepl = 13
|
||||||
|
}
|
161
src/IPMeow.Dhcp/Server/Dhcp6/DhcpOption.cs
Normal file
161
src/IPMeow.Dhcp/Server/Dhcp6/DhcpOption.cs
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server.Dhcp6;
|
||||||
|
|
||||||
|
public enum DhcpOptionCode : short
|
||||||
|
{
|
||||||
|
ClientId = 1,
|
||||||
|
ServerId = 2,
|
||||||
|
IANonTempAssoc = 3,
|
||||||
|
IATempAssoc = 4,
|
||||||
|
IAAddr = 5,
|
||||||
|
OptionRequest = 6,
|
||||||
|
Preference = 7,
|
||||||
|
ElapsedTime = 8,
|
||||||
|
ServerUnicast = 12,
|
||||||
|
StatusCode = 13,
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDhcpOption
|
||||||
|
{
|
||||||
|
public DhcpOptionCode Code { get; }
|
||||||
|
public byte[] Data { get; set; }
|
||||||
|
public Type DataType { get; }
|
||||||
|
public abstract object GetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ClientIdentifierOption : IDhcpOption
|
||||||
|
{
|
||||||
|
public DhcpOptionCode Code => DhcpOptionCode.ClientId;
|
||||||
|
|
||||||
|
public required byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public Type DataType => typeof(Duid);
|
||||||
|
|
||||||
|
public object GetData()
|
||||||
|
{
|
||||||
|
return new Duid(Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServerIdentifierOption : IDhcpOption
|
||||||
|
{
|
||||||
|
public DhcpOptionCode Code => DhcpOptionCode.ServerId;
|
||||||
|
|
||||||
|
public required byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public Type DataType => typeof(Duid);
|
||||||
|
|
||||||
|
public object GetData()
|
||||||
|
{
|
||||||
|
return new Duid(Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NonTempIdentityAssociationOption : IDhcpOption
|
||||||
|
{
|
||||||
|
public DhcpOptionCode Code => DhcpOptionCode.IANonTempAssoc;
|
||||||
|
|
||||||
|
public required byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public Type DataType => typeof(IdentityAssociation);
|
||||||
|
|
||||||
|
public object GetData()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TempIdentityAssociationOption : IDhcpOption
|
||||||
|
{
|
||||||
|
public DhcpOptionCode Code => DhcpOptionCode.IATempAssoc;
|
||||||
|
|
||||||
|
public required byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public Type DataType => typeof(IdentityAssociation);
|
||||||
|
|
||||||
|
public object GetData()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IAAddressOption : IDhcpOption
|
||||||
|
{
|
||||||
|
public DhcpOptionCode Code => DhcpOptionCode.IAAddr;
|
||||||
|
|
||||||
|
public required byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public Type DataType => typeof(AddressAssociation);
|
||||||
|
|
||||||
|
public object GetData()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OptionRequestOption : IDhcpOption
|
||||||
|
{
|
||||||
|
public DhcpOptionCode Code => DhcpOptionCode.OptionRequest;
|
||||||
|
|
||||||
|
public required byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public Type DataType => typeof(ICollection<DhcpOptionCode>);
|
||||||
|
|
||||||
|
public object GetData()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ElapsedTimeOption : IDhcpOption
|
||||||
|
{
|
||||||
|
public DhcpOptionCode Code => DhcpOptionCode.ElapsedTime;
|
||||||
|
|
||||||
|
public required byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public Type DataType => typeof(TimeSpan);
|
||||||
|
|
||||||
|
public object GetData()
|
||||||
|
{
|
||||||
|
return TimeSpan.FromSeconds(BitConverter.ToInt16(Data, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServerUnicastOption : IDhcpOption
|
||||||
|
{
|
||||||
|
public DhcpOptionCode Code => DhcpOptionCode.ServerUnicast;
|
||||||
|
public required byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public Type DataType => typeof(IPAddress);
|
||||||
|
|
||||||
|
public object GetData()
|
||||||
|
{
|
||||||
|
return new IPAddress(Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatusCodeOption : IDhcpOption
|
||||||
|
{
|
||||||
|
public DhcpOptionCode Code => DhcpOptionCode.StatusCode;
|
||||||
|
|
||||||
|
public required byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public Type DataType => typeof(DhcpStatus);
|
||||||
|
|
||||||
|
public object GetData()
|
||||||
|
{
|
||||||
|
ushort code = BitConverter.ToUInt16(Data.AsSpan()[..2]);
|
||||||
|
|
||||||
|
string? message = Data.Length > 2 ? Encoding.UTF8.GetString(Data.AsSpan()[2..]) : null;
|
||||||
|
|
||||||
|
return new DhcpStatus(code, message);
|
||||||
|
}
|
||||||
|
}
|
66
src/IPMeow.Dhcp/Server/Dhcp6/DhcpPacket.cs
Normal file
66
src/IPMeow.Dhcp/Server/Dhcp6/DhcpPacket.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using IPMeow.Lib;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server.Dhcp6;
|
||||||
|
|
||||||
|
public class DhcpPacket
|
||||||
|
{
|
||||||
|
public required DhcpMessageType MessageType { get; init; }
|
||||||
|
public required uint Xid { get; init; }
|
||||||
|
public ICollection<IDhcpOption> Options { get; init; } = new List<IDhcpOption>();
|
||||||
|
|
||||||
|
public static IDhcpOption GetOption(DhcpOptionCode type, byte[] data)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
DhcpOptionCode.ClientId => new ClientIdentifierOption()
|
||||||
|
{
|
||||||
|
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.Preference => throw new NotImplementedException(),
|
||||||
|
DhcpOptionCode.ElapsedTime => throw new NotImplementedException(),
|
||||||
|
DhcpOptionCode.StatusCode => throw new NotImplementedException(),
|
||||||
|
_ => throw new InvalidOperationException($"Invalid option type {type}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DhcpPacket Deserialize(byte[] data)
|
||||||
|
{
|
||||||
|
BitEnumerator enu = new(data);
|
||||||
|
|
||||||
|
byte[] header = enu.TakeBytes(4);
|
||||||
|
|
||||||
|
DhcpMessageType MessageType = (DhcpMessageType)header[0];
|
||||||
|
|
||||||
|
uint Xid = BitConverter.ToUInt32(header, 1);
|
||||||
|
|
||||||
|
while (enu.Position < data.Length)
|
||||||
|
{
|
||||||
|
short optionCode = enu.TakeInt16();
|
||||||
|
short optionLen = enu.TakeInt16();
|
||||||
|
|
||||||
|
if (enu.Position + optionLen > data.Length)
|
||||||
|
{
|
||||||
|
throw new DataMisalignedException("Invalid option length.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] optionData = enu.TakeBytes(optionLen);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
MessageType = MessageType,
|
||||||
|
Xid = Xid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
49
src/IPMeow.Dhcp/Server/Dhcp6/DhcpServer.cs
Normal file
49
src/IPMeow.Dhcp/Server/Dhcp6/DhcpServer.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using IPMeow.Dhcp.Server.Dhcp4;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server.Dhcp6;
|
||||||
|
|
||||||
|
public class DhcpServer : IDhcpServer, IDisposable
|
||||||
|
{
|
||||||
|
public const int clientPort = 546, serverPort = 547;
|
||||||
|
|
||||||
|
public DhcpServer()
|
||||||
|
{
|
||||||
|
_listener = new(new IPEndPoint(IPAddress.Parse("ff02::1:2"), serverPort));
|
||||||
|
_sender = new(clientPort, AddressFamily.InterNetworkV6);
|
||||||
|
}
|
||||||
|
private UdpClient _listener, _sender;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
_listener.Dispose();
|
||||||
|
_sender.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Run()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await Receive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task Receive()
|
||||||
|
{
|
||||||
|
var datagram = await _listener.ReceiveAsync();
|
||||||
|
|
||||||
|
var packet = DhcpPacket.Deserialize(datagram.Buffer);
|
||||||
|
|
||||||
|
if (packet.MessageType is DhcpMessageType.Invalid or DhcpMessageType.Solicit or DhcpMessageType.Confirm or DhcpMessageType.Rebind)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
47
src/IPMeow.Dhcp/Server/Dhcp6/DhcpStatus.cs
Normal file
47
src/IPMeow.Dhcp/Server/Dhcp6/DhcpStatus.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server.Dhcp6;
|
||||||
|
|
||||||
|
public struct DhcpStatus
|
||||||
|
{
|
||||||
|
public DhcpStatus(ushort code, string? message)
|
||||||
|
{
|
||||||
|
Code = code;
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DhcpStatus? TryParse(byte[] data)
|
||||||
|
{
|
||||||
|
if (data.Length < 2)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort code = BitConverter.ToUInt16(data.AsSpan()[..2]);
|
||||||
|
string? message = data.Length > 2 ? Encoding.UTF8.GetString(data.AsSpan()[2..]) : null;
|
||||||
|
|
||||||
|
return new(code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort Code { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
public byte[] Serialize()
|
||||||
|
{
|
||||||
|
byte[] buf = new byte[2 + Message?.Length ?? 0];
|
||||||
|
|
||||||
|
buf[0] = (byte)((Code & 0xff00) >> 8);
|
||||||
|
buf[1] = (byte)(Code & 0x00ff);
|
||||||
|
|
||||||
|
if (Message is not null)
|
||||||
|
{
|
||||||
|
Encoding.UTF8.GetBytes(Message).CopyTo(buf, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
}
|
26
src/IPMeow.Dhcp/Server/Dhcp6/Duid.cs
Normal file
26
src/IPMeow.Dhcp/Server/Dhcp6/Duid.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server.Dhcp6;
|
||||||
|
|
||||||
|
public enum DuidType : short
|
||||||
|
{
|
||||||
|
LLAddrWithTime = 1,
|
||||||
|
VendorUID = 2,
|
||||||
|
LLAddr = 3,
|
||||||
|
Uuid = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct Duid
|
||||||
|
{
|
||||||
|
public Duid(byte[] data)
|
||||||
|
{
|
||||||
|
Type = (DuidType)BitConverter.ToInt16(data.AsSpan()[0..2]);
|
||||||
|
Data = data[2..];
|
||||||
|
}
|
||||||
|
|
||||||
|
public DuidType Type { get; }
|
||||||
|
public byte[] Data { get; }
|
||||||
|
}
|
13
src/IPMeow.Dhcp/Server/Dhcp6/IdentityAssociation.cs
Normal file
13
src/IPMeow.Dhcp/Server/Dhcp6/IdentityAssociation.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server.Dhcp6;
|
||||||
|
|
||||||
|
public struct IdentityAssociation
|
||||||
|
{
|
||||||
|
public int IAId { get; }
|
||||||
|
public int TimeToRefresh { get; }
|
||||||
|
public int TimeToNextAdvertise { get; } //T2 in RFC8415, 21.4
|
||||||
|
}
|
|
@ -1,38 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using IPMeow.Lib;
|
|
||||||
|
|
||||||
namespace IPMeow.Dhcp.Server;
|
|
||||||
|
|
||||||
public class DhcpPacket
|
|
||||||
{
|
|
||||||
public DhcpPacket(byte[] data)
|
|
||||||
{
|
|
||||||
BitEnumerator enu = new(data);
|
|
||||||
|
|
||||||
DhcpOpType opType = (DhcpOpType)enu.TakeByte();
|
|
||||||
|
|
||||||
byte hType = enu.TakeByte();
|
|
||||||
byte hLen = enu.TakeByte();
|
|
||||||
byte hOps = enu.TakeByte();
|
|
||||||
|
|
||||||
uint xid = enu.TakeUInt32();
|
|
||||||
|
|
||||||
short secs = enu.TakeInt16();
|
|
||||||
|
|
||||||
short flags = enu.TakeInt16();
|
|
||||||
|
|
||||||
IPAddress ciaddr = new(enu.TakeBytes(4));
|
|
||||||
IPAddress yiaddr = new(enu.TakeBytes(4));
|
|
||||||
IPAddress siaddr = new(enu.TakeBytes(4));
|
|
||||||
IPAddress giaddr = new(enu.TakeBytes(4));
|
|
||||||
|
|
||||||
byte[] chaddr = enu.TakeBytes(16);
|
|
||||||
|
|
||||||
byte[] sname = enu.TakeBytes(64);
|
|
||||||
byte[] file = enu.TakeBytes(128);
|
|
||||||
}
|
|
||||||
}
|
|
11
src/IPMeow.Dhcp/Server/IDhcpServer.cs
Normal file
11
src/IPMeow.Dhcp/Server/IDhcpServer.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IPMeow.Dhcp.Server;
|
||||||
|
|
||||||
|
public interface IDhcpServer
|
||||||
|
{
|
||||||
|
public Task Run();
|
||||||
|
}
|
Loading…
Reference in a new issue