DhcpServer.cs 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using DnsServerCore.Auth;
  16. using DnsServerCore.Dhcp.Options;
  17. using DnsServerCore.Dns;
  18. using DnsServerCore.Dns.ResourceRecords;
  19. using DnsServerCore.Dns.Zones;
  20. using System;
  21. using System.Collections.Concurrent;
  22. using System.Collections.Generic;
  23. using System.IO;
  24. using System.Net;
  25. using System.Net.Sockets;
  26. using System.Text;
  27. using System.Threading;
  28. using System.Threading.Tasks;
  29. using TechnitiumLibrary.Net.Dns;
  30. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  31. namespace DnsServerCore.Dhcp
  32. {
  33. //Dynamic Host Configuration Protocol
  34. //https://datatracker.ietf.org/doc/html/rfc2131
  35. //DHCP Options and BOOTP Vendor Extensions
  36. //https://datatracker.ietf.org/doc/html/rfc2132
  37. //Encoding Long Options in the Dynamic Host Configuration Protocol (DHCPv4)
  38. //https://datatracker.ietf.org/doc/html/rfc3396
  39. //Client Fully Qualified Domain Name(FQDN) Option
  40. //https://datatracker.ietf.org/doc/html/rfc4702
  41. public sealed class DhcpServer : IDisposable
  42. {
  43. #region enum
  44. enum ServiceState
  45. {
  46. Stopped = 0,
  47. Starting = 1,
  48. Running = 2,
  49. Stopping = 3
  50. }
  51. #endregion
  52. #region variables
  53. readonly string _scopesFolder;
  54. LogManager _log;
  55. readonly ConcurrentDictionary<IPAddress, UdpListener> _udpListeners = new ConcurrentDictionary<IPAddress, UdpListener>();
  56. readonly ConcurrentDictionary<string, Scope> _scopes = new ConcurrentDictionary<string, Scope>();
  57. DnsServer _dnsServer;
  58. AuthManager _authManager;
  59. volatile ServiceState _state = ServiceState.Stopped;
  60. readonly IPEndPoint _dhcpDefaultEP = new IPEndPoint(IPAddress.Any, 67);
  61. Timer _maintenanceTimer;
  62. const int MAINTENANCE_TIMER_INTERVAL = 10000;
  63. DateTime _lastModifiedScopesSavedOn;
  64. #endregion
  65. #region constructor
  66. public DhcpServer(string scopesFolder, LogManager log = null)
  67. {
  68. _scopesFolder = scopesFolder;
  69. _log = log;
  70. if (!Directory.Exists(_scopesFolder))
  71. {
  72. Directory.CreateDirectory(_scopesFolder);
  73. //create default scope
  74. Scope scope = new Scope("Default", false, IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.254"), IPAddress.Parse("255.255.255.0"), _log, this);
  75. scope.Exclusions = new Exclusion[] { new Exclusion(IPAddress.Parse("192.168.1.1"), IPAddress.Parse("192.168.1.10")) };
  76. scope.RouterAddress = IPAddress.Parse("192.168.1.1");
  77. scope.UseThisDnsServer = true;
  78. scope.DomainName = "home";
  79. scope.LeaseTimeDays = 1;
  80. scope.IgnoreClientIdentifierOption = true;
  81. SaveScopeFile(scope);
  82. }
  83. }
  84. #endregion
  85. #region IDisposable
  86. private bool _disposed = false;
  87. private void Dispose(bool disposing)
  88. {
  89. if (_disposed)
  90. return;
  91. if (disposing)
  92. {
  93. _maintenanceTimer?.Dispose();
  94. Stop();
  95. if (_scopes is not null)
  96. {
  97. foreach (KeyValuePair<string, Scope> scope in _scopes)
  98. scope.Value.Dispose();
  99. _scopes.Clear();
  100. }
  101. }
  102. _disposed = true;
  103. }
  104. public void Dispose()
  105. {
  106. Dispose(true);
  107. }
  108. #endregion
  109. #region private
  110. private async Task ReadUdpRequestAsync(Socket udpListener)
  111. {
  112. byte[] recvBuffer = new byte[1500];
  113. try
  114. {
  115. bool processOnlyUnicastMessages = !(udpListener.LocalEndPoint as IPEndPoint).Address.Equals(IPAddress.Any); //only 0.0.0.0 ip should process broadcast to avoid duplicate offers on Windows
  116. EndPoint epAny = new IPEndPoint(IPAddress.Any, 0);
  117. SocketReceiveMessageFromResult result;
  118. while (true)
  119. {
  120. try
  121. {
  122. result = await udpListener.ReceiveMessageFromAsync(recvBuffer, SocketFlags.None, epAny);
  123. }
  124. catch (SocketException ex)
  125. {
  126. switch (ex.SocketErrorCode)
  127. {
  128. case SocketError.ConnectionReset:
  129. case SocketError.HostUnreachable:
  130. case SocketError.NetworkReset:
  131. result = default;
  132. break;
  133. case SocketError.MessageSize:
  134. _log?.Write(ex);
  135. result = default;
  136. break;
  137. default:
  138. throw;
  139. }
  140. }
  141. if (result.ReceivedBytes > 0)
  142. {
  143. if (processOnlyUnicastMessages && result.PacketInformation.Address.Equals(IPAddress.Broadcast))
  144. continue;
  145. try
  146. {
  147. DhcpMessage request = new DhcpMessage(new MemoryStream(recvBuffer, 0, result.ReceivedBytes, false));
  148. _ = ProcessDhcpRequestAsync(request, result.RemoteEndPoint as IPEndPoint, result.PacketInformation, udpListener);
  149. }
  150. catch (Exception ex)
  151. {
  152. _log?.Write(result.RemoteEndPoint as IPEndPoint, ex);
  153. }
  154. }
  155. }
  156. }
  157. catch (ObjectDisposedException)
  158. {
  159. //server stopped
  160. }
  161. catch (SocketException ex)
  162. {
  163. switch (ex.SocketErrorCode)
  164. {
  165. case SocketError.OperationAborted:
  166. case SocketError.Interrupted:
  167. break; //server stopping
  168. default:
  169. _log?.Write(ex);
  170. throw;
  171. }
  172. }
  173. catch (Exception ex)
  174. {
  175. if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
  176. return; //server stopping
  177. _log?.Write(ex);
  178. throw;
  179. }
  180. }
  181. private async Task ProcessDhcpRequestAsync(DhcpMessage request, IPEndPoint remoteEP, IPPacketInformation ipPacketInformation, Socket udpListener)
  182. {
  183. try
  184. {
  185. DhcpMessage response = await ProcessDhcpMessageAsync(request, remoteEP, ipPacketInformation);
  186. //send response
  187. if (response != null)
  188. {
  189. byte[] sendBuffer = new byte[1024];
  190. MemoryStream sendBufferStream = new MemoryStream(sendBuffer);
  191. response.WriteTo(sendBufferStream);
  192. //send dns datagram
  193. if (!request.RelayAgentIpAddress.Equals(IPAddress.Any))
  194. {
  195. //received request via relay agent so send unicast response to relay agent on port 67
  196. await udpListener.SendToAsync(new ArraySegment<byte>(sendBuffer, 0, (int)sendBufferStream.Position), SocketFlags.None, new IPEndPoint(request.RelayAgentIpAddress, 67));
  197. }
  198. else if (!request.ClientIpAddress.Equals(IPAddress.Any))
  199. {
  200. //client is already configured and renewing lease so send unicast response on port 68
  201. await udpListener.SendToAsync(new ArraySegment<byte>(sendBuffer, 0, (int)sendBufferStream.Position), SocketFlags.None, new IPEndPoint(request.ClientIpAddress, 68));
  202. }
  203. else
  204. {
  205. Socket udpSocket;
  206. //send response as broadcast on port 68 on appropriate interface bound socket
  207. if (_udpListeners.TryGetValue(response.ServerIdentifier.Address, out UdpListener listener))
  208. udpSocket = listener.Socket; //found scope specific socket
  209. else
  210. udpSocket = udpListener; //no appropriate socket found so use default socket
  211. await udpSocket.SendToAsync(new ArraySegment<byte>(sendBuffer, 0, (int)sendBufferStream.Position), SocketFlags.DontRoute, new IPEndPoint(IPAddress.Broadcast, 68)); //no routing for broadcast
  212. }
  213. }
  214. }
  215. catch (ObjectDisposedException)
  216. {
  217. //socket disposed
  218. }
  219. catch (Exception ex)
  220. {
  221. if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
  222. return; //server stopping
  223. _log?.Write(remoteEP, ex);
  224. }
  225. }
  226. private async Task<DhcpMessage> ProcessDhcpMessageAsync(DhcpMessage request, IPEndPoint remoteEP, IPPacketInformation ipPacketInformation)
  227. {
  228. if (request.OpCode != DhcpMessageOpCode.BootRequest)
  229. return null;
  230. switch (request.DhcpMessageType?.Type)
  231. {
  232. case DhcpMessageType.Discover:
  233. {
  234. Scope scope = FindScope(request, remoteEP.Address, ipPacketInformation);
  235. if (scope == null)
  236. return null; //no scope available; do nothing
  237. if ((request.ServerHostName != null) && (request.ServerHostName != scope.ServerHostName))
  238. return null; //discard request; since this request is for another server with the specified server host name
  239. if ((request.BootFileName != null) && (request.BootFileName != scope.BootFileName))
  240. return null; //discard request; since this request wants boot file not available on this server
  241. if (scope.OfferDelayTime > 0)
  242. await Task.Delay(scope.OfferDelayTime); //delay sending offer
  243. Lease offer = await scope.GetOfferAsync(request);
  244. if (offer == null)
  245. return null; //no offer available, do nothing
  246. IPAddress serverIdentifierAddress = scope.InterfaceAddress.Equals(IPAddress.Any) ? ipPacketInformation.Address : scope.InterfaceAddress;
  247. string reservedLeaseHostName = null;
  248. if (!string.IsNullOrWhiteSpace(scope.DomainName))
  249. {
  250. //get override host name from reserved lease
  251. Lease reservedLease = scope.GetReservedLease(request);
  252. if (reservedLease is not null)
  253. reservedLeaseHostName = reservedLease.HostName;
  254. }
  255. List<DhcpOption> options = await scope.GetOptionsAsync(request, serverIdentifierAddress, reservedLeaseHostName, _dnsServer);
  256. if (options is null)
  257. return null;
  258. //log ip offer
  259. _log?.Write(remoteEP, "DHCP Server offered IP address [" + offer.Address.ToString() + "] to " + request.GetClientFullIdentifier() + " for scope: " + scope.Name);
  260. return DhcpMessage.CreateReply(request, offer.Address, scope.ServerAddress ?? serverIdentifierAddress, scope.ServerHostName, scope.BootFileName, options);
  261. }
  262. case DhcpMessageType.Request:
  263. {
  264. //request ip address lease or extend existing lease
  265. Scope scope = FindScope(request, remoteEP.Address, ipPacketInformation);
  266. if (scope == null)
  267. return null; //no scope available; do nothing
  268. IPAddress serverIdentifierAddress = scope.InterfaceAddress.Equals(IPAddress.Any) ? ipPacketInformation.Address : scope.InterfaceAddress;
  269. Lease leaseOffer;
  270. if (request.ServerIdentifier == null)
  271. {
  272. if (request.RequestedIpAddress == null)
  273. {
  274. //renewing or rebinding
  275. if (request.ClientIpAddress.Equals(IPAddress.Any))
  276. return null; //client must set IP address in ciaddr; do nothing
  277. leaseOffer = scope.GetExistingLeaseOrOffer(request);
  278. if (leaseOffer == null)
  279. {
  280. //no existing lease or offer available for client
  281. //send nak
  282. return DhcpMessage.CreateReply(request, IPAddress.Any, IPAddress.Any, null, null, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(scope.InterfaceAddress), DhcpOption.CreateEndOption() });
  283. }
  284. if (!request.ClientIpAddress.Equals(leaseOffer.Address))
  285. {
  286. //client ip is incorrect
  287. //send nak
  288. return DhcpMessage.CreateReply(request, IPAddress.Any, IPAddress.Any, null, null, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(scope.InterfaceAddress), DhcpOption.CreateEndOption() });
  289. }
  290. }
  291. else
  292. {
  293. //init-reboot
  294. leaseOffer = scope.GetExistingLeaseOrOffer(request);
  295. if (leaseOffer == null)
  296. {
  297. //no existing lease or offer available for client
  298. //send nak
  299. return DhcpMessage.CreateReply(request, IPAddress.Any, IPAddress.Any, null, null, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(scope.InterfaceAddress), DhcpOption.CreateEndOption() });
  300. }
  301. if (!request.RequestedIpAddress.Address.Equals(leaseOffer.Address))
  302. {
  303. //the client's notion of its IP address is not correct - RFC 2131
  304. //send nak
  305. return DhcpMessage.CreateReply(request, IPAddress.Any, IPAddress.Any, null, null, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(scope.InterfaceAddress), DhcpOption.CreateEndOption() });
  306. }
  307. }
  308. if ((leaseOffer.Type == LeaseType.Dynamic) && (scope.IsAddressExcluded(leaseOffer.Address) || scope.IsAddressReserved(leaseOffer.Address)))
  309. {
  310. //client ip is excluded/reserved for dynamic allocations
  311. scope.ReleaseLease(leaseOffer);
  312. //send nak
  313. return DhcpMessage.CreateReply(request, IPAddress.Any, IPAddress.Any, null, null, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(scope.InterfaceAddress), DhcpOption.CreateEndOption() });
  314. }
  315. Lease reservedLease = scope.GetReservedLease(request);
  316. if (reservedLease == null)
  317. {
  318. if (leaseOffer.Type == LeaseType.Reserved)
  319. {
  320. //client's reserved lease has been removed so release the current lease and send NAK to allow it to get new allocation
  321. scope.ReleaseLease(leaseOffer);
  322. //send nak
  323. return DhcpMessage.CreateReply(request, IPAddress.Any, IPAddress.Any, null, null, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(scope.InterfaceAddress), DhcpOption.CreateEndOption() });
  324. }
  325. }
  326. else
  327. {
  328. if (!reservedLease.Address.Equals(leaseOffer.Address))
  329. {
  330. //client has a new reserved lease so release the current lease and send NAK to allow it to get new allocation
  331. scope.ReleaseLease(leaseOffer);
  332. //send nak
  333. return DhcpMessage.CreateReply(request, IPAddress.Any, IPAddress.Any, null, null, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(scope.InterfaceAddress), DhcpOption.CreateEndOption() });
  334. }
  335. }
  336. }
  337. else
  338. {
  339. //selecting offer
  340. if (request.RequestedIpAddress == null)
  341. return null; //client MUST include this option; do nothing
  342. if (!request.ServerIdentifier.Address.Equals(serverIdentifierAddress))
  343. return null; //offer declined by client; do nothing
  344. leaseOffer = scope.GetExistingLeaseOrOffer(request);
  345. if (leaseOffer == null)
  346. {
  347. //no existing lease or offer available for client
  348. //send nak
  349. return DhcpMessage.CreateReply(request, IPAddress.Any, IPAddress.Any, null, null, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(scope.InterfaceAddress), DhcpOption.CreateEndOption() });
  350. }
  351. if (!request.RequestedIpAddress.Address.Equals(leaseOffer.Address))
  352. {
  353. //requested ip is incorrect
  354. //send nak
  355. return DhcpMessage.CreateReply(request, IPAddress.Any, IPAddress.Any, null, null, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(scope.InterfaceAddress), DhcpOption.CreateEndOption() });
  356. }
  357. }
  358. string reservedLeaseHostName = null;
  359. if (!string.IsNullOrWhiteSpace(scope.DomainName))
  360. {
  361. //get override host name from reserved lease
  362. Lease reservedLease = scope.GetReservedLease(request);
  363. if (reservedLease is not null)
  364. reservedLeaseHostName = reservedLease.HostName;
  365. }
  366. List<DhcpOption> options = await scope.GetOptionsAsync(request, serverIdentifierAddress, reservedLeaseHostName, _dnsServer);
  367. if (options is null)
  368. return null;
  369. scope.CommitLease(leaseOffer);
  370. //log ip lease
  371. _log?.Write(remoteEP, "DHCP Server leased IP address [" + leaseOffer.Address.ToString() + "] to " + request.GetClientFullIdentifier() + " for scope: " + scope.Name);
  372. if (string.IsNullOrWhiteSpace(scope.DomainName))
  373. {
  374. //update lease hostname
  375. leaseOffer.SetHostName(request.HostName?.HostName);
  376. }
  377. else
  378. {
  379. //update dns
  380. string clientDomainName = null;
  381. if (!string.IsNullOrWhiteSpace(reservedLeaseHostName))
  382. clientDomainName = GetSanitizedHostName(reservedLeaseHostName) + "." + scope.DomainName;
  383. if (string.IsNullOrWhiteSpace(clientDomainName))
  384. {
  385. foreach (DhcpOption option in options)
  386. {
  387. if (option.Code == DhcpOptionCode.ClientFullyQualifiedDomainName)
  388. {
  389. clientDomainName = (option as ClientFullyQualifiedDomainNameOption).DomainName;
  390. break;
  391. }
  392. }
  393. }
  394. if (string.IsNullOrWhiteSpace(clientDomainName))
  395. {
  396. if ((request.HostName is not null) && !string.IsNullOrWhiteSpace(request.HostName.HostName))
  397. clientDomainName = GetSanitizedHostName(request.HostName.HostName) + "." + scope.DomainName;
  398. }
  399. if (!string.IsNullOrWhiteSpace(clientDomainName))
  400. {
  401. if (!clientDomainName.Equals(leaseOffer.HostName, StringComparison.OrdinalIgnoreCase))
  402. UpdateDnsAuthZone(false, scope, leaseOffer); //hostname changed! delete old hostname entry from DNS
  403. leaseOffer.SetHostName(clientDomainName);
  404. UpdateDnsAuthZone(true, scope, leaseOffer);
  405. }
  406. }
  407. return DhcpMessage.CreateReply(request, leaseOffer.Address, scope.ServerAddress ?? serverIdentifierAddress, scope.ServerHostName, scope.BootFileName, options);
  408. }
  409. case DhcpMessageType.Decline:
  410. {
  411. //ip address is already in use as detected by client via ARP
  412. if ((request.ServerIdentifier == null) || (request.RequestedIpAddress == null))
  413. return null; //client MUST include these option; do nothing
  414. Scope scope = FindScope(request, remoteEP.Address, ipPacketInformation);
  415. if (scope == null)
  416. return null; //no scope available; do nothing
  417. IPAddress serverIdentifierAddress = scope.InterfaceAddress.Equals(IPAddress.Any) ? ipPacketInformation.Address : scope.InterfaceAddress;
  418. if (!request.ServerIdentifier.Address.Equals(serverIdentifierAddress))
  419. return null; //request not for this server; do nothing
  420. Lease lease = scope.GetExistingLeaseOrOffer(request);
  421. if (lease == null)
  422. return null; //no existing lease or offer available for client; do nothing
  423. if (!lease.Address.Equals(request.RequestedIpAddress.Address))
  424. return null; //the client's notion of its IP address is not correct; do nothing
  425. //remove lease since the IP address is used by someone else
  426. scope.ReleaseLease(lease);
  427. //log issue
  428. _log?.Write(remoteEP, "DHCP Server received DECLINE message for scope '" + scope.Name + "': " + lease.GetClientInfo() + " detected that IP address [" + lease.Address + "] is already in use.");
  429. //update dns
  430. UpdateDnsAuthZone(false, scope, lease);
  431. //do nothing
  432. return null;
  433. }
  434. case DhcpMessageType.Release:
  435. {
  436. //cancel ip address lease
  437. if (request.ServerIdentifier == null)
  438. return null; //client MUST include this option; do nothing
  439. Scope scope = FindScope(request, remoteEP.Address, ipPacketInformation);
  440. if (scope == null)
  441. return null; //no scope available; do nothing
  442. IPAddress serverIdentifierAddress = scope.InterfaceAddress.Equals(IPAddress.Any) ? ipPacketInformation.Address : scope.InterfaceAddress;
  443. if (!request.ServerIdentifier.Address.Equals(serverIdentifierAddress))
  444. return null; //request not for this server; do nothing
  445. Lease lease = scope.GetExistingLeaseOrOffer(request);
  446. if (lease == null)
  447. return null; //no existing lease or offer available for client; do nothing
  448. if (!lease.Address.Equals(request.ClientIpAddress))
  449. return null; //the client's notion of its IP address is not correct; do nothing
  450. //release lease
  451. scope.ReleaseLease(lease);
  452. //log ip lease release
  453. _log?.Write(remoteEP, "DHCP Server released IP address [" + lease.Address.ToString() + "] that was leased to " + lease.GetClientInfo() + " for scope: " + scope.Name);
  454. //update dns
  455. UpdateDnsAuthZone(false, scope, lease);
  456. //do nothing
  457. return null;
  458. }
  459. case DhcpMessageType.Inform:
  460. {
  461. //need only local config; already has ip address assigned externally/manually
  462. Scope scope = FindScope(request, remoteEP.Address, ipPacketInformation);
  463. if (scope == null)
  464. return null; //no scope available; do nothing
  465. IPAddress serverIdentifierAddress = scope.InterfaceAddress.Equals(IPAddress.Any) ? ipPacketInformation.Address : scope.InterfaceAddress;
  466. //log inform
  467. _log?.Write(remoteEP, "DHCP Server received INFORM message from " + request.GetClientFullIdentifier() + " for scope: " + scope.Name);
  468. List<DhcpOption> options = await scope.GetOptionsAsync(request, serverIdentifierAddress, null, _dnsServer);
  469. if (options is null)
  470. return null;
  471. if (!string.IsNullOrWhiteSpace(scope.DomainName))
  472. {
  473. //update dns
  474. string clientDomainName = null;
  475. foreach (DhcpOption option in options)
  476. {
  477. if (option.Code == DhcpOptionCode.ClientFullyQualifiedDomainName)
  478. {
  479. clientDomainName = (option as ClientFullyQualifiedDomainNameOption).DomainName;
  480. break;
  481. }
  482. }
  483. if (string.IsNullOrWhiteSpace(clientDomainName))
  484. {
  485. if (request.HostName is not null)
  486. clientDomainName = GetSanitizedHostName(request.HostName.HostName) + "." + scope.DomainName;
  487. }
  488. if (!string.IsNullOrWhiteSpace(clientDomainName))
  489. UpdateDnsAuthZone(true, scope, clientDomainName, request.ClientIpAddress, false);
  490. }
  491. return DhcpMessage.CreateReply(request, IPAddress.Any, scope.ServerAddress ?? serverIdentifierAddress, null, null, options);
  492. }
  493. default:
  494. return null;
  495. }
  496. }
  497. private Scope FindScope(DhcpMessage request, IPAddress remoteAddress, IPPacketInformation ipPacketInformation)
  498. {
  499. if (request.RelayAgentIpAddress.Equals(IPAddress.Any))
  500. {
  501. //no relay agent
  502. if (request.ClientIpAddress.Equals(IPAddress.Any))
  503. {
  504. if (!ipPacketInformation.Address.Equals(IPAddress.Broadcast))
  505. return null; //message destination address must be broadcast address
  506. //broadcast request
  507. Scope foundScope = null;
  508. foreach (KeyValuePair<string, Scope> entry in _scopes)
  509. {
  510. Scope scope = entry.Value;
  511. if (scope.Enabled && (scope.InterfaceIndex == ipPacketInformation.Interface))
  512. {
  513. if (scope.GetReservedLease(request) != null)
  514. return scope; //found reserved lease on this scope
  515. if ((foundScope == null) && !scope.AllowOnlyReservedLeases)
  516. foundScope = scope;
  517. }
  518. }
  519. return foundScope;
  520. }
  521. else
  522. {
  523. if ((request.DhcpMessageType?.Type != DhcpMessageType.Decline) && !remoteAddress.Equals(request.ClientIpAddress))
  524. return null; //client ip must match udp src addr
  525. //unicast request
  526. foreach (KeyValuePair<string, Scope> entry in _scopes)
  527. {
  528. Scope scope = entry.Value;
  529. if (scope.Enabled && scope.IsAddressInRange(request.ClientIpAddress))
  530. return scope;
  531. }
  532. return null;
  533. }
  534. }
  535. else
  536. {
  537. //relay agent unicast
  538. Scope foundScope = null;
  539. foreach (KeyValuePair<string, Scope> entry in _scopes)
  540. {
  541. Scope scope = entry.Value;
  542. if (scope.Enabled && scope.InterfaceAddress.Equals(IPAddress.Any) && scope.IsAddressInNetwork(request.RelayAgentIpAddress))
  543. {
  544. if (scope.GetReservedLease(request) != null)
  545. return scope; //found reserved lease on this scope
  546. if (!request.ClientIpAddress.Equals(IPAddress.Any) && scope.IsAddressInRange(request.ClientIpAddress))
  547. return scope; //client IP address is in scope range
  548. if ((foundScope == null) && !scope.AllowOnlyReservedLeases)
  549. foundScope = scope;
  550. }
  551. }
  552. return foundScope;
  553. }
  554. }
  555. internal static string GetSanitizedHostName(string hostname)
  556. {
  557. StringBuilder sb = new StringBuilder(hostname.Length);
  558. foreach (char c in hostname)
  559. {
  560. if ((c >= 97) && (c <= 122)) //[a-z]
  561. sb.Append(c);
  562. else if ((c >= 65) && (c <= 90)) //[A-Z]
  563. sb.Append(c);
  564. else if ((c >= 48) && (c <= 57)) //[0-9]
  565. sb.Append(c);
  566. else if (c == 45) //[-]
  567. sb.Append(c);
  568. else if (c == 95) //[_]
  569. sb.Append(c);
  570. else if (c == '.')
  571. sb.Append(c);
  572. else if (c == ' ')
  573. sb.Append('-');
  574. }
  575. return sb.ToString();
  576. }
  577. private void UpdateDnsAuthZone(bool add, Scope scope, Lease lease)
  578. {
  579. UpdateDnsAuthZone(add, scope, lease.HostName, lease.Address, lease.Type == LeaseType.Reserved);
  580. }
  581. private void UpdateDnsAuthZone(bool add, Scope scope, string domain, IPAddress address, bool isReservedLease)
  582. {
  583. if ((_dnsServer is null) || (_authManager is null))
  584. return;
  585. if (string.IsNullOrWhiteSpace(scope.DomainName) || !scope.DnsUpdates)
  586. return;
  587. if (string.IsNullOrWhiteSpace(domain))
  588. return;
  589. if (!DnsClient.IsDomainNameValid(domain))
  590. return;
  591. if (!domain.EndsWith("." + scope.DomainName, StringComparison.OrdinalIgnoreCase))
  592. return; //domain does not end with scope domain name
  593. try
  594. {
  595. string zoneName = null;
  596. string reverseDomain = Zone.GetReverseZone(address, 32);
  597. string reverseZoneName = null;
  598. if (add)
  599. {
  600. //update forward zone
  601. AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.FindAuthZoneInfo(scope.DomainName);
  602. if (zoneInfo is null)
  603. {
  604. //zone does not exists; create new primary zone
  605. zoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(scope.DomainName);
  606. if (zoneInfo is null)
  607. {
  608. _log?.Write("DHCP Server failed to create DNS primary zone '" + scope.DomainName + "'.");
  609. return;
  610. }
  611. //set permissions
  612. _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  613. _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  614. _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  615. _authManager.SaveConfigFile();
  616. _log?.Write("DHCP Server create DNS primary zone '" + zoneInfo.DisplayName + "'.");
  617. }
  618. else if ((zoneInfo.Type != AuthZoneType.Primary) && (zoneInfo.Type != AuthZoneType.Forwarder))
  619. {
  620. if (zoneInfo.Name.Equals(scope.DomainName, StringComparison.OrdinalIgnoreCase))
  621. throw new DhcpServerException("Cannot update DNS zone '" + zoneInfo.DisplayName + "': not a primary or a forwarder zone.");
  622. //create new primary zone
  623. zoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(scope.DomainName);
  624. if (zoneInfo is null)
  625. {
  626. _log?.Write("DHCP Server failed to create DNS primary zone '" + scope.DomainName + "'.");
  627. return;
  628. }
  629. //set permissions
  630. _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  631. _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  632. _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  633. _authManager.SaveConfigFile();
  634. _log?.Write("DHCP Server create DNS primary zone '" + zoneInfo.DisplayName + "'.");
  635. }
  636. zoneName = zoneInfo.Name;
  637. if (!isReservedLease)
  638. {
  639. //check for existing record for the dynamic leases
  640. IReadOnlyList<DnsResourceRecord> existingRecords = _dnsServer.AuthZoneManager.GetRecords(zoneName, domain, DnsResourceRecordType.A);
  641. if (existingRecords.Count > 0)
  642. {
  643. foreach (DnsResourceRecord existingRecord in existingRecords)
  644. {
  645. IPAddress existingAddress = (existingRecord.RDATA as DnsARecordData).Address;
  646. if (!existingAddress.Equals(address))
  647. {
  648. //a DNS record already exists for the specified domain name with a different address
  649. //do not change DNS record for this dynamic lease
  650. _log?.Write("DHCP Server cannot update DNS: an A record already exists for '" + domain + "' with a different IP address [" + existingAddress.ToString() + "].");
  651. return;
  652. }
  653. }
  654. }
  655. }
  656. DnsResourceRecord aRecord = new DnsResourceRecord(domain, DnsResourceRecordType.A, DnsClass.IN, scope.DnsTtl, new DnsARecordData(address));
  657. aRecord.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
  658. _dnsServer.AuthZoneManager.SetRecord(zoneName, aRecord);
  659. _log?.Write("DHCP Server updated DNS A record '" + domain + "' with IP address [" + address.ToString() + "].");
  660. //update reverse zone
  661. AuthZoneInfo reverseZoneInfo = _dnsServer.AuthZoneManager.FindAuthZoneInfo(reverseDomain);
  662. if (reverseZoneInfo is null)
  663. {
  664. string reverseZone = Zone.GetReverseZone(address, scope.SubnetMask);
  665. //reverse zone does not exists; create new reverse primary zone
  666. reverseZoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(reverseZone);
  667. if (reverseZoneInfo is null)
  668. {
  669. _log?.Write("DHCP Server failed to create DNS primary zone '" + reverseZone + "'.");
  670. return;
  671. }
  672. //set permissions
  673. _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  674. _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  675. _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  676. _authManager.SaveConfigFile();
  677. _log?.Write("DHCP Server create DNS primary zone '" + reverseZoneInfo.DisplayName + "'.");
  678. }
  679. else if ((reverseZoneInfo.Type != AuthZoneType.Primary) && (reverseZoneInfo.Type != AuthZoneType.Forwarder))
  680. {
  681. string reverseZone = Zone.GetReverseZone(address, scope.SubnetMask);
  682. if (reverseZoneInfo.Name.Equals(reverseZone, StringComparison.OrdinalIgnoreCase))
  683. throw new DhcpServerException("Cannot update reverse DNS zone '" + reverseZoneInfo.DisplayName + "': not a primary or a forwarder zone.");
  684. //create new reverse primary zone
  685. reverseZoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(reverseZone);
  686. if (reverseZoneInfo is null)
  687. {
  688. _log?.Write("DHCP Server failed to create DNS primary zone '" + reverseZone + "'.");
  689. return;
  690. }
  691. //set permissions
  692. _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  693. _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  694. _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  695. _authManager.SaveConfigFile();
  696. _log?.Write("DHCP Server create DNS primary zone '" + reverseZoneInfo.DisplayName + "'.");
  697. }
  698. reverseZoneName = reverseZoneInfo.Name;
  699. DnsResourceRecord ptrRecord = new DnsResourceRecord(reverseDomain, DnsResourceRecordType.PTR, DnsClass.IN, scope.DnsTtl, new DnsPTRRecordData(domain));
  700. ptrRecord.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
  701. _dnsServer.AuthZoneManager.SetRecord(reverseZoneName, ptrRecord);
  702. _log?.Write("DHCP Server updated DNS PTR record '" + reverseDomain + "' with domain name '" + domain + "'.");
  703. }
  704. else
  705. {
  706. //remove from forward zone
  707. AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.FindAuthZoneInfo(domain);
  708. if ((zoneInfo is not null) && ((zoneInfo.Type == AuthZoneType.Primary) || (zoneInfo.Type == AuthZoneType.Forwarder)))
  709. {
  710. //primary zone exists
  711. zoneName = zoneInfo.Name;
  712. _dnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, DnsResourceRecordType.A, new DnsARecordData(address));
  713. _log?.Write("DHCP Server deleted DNS A record '" + domain + "' with address [" + address.ToString() + "].");
  714. }
  715. //remove from reverse zone
  716. AuthZoneInfo reverseZoneInfo = _dnsServer.AuthZoneManager.FindAuthZoneInfo(reverseDomain);
  717. if ((reverseZoneInfo != null) && ((reverseZoneInfo.Type == AuthZoneType.Primary) || (reverseZoneInfo.Type == AuthZoneType.Forwarder)))
  718. {
  719. //primary reverse zone exists
  720. reverseZoneName = reverseZoneInfo.Name;
  721. _dnsServer.AuthZoneManager.DeleteRecord(reverseZoneName, reverseDomain, DnsResourceRecordType.PTR, new DnsPTRRecordData(domain));
  722. _log?.Write("DHCP Server deleted DNS PTR record '" + reverseDomain + "' with domain '" + domain + "'.");
  723. }
  724. }
  725. //save auth zone file
  726. if (zoneName is not null)
  727. _dnsServer?.AuthZoneManager.SaveZoneFile(zoneName);
  728. //save reverse auth zone file
  729. if (reverseZoneName is not null)
  730. _dnsServer?.AuthZoneManager.SaveZoneFile(reverseZoneName);
  731. }
  732. catch (Exception ex)
  733. {
  734. _log?.Write(ex);
  735. }
  736. }
  737. private void BindUdpListener(IPEndPoint dhcpEP)
  738. {
  739. UdpListener listener = _udpListeners.GetOrAdd(dhcpEP.Address, delegate (IPAddress key)
  740. {
  741. Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  742. try
  743. {
  744. #region this code ignores ICMP port unreachable responses which creates SocketException in ReceiveFrom()
  745. if (Environment.OSVersion.Platform == PlatformID.Win32NT)
  746. {
  747. const uint IOC_IN = 0x80000000;
  748. const uint IOC_VENDOR = 0x18000000;
  749. const uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
  750. udpSocket.IOControl((IOControlCode)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null);
  751. }
  752. #endregion
  753. //bind to interface address
  754. if (Environment.OSVersion.Platform == PlatformID.Unix)
  755. udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); //to allow binding to same port with different addresses
  756. udpSocket.EnableBroadcast = true;
  757. udpSocket.ExclusiveAddressUse = false;
  758. udpSocket.Bind(dhcpEP);
  759. //start reading dhcp packets
  760. _ = Task.Factory.StartNew(delegate ()
  761. {
  762. return ReadUdpRequestAsync(udpSocket);
  763. }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Current);
  764. return new UdpListener(udpSocket);
  765. }
  766. catch
  767. {
  768. udpSocket.Dispose();
  769. throw;
  770. }
  771. });
  772. listener.IncrementScopeCount();
  773. }
  774. private bool UnbindUdpListener(IPEndPoint dhcpEP)
  775. {
  776. if (_udpListeners.TryGetValue(dhcpEP.Address, out UdpListener listener))
  777. {
  778. listener.DecrementScopeCount();
  779. if (listener.ScopeCount < 1)
  780. {
  781. if (_udpListeners.TryRemove(dhcpEP.Address, out _))
  782. {
  783. listener.Socket.Dispose();
  784. return true;
  785. }
  786. }
  787. }
  788. return false;
  789. }
  790. private async Task<bool> ActivateScopeAsync(Scope scope, bool waitForInterface, bool throwException = false)
  791. {
  792. IPEndPoint dhcpEP = null;
  793. try
  794. {
  795. //find scope interface for binding socket
  796. if (waitForInterface)
  797. {
  798. //retry for 30 seconds for interface to come up
  799. int tries = 0;
  800. while (true)
  801. {
  802. if (scope.FindInterface())
  803. {
  804. if (!scope.InterfaceAddress.Equals(IPAddress.Any))
  805. break; //break only when specific interface address is found
  806. }
  807. if (++tries >= 30)
  808. {
  809. if (scope.InterfaceAddress == null)
  810. throw new DhcpServerException("DHCP Server requires static IP address to work correctly but no network interface was found to have any static IP address configured.");
  811. break; //use the available ANY interface address
  812. }
  813. await Task.Delay(1000);
  814. }
  815. }
  816. else
  817. {
  818. if (!scope.FindInterface())
  819. throw new DhcpServerException("DHCP Server requires static IP address to work correctly but no network interface was found to have any static IP address configured.");
  820. }
  821. //find this dns server address in case the network config has changed
  822. if (scope.UseThisDnsServer)
  823. scope.FindThisDnsServerAddress();
  824. dhcpEP = new IPEndPoint(scope.InterfaceAddress, 67);
  825. if (!dhcpEP.Address.Equals(IPAddress.Any))
  826. BindUdpListener(dhcpEP);
  827. try
  828. {
  829. BindUdpListener(_dhcpDefaultEP);
  830. }
  831. catch
  832. {
  833. if (!dhcpEP.Address.Equals(IPAddress.Any))
  834. UnbindUdpListener(dhcpEP);
  835. throw;
  836. }
  837. if (_dnsServer is not null)
  838. {
  839. //update valid leases into dns
  840. DateTime utcNow = DateTime.UtcNow;
  841. foreach (KeyValuePair<ClientIdentifierOption, Lease> lease in scope.Leases)
  842. UpdateDnsAuthZone(utcNow < lease.Value.LeaseExpires, scope, lease.Value); //lease valid
  843. }
  844. _log?.Write(dhcpEP, "DHCP Server successfully activated scope: " + scope.Name);
  845. return true;
  846. }
  847. catch (Exception ex)
  848. {
  849. _log?.Write(dhcpEP, "DHCP Server failed to activate scope: " + scope.Name + "\r\n" + ex.ToString());
  850. if (throwException)
  851. throw;
  852. }
  853. return false;
  854. }
  855. private bool DeactivateScope(Scope scope, bool throwException = false)
  856. {
  857. IPEndPoint dhcpEP = null;
  858. try
  859. {
  860. IPAddress interfaceAddress = scope.InterfaceAddress;
  861. dhcpEP = new IPEndPoint(interfaceAddress, 67);
  862. if (!interfaceAddress.Equals(IPAddress.Any))
  863. UnbindUdpListener(dhcpEP);
  864. UnbindUdpListener(_dhcpDefaultEP);
  865. if (_dnsServer is not null)
  866. {
  867. //remove all leases from dns
  868. foreach (KeyValuePair<ClientIdentifierOption, Lease> lease in scope.Leases)
  869. UpdateDnsAuthZone(false, scope, lease.Value);
  870. }
  871. _log?.Write(dhcpEP, "DHCP Server successfully deactivated scope: " + scope.Name);
  872. return true;
  873. }
  874. catch (Exception ex)
  875. {
  876. _log?.Write(dhcpEP, "DHCP Server failed to deactivate scope: " + scope.Name + "\r\n" + ex.ToString());
  877. if (throwException)
  878. throw;
  879. }
  880. return false;
  881. }
  882. private async Task LoadScopeAsync(Scope scope, bool waitForInterface)
  883. {
  884. foreach (KeyValuePair<string, Scope> entry in _scopes)
  885. {
  886. Scope existingScope = entry.Value;
  887. if (existingScope.IsAddressInRange(scope.StartingAddress) || existingScope.IsAddressInRange(scope.EndingAddress))
  888. throw new DhcpServerException("Scope with overlapping range already exists: " + existingScope.StartingAddress.ToString() + "-" + existingScope.EndingAddress.ToString());
  889. }
  890. if (!_scopes.TryAdd(scope.Name, scope))
  891. throw new DhcpServerException("Scope with same name already exists.");
  892. if (scope.Enabled)
  893. {
  894. if (!await ActivateScopeAsync(scope, waitForInterface))
  895. scope.SetEnabled(false);
  896. }
  897. _log?.Write("DHCP Server successfully loaded scope: " + scope.Name);
  898. }
  899. private void UnloadScope(Scope scope)
  900. {
  901. if (scope.Enabled)
  902. DeactivateScope(scope);
  903. if (_scopes.TryRemove(scope.Name, out Scope removedScope))
  904. {
  905. removedScope.Dispose();
  906. _log?.Write("DHCP Server successfully unloaded scope: " + scope.Name);
  907. }
  908. }
  909. private void LoadAllScopeFiles()
  910. {
  911. string[] scopeFiles = Directory.GetFiles(_scopesFolder, "*.scope");
  912. foreach (string scopeFile in scopeFiles)
  913. _ = LoadScopeFileAsync(scopeFile);
  914. _lastModifiedScopesSavedOn = DateTime.UtcNow;
  915. }
  916. private async Task LoadScopeFileAsync(string scopeFile)
  917. {
  918. //load scope file async to allow waiting for interface to come up
  919. try
  920. {
  921. using (FileStream fS = new FileStream(scopeFile, FileMode.Open, FileAccess.Read))
  922. {
  923. await LoadScopeAsync(new Scope(new BinaryReader(fS), _log, this), true);
  924. }
  925. _log?.Write("DHCP Server successfully loaded scope file: " + scopeFile);
  926. }
  927. catch (Exception ex)
  928. {
  929. _log?.Write("DHCP Server failed to load scope file: " + scopeFile + "\r\n" + ex.ToString());
  930. }
  931. }
  932. private void SaveScopeFile(Scope scope)
  933. {
  934. string scopeFile = Path.Combine(_scopesFolder, scope.Name + ".scope");
  935. try
  936. {
  937. using (FileStream fS = new FileStream(scopeFile, FileMode.Create, FileAccess.Write))
  938. {
  939. scope.WriteTo(new BinaryWriter(fS));
  940. }
  941. _log?.Write("DHCP Server successfully saved scope file: " + scopeFile);
  942. }
  943. catch (Exception ex)
  944. {
  945. _log?.Write("DHCP Server failed to save scope file: " + scopeFile + "\r\n" + ex.ToString());
  946. }
  947. }
  948. private void DeleteScopeFile(string scopeName)
  949. {
  950. string scopeFile = Path.Combine(_scopesFolder, scopeName + ".scope");
  951. try
  952. {
  953. File.Delete(scopeFile);
  954. _log?.Write("DHCP Server successfully deleted scope file: " + scopeFile);
  955. }
  956. catch (Exception ex)
  957. {
  958. _log?.Write("DHCP Server failed to delete scope file: " + scopeFile + "\r\n" + ex.ToString());
  959. }
  960. }
  961. private void SaveModifiedScopes()
  962. {
  963. DateTime currentDateTime = DateTime.UtcNow;
  964. foreach (KeyValuePair<string, Scope> scope in _scopes)
  965. {
  966. if (scope.Value.LastModified > _lastModifiedScopesSavedOn)
  967. SaveScopeFile(scope.Value);
  968. }
  969. _lastModifiedScopesSavedOn = currentDateTime;
  970. }
  971. private void StartMaintenanceTimer()
  972. {
  973. if (_maintenanceTimer == null)
  974. {
  975. _maintenanceTimer = new Timer(delegate (object state)
  976. {
  977. try
  978. {
  979. foreach (KeyValuePair<string, Scope> scope in _scopes)
  980. {
  981. scope.Value.RemoveExpiredOffers();
  982. List<Lease> expiredLeases = scope.Value.RemoveExpiredLeases();
  983. if (expiredLeases.Count > 0)
  984. {
  985. _log?.Write("DHCP Server removed " + expiredLeases.Count + " lease(s) from scope: " + scope.Value.Name);
  986. foreach (Lease expiredLease in expiredLeases)
  987. UpdateDnsAuthZone(false, scope.Value, expiredLease);
  988. }
  989. }
  990. SaveModifiedScopes();
  991. }
  992. catch (Exception ex)
  993. {
  994. _log?.Write(ex);
  995. }
  996. finally
  997. {
  998. if (!_disposed)
  999. _maintenanceTimer.Change(MAINTENANCE_TIMER_INTERVAL, Timeout.Infinite);
  1000. }
  1001. }, null, Timeout.Infinite, Timeout.Infinite);
  1002. }
  1003. _maintenanceTimer.Change(MAINTENANCE_TIMER_INTERVAL, Timeout.Infinite);
  1004. }
  1005. private void StopMaintenanceTimer()
  1006. {
  1007. _maintenanceTimer.Change(Timeout.Infinite, Timeout.Infinite);
  1008. }
  1009. #endregion
  1010. #region public
  1011. public void Start()
  1012. {
  1013. if (_disposed)
  1014. ObjectDisposedException.ThrowIf(_disposed, this);
  1015. if (_state != ServiceState.Stopped)
  1016. throw new InvalidOperationException("DHCP Server is already running.");
  1017. _state = ServiceState.Starting;
  1018. LoadAllScopeFiles();
  1019. StartMaintenanceTimer();
  1020. _state = ServiceState.Running;
  1021. }
  1022. public void Stop()
  1023. {
  1024. if (_state != ServiceState.Running)
  1025. return;
  1026. _state = ServiceState.Stopping;
  1027. StopMaintenanceTimer();
  1028. SaveModifiedScopes();
  1029. foreach (KeyValuePair<string, Scope> scope in _scopes)
  1030. UnloadScope(scope.Value);
  1031. _udpListeners.Clear();
  1032. _state = ServiceState.Stopped;
  1033. }
  1034. public async Task AddScopeAsync(Scope scope)
  1035. {
  1036. await LoadScopeAsync(scope, false);
  1037. SaveScopeFile(scope);
  1038. }
  1039. public Scope GetScope(string name)
  1040. {
  1041. if (_scopes.TryGetValue(name, out Scope scope))
  1042. return scope;
  1043. return null;
  1044. }
  1045. public void RenameScope(string oldName, string newName)
  1046. {
  1047. Scope.ValidateScopeName(newName);
  1048. if (!_scopes.TryGetValue(oldName, out Scope scope))
  1049. throw new DhcpServerException("Scope with name '" + oldName + "' does not exists.");
  1050. if (!_scopes.TryAdd(newName, scope))
  1051. throw new DhcpServerException("Scope with name '" + newName + "' already exists.");
  1052. scope.Name = newName;
  1053. _scopes.TryRemove(oldName, out _);
  1054. SaveScopeFile(scope);
  1055. DeleteScopeFile(oldName);
  1056. }
  1057. public void DeleteScope(string name)
  1058. {
  1059. if (_scopes.TryGetValue(name, out Scope scope))
  1060. {
  1061. UnloadScope(scope);
  1062. DeleteScopeFile(scope.Name);
  1063. }
  1064. }
  1065. public async Task<bool> EnableScopeAsync(string name, bool throwException = false)
  1066. {
  1067. if (_scopes.TryGetValue(name, out Scope scope))
  1068. {
  1069. if (!scope.Enabled && await ActivateScopeAsync(scope, false, throwException))
  1070. {
  1071. scope.SetEnabled(true);
  1072. SaveScopeFile(scope);
  1073. return true;
  1074. }
  1075. }
  1076. return false;
  1077. }
  1078. public bool DisableScope(string name, bool throwException = false)
  1079. {
  1080. if (_scopes.TryGetValue(name, out Scope scope))
  1081. {
  1082. if (scope.Enabled && DeactivateScope(scope, throwException))
  1083. {
  1084. scope.SetEnabled(false);
  1085. SaveScopeFile(scope);
  1086. return true;
  1087. }
  1088. }
  1089. return false;
  1090. }
  1091. public void SaveScope(string name)
  1092. {
  1093. if (_scopes.TryGetValue(name, out Scope scope))
  1094. SaveScopeFile(scope);
  1095. }
  1096. public IDictionary<string, string> GetAddressHostNameMap()
  1097. {
  1098. Dictionary<string, string> map = new Dictionary<string, string>();
  1099. foreach (KeyValuePair<string, Scope> scope in _scopes)
  1100. {
  1101. foreach (KeyValuePair<ClientIdentifierOption, Lease> lease in scope.Value.Leases)
  1102. {
  1103. if (!string.IsNullOrEmpty(lease.Value.HostName))
  1104. map.Add(lease.Value.Address.ToString(), lease.Value.HostName);
  1105. }
  1106. }
  1107. return map;
  1108. }
  1109. #endregion
  1110. #region properties
  1111. public IReadOnlyDictionary<string, Scope> Scopes
  1112. { get { return _scopes; } }
  1113. public DnsServer DnsServer
  1114. {
  1115. get { return _dnsServer; }
  1116. set { _dnsServer = value; }
  1117. }
  1118. internal AuthManager AuthManager
  1119. {
  1120. get { return _authManager; }
  1121. set { _authManager = value; }
  1122. }
  1123. public LogManager LogManager
  1124. {
  1125. get { return _log; }
  1126. set { _log = value; }
  1127. }
  1128. #endregion
  1129. class UdpListener
  1130. {
  1131. #region private
  1132. readonly Socket _socket;
  1133. volatile int _scopeCount;
  1134. #endregion
  1135. #region constructor
  1136. public UdpListener(Socket socket)
  1137. {
  1138. _socket = socket;
  1139. }
  1140. #endregion
  1141. #region public
  1142. public void IncrementScopeCount()
  1143. {
  1144. Interlocked.Increment(ref _scopeCount);
  1145. }
  1146. public void DecrementScopeCount()
  1147. {
  1148. Interlocked.Decrement(ref _scopeCount);
  1149. }
  1150. #endregion
  1151. #region properties
  1152. public Socket Socket
  1153. { get { return _socket; } }
  1154. public int ScopeCount
  1155. { get { return _scopeCount; } }
  1156. #endregion
  1157. }
  1158. }
  1159. }