DhcpServer.cs 60 KB

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