|
@@ -1,21 +1,40 @@
|
|
|
-using System;
|
|
|
+using DhcpServer.Models;
|
|
|
+using DhcpServer.Properties;
|
|
|
+using SharpPcap;
|
|
|
+using SharpPcap.LibPcap;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Configuration;
|
|
|
+using System.Linq;
|
|
|
using System.Net;
|
|
|
using System.Net.Sockets;
|
|
|
using System.Threading;
|
|
|
|
|
|
namespace DhcpServer
|
|
|
{
|
|
|
- internal class DhcpProgram
|
|
|
+ public static class DhcpProgram
|
|
|
{
|
|
|
private static bool _isRunning;
|
|
|
|
|
|
+ private static LibPcapLiveDevice _nic;
|
|
|
+ private static IPEndPoint _listenOn;
|
|
|
+ private static IPAddress _poolStart;
|
|
|
+ private static int _poolSize;
|
|
|
+ private static IPAddress _subNet;
|
|
|
+ private static IPAddress _router;
|
|
|
+ private static IPAddress _broadcastAddress;
|
|
|
+ private static IPAddress _dns;
|
|
|
+ private static IPAddress _pxeTftp;
|
|
|
+ private static string _pxeFileName;
|
|
|
+ private static string _ipxeScriptUri;
|
|
|
+
|
|
|
+ private static IReadOnlyCollection<PoolSlot> _pool;
|
|
|
+
|
|
|
private static void Main(string[] args)
|
|
|
{
|
|
|
Console.WriteLine("Starting...");
|
|
|
|
|
|
- if (DhcpEntryManager.CreateDefaultEntryIfNoExist()) Console.WriteLine("Default client entry config created.");
|
|
|
-
|
|
|
- var tWorker = new Thread(Working);
|
|
|
+ var tWorker = new Thread(Working2);
|
|
|
_isRunning = true;
|
|
|
tWorker.Start();
|
|
|
|
|
@@ -33,9 +52,347 @@ namespace DhcpServer
|
|
|
Console.ReadLine();
|
|
|
}
|
|
|
|
|
|
- private static void Working(object obj)
|
|
|
+
|
|
|
+
|
|
|
+ private static void Working2()
|
|
|
+ {
|
|
|
+ var upTime = DateTime.Now;
|
|
|
+
|
|
|
+ LoadConfig();
|
|
|
+
|
|
|
+ InitConfig();
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ var socket = new Socket(_listenOn.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
|
|
|
+ {
|
|
|
+ EnableBroadcast = true,
|
|
|
+ SendBufferSize = 65536,
|
|
|
+ ReceiveBufferSize = 65536
|
|
|
+ };
|
|
|
+ socket.Bind(_listenOn);
|
|
|
+ var buffer = new byte[65536];
|
|
|
+ EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 68);
|
|
|
+ var polling = DateTime.Now;
|
|
|
+
|
|
|
+ var pooled = false;
|
|
|
+ while (_isRunning)
|
|
|
+ {
|
|
|
+ Console.CursorLeft = 0;
|
|
|
+ if (false == socket.Poll(500 * 1000, SelectMode.SelectRead))
|
|
|
+ {
|
|
|
+ var timeSpan = DateTime.Now - polling;
|
|
|
+ var up = DateTime.Now - upTime;
|
|
|
+ Console.Write("Polling sockets..." +
|
|
|
+ $" {timeSpan.Days:00}D {timeSpan.Hours:00}H {timeSpan.Minutes:00}M {timeSpan.Seconds:00}S {timeSpan.Milliseconds:000}" +
|
|
|
+ $" / UP {up.Days:00}D {up.Hours:00}H {up.Minutes:00}M {up.Seconds:00}S {up.Milliseconds:000}");
|
|
|
+ pooled = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (pooled)
|
|
|
+ {
|
|
|
+ pooled = false;
|
|
|
+ Console.WriteLine();
|
|
|
+ }
|
|
|
+
|
|
|
+ var bytes = socket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref remoteEndPoint);
|
|
|
+ Console.Write($"Receive {bytes} byte From {remoteEndPoint}");
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var reply = ProcessRequest(buffer);
|
|
|
+ if (reply.MessageType != DhcpMessageType.Unknown)
|
|
|
+ {
|
|
|
+ bytes = reply.WriteToBuffer(buffer);
|
|
|
+ var to = new IPEndPoint(IPAddress.Broadcast, 68);
|
|
|
+ Console.WriteLine($"Send {bytes} bytes to {to} {reply.MessageType} by {reply.ClientMacAddressHex}");
|
|
|
+ socket.SendTo(buffer, 0, bytes, SocketFlags.None, to);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ Console.WriteLine(e);
|
|
|
+ }
|
|
|
+ polling = DateTime.Now;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ socket.Close();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static DhcpPacket ProcessRequest(byte[] buffer)
|
|
|
+ {
|
|
|
+ var packet = new DhcpPacket(buffer);
|
|
|
+
|
|
|
+ var clientMac = packet.ClientMacAddressHex;
|
|
|
+ Console.WriteLine($" {packet.MessageType} by {clientMac}");
|
|
|
+
|
|
|
+ DhcpMessageType reply;
|
|
|
+ switch (packet.MessageType)
|
|
|
+ {
|
|
|
+ default:
|
|
|
+ reply = DhcpMessageType.Unknown;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case DhcpMessageType.Discover:
|
|
|
+ reply = DhcpMessageType.Offer;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case DhcpMessageType.Request:
|
|
|
+ reply = DhcpMessageType.Ack;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (reply == DhcpMessageType.Unknown)
|
|
|
+ {
|
|
|
+ packet.MessageType = DhcpMessageType.Unknown;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+
|
|
|
+ var requestParameters = packet.Options[DhcpOption.ParameterRequestList];
|
|
|
+ var hostName = packet.HostName;
|
|
|
+ var userClass = packet.UserClass;
|
|
|
+ var ven = packet.Vendor;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ PoolSlot allocateSlot = null;
|
|
|
+ {
|
|
|
+ var mSlot = _pool.FirstOrDefault(p => p.Mac == clientMac);
|
|
|
+ if (mSlot != null)
|
|
|
+ {
|
|
|
+ if (CheckMac(mSlot, clientMac))
|
|
|
+ {
|
|
|
+ allocateSlot = mSlot;
|
|
|
+ Console.WriteLine("Prefer last time address");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (allocateSlot == null)
|
|
|
+ {
|
|
|
+ foreach (var slot in _pool)
|
|
|
+ {
|
|
|
+ if (slot.LastConfirm.HasValue && (DateTime.Now - slot.LastConfirm.Value).TotalSeconds < 600) continue;
|
|
|
+
|
|
|
+ if (CheckMac(slot))
|
|
|
+ {
|
|
|
+ allocateSlot = slot;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ packet.Options.Clear();
|
|
|
+
|
|
|
+ if (null == allocateSlot)
|
|
|
+ {
|
|
|
+ packet.MessageType = DhcpMessageType.Nak;
|
|
|
+ Console.Write(" *** Allocate fail");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ allocateSlot.Mac = clientMac;
|
|
|
+ allocateSlot.HostName = hostName;
|
|
|
+ allocateSlot.LastConfirm = DateTime.Now;
|
|
|
+
|
|
|
+ packet.OpCode = DhcpOpCode.BootReply;
|
|
|
+ packet.MessageType = reply;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ packet.YourIpAddress = allocateSlot.Address;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ packet.Router = _router;
|
|
|
+ packet.SubNetMask = _subNet;
|
|
|
+ packet.BroadcastAddress = _broadcastAddress;
|
|
|
+ packet.DnsServer = _dns;
|
|
|
+ packet.LeaseTime = TimeSpan.FromDays(8);
|
|
|
+ packet.RebindingTime = packet.LeaseTime;
|
|
|
+ packet.RenewalTime = packet.LeaseTime;
|
|
|
+
|
|
|
+ packet.DhcpServerIdentifier = _listenOn.Address;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if (userClass == "iPXE")
|
|
|
+ {
|
|
|
+ packet.BootFileName = _ipxeScriptUri;
|
|
|
+ }
|
|
|
+ else if (requestParameters != null && requestParameters.Contains((byte)67))
|
|
|
+ {
|
|
|
+ packet.BootFileName = _pxeFileName;
|
|
|
+ packet.TftpServer = _pxeTftp.ToString();
|
|
|
+ packet.NextServerIpAddress = _pxeTftp;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return packet;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool CheckMac(PoolSlot slot, string clientMac = null)
|
|
|
+ {
|
|
|
+ Console.Write($"Checking mac for {slot.Address}...");
|
|
|
+ var mac = GetMac(slot.Address, out var err);
|
|
|
+
|
|
|
+ if (err != null)
|
|
|
+ {
|
|
|
+ Console.WriteLine($"Err:{err}");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mac == null)
|
|
|
+ {
|
|
|
+ Console.WriteLine("Available");
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mac == clientMac)
|
|
|
+ {
|
|
|
+ Console.WriteLine("DupReq");
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ slot.Mac = mac;
|
|
|
+ slot.LastConfirm = DateTime.Now;
|
|
|
+ Console.WriteLine($"In using by {mac}");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void InitConfig()
|
|
|
+ {
|
|
|
+ _nic = LibPcapLiveDeviceList.Instance.FirstOrDefault(p => p.Addresses.Any(q => Equals(q.Addr.ipAddress, _listenOn.Address)));
|
|
|
+ if (null == _nic) throw new ConfigurationErrorsException("Device not found");
|
|
|
+
|
|
|
+ var slots = new PoolSlot[_poolSize];
|
|
|
+ slots[0] = new PoolSlot(_poolStart);
|
|
|
+ for (var i = 1; i < _poolSize; i++) slots[i] = new PoolSlot(slots[i - 1].Address.NextAddress());
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ _pool = slots;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void LoadConfig()
|
|
|
{
|
|
|
- var listenEndPoint = new IPEndPoint(IPAddress.Parse(Properties.Settings.Default.ListenOn), 67);
|
|
|
+ _listenOn = new IPEndPoint(IPAddress.Parse(Settings.Default.ListenOn), 67);
|
|
|
+ _poolStart = IPAddress.Parse(Settings.Default.PoolStart);
|
|
|
+ _poolSize = Settings.Default.PoolSize;
|
|
|
+ _subNet = IPAddress.Parse(Settings.Default.SubNet);
|
|
|
+ _router = IPAddress.Parse(Settings.Default.Router);
|
|
|
+ _dns = IPAddress.Parse(Settings.Default.Dns);
|
|
|
+ _pxeTftp = IPAddress.Parse(Settings.Default.PxeTftp);
|
|
|
+ _pxeFileName = Settings.Default.PxeFileName;
|
|
|
+ _ipxeScriptUri = Settings.Default.IpxeScriptUri;
|
|
|
+ _broadcastAddress = IPAddress.Parse(Settings.Default.BroadcastAddress);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string GetHostName(IPAddress address, out string error)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var ipHostEntry = Dns.GetHostEntry(address);
|
|
|
+ error = null;
|
|
|
+ return ipHostEntry.HostName;
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ error = e.GetMessageRecursively();
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string GetMac(IPAddress targetAddress, out string error)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var arp = new ARP(_nic);
|
|
|
+ var physicalAddress = arp.Resolve(targetAddress);
|
|
|
+ if (null == physicalAddress)
|
|
|
+ {
|
|
|
+ error = null;
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ var mac = string.Join("-", physicalAddress.GetAddressBytes().Select(p => p.ToString("X2")));
|
|
|
+ error = null;
|
|
|
+ return mac;
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ error = e.GetMessageRecursively();
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IPAddress NextAddress(this IPAddress address, uint increment = 1)
|
|
|
+ {
|
|
|
+ if (address.AddressFamily != AddressFamily.InterNetwork) throw new NotSupportedException();
|
|
|
+
|
|
|
+ var bytes = address.GetAddressBytes();
|
|
|
+
|
|
|
+ var value =
|
|
|
+ (uint)bytes[0] << 24 |
|
|
|
+ (uint)bytes[1] << 16 |
|
|
|
+ (uint)bytes[2] << 8 |
|
|
|
+ (uint)bytes[3];
|
|
|
+
|
|
|
+ value += increment;
|
|
|
+
|
|
|
+ bytes[0] = (byte)(value >> 24);
|
|
|
+ bytes[1] = (byte)(value >> 16);
|
|
|
+ bytes[2] = (byte)(value >> 8);
|
|
|
+ bytes[3] = (byte)value;
|
|
|
+
|
|
|
+ return new IPAddress(bytes);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string GetMessageRecursively(this Exception exception)
|
|
|
+ {
|
|
|
+ var msg = exception.Message;
|
|
|
+ if (null != exception.InnerException) msg += " --> " + exception.InnerException.GetMessageRecursively();
|
|
|
+
|
|
|
+ return msg;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ private static void Working()
|
|
|
+ {
|
|
|
+ if (DhcpEntryManager.CreateDefaultEntryIfNoExist()) Console.WriteLine("Default client entry config created.");
|
|
|
+
|
|
|
+ var listenEndPoint = new IPEndPoint(IPAddress.Parse(Settings.Default.ListenOn), 67);
|
|
|
|
|
|
var socket = new Socket(listenEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
|
|
|
{
|
|
@@ -140,4 +497,4 @@ namespace DhcpServer
|
|
|
socket.Close();
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
+}
|