DhcpServer.cs 59 KB

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