DhcpServer.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. using NetBootServerCore.Components.Logging;
  2. using NetBootServerCore.Components.NetBoot.Dhcp.Pools;
  3. using NetBootServerCore.Components.NetBoot.Dhcp.Protocol;
  4. using SharpPcap;
  5. using SharpPcap.LibPcap;
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using static NetBootServerCore.Components.NetBoot.Dhcp.Protocol.Enums;
  9. using ProtocolType = System.Net.Sockets.ProtocolType;
  10. namespace NetBootServerCore.Components.NetBoot.Dhcp
  11. {
  12. public class DhcpServer : NbsComponentBase
  13. {
  14. private const int Port = 67;
  15. private readonly IPEndPoint _broadcastTargetAddress = new IPEndPoint(IPAddress.Broadcast, Port + 1);
  16. private readonly IConfigurationSection _config;
  17. private IPEndPoint _listenOn;
  18. private IPAddress _poolStart;
  19. private int _poolSize;
  20. private TimeSpan _leaseTime;
  21. private IPAddress _subNet;
  22. private IPAddress[] _gateway;
  23. private IPAddress[] _dns;
  24. private IPAddress _broadcastAddress;
  25. private IPAddress _tftp;
  26. private string _fileName;
  27. private string _fileNameForIpxe;
  28. private LibPcapLiveDevice _nic;
  29. private ARP _arp;
  30. public AddressPool Pool { get; private set; }
  31. private bool _isRunning;
  32. private Task _task;
  33. private Socket _server;
  34. private bool _enable;
  35. public DhcpServer(IConfigurationSection config) : base(new Logger("DHCP")) => _config = config;
  36. private void ProcessRequest(byte[] buffer)
  37. {
  38. var pdu = new DhcpPdu(buffer);
  39. var clientMac = pdu.ClientMacAddressHex;
  40. Logger.LogDebug($"Request from {clientMac}");
  41. //extract options before clear
  42. var vendor = pdu.Vendor;
  43. var userClass = pdu.UserClass;
  44. var hostName = pdu.HostName;
  45. var requestMessageType = pdu.MessageType;
  46. Logger.LogDebug($"Message type: {requestMessageType}");
  47. Logger.LogDebug($"Vendor: {vendor ?? "(null)"}");
  48. Logger.LogDebug($"UserClass: {userClass ?? "(null)"}");
  49. Logger.LogDebug($"HostName: {hostName ?? "(null)"}");
  50. AddressPoolSlot aSlot = null;
  51. {
  52. var timeFilter = DateTime.Now.Add(-_leaseTime);
  53. var dSlot = Pool.Slots.FirstOrDefault(p => p.Mac == clientMac && (p.LastUpdateTime.HasValue == false || p.LastUpdateTime.Value > timeFilter));
  54. if (dSlot != null)
  55. {
  56. var eMac = _arp.Resolve(dSlot.Address)?.GetAddressBytes().ToHypHexString();
  57. if (dSlot.Mac == eMac || eMac == null) aSlot = dSlot;
  58. }
  59. if (aSlot == null)
  60. {
  61. foreach (var slot in Pool.Slots)
  62. {
  63. if (slot.LastUpdateTime.HasValue && slot.LastUpdateTime > timeFilter && slot.Mac != clientMac) continue;
  64. var physicalAddress = _arp.Resolve(slot.Address);
  65. if (physicalAddress == null)
  66. {
  67. aSlot = slot;
  68. break;
  69. }
  70. var macHex = physicalAddress.GetAddressBytes().ToHypHexString();
  71. if (slot.Mac != macHex)
  72. {
  73. slot.IsDirty = true;
  74. slot.LastUpdateTime = DateTime.Now;
  75. }
  76. else if (slot.Mac == null)
  77. {
  78. slot.Mac = macHex;
  79. slot.LastUpdateTime = DateTime.Now;
  80. }
  81. }
  82. }
  83. }
  84. // ready for reply
  85. pdu.OpCode = DhcpOpCode.BootReply;
  86. pdu.Options.Clear();
  87. if (aSlot == null)
  88. {
  89. pdu.MessageType = DhcpMessageType.Nak;
  90. Logger.LogWarn($"Allocate fail {clientMac}");
  91. }
  92. else
  93. {
  94. if (requestMessageType == DhcpMessageType.Discover)
  95. pdu.MessageType = DhcpMessageType.Offer;
  96. else if (requestMessageType == DhcpMessageType.Request)
  97. pdu.MessageType = DhcpMessageType.Ack;
  98. else
  99. {
  100. Logger.LogWarn($"Unexpected message type: {requestMessageType}({(byte)requestMessageType})");
  101. return;
  102. }
  103. aSlot.Mac = clientMac;
  104. aSlot.HostName = hostName;
  105. aSlot.Vendor = vendor;
  106. aSlot.LastUpdateTime = DateTime.Now;
  107. aSlot.IsDirty = false;
  108. pdu.YourIpAddress = aSlot.Address;
  109. pdu.Routers = _gateway;
  110. pdu.SubNetMask = _subNet;
  111. pdu.DnsServers = _dns;
  112. pdu.BroadcastAddress = _broadcastAddress;
  113. pdu.LeaseTime = _leaseTime;
  114. pdu.DhcpServerIdentifier = _listenOn.Address;
  115. pdu.TftpServer = _tftp.ToString();
  116. pdu.NextServerIpAddress = _tftp;
  117. if (userClass == "iPXE") pdu.BootFileName = _fileNameForIpxe;
  118. else
  119. {
  120. if (vendor == null || !vendor.StartsWith("PXEClient")) pdu.BootFileName = _fileName;
  121. else
  122. {
  123. var parts = vendor.Split(':');
  124. if (parts.Length < 3) pdu.BootFileName = _fileName;
  125. else
  126. {
  127. var prefix = string.Join(":", parts[..3]);
  128. switch (prefix)
  129. {
  130. //case "PXEClient:Arch:00002":
  131. //case "PXEClient:Arch:00006":
  132. // UEFI x86
  133. // break;
  134. //case "PXEClient:Arch:00007":
  135. //case "PXEClient:Arch:00008":
  136. //case "PXEClient:Arch:00009":
  137. // UEFI x64
  138. // break;
  139. //case "PXEClient:Arch:00000": //Legacy
  140. default:
  141. pdu.BootFileName = _fileName;
  142. break;
  143. }
  144. }
  145. }
  146. }
  147. }
  148. var len = pdu.WriteToBuffer(buffer);
  149. _server.SendTo(buffer, 0, len, SocketFlags.None, _broadcastTargetAddress);
  150. Logger.LogVerbose($"{clientMac} -> {pdu.YourIpAddress} {pdu.MessageType}");
  151. }
  152. protected override void OnInit()
  153. {
  154. _enable = bool.Parse(_config["Enable"]);
  155. if (!_enable)
  156. {
  157. Logger.LogInfo("Disabled by config");
  158. return;
  159. }
  160. _listenOn = new IPEndPoint(IPAddress.Parse(_config["ListenOn"]), Port);
  161. _poolStart = IPAddress.Parse(_config["PoolStart"]);
  162. _poolSize = int.Parse(_config["PoolSize"]);
  163. _leaseTime = TimeSpan.FromHours(int.Parse(_config["LeaseTimeInHours"]));
  164. _subNet = IPAddress.Parse(_config["SubNet"]);
  165. _gateway = _config.GetSection("Gateway").Get<string[]>().Select(IPAddress.Parse).ToArray();
  166. _dns = _config.GetSection("DNS").Get<string[]>().Select(IPAddress.Parse).ToArray();
  167. _broadcastAddress = IPAddress.Parse(_config["BroadcastAddress"]);
  168. _tftp = IPAddress.Parse(_config["Tftp"]);
  169. _fileName = _config["FileName"];
  170. _fileNameForIpxe = _config["FileName_iPXE"];
  171. Pool = new AddressPool(_poolStart, _poolSize);
  172. _nic?.Dispose();
  173. _nic = LibPcapLiveDeviceList.Instance.FirstOrDefault(p => p.Addresses.Any(q => Equals(q.Addr.ipAddress, _listenOn.Address)));
  174. _arp = new ARP(_nic);
  175. }
  176. protected override void OnStart()
  177. {
  178. if (!_enable)
  179. {
  180. Logger.LogInfo("Disabled by config");
  181. return;
  182. }
  183. _isRunning = true;
  184. _task = Task.Run(() =>
  185. {
  186. _server = new Socket(_listenOn.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
  187. {
  188. EnableBroadcast = true,
  189. SendBufferSize = 1500,
  190. ReceiveBufferSize = 1500
  191. };
  192. _server.Bind(_listenOn);
  193. var buffer = new byte[1500];
  194. EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 68);
  195. while (_isRunning)
  196. {
  197. if (!_server.Poll(500 * 1000, SelectMode.SelectRead)) continue;
  198. _server.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref remoteEndPoint);
  199. try
  200. {
  201. ProcessRequest(buffer);
  202. }
  203. catch (Exception e)
  204. {
  205. Logger.LogError(nameof(ProcessRequest), e);
  206. }
  207. }
  208. });
  209. }
  210. protected override void OnStop()
  211. {
  212. if (!_enable)
  213. {
  214. Logger.LogInfo("Disabled by config");
  215. return;
  216. }
  217. _isRunning = false;
  218. _task.Wait(10 * 1000);
  219. }
  220. }
  221. }