Refactor DHCP to implement DHCP and DHCPv6. Implement most DHCPv6 options.

This commit is contained in:
femsci 2023-04-21 12:03:00 +02:00
parent 22cd2dbf3a
commit 5ae056ad64
Signed by: femsci
GPG key ID: 08F7911F0E650C67
15 changed files with 642 additions and 41 deletions

View file

@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\IPMeow.Lib\IPMeow.Lib.csproj" />
<ItemGroup>
<ProjectReference Include="..\IPMeow.Lib\IPMeow.Lib.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

View 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();
}
}
}

View 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; }
}

View file

@ -1,4 +1,4 @@
namespace IPMeow.Dhcp.Server;
namespace IPMeow.Dhcp.Server.Dhcp4;
public enum DhcpMessageType
{

View 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();
}
}

View 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;
}

View 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
}

View 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);
}
}

View 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
};
}
}

View 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;
}
}
}

View 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;
}
}

View 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; }
}

View 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
}

View file

@ -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);
}
}

View 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();
}