414 lines
15 KiB
C#
414 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; }
|
|
|
|
public Pool Pool { get; private set; } = new Pool(8);
|
|
|
|
public ODBCollection<DHCPServerInterface> serverInterfacesCollection;
|
|
public DHCPServerInterface[] Interfaces => serverInterfacesCollection.ToArray();
|
|
|
|
public ODBCollection<IPPool> ippoolsCollection;
|
|
public IPPool[] IPPools => ippoolsCollection.ToArray();
|
|
|
|
public ODBCollection<Option> defaultOptionsCollection;
|
|
public Option[] DefaultOptions => defaultOptionsCollection.ToArray();
|
|
|
|
public ODBCollection<DHCPLease> leasesCollection;
|
|
public DHCPLease[] Leases => leasesCollection.ToArray();
|
|
|
|
Dictionary<DHCPServerInterface, DHCPListener> listeners = new Dictionary<DHCPServerInterface, DHCPListener>();
|
|
|
|
ODB odb;
|
|
|
|
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);
|
|
defaultOptionsCollection = odb.GetCollection<Option>();
|
|
ippoolsCollection = odb.GetCollection<IPPool>();
|
|
serverInterfacesCollection = odb.GetCollection<DHCPServerInterface>();
|
|
leasesCollection = odb.GetCollection<DHCPLease>();
|
|
|
|
leasesAPI = new LeasesAPI(this);
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
lock (this)
|
|
{
|
|
if (!IsStarted && serverInterfacesCollection.Count > 0)
|
|
{
|
|
foreach (DHCPServerInterface serverInterface in serverInterfacesCollection)
|
|
StartInterface(serverInterface);
|
|
|
|
IsStarted = true;
|
|
}
|
|
}
|
|
}
|
|
public void Stop()
|
|
{
|
|
lock (this)
|
|
{
|
|
foreach (DHCPServerInterface serverInterface in serverInterfacesCollection)
|
|
StartInterface(serverInterface);
|
|
|
|
IsStarted = false;
|
|
}
|
|
}
|
|
|
|
public void EnsureInterface(IPv4 interfaceAddr)
|
|
{
|
|
foreach (DHCPServerInterface serverInterface in serverInterfacesCollection)
|
|
if (serverInterface.InterfaceAddress.Equals(interfaceAddr))
|
|
return;
|
|
|
|
AddInterface(interfaceAddr);
|
|
}
|
|
public void EnsurePool(String name, IPv4 first, IPv4 last)
|
|
{
|
|
foreach (IPPool pool in IPPools)
|
|
if (pool.Name.Equals(name))
|
|
return;
|
|
AddPool(new IPPool(name, first, last));
|
|
}
|
|
|
|
private void CleanupTask()
|
|
{
|
|
|
|
}
|
|
|
|
private void StartInterface(DHCPServerInterface serverInterface)
|
|
{
|
|
lock (this)
|
|
{
|
|
Logging.Log(LogLevel.INFO, "Starting ServerInterface {0}",serverInterface.InterfaceAddress);
|
|
|
|
if (!listeners.ContainsKey(serverInterface))
|
|
listeners.Add(serverInterface, new DHCPListener(this, serverInterface));
|
|
|
|
listeners[serverInterface].Start();
|
|
}
|
|
}
|
|
private void StopInterface(DHCPServerInterface serverInterface)
|
|
{
|
|
lock (this)
|
|
{
|
|
if (listeners.ContainsKey(serverInterface))
|
|
listeners[serverInterface].Close();
|
|
}
|
|
}
|
|
|
|
public 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void AddInterface(IPv4 ip)
|
|
{
|
|
DHCPServerInterface serverInterface = new DHCPServerInterface(ip);
|
|
lock (this)
|
|
{
|
|
if (!serverInterfacesCollection.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 (!ippoolsCollection.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 if (!request.YIAddr.Equals(IPv4.ANY))
|
|
reply.RemoteEndpoint = new IPEndPoint(request.YIAddr, 68);
|
|
else
|
|
reply.RemoteEndpoint = new IPEndPoint(IPAddress.Broadcast, 68);
|
|
return reply;
|
|
}
|
|
|
|
private void CollectDefaultOptions(DHCPServerInterface serverInterface,BootPPacket packet)
|
|
{
|
|
foreach (Option defaultOption in defaultOptionsCollection)
|
|
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;
|
|
lease.LeaseState = DHCPLeaseState.OFFERED;
|
|
Commit(lease);
|
|
logLease.Log(LogLevel.INFO, "[OFFERED] {0} {1} {2}", lease.ClientMAC, lease.ClientIP, lease.ClientName);
|
|
}
|
|
|
|
SendOffer(serverInterface, lease, packet);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Send(DHCPServerInterface serverInterface,byte[] packet,IPEndPoint remoteEndpoint)
|
|
{
|
|
if (listeners.ContainsKey(serverInterface))
|
|
{
|
|
listeners[serverInterface].Send(packet, remoteEndpoint);
|
|
}
|
|
}
|
|
|
|
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();
|
|
Send(serverInterface, replyBytes, 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();
|
|
Send(serverInterface, replyBytes, reply.RemoteEndpoint);
|
|
}
|
|
private void SendNAK(DHCPServerInterface serverInterface, BootPPacket request)
|
|
{
|
|
BootPPacket reply = ConstructReply(serverInterface, request, DHCPMessageType.NAK);
|
|
|
|
byte[] replyBytes = reply.ToBytes();
|
|
Send(serverInterface, replyBytes, 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;
|
|
}
|
|
lease.LeaseState = DHCPLeaseState.BOUND;
|
|
lease.LastUpdate = DateTimeOffset.Now;
|
|
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)
|
|
{
|
|
DHCPLease lease = leasesAPI.FindLease(serverInterface.InterfaceAddress, packet.CHAddr);
|
|
if (lease != null)
|
|
{
|
|
lease.Released = DateTimeOffset.Now;
|
|
lease.LeaseState = DHCPLeaseState.DECLINED;
|
|
Commit(lease);
|
|
}
|
|
}
|
|
private void DHCPRelease(DHCPServerInterface serverInterface, BootPPacket packet)
|
|
{
|
|
DHCPLease lease = leasesAPI.FindLease(serverInterface.InterfaceAddress, packet.CHAddr);
|
|
if (lease != null)
|
|
{
|
|
lease.Released = DateTimeOffset.Now;
|
|
lease.LeaseState = DHCPLeaseState.RELEASED;
|
|
Commit(lease);
|
|
}
|
|
}
|
|
|
|
private IPv4 AcquireIP(IPPool ippool)
|
|
{
|
|
lock (this)
|
|
{
|
|
IPv4 start = ippool.NextIP;
|
|
IPv4 test = start;
|
|
while (leasesAPI.FindLease(test) != null)
|
|
{
|
|
test = ippool.GetNextIP();
|
|
if (test.Equals(start))
|
|
return null;
|
|
}
|
|
Commit(ippool);
|
|
return test;
|
|
}
|
|
}
|
|
|
|
public bool Commit(IPPool ippool) => ippoolsCollection.Upsert(ippool);
|
|
public bool Commit(DHCPServerInterface serverInterface) => serverInterfacesCollection.Upsert(serverInterface);
|
|
public bool Commit(DHCPLease lease) => leasesCollection.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.leasesCollection.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.leasesCollection.Query(queryLease).FirstOrDefault();
|
|
return lease;
|
|
}
|
|
}
|
|
public DHCPLease FindLease(IPv4 ip)
|
|
{
|
|
lock (this)
|
|
{
|
|
DHCPLease lease = dhcp.leasesCollection.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.leasesCollection.Delete(lease);
|
|
dhcp.logLease.Log(LogLevel.INFO, "[REMOVE] {0} {1} {2}", lease.ClientMAC, lease.ClientIP, lease.ClientName);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|