DhcpProgram.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. using DhcpServer.Models;
  2. using DhcpServer.Properties;
  3. using SharpPcap;
  4. using SharpPcap.LibPcap;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Configuration;
  8. using System.Linq;
  9. using System.Net;
  10. using System.Net.Sockets;
  11. using System.Threading;
  12. namespace DhcpServer
  13. {
  14. public static class DhcpProgram
  15. {
  16. private const int PoolEntryKeepHours = 10;
  17. private static bool _isRunning;
  18. private static LibPcapLiveDevice _nic;
  19. private static IPEndPoint _listenOn;
  20. private static IPAddress _poolStart;
  21. private static int _poolSize;
  22. private static IPAddress _subNet;
  23. private static IPAddress _router;
  24. private static IPAddress _broadcastAddress;
  25. private static IPAddress _dns;
  26. private static IPAddress _pxeTftp;
  27. private static string _pxeFileName;
  28. private static string _ipxeScriptUri;
  29. private static string _u64FileName;
  30. private static string _u86FileName;
  31. private static string _uHttpFileName;
  32. private static IReadOnlyCollection<PoolSlot> _pool;
  33. private static IPAddress _ipxeRouter;
  34. private static IPAddress _pxeRouter;
  35. private static void Main(string[] args)
  36. {
  37. Console.WriteLine("Starting...");
  38. var tWorker = new Thread(Working2);
  39. _isRunning = true;
  40. tWorker.Start();
  41. Console.WriteLine("Press ENTER to Stop.");
  42. Console.ReadLine();
  43. Console.Write("Shutting down...");
  44. _isRunning = false;
  45. tWorker.Join();
  46. Console.Write("Stopped.");
  47. Console.WriteLine();
  48. Console.Write("Press ENTER to Exit.");
  49. Console.ReadLine();
  50. }
  51. //-----------------------------
  52. private static void Working2()
  53. {
  54. var upTime = DateTime.Now;
  55. LoadConfig();
  56. InitConfig();
  57. //Listing
  58. var socket = new Socket(_listenOn.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
  59. {
  60. EnableBroadcast = true,
  61. SendBufferSize = 65536,
  62. ReceiveBufferSize = 65536
  63. };
  64. socket.Bind(_listenOn);
  65. var buffer = new byte[65536];
  66. EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 68);
  67. var polling = DateTime.Now;
  68. var pooled = false;
  69. while (_isRunning)
  70. {
  71. Console.CursorLeft = 0;
  72. if (false == socket.Poll(500 * 1000, SelectMode.SelectRead))
  73. {
  74. var timeSpan = DateTime.Now - polling;
  75. var up = DateTime.Now - upTime;
  76. Console.Write("Polling sockets..." +
  77. $" {timeSpan.Days:00}D {timeSpan.Hours:00}H {timeSpan.Minutes:00}M {timeSpan.Seconds:00}S {timeSpan.Milliseconds:000}" +
  78. $" / UP {up.Days:00}D {up.Hours:00}H {up.Minutes:00}M {up.Seconds:00}S {up.Milliseconds:000}");
  79. pooled = true;
  80. }
  81. else // polled
  82. {
  83. if (pooled)
  84. {
  85. pooled = false;
  86. Console.WriteLine();
  87. }
  88. var bytes = socket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref remoteEndPoint);
  89. Console.Write($"Receive {bytes} byte From {remoteEndPoint}");
  90. try
  91. {
  92. var reply = ProcessRequest(buffer);
  93. if (reply.MessageType != DhcpMessageType.Unknown)
  94. {
  95. bytes = reply.WriteToBuffer(buffer);
  96. var to = new IPEndPoint(IPAddress.Broadcast, 68);
  97. Console.WriteLine($"Send {bytes} bytes to {to} {reply.MessageType} by {reply.ClientMacAddressHex}");
  98. socket.SendTo(buffer, 0, bytes, SocketFlags.None, to);
  99. }
  100. }
  101. catch (Exception e)
  102. {
  103. Console.WriteLine(e);
  104. }
  105. polling = DateTime.Now;
  106. }// end if poll
  107. }// end while
  108. socket.Close();
  109. }
  110. private static DhcpPacket ProcessRequest(byte[] buffer)
  111. {
  112. var packet = new DhcpPacket(buffer);
  113. var clientMac = packet.ClientMacAddressHex;
  114. Console.WriteLine($" {packet.MessageType} by {clientMac}");
  115. Console.WriteLine($" {packet.Vendor ?? "Unknown Vendor"} / {packet.UserClass ?? "Unknown Class"}");
  116. DhcpMessageType reply;
  117. switch (packet.MessageType)
  118. {
  119. default:
  120. reply = DhcpMessageType.Unknown;
  121. break;
  122. case DhcpMessageType.Discover:
  123. reply = DhcpMessageType.Offer;
  124. break;
  125. case DhcpMessageType.Request:
  126. reply = DhcpMessageType.Ack;
  127. break;
  128. }
  129. if (reply == DhcpMessageType.Unknown)
  130. {
  131. packet.MessageType = DhcpMessageType.Unknown;
  132. }
  133. else
  134. {
  135. // extract params
  136. var requestParameters = packet.Options[DhcpOption.ParameterRequestList];
  137. var hostName = packet.HostName;
  138. var userClass = packet.UserClass;
  139. var vendor = packet.Vendor;
  140. //byte arch = 0;
  141. //if (packet.Options.TryGetValue(DhcpOption.ClientIdentifier, out var bufArch))
  142. //{
  143. // arch = bufArch?.FirstOrDefault() ?? 0;
  144. //}
  145. // Allocate ip address
  146. PoolSlot allocateSlot = null;
  147. {
  148. var mSlot = _pool.FirstOrDefault(p => p.Mac == clientMac);
  149. if (mSlot != null)
  150. {
  151. if (CheckMac(mSlot, clientMac))
  152. {
  153. allocateSlot = mSlot;
  154. Console.WriteLine("Prefer last time address");
  155. }
  156. }
  157. }
  158. if (allocateSlot == null)
  159. {
  160. foreach (var slot in _pool)
  161. {
  162. if (slot.LastConfirm.HasValue && (DateTime.Now - slot.LastConfirm.Value).Hours < PoolEntryKeepHours) continue;
  163. if (CheckMac(slot))
  164. {
  165. allocateSlot = slot;
  166. break;
  167. }
  168. }
  169. }
  170. // ready for reply
  171. packet.Options.Clear();
  172. if (null == allocateSlot)
  173. {
  174. packet.MessageType = DhcpMessageType.Nak;
  175. Console.Write(" *** Allocate fail");
  176. }
  177. else
  178. {
  179. allocateSlot.Mac = clientMac;
  180. allocateSlot.HostName = hostName;
  181. allocateSlot.LastConfirm = DateTime.Now;
  182. packet.OpCode = DhcpOpCode.BootReply;
  183. packet.MessageType = reply;
  184. //fill dynamic params
  185. packet.YourIpAddress = allocateSlot.Address;
  186. //fill static params
  187. packet.Router = _router;
  188. packet.SubNetMask = _subNet;
  189. packet.BroadcastAddress = _broadcastAddress;
  190. packet.DnsServer = _dns;
  191. packet.LeaseTime = TimeSpan.FromDays(8);
  192. packet.RebindingTime = packet.LeaseTime;
  193. packet.RenewalTime = packet.LeaseTime;
  194. packet.DhcpServerIdentifier = _listenOn.Address;
  195. // vendor spec
  196. packet.Router = _pxeRouter;
  197. packet.TftpServer = _pxeTftp.ToString();
  198. packet.NextServerIpAddress = _pxeTftp;
  199. if (userClass == "iPXE")
  200. {
  201. packet.BootFileName = _ipxeScriptUri;
  202. packet.Router = _ipxeRouter;
  203. }
  204. else
  205. {
  206. if (true == vendor?.StartsWith("PXEClient:Arch:00000"))
  207. {
  208. //Legacy
  209. packet.BootFileName = _pxeFileName;
  210. }
  211. else if (true == vendor?.StartsWith("PXEClient:Arch:00002")
  212. || true == vendor?.StartsWith("PXEClient:Arch:00006"))
  213. {
  214. //UEFI x86
  215. packet.BootFileName = _u86FileName;
  216. }
  217. else if (true == vendor?.StartsWith("PXEClient:Arch:00007")
  218. || true == vendor?.StartsWith("PXEClient:Arch:00008")
  219. || true == vendor?.StartsWith("PXEClient:Arch:00009")
  220. )
  221. {
  222. //UEFI x64
  223. packet.BootFileName = _u64FileName;
  224. }
  225. }
  226. }
  227. }
  228. return packet;
  229. }
  230. private static bool CheckMac(PoolSlot slot, string clientMac = null)
  231. {
  232. Console.Write($"Checking mac for {slot.Address}...");
  233. var mac = GetMac(slot.Address, out var err);
  234. if (err != null)
  235. {
  236. Console.WriteLine($"Err:{err}");
  237. return false;
  238. }
  239. if (mac == null)
  240. {
  241. Console.WriteLine("Available");
  242. return true;
  243. }
  244. if (mac == clientMac)
  245. {
  246. Console.WriteLine("DupReq");
  247. return true;
  248. }
  249. slot.Mac = mac;
  250. slot.LastConfirm = DateTime.Now;
  251. Console.WriteLine($"In using by {mac}");
  252. return false;
  253. }
  254. private static void InitConfig()
  255. {
  256. _nic = LibPcapLiveDeviceList.Instance.FirstOrDefault(p => p.Addresses.Any(q => Equals(q.Addr.ipAddress, _listenOn.Address)));
  257. if (null == _nic) throw new ConfigurationErrorsException("Device not found");
  258. var slots = new PoolSlot[_poolSize];
  259. slots[0] = new PoolSlot(_poolStart);
  260. for (var i = 1; i < _poolSize; i++) slots[i] = new PoolSlot(slots[i - 1].Address.NextAddress());
  261. _pool = slots;
  262. }
  263. private static void LoadConfig()
  264. {
  265. _listenOn = new IPEndPoint(IPAddress.Parse(Settings.Default.ListenOn), 67);
  266. _poolStart = IPAddress.Parse(Settings.Default.PoolStart);
  267. _poolSize = Settings.Default.PoolSize;
  268. _subNet = IPAddress.Parse(Settings.Default.SubNet);
  269. _router = IPAddress.Parse(Settings.Default.Router);
  270. _dns = IPAddress.Parse(Settings.Default.Dns);
  271. _pxeRouter = IPAddress.Parse(Settings.Default.PxeRouter);
  272. _pxeTftp = IPAddress.Parse(Settings.Default.PxeTftp);
  273. _pxeFileName = Settings.Default.FileName;
  274. _u64FileName = Settings.Default.FileName_UEFI_x64;
  275. _u86FileName = Settings.Default.FileName_UEFI_I386;
  276. _ipxeScriptUri = Settings.Default.FileName_iPXE_HTTP;
  277. _ipxeRouter = IPAddress.Parse(Settings.Default.IpxeRouter);
  278. _broadcastAddress = IPAddress.Parse(Settings.Default.BroadcastAddress);
  279. }
  280. public static string GetHostName(IPAddress address, out string error)
  281. {
  282. try
  283. {
  284. var ipHostEntry = Dns.GetHostEntry(address);
  285. error = null;
  286. return ipHostEntry.HostName;
  287. }
  288. catch (Exception e)
  289. {
  290. error = e.GetMessageRecursively();
  291. }
  292. return null;
  293. }
  294. public static string GetMac(IPAddress targetAddress, out string error)
  295. {
  296. try
  297. {
  298. var arp = new ARP(_nic);
  299. var physicalAddress = arp.Resolve(targetAddress);
  300. if (null == physicalAddress)
  301. {
  302. error = null;
  303. return null;
  304. }
  305. var mac = string.Join("-", physicalAddress.GetAddressBytes().Select(p => p.ToString("X2")));
  306. error = null;
  307. return mac;
  308. }
  309. catch (Exception e)
  310. {
  311. error = e.GetMessageRecursively();
  312. }
  313. return null;
  314. }
  315. public static IPAddress NextAddress(this IPAddress address, uint increment = 1)
  316. {
  317. if (address.AddressFamily != AddressFamily.InterNetwork) throw new NotSupportedException();
  318. var bytes = address.GetAddressBytes();
  319. var value =
  320. (uint)bytes[0] << 24 |
  321. (uint)bytes[1] << 16 |
  322. (uint)bytes[2] << 8 |
  323. (uint)bytes[3];
  324. value += increment;
  325. bytes[0] = (byte)(value >> 24);
  326. bytes[1] = (byte)(value >> 16);
  327. bytes[2] = (byte)(value >> 8);
  328. bytes[3] = (byte)value;
  329. return new IPAddress(bytes);
  330. }
  331. public static string GetMessageRecursively(this Exception exception)
  332. {
  333. var msg = exception.Message;
  334. if (null != exception.InnerException) msg += " --> " + exception.InnerException.GetMessageRecursively();
  335. return msg;
  336. }
  337. }
  338. }