400 lines
15 KiB
C#
400 lines
15 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using ln.types.threads;
|
|||
|
using ln.types.net;
|
|||
|
using System.Net.Sockets;
|
|||
|
using System.Net;
|
|||
|
using ln.logging;
|
|||
|
using ln.types.odb;
|
|||
|
using System.IO;
|
|||
|
using ln.types.odb.mapped;
|
|||
|
using System.Linq;
|
|||
|
using System.Threading;
|
|||
|
namespace ln.dhcp
|
|||
|
{
|
|||
|
public class DHCPServer
|
|||
|
{
|
|||
|
public string StoragePath { get; private set; }
|
|||
|
public bool IsStarted { get; private set; }
|
|||
|
|
|||
|
Pool pool = new Pool(8);
|
|||
|
|
|||
|
ODBCollection<DHCPServerInterface> interfaces;
|
|||
|
public DHCPServerInterface[] Interfaces => interfaces.ToArray();
|
|||
|
|
|||
|
ODBCollection<IPPool> ippools;
|
|||
|
public IPPool[] IPPools => ippools.ToArray();
|
|||
|
|
|||
|
ODBCollection<Option> defaultOptions;
|
|||
|
public Option[] DefaultOptions => defaultOptions.ToArray();
|
|||
|
|
|||
|
ODBCollection<DHCPLease> leases;
|
|||
|
public DHCPLease[] Leases => leases.ToArray();
|
|||
|
|
|||
|
ODB odb;
|
|||
|
Dictionary<IPv4, UdpClient> udpClients = new Dictionary<IPv4, UdpClient>();
|
|||
|
|
|||
|
FileLogger logLease;
|
|||
|
|
|||
|
LeasesAPI leasesAPI;
|
|||
|
|
|||
|
public DHCPServer()
|
|||
|
:this("default")
|
|||
|
{
|
|||
|
}
|
|||
|
public DHCPServer(String uniqueName)
|
|||
|
{
|
|||
|
StoragePath = Path.Combine("/var/cache/ln.dhcp", uniqueName);
|
|||
|
if (!Directory.Exists(StoragePath))
|
|||
|
Directory.CreateDirectory(StoragePath);
|
|||
|
|
|||
|
logLease = new FileLogger(Path.Combine(StoragePath, "lease.log"));
|
|||
|
|
|||
|
odb = new ODB(StoragePath);
|
|||
|
defaultOptions = odb.GetCollection<Option>();
|
|||
|
ippools = odb.GetCollection<IPPool>();
|
|||
|
interfaces = odb.GetCollection<DHCPServerInterface>();
|
|||
|
leases = odb.GetCollection<DHCPLease>();
|
|||
|
|
|||
|
leasesAPI = new LeasesAPI(this);
|
|||
|
}
|
|||
|
|
|||
|
public void Start()
|
|||
|
{
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
if (!IsStarted && interfaces.Count > 0)
|
|||
|
{
|
|||
|
foreach (DHCPServerInterface serverInterface in interfaces)
|
|||
|
StartInterface(serverInterface);
|
|||
|
|
|||
|
IsStarted = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
public void Stop()
|
|||
|
{
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
foreach (DHCPServerInterface serverInterface in interfaces)
|
|||
|
StartInterface(serverInterface);
|
|||
|
|
|||
|
IsStarted = false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void StartInterface(DHCPServerInterface serverInterface)
|
|||
|
{
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
Logging.Log(LogLevel.INFO, "Starting ServerInterface {0}",serverInterface.InterfaceAddress);
|
|||
|
|
|||
|
if (!udpClients.ContainsKey(serverInterface.InterfaceAddress))
|
|||
|
{
|
|||
|
Thread iThread = new Thread(() => InterfaceListener(serverInterface,null));
|
|||
|
iThread.Start();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
private void StopInterface(DHCPServerInterface serverInterface)
|
|||
|
{
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
if (udpClients.ContainsKey(serverInterface.InterfaceAddress))
|
|||
|
udpClients[serverInterface.InterfaceAddress].Close();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void Received(DHCPServerInterface serverInterface,BootPPacket packet)
|
|||
|
{
|
|||
|
Logging.Log(LogLevel.INFO, "Received: {0}",packet);
|
|||
|
|
|||
|
DHCPMessageTypeOption messageType = packet.GetFirstOption<DHCPMessageTypeOption>();
|
|||
|
if (messageType != null)
|
|||
|
{
|
|||
|
switch (messageType.DHCPMessageType)
|
|||
|
{
|
|||
|
case DHCPMessageType.DISCOVER:
|
|||
|
DHCPDiscover(serverInterface, packet);
|
|||
|
break;
|
|||
|
case DHCPMessageType.RELEASE:
|
|||
|
DHCPRelease(serverInterface, packet);
|
|||
|
break;
|
|||
|
case DHCPMessageType.REQUEST:
|
|||
|
DHCPRequest(serverInterface, packet);
|
|||
|
break;
|
|||
|
case DHCPMessageType.DECLINE:
|
|||
|
DHCPDecline(serverInterface, packet);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
private void InterfaceListener(DHCPServerInterface serverInterface, UdpClient udp)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
Logging.Log(LogLevel.INFO, "InterfaceListener started: {0}",serverInterface.InterfaceAddress);
|
|||
|
|
|||
|
//udp = new UdpClient(new IPEndPoint(serverInterface.InterfaceAddress, 67));
|
|||
|
udp = new UdpClient(new IPEndPoint(IPAddress.Any, 67));
|
|||
|
udp.EnableBroadcast = true;
|
|||
|
|
|||
|
while (IsStarted)
|
|||
|
{
|
|||
|
IPEndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, 0);
|
|||
|
byte[] rxBytes = udp.Receive(ref remoteEndpoint);
|
|||
|
Logging.Log(LogLevel.INFO, "RX: {0}", BitConverter.ToString(rxBytes));
|
|||
|
BootPPacket bootPPacket = new BootPPacket(rxBytes, remoteEndpoint);
|
|||
|
Logging.Log(LogLevel.INFO, "RX: {0}", bootPPacket);
|
|||
|
pool.Enqueue(() => Received(serverInterface,bootPPacket));
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
Logging.Log(LogLevel.WARNING, "DHCPServer.InterfaceListener(): Caught exception: {0}", e);
|
|||
|
}
|
|||
|
|
|||
|
Logging.Log(LogLevel.INFO, "InterfaceListener stopped: {0}", serverInterface.InterfaceAddress);
|
|||
|
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
udpClients.Remove(serverInterface.InterfaceAddress);
|
|||
|
udp.Dispose();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void AddInterface(IPv4 ip)
|
|||
|
{
|
|||
|
DHCPServerInterface serverInterface = new DHCPServerInterface(ip);
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
if (!interfaces.Insert(serverInterface))
|
|||
|
throw new ArgumentException(nameof(ip),String.Format("DHCPServer already has interface for IP {0}",ip));
|
|||
|
if (IsStarted)
|
|||
|
StartInterface(serverInterface);
|
|||
|
}
|
|||
|
}
|
|||
|
public void AddPool(IPPool pool)
|
|||
|
{
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
if (!ippools.Insert(pool))
|
|||
|
throw new ArgumentException(nameof(pool), String.Format("DHCPServer could not insert new pool {0}", pool));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private BootPPacket ConstructReply(DHCPServerInterface serverInterface,BootPPacket request,DHCPMessageType replyType)
|
|||
|
{
|
|||
|
BootPPacket reply = new BootPPacket();
|
|||
|
reply.OP = OP.BOOTREPLY;
|
|||
|
reply.HType = request.HType;
|
|||
|
reply.HLen = request.HLen;
|
|||
|
reply.XID = request.XID;
|
|||
|
reply.Flags = request.Flags;
|
|||
|
reply.GIAddr = request.GIAddr;
|
|||
|
reply.CHAddr = request.CHAddr;
|
|||
|
|
|||
|
reply.AddOption(new DHCPMessageTypeOption(replyType));
|
|||
|
reply.AddOption(new DHCPServerIdentifierOption(serverInterface.InterfaceAddress));
|
|||
|
|
|||
|
if (!request.GIAddr.Equals(IPv4.ANY))
|
|||
|
reply.RemoteEndpoint = new IPEndPoint(request.GIAddr, 67);
|
|||
|
else if (replyType == DHCPMessageType.NAK)
|
|||
|
reply.RemoteEndpoint = new IPEndPoint(IPAddress.Broadcast, 68);
|
|||
|
else if (!request.CIAddr.Equals(IPv4.ANY))
|
|||
|
reply.RemoteEndpoint = new IPEndPoint(request.CIAddr, 68);
|
|||
|
else if (request.Flags.HasFlag(BootPFlags.BROADCAST))
|
|||
|
reply.RemoteEndpoint = new IPEndPoint(IPAddress.Broadcast, 68);
|
|||
|
else
|
|||
|
reply.RemoteEndpoint = new IPEndPoint(request.YIAddr, 68);
|
|||
|
|
|||
|
return reply;
|
|||
|
}
|
|||
|
|
|||
|
private void CollectDefaultOptions(DHCPServerInterface serverInterface,BootPPacket packet)
|
|||
|
{
|
|||
|
foreach (Option defaultOption in defaultOptions)
|
|||
|
packet.SetOption(defaultOption);
|
|||
|
|
|||
|
foreach (Option defaultOption in serverInterface.DefaultOptions)
|
|||
|
packet.SetOption(defaultOption);
|
|||
|
}
|
|||
|
|
|||
|
private void DHCPDiscover(DHCPServerInterface serverInterface, BootPPacket packet)
|
|||
|
{
|
|||
|
lock (leasesAPI)
|
|||
|
{
|
|||
|
DHCPLease lease = leasesAPI.FindLease(serverInterface.InterfaceAddress, packet.CHAddr);
|
|||
|
if (lease == null)
|
|||
|
{
|
|||
|
IPv4 offeredIP = AcquireIP(serverInterface.Pool);
|
|||
|
if (offeredIP != null)
|
|||
|
{
|
|||
|
lease = new DHCPLease(serverInterface.InterfaceAddress, packet.CHAddr);
|
|||
|
lease.ClientIP = offeredIP;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (lease != null)
|
|||
|
{
|
|||
|
DateTimeOffset reservationTimeout = DateTimeOffset.Now + TimeSpan.FromSeconds(60);
|
|||
|
if (lease.ValidThrough < reservationTimeout)
|
|||
|
{
|
|||
|
lease.ValidThrough = reservationTimeout;
|
|||
|
Commit(lease);
|
|||
|
logLease.Log(LogLevel.INFO, "[OFFERED] {0} {1} {2}", lease.ClientMAC, lease.ClientIP, lease.ClientName);
|
|||
|
}
|
|||
|
SendOffer(serverInterface, lease, packet);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void SendOffer(DHCPServerInterface serverInterface, DHCPLease lease, BootPPacket request)
|
|||
|
{
|
|||
|
BootPPacket reply = ConstructReply(serverInterface, request, DHCPMessageType.OFFER);
|
|||
|
CollectDefaultOptions(serverInterface, reply);
|
|||
|
|
|||
|
reply.YIAddr = lease.ClientIP;
|
|||
|
reply.AddOption(new DHCPLeaseTimeOption(lease.ValidThrough));
|
|||
|
|
|||
|
byte[] replyBytes = reply.ToBytes();
|
|||
|
UdpClient udpClient = udpClients[serverInterface.InterfaceAddress];
|
|||
|
udpClient.Send(replyBytes, replyBytes.Length, reply.RemoteEndpoint);
|
|||
|
}
|
|||
|
private void SendACK(DHCPServerInterface serverInterface, DHCPLease lease, BootPPacket request)
|
|||
|
{
|
|||
|
BootPPacket reply = ConstructReply(serverInterface, request, DHCPMessageType.ACK);
|
|||
|
CollectDefaultOptions(serverInterface, reply);
|
|||
|
|
|||
|
reply.YIAddr = lease.ClientIP;
|
|||
|
reply.AddOption(new DHCPLeaseTimeOption(lease.ValidThrough));
|
|||
|
|
|||
|
byte[] replyBytes = reply.ToBytes();
|
|||
|
UdpClient udpClient = udpClients[serverInterface.InterfaceAddress];
|
|||
|
udpClient.Send(replyBytes, replyBytes.Length, reply.RemoteEndpoint);
|
|||
|
}
|
|||
|
private void SendNAK(DHCPServerInterface serverInterface, BootPPacket request)
|
|||
|
{
|
|||
|
BootPPacket reply = ConstructReply(serverInterface, request, DHCPMessageType.NAK);
|
|||
|
|
|||
|
byte[] replyBytes = reply.ToBytes();
|
|||
|
UdpClient udpClient = udpClients[serverInterface.InterfaceAddress];
|
|||
|
udpClient.Send(replyBytes, replyBytes.Length, reply.RemoteEndpoint);
|
|||
|
}
|
|||
|
|
|||
|
private void DHCPRequest(DHCPServerInterface serverInterface, BootPPacket packet)
|
|||
|
{
|
|||
|
lock (leasesAPI)
|
|||
|
{
|
|||
|
DHCPLease lease = leasesAPI.FindLease(serverInterface.InterfaceAddress, packet.CHAddr);
|
|||
|
DHCPRequestedIPOption requestedIP = packet.GetFirstOption<DHCPRequestedIPOption>();
|
|||
|
if ((requestedIP != null) && (lease != null) && (lease.ClientIP.Equals(requestedIP.IP)))
|
|||
|
{
|
|||
|
DateTimeOffset validThrough = DateTimeOffset.Now + serverInterface.Pool.DefaultLeaseTime;
|
|||
|
if (lease.ValidThrough < validThrough)
|
|||
|
{
|
|||
|
lease.ValidThrough = validThrough;
|
|||
|
Commit(lease);
|
|||
|
}
|
|||
|
logLease.Log(LogLevel.INFO, "[ACK ] {0} {1} {2}", lease.ClientMAC, lease.ClientIP, lease.ClientName);
|
|||
|
SendACK(serverInterface, lease, packet);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SendNAK(serverInterface, packet);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
private void DHCPDecline(DHCPServerInterface serverInterface, BootPPacket packet)
|
|||
|
{
|
|||
|
}
|
|||
|
private void DHCPRelease(DHCPServerInterface serverInterface, BootPPacket packet)
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
private IPv4 AcquireIP(IPPool ippool)
|
|||
|
{
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
IPv4 start = ippool.NextIP;
|
|||
|
IPv4 test = start;
|
|||
|
while (leasesAPI.FindLease(test) != null)
|
|||
|
{
|
|||
|
test = ippool.NextIP;
|
|||
|
if (test.Equals(start))
|
|||
|
return null;
|
|||
|
}
|
|||
|
return test;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public bool Commit(IPPool ippool) => ippools.Upsert(ippool);
|
|||
|
public bool Commit(DHCPServerInterface serverInterface) => interfaces.Upsert(serverInterface);
|
|||
|
public bool Commit(DHCPLease lease) => leases.Upsert(lease);
|
|||
|
|
|||
|
public class LeasesAPI
|
|||
|
{
|
|||
|
DHCPServer dhcp;
|
|||
|
|
|||
|
public LeasesAPI(DHCPServer dhcpServer)
|
|||
|
{
|
|||
|
dhcp = dhcpServer;
|
|||
|
}
|
|||
|
|
|||
|
public void CleanupLeases() => CleanupLeases(TimeSpan.Zero);
|
|||
|
public void CleanupLeases(TimeSpan acceptedOverdue)
|
|||
|
{
|
|||
|
DateTimeOffset now = DateTimeOffset.Now;
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
foreach (DHCPLease lease in dhcp.Leases)
|
|||
|
{
|
|||
|
if ((lease.ValidThrough + acceptedOverdue) < now)
|
|||
|
dhcp.leases.Delete(lease);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public DHCPLease FindLease(IPv4 serverInterface,MAC mac)
|
|||
|
{
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
Query queryLease = Query.AND(
|
|||
|
Query.Equals<DHCPLease>("ClientMAC", mac),
|
|||
|
Query.Equals<DHCPLease>("ServerInterface", serverInterface)
|
|||
|
);
|
|||
|
DHCPLease lease = dhcp.leases.Query(queryLease).FirstOrDefault();
|
|||
|
return lease;
|
|||
|
}
|
|||
|
}
|
|||
|
public DHCPLease FindLease(IPv4 ip)
|
|||
|
{
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
DHCPLease lease = dhcp.leases.Query(Query.Equals<DHCPLease>("ClientIP", ip)).FirstOrDefault();
|
|||
|
return lease;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void ReleaseLease(DHCPLease lease)
|
|||
|
{
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
lease.Released = DateTimeOffset.Now;
|
|||
|
dhcp.logLease.Log(LogLevel.INFO, "[RELEASE] {0} {1} {2}", lease.ClientMAC, lease.ClientIP, lease.ClientName);
|
|||
|
}
|
|||
|
}
|
|||
|
public void RemoveLease(DHCPLease lease)
|
|||
|
{
|
|||
|
lock (this)
|
|||
|
{
|
|||
|
dhcp.leases.Delete(lease);
|
|||
|
dhcp.logLease.Log(LogLevel.INFO, "[REMOVE] {0} {1} {2}", lease.ClientMAC, lease.ClientIP, lease.ClientName);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
}
|