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
|
EXPOSE 443
|
||||||
|
|
||||||
ENTRYPOINT [ "dotnet", "IPMeow.Viewer.dll" ]
|
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,
|
DhcpRelease,
|
||||||
DhcpInform,
|
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 class DhcpPacket
|
||||||
{
|
{
|
||||||
public required DhcpOpType OpType { get; init; }
|
public required BootpHeader Header { get; init; }
|
||||||
public required byte HType { get; init; }
|
|
||||||
public required byte HLen { get; init; }
|
public ICollection<IDhcpOption> Options { get; init; } = null!;
|
||||||
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 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)
|
public static DhcpPacket? Deserialize(byte[] data)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
BitEnumerator enu = new(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();
|
if (header == null)
|
||||||
var HLen = enu.TakeByte();
|
{
|
||||||
var Hops = enu.TakeByte();
|
return null;
|
||||||
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());
|
//Check if hardware address is 48 bits
|
||||||
var ServerHostName = Encoding.UTF8.GetString(sname.TakeWhile(x => x != 0).ToArray());
|
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()
|
return new()
|
||||||
{
|
{
|
||||||
OpType = OpType,
|
Header = header,
|
||||||
HType = HType,
|
Options = options
|
||||||
HLen = HLen,
|
|
||||||
Hops = Hops,
|
|
||||||
Xid = Xid,
|
|
||||||
Seconds = Seconds,
|
|
||||||
Flags = Flags,
|
|
||||||
ClientAddress = ClientAddress,
|
|
||||||
YourAddress = YourAddress,
|
|
||||||
NextServerAddress = NextServerAddress,
|
|
||||||
RelayAgentAddress = RelayAgentAddress,
|
|
||||||
ClientHWAddress = chaddr,
|
|
||||||
ServerHostName = ServerHostName,
|
|
||||||
BootFileName = BootFileName
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,20 +70,7 @@ public class DhcpPacket
|
||||||
using MemoryStream buffer = new();
|
using MemoryStream buffer = new();
|
||||||
BinaryWriter writer = new(buffer);
|
BinaryWriter writer = new(buffer);
|
||||||
|
|
||||||
writer.Write((byte)OpType);
|
writer.Write(Header.Serialize());
|
||||||
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();
|
return buffer.GetBuffer();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
using IPMeow.Lib.Address;
|
||||||
using IPMeow.Lib.Request;
|
using IPMeow.Lib.Request;
|
||||||
|
|
||||||
namespace IPMeow.Dhcp.Server.Dhcp4;
|
namespace IPMeow.Dhcp.Server.Dhcp4;
|
||||||
|
@ -21,38 +23,48 @@ public class Dhcp4Server : IDhcpServer, IDisposable
|
||||||
protected async Task Receive(CancellationToken token)
|
protected async Task Receive(CancellationToken token)
|
||||||
{
|
{
|
||||||
var datagram = await _udp.ReceiveAsync(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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var packet = DhcpPacket.Deserialize(datagram.Buffer);
|
var packet = DhcpPacket.Deserialize(datagram.Buffer);
|
||||||
|
|
||||||
|
if (packet is null)
|
||||||
|
{
|
||||||
|
Debug.Write("Failed packet parsing. Invalid structure.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ValidateIncoming(packet))
|
if (!ValidateIncoming(packet))
|
||||||
{
|
{
|
||||||
|
Debug.Write("Packet validation failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
packet.IsValidated = true;
|
packet.IsValidated = true;
|
||||||
|
|
||||||
Console.WriteLine("Solicitation: ");
|
DhcpRequestedEvent.Invoke(this, new()
|
||||||
Console.WriteLine(packet.ToString());
|
{
|
||||||
|
IsV6 = false,
|
||||||
|
MacAddress = MacAddress.Parse(packet.GetMacAddress()),
|
||||||
|
RequestOrigin = packet.ClientAddress
|
||||||
|
});
|
||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ValidateIncoming(DhcpPacket packet)
|
public bool ValidateIncoming(DhcpPacket packet)
|
||||||
{
|
{
|
||||||
Console.WriteLine(packet.ToString());
|
Console.WriteLine(packet.ToString());
|
||||||
if (packet.OpType != DhcpOpType.BOOTREQUEST)
|
if (packet.Header.OpType != BootpOpType.BOOTREQUEST)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Discard unsupported
|
//Discard unsupported
|
||||||
bool supported = packet.HLen == 6 && packet.HType == 1;
|
bool supported = packet.Header.HLen == 6 && packet.Header.HType == 1;
|
||||||
|
|
||||||
if (!supported)
|
if (!supported)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,30 +5,63 @@ namespace IPMeow.Dhcp.Server.Dhcp6;
|
||||||
|
|
||||||
public enum DhcpOptionCode : short
|
public enum DhcpOptionCode : short
|
||||||
{
|
{
|
||||||
|
[ORO]
|
||||||
ClientId = 1,
|
ClientId = 1,
|
||||||
|
[ORO(false)]
|
||||||
ServerId = 2,
|
ServerId = 2,
|
||||||
|
[ORO(false)]
|
||||||
IANonTempAssoc = 3,
|
IANonTempAssoc = 3,
|
||||||
|
[ORO(false)]
|
||||||
IATempAssoc = 4,
|
IATempAssoc = 4,
|
||||||
|
[ORO(false)]
|
||||||
IAAddr = 5,
|
IAAddr = 5,
|
||||||
|
[ORO(false)]
|
||||||
OptionRequest = 6,
|
OptionRequest = 6,
|
||||||
|
[ORO]
|
||||||
Preference = 7,
|
Preference = 7,
|
||||||
|
[ORO]
|
||||||
ElapsedTime = 8,
|
ElapsedTime = 8,
|
||||||
|
[ORO(false)]
|
||||||
ServerUnicast = 12,
|
ServerUnicast = 12,
|
||||||
|
[ORO(false)]
|
||||||
StatusCode = 13,
|
StatusCode = 13,
|
||||||
|
[ORO]
|
||||||
VendorClass = 16,
|
VendorClass = 16,
|
||||||
|
[ORO]
|
||||||
VendorOptions = 17,
|
VendorOptions = 17,
|
||||||
|
[ORO]
|
||||||
IfaceId = 18,
|
IfaceId = 18,
|
||||||
|
[ORO]
|
||||||
DnsServers = 23,
|
DnsServers = 23,
|
||||||
|
[ORO]
|
||||||
DomainList = 24,
|
DomainList = 24,
|
||||||
|
[ORO]
|
||||||
SntpServers = 31,
|
SntpServers = 31,
|
||||||
|
[ORO]
|
||||||
SubscriberId = 38,
|
SubscriberId = 38,
|
||||||
|
[ORO]
|
||||||
ClientFqdn = 39,
|
ClientFqdn = 39,
|
||||||
|
[ORO]
|
||||||
V6Lost = 51,
|
V6Lost = 51,
|
||||||
|
[ORO]
|
||||||
NtpServer = 56,
|
NtpServer = 56,
|
||||||
|
[ORO]
|
||||||
Addrsel = 84,
|
Addrsel = 84,
|
||||||
|
[ORO]
|
||||||
AddrselTable = 85,
|
AddrselTable = 85,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class OROAttribute : Attribute
|
||||||
|
{
|
||||||
|
public OROAttribute(bool isOro = true)
|
||||||
|
{
|
||||||
|
IsORO = isOro;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsORO { get; }
|
||||||
|
}
|
||||||
|
|
||||||
public interface IDhcpOption
|
public interface IDhcpOption
|
||||||
{
|
{
|
||||||
public DhcpOptionCode Code { get; }
|
public DhcpOptionCode Code { get; }
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using IPMeow.Lib.Request;
|
using IPMeow.Lib.Request;
|
||||||
|
@ -32,14 +33,19 @@ public class Dhcp6Server : IDhcpServer, IDisposable
|
||||||
{
|
{
|
||||||
var datagram = await _listener.ReceiveAsync(token);
|
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);
|
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.Advertise or DhcpMessageType.Reply or DhcpMessageType.Reconfigure or DhcpMessageType.RelayRepl)
|
||||||
|
|
||||||
if (packet.MessageType is DhcpMessageType.Invalid or DhcpMessageType.Solicit or DhcpMessageType.Confirm or DhcpMessageType.Rebind)
|
|
||||||
{
|
{
|
||||||
|
//Drop
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.Options.Any(o => !o.Code.IsValidORO()))
|
||||||
|
{
|
||||||
|
//Reply with invalid
|
||||||
return;
|
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