ln.dhcp/DHCPServer.cs

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