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 { public static class DhcpProgram { private const int PoolEntryKeepHours = 10; 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 string _u64FileName; private static string _u86FileName; private static string _uHttpFileName; private static IReadOnlyCollection _pool; private static IPAddress _ipxeRouter; private static IPAddress _pxeRouter; private static void Main(string[] args) { Console.WriteLine("Starting..."); var tWorker = new Thread(Working2); _isRunning = true; tWorker.Start(); Console.WriteLine("Press ENTER to Stop."); Console.ReadLine(); Console.Write("Shutting down..."); _isRunning = false; tWorker.Join(); Console.Write("Stopped."); Console.WriteLine(); Console.Write("Press ENTER to Exit."); Console.ReadLine(); } //----------------------------- private static void Working2() { var upTime = DateTime.Now; LoadConfig(); InitConfig(); //Listing 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 // polled { 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; }// end if poll }// end while socket.Close(); } private static DhcpPacket ProcessRequest(byte[] buffer) { var packet = new DhcpPacket(buffer); var clientMac = packet.ClientMacAddressHex; Console.WriteLine($" {packet.MessageType} by {clientMac}"); Console.WriteLine($" {packet.Vendor ?? "Unknown Vendor"} / {packet.UserClass ?? "Unknown Class"}"); 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 { // extract params var requestParameters = packet.Options[DhcpOption.ParameterRequestList]; var hostName = packet.HostName; var userClass = packet.UserClass; var vendor = packet.Vendor; //byte arch = 0; //if (packet.Options.TryGetValue(DhcpOption.ClientIdentifier, out var bufArch)) //{ // arch = bufArch?.FirstOrDefault() ?? 0; //} // Allocate ip address 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).Hours < PoolEntryKeepHours) continue; if (CheckMac(slot)) { allocateSlot = slot; break; } } } // ready for reply 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; //fill dynamic params packet.YourIpAddress = allocateSlot.Address; //fill static params 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; // vendor spec packet.Router = _pxeRouter; packet.TftpServer = _pxeTftp.ToString(); packet.NextServerIpAddress = _pxeTftp; if (userClass == "iPXE") { packet.BootFileName = _ipxeScriptUri; packet.Router = _ipxeRouter; } else { if (true == vendor?.StartsWith("PXEClient:Arch:00000")) { //Legacy packet.BootFileName = _pxeFileName; } else if (true == vendor?.StartsWith("PXEClient:Arch:00002") || true == vendor?.StartsWith("PXEClient:Arch:00006")) { //UEFI x86 packet.BootFileName = _u86FileName; } else if (true == vendor?.StartsWith("PXEClient:Arch:00007") || true == vendor?.StartsWith("PXEClient:Arch:00008") || true == vendor?.StartsWith("PXEClient:Arch:00009") ) { //UEFI x64 packet.BootFileName = _u64FileName; } } } } 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() { _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); _pxeRouter = IPAddress.Parse(Settings.Default.PxeRouter); _pxeTftp = IPAddress.Parse(Settings.Default.PxeTftp); _pxeFileName = Settings.Default.FileName; _u64FileName = Settings.Default.FileName_UEFI_x64; _u86FileName = Settings.Default.FileName_UEFI_I386; _ipxeScriptUri = Settings.Default.FileName_iPXE_HTTP; _ipxeRouter = IPAddress.Parse(Settings.Default.IpxeRouter); _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; } } }