DhcpServer.cs 61 KB

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