Abstract away BOOTP
This commit is contained in:
parent
51909d18f4
commit
0823cc25d3
12 changed files with 494 additions and 120 deletions
35
.vscode/launch.json
vendored
Normal file
35
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/src/IPMeow.Server/bin/Debug/net7.0/IPMeow.Server.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/src/IPMeow.Server",
|
||||
"stopAtEntry": false,
|
||||
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
|
||||
"serverReadyAction": {
|
||||
"action": "openExternally",
|
||||
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
|
||||
},
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
41
.vscode/tasks.json
vendored
Normal file
41
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/src/IPMeow.Server/IPMeow.Server.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/src/IPMeow.Server/IPMeow.Server.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/src/IPMeow.Server/IPMeow.Server.csproj"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -71,4 +71,3 @@ EXPOSE 80
|
|||
EXPOSE 443
|
||||
|
||||
ENTRYPOINT [ "dotnet", "IPMeow.Viewer.dll" ]
|
||||
|
||||
|
|
66
README.md
Normal file
66
README.md
Normal file
|
@ -0,0 +1,66 @@
|
|||
# IPMeow
|
||||
|
||||
NYANET IP address management suite (DHCP, DNS & IPAM).
|
||||
|
||||
## Features
|
||||
|
||||
//TODO
|
||||
|
||||
## Building
|
||||
|
||||
With docker:
|
||||
|
||||
```bash
|
||||
docker build -t nyanet/ipmeow-server .
|
||||
#TODO
|
||||
```
|
||||
|
||||
## Deploying
|
||||
|
||||
The most efficient method is with docker compose:
|
||||
|
||||
```yaml
|
||||
version: '3.3'
|
||||
services:
|
||||
# Main server
|
||||
ipam:
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
- '67:67/udp' # if using integrated DHCP server
|
||||
- '547:547/udp' # then include these two ports
|
||||
volumes:
|
||||
- './data:/etc/ipmeow'
|
||||
- './certs:/etc/tls/certs:ro'
|
||||
- './log:/var/log/nyangate'
|
||||
restart: always
|
||||
image: nyanet/ipmeow:ipam
|
||||
|
||||
# If you want a standalone DHCP server container, include this service
|
||||
dhcp:
|
||||
ports:
|
||||
- '67:67/udp'
|
||||
- '547:547/udp'
|
||||
volumes:
|
||||
- './log:/var/log/nyangate'
|
||||
restart: always
|
||||
image: nyanet/ipmeow:dhcp
|
||||
|
||||
# UI
|
||||
viewer:
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
volumes:
|
||||
- './certs:/etc/tls/certs:ro'
|
||||
- './log:/var/log/nyangate'
|
||||
restart: always
|
||||
image: nyanet/ipmeow:viewer
|
||||
|
||||
# Database
|
||||
viewer:
|
||||
volumes:
|
||||
- './db:/var/data/postgres'
|
||||
restart: always
|
||||
image: postgres:latest
|
||||
```
|
130
src/IPMeow.Dhcp/Server/Dhcp4/Bootp.cs
Normal file
130
src/IPMeow.Dhcp/Server/Dhcp4/Bootp.cs
Normal file
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using IPMeow.Lib;
|
||||
|
||||
namespace IPMeow.Dhcp.Server.Dhcp4;
|
||||
|
||||
public enum BootpOpType : byte
|
||||
{
|
||||
BOOTREQUEST = 0x1,
|
||||
BOOTREPLY = 0x2
|
||||
}
|
||||
|
||||
public class BootpHeader
|
||||
{
|
||||
public BootpOpType OpType { get; set; }
|
||||
public byte HType { get; set; }
|
||||
public byte HLen { get; set; }
|
||||
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 override string ToString()
|
||||
{
|
||||
return $"{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()
|
||||
{
|
||||
int padLen = 16 - ClientHWAddress.Length;
|
||||
|
||||
if (padLen == 16)
|
||||
{
|
||||
return ClientHWAddress;
|
||||
}
|
||||
|
||||
var buf = new byte[16];
|
||||
ClientHWAddress.CopyTo(buf, 0);
|
||||
return buf;
|
||||
}
|
||||
|
||||
public byte[] GetMacAddress() => ClientHWAddress[0..6];
|
||||
|
||||
public static BootpHeader? Deserialize(byte[] data)
|
||||
{
|
||||
BitEnumerator enu = new(data);
|
||||
|
||||
if (data.Length < 240 || data.Length > 576)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var OpType = (BootpOpType)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());
|
||||
|
||||
//Check if hardware address is 48 bits
|
||||
if (HLen != 6)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//Validate presence of magic cookie
|
||||
byte[] magic = enu.TakeBytes(4);
|
||||
if (magic[0] != 0x63 || magic[1] != 0x82 || magic[2] != 0x53 || magic[3] != 0x63)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
Hops = Hops,
|
||||
Xid = Xid,
|
||||
Seconds = Seconds,
|
||||
Flags = Flags,
|
||||
ClientAddress = ClientAddress,
|
||||
YourAddress = YourAddress,
|
||||
NextServerAddress = NextServerAddress,
|
||||
RelayAgentAddress = RelayAgentAddress,
|
||||
ClientHWAddress = chaddr,
|
||||
};
|
||||
}
|
||||
|
||||
public byte[] Serialize()
|
||||
{
|
||||
using MemoryStream buffer = new();
|
||||
BinaryWriter writer = new(buffer);
|
||||
|
||||
writer.Write((byte));
|
||||
writer.Write((byte)1);
|
||||
writer.Write((byte)6);
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -11,9 +11,3 @@ public enum DhcpMessageType
|
|||
DhcpRelease,
|
||||
DhcpInform,
|
||||
}
|
||||
|
||||
public enum DhcpOpType : byte
|
||||
{
|
||||
BOOTREQUEST = 0x1,
|
||||
BOOTREPLY = 0x2
|
||||
}
|
||||
|
|
101
src/IPMeow.Dhcp/Server/Dhcp4/DhcpOption.cs
Normal file
101
src/IPMeow.Dhcp/Server/Dhcp4/DhcpOption.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IPMeow.Dhcp.Server.Dhcp4;
|
||||
|
||||
public enum Dhcp4OptionCode : byte
|
||||
{
|
||||
Pad = 0,
|
||||
SubnetMask = 1,
|
||||
TimeOffset = 2,
|
||||
Router = 3,
|
||||
TimeServer = 4,
|
||||
NameServer = 5,
|
||||
Dns = 6,
|
||||
LogServer = 7,
|
||||
CookieServer = 8,
|
||||
LprServer = 9,
|
||||
ImpressServer = 10,
|
||||
ResourceLocationServer = 11,
|
||||
Hostname = 12,
|
||||
BootFileSize = 13,
|
||||
MeritDumpFile = 14,
|
||||
DomainName = 15,
|
||||
SwapServer = 16,
|
||||
RootPath = 17,
|
||||
ExtensionsPath = 18,
|
||||
IPForwardingEnabled = 19,
|
||||
NonLocalSourceRoutingEnabled = 20,
|
||||
PolicyFilter = 21,
|
||||
MaximumDatagramReassembly = 22,
|
||||
DefaultTtl = 23,
|
||||
PmtuTimeout = 24,
|
||||
PmtuPlateauTable = 25,
|
||||
IfaceMtu = 26,
|
||||
AllSubnetsLocal = 27,
|
||||
BroadcastAddress = 28,
|
||||
MaskDiscoveryEnabled = 29,
|
||||
MaskSupplier = 30,
|
||||
RouterDiscoveryEnabled = 31,
|
||||
RouterSolicitationAddress = 32,
|
||||
StaticRoute = 33,
|
||||
TrailerEncapsulationEnabled = 34,
|
||||
ArpCacheTimeout = 35,
|
||||
EthernetEncapsulation = 36,
|
||||
TcpTtl = 37,
|
||||
TcpKeepaliveInterval = 38,
|
||||
TcpKeepaliveGarbage = 39,
|
||||
NisDomain = 40,
|
||||
NisServers = 41,
|
||||
NtpServers = 42,
|
||||
VendorSpecific = 43,
|
||||
NBNameServer = 44,
|
||||
NBDistroServer = 45,
|
||||
//
|
||||
RequestedAddress = 50,
|
||||
IPLeaseTime = 51,
|
||||
OptionOverload = 52,
|
||||
TftpServerName = 66,
|
||||
BootfileName = 67,
|
||||
DhcpMessageType = 53,
|
||||
ServerIdentifier = 54,
|
||||
ParameterRequest = 55,
|
||||
TextMessage = 56,
|
||||
MaximumTextMessageLen = 57,
|
||||
RenewalTime = 58,
|
||||
RebindingTime = 59,
|
||||
VendorClassId = 60,
|
||||
ClientId = 61,
|
||||
|
||||
//
|
||||
End = 255
|
||||
}
|
||||
|
||||
public enum Dhcp4MessageType : byte
|
||||
{
|
||||
DhcpDiscover = 1,
|
||||
DhcpOffer = 2,
|
||||
DhcpRequest = 3,
|
||||
DhcpDecline = 4,
|
||||
DhcpPack = 5,
|
||||
DhcpNak = 6,
|
||||
DhcpRelease = 7,
|
||||
DhcpInform = 8,
|
||||
DhcpForceRenew = 9,
|
||||
DhcpLeaseQuery = 10,
|
||||
DhcpLeaseUnassigned = 11,
|
||||
DhcpLeaseUnknown = 12,
|
||||
DhcpLeaseActive = 13,
|
||||
DhcpBulkLeaseQuery = 14,
|
||||
DhcpLeaseQueryDone = 15,
|
||||
DhcpActiveLeaseQuery = 16,
|
||||
DhcpLeaseQueryStatus = 17,
|
||||
DhcpTls = 18
|
||||
}
|
||||
|
||||
public interface IDhcpOption
|
||||
{
|
||||
|
||||
}
|
|
@ -7,109 +7,61 @@ 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 required BootpHeader Header { get; init; }
|
||||
|
||||
public ICollection<IDhcpOption> Options { get; init; } = null!;
|
||||
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()
|
||||
{
|
||||
int padLen = 16 - ClientHWAddress.Length;
|
||||
|
||||
if (padLen == 16)
|
||||
{
|
||||
return ClientHWAddress;
|
||||
}
|
||||
|
||||
var buf = new byte[16];
|
||||
ClientHWAddress.CopyTo(buf, 0);
|
||||
return buf;
|
||||
}
|
||||
|
||||
public byte[] GetMacAddress() => ClientHWAddress[0..6];
|
||||
|
||||
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)
|
||||
public static DhcpPacket? Deserialize(byte[] data)
|
||||
{
|
||||
BitEnumerator enu = new(data);
|
||||
|
||||
if (data.Length < 236 || data.Length > 576)
|
||||
if (data.Length < 240 || data.Length > 576)
|
||||
{
|
||||
throw new ArgumentException("Invalid datagram.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var OpType = (DhcpOpType)enu.TakeByte();
|
||||
BootpHeader? header = BootpHeader.Deserialize(data[..240]);
|
||||
|
||||
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);
|
||||
if (header == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var BootFileName = Encoding.UTF8.GetString(file.TakeWhile(x => x != 0).ToArray());
|
||||
var ServerHostName = Encoding.UTF8.GetString(sname.TakeWhile(x => x != 0).ToArray());
|
||||
//Check if hardware address is 48 bits
|
||||
if (header.HLen != 6)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//Validate presence of magic cookie
|
||||
byte[] magic = enu.TakeBytes(4);
|
||||
if (magic[0] != 0x63 || magic[1] != 0x82 || magic[2] != 0x53 || magic[3] != 0x63)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<IDhcpOption> options = new();
|
||||
|
||||
Dhcp4OptionCode opcode;
|
||||
|
||||
while ((opcode = (Dhcp4OptionCode)enu.TakeByte()) != Dhcp4OptionCode.End)
|
||||
{
|
||||
if (opcode == Dhcp4OptionCode.Pad)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
byte len = enu.TakeByte();
|
||||
byte[] optionData = enu.TakeBytes(len);
|
||||
}
|
||||
|
||||
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
|
||||
Header = header,
|
||||
Options = options
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -118,20 +70,7 @@ public class DhcpPacket
|
|||
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());
|
||||
writer.Write(Header.Serialize());
|
||||
|
||||
return buffer.GetBuffer();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using IPMeow.Lib.Address;
|
||||
using IPMeow.Lib.Request;
|
||||
|
||||
namespace IPMeow.Dhcp.Server.Dhcp4;
|
||||
|
@ -21,38 +23,48 @@ public class Dhcp4Server : IDhcpServer, IDisposable
|
|||
protected async Task Receive(CancellationToken token)
|
||||
{
|
||||
var datagram = await _udp.ReceiveAsync(token);
|
||||
Console.WriteLine($"New v4 request: {datagram.Buffer.Length}");
|
||||
|
||||
if (datagram.Buffer.Length < 236 || datagram.Buffer.Length > 576)
|
||||
if (datagram.Buffer.Length < 240 || datagram.Buffer.Length > 576)
|
||||
{
|
||||
Debug.Write($"Invalid packet received. (Expected: 240-576; received: {datagram.Buffer.Length})");
|
||||
return;
|
||||
}
|
||||
|
||||
var packet = DhcpPacket.Deserialize(datagram.Buffer);
|
||||
|
||||
if (packet is null)
|
||||
{
|
||||
Debug.Write("Failed packet parsing. Invalid structure.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ValidateIncoming(packet))
|
||||
{
|
||||
Debug.Write("Packet validation failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
packet.IsValidated = true;
|
||||
|
||||
Console.WriteLine("Solicitation: ");
|
||||
Console.WriteLine(packet.ToString());
|
||||
|
||||
DhcpRequestedEvent.Invoke(this, new()
|
||||
{
|
||||
IsV6 = false,
|
||||
MacAddress = MacAddress.Parse(packet.GetMacAddress()),
|
||||
RequestOrigin = packet.ClientAddress
|
||||
});
|
||||
//TODO
|
||||
}
|
||||
|
||||
public bool ValidateIncoming(DhcpPacket packet)
|
||||
{
|
||||
Console.WriteLine(packet.ToString());
|
||||
if (packet.OpType != DhcpOpType.BOOTREQUEST)
|
||||
if (packet.Header.OpType != BootpOpType.BOOTREQUEST)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//Discard unsupported
|
||||
bool supported = packet.HLen == 6 && packet.HType == 1;
|
||||
bool supported = packet.Header.HLen == 6 && packet.Header.HType == 1;
|
||||
|
||||
if (!supported)
|
||||
{
|
||||
|
|
|
@ -5,30 +5,63 @@ namespace IPMeow.Dhcp.Server.Dhcp6;
|
|||
|
||||
public enum DhcpOptionCode : short
|
||||
{
|
||||
[ORO]
|
||||
ClientId = 1,
|
||||
[ORO(false)]
|
||||
ServerId = 2,
|
||||
[ORO(false)]
|
||||
IANonTempAssoc = 3,
|
||||
[ORO(false)]
|
||||
IATempAssoc = 4,
|
||||
[ORO(false)]
|
||||
IAAddr = 5,
|
||||
[ORO(false)]
|
||||
OptionRequest = 6,
|
||||
[ORO]
|
||||
Preference = 7,
|
||||
[ORO]
|
||||
ElapsedTime = 8,
|
||||
[ORO(false)]
|
||||
ServerUnicast = 12,
|
||||
[ORO(false)]
|
||||
StatusCode = 13,
|
||||
[ORO]
|
||||
VendorClass = 16,
|
||||
[ORO]
|
||||
VendorOptions = 17,
|
||||
[ORO]
|
||||
IfaceId = 18,
|
||||
[ORO]
|
||||
DnsServers = 23,
|
||||
[ORO]
|
||||
DomainList = 24,
|
||||
[ORO]
|
||||
SntpServers = 31,
|
||||
[ORO]
|
||||
SubscriberId = 38,
|
||||
[ORO]
|
||||
ClientFqdn = 39,
|
||||
[ORO]
|
||||
V6Lost = 51,
|
||||
[ORO]
|
||||
NtpServer = 56,
|
||||
[ORO]
|
||||
Addrsel = 84,
|
||||
[ORO]
|
||||
AddrselTable = 85,
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class OROAttribute : Attribute
|
||||
{
|
||||
public OROAttribute(bool isOro = true)
|
||||
{
|
||||
IsORO = isOro;
|
||||
}
|
||||
|
||||
public bool IsORO { get; }
|
||||
}
|
||||
|
||||
public interface IDhcpOption
|
||||
{
|
||||
public DhcpOptionCode Code { get; }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using IPMeow.Lib.Request;
|
||||
|
@ -32,14 +33,19 @@ public class Dhcp6Server : IDhcpServer, IDisposable
|
|||
{
|
||||
var datagram = await _listener.ReceiveAsync(token);
|
||||
|
||||
Console.WriteLine($"From v6: {datagram.RemoteEndPoint}, len: {datagram.Buffer.Length}");
|
||||
Debug.Print($"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)
|
||||
if (packet.MessageType is DhcpMessageType.Invalid or DhcpMessageType.Advertise or DhcpMessageType.Reply or DhcpMessageType.Reconfigure or DhcpMessageType.RelayRepl)
|
||||
{
|
||||
//Drop
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.Options.Any(o => !o.Code.IsValidORO()))
|
||||
{
|
||||
//Reply with invalid
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
18
src/IPMeow.Dhcp/Server/Dhcp6/DhcpUtils.cs
Normal file
18
src/IPMeow.Dhcp/Server/Dhcp6/DhcpUtils.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace IPMeow.Dhcp.Server.Dhcp6;
|
||||
|
||||
internal static class DhcpUtils
|
||||
{
|
||||
internal static bool IsValidORO(this DhcpOptionCode code)
|
||||
{
|
||||
var fieldName = Enum.GetName(code);
|
||||
if (fieldName is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var oro = typeof(DhcpOptionCode).GetField(fieldName)!.GetCustomAttribute<OROAttribute>();
|
||||
return oro?.IsORO ?? false;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue