diff --git a/DnsServerCore/Dhcp/DhcpServer.cs b/DnsServerCore/Dhcp/DhcpServer.cs index f0a3c170..4f09a001 100644 --- a/DnsServerCore/Dhcp/DhcpServer.cs +++ b/DnsServerCore/Dhcp/DhcpServer.cs @@ -18,12 +18,16 @@ along with this program. If not, see . */ using DnsServerCore.Dhcp.Options; +using DnsServerCore.Dns; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; +using TechnitiumLibrary.Net.Dns; +using TechnitiumLibrary.Net.Dns.ResourceRecords; namespace DnsServerCore.Dhcp { @@ -55,25 +59,34 @@ namespace DnsServerCore.Dhcp #region variables + readonly string _configFolder; + readonly List _udpListeners = new List(); readonly List _listenerThreads = new List(); - readonly List _scopes = new List(); + readonly ConcurrentDictionary _scopes = new ConcurrentDictionary(); + Zone _authoritativeZoneRoot; LogManager _log; + int _serverAnyAddressScopeCount; volatile ServiceState _state = ServiceState.Stopped; + Timer _maintenanceTimer; + const int MAINTENANCE_TIMER_INTERVAL = 10000; + + DateTime _lastModifiedScopesSavedOn; + #endregion #region constructor - public DhcpServer() - { } - - public DhcpServer(ICollection scopes) + public DhcpServer(string configFolder) { - _scopes.AddRange(scopes); + _configFolder = configFolder; + + if (!Directory.Exists(_configFolder)) + Directory.CreateDirectory(_configFolder); } #endregion @@ -89,10 +102,12 @@ namespace DnsServerCore.Dhcp if (disposing) { + if (_maintenanceTimer != null) + _maintenanceTimer.Dispose(); + Stop(); - if (_log != null) - _log.Dispose(); + SaveModifiedScopes(); } _disposed = true; @@ -118,8 +133,6 @@ namespace DnsServerCore.Dhcp { while (true) { - remoteEP = new IPEndPoint(IPAddress.Any, 0); - try { bytesRecv = udpListener.ReceiveFrom(recvBuffer, ref remoteEP); @@ -237,8 +250,8 @@ namespace DnsServerCore.Dhcp if (scope == null) return null; //no scope available; do nothing - if (scope.DelayTime > 0) - Thread.Sleep(scope.DelayTime * 1000); //delay sending offer + if (scope.OfferDelayTime > 0) + Thread.Sleep(scope.OfferDelayTime * 1000); //delay sending offer Lease offer = scope.GetOffer(request); if (offer == null) @@ -248,6 +261,11 @@ namespace DnsServerCore.Dhcp if (options == null) return null; + //log ip offer + LogManager log = _log; + if (log != null) + log.Write(remoteEP as IPEndPoint, "DHCP Server offered IP address [" + offer.Address.ToString() + "] to " + request.GetClientFullIdentifier() + "."); + return new DhcpMessage(request, offer.Address, interfaceEP.Address, options); } @@ -359,6 +377,32 @@ namespace DnsServerCore.Dhcp if (log != null) log.Write(remoteEP as IPEndPoint, "DHCP Server leased IP address [" + leaseOffer.Address.ToString() + "] to " + request.GetClientFullIdentifier() + "."); + { + //update dns + string clientDomainName = null; + + foreach (DhcpOption option in options) + { + if (option.Code == DhcpOptionCode.ClientFullyQualifiedDomainName) + { + clientDomainName = (option as ClientFullyQualifiedDomainNameOption).DomainName; + break; + } + } + + if (clientDomainName == null) + { + if (request.HostName != null) + clientDomainName = request.HostName.HostName + "." + scope.DomainName; + } + + if (clientDomainName != null) + { + leaseOffer.SetHostName(clientDomainName.ToLower()); + UpdateDnsAuthZone(true, scope, leaseOffer); + } + } + return new DhcpMessage(request, leaseOffer.Address, interfaceEP.Address, options); } @@ -389,8 +433,12 @@ namespace DnsServerCore.Dhcp //log issue LogManager log = _log; if (log != null) - log.Write(remoteEP as IPEndPoint, "DHCP Server received DECLINE message: " + request.GetClientFullIdentifier() + " detected that IP address [" + lease.Address + "] is already in use."); + log.Write(remoteEP as IPEndPoint, "DHCP Server received DECLINE message: " + lease.GetClientFullIdentifier() + " detected that IP address [" + lease.Address + "] is already in use."); + //update dns + UpdateDnsAuthZone(false, scope, lease); + + //do nothing return null; } @@ -421,7 +469,10 @@ namespace DnsServerCore.Dhcp //log ip lease release LogManager log = _log; if (log != null) - log.Write(remoteEP as IPEndPoint, "DHCP Server released IP address [" + lease.Address.ToString() + "] that was leased to " + request.GetClientFullIdentifier() + "."); + log.Write(remoteEP as IPEndPoint, "DHCP Server released IP address [" + lease.Address.ToString() + "] that was leased to " + lease.GetClientFullIdentifier() + "."); + + //update dns + UpdateDnsAuthZone(false, scope, lease); //do nothing return null; @@ -439,6 +490,11 @@ namespace DnsServerCore.Dhcp if (options == null) return null; + //log inform + LogManager log = _log; + if (log != null) + log.Write(remoteEP as IPEndPoint, "DHCP Server received INFORM message from " + request.GetClientFullIdentifier() + "."); + return new DhcpMessage(request, IPAddress.Any, interfaceEP.Address, options); } @@ -476,18 +532,60 @@ namespace DnsServerCore.Dhcp address = request.RelayAgentIpAddress; } - lock (_scopes) + foreach (KeyValuePair scope in _scopes) { - foreach (Scope scope in _scopes) - { - if (scope.InterfaceAddress.Equals(interfaceAddress) && scope.IsAddressInRange(address)) - return scope; - } + if (scope.Value.InterfaceAddress.Equals(interfaceAddress) && scope.Value.IsAddressInRange(address)) + return scope.Value; } return null; } + private void UpdateDnsAuthZone(bool add, Scope scope, Lease lease) + { + if (_authoritativeZoneRoot == null) + return; + + if (string.IsNullOrEmpty(scope.DomainName)) + return; + + if (add) + { + //update forward zone + if (!string.IsNullOrEmpty(scope.DomainName)) + { + if (!_authoritativeZoneRoot.ZoneExists(scope.DomainName)) + { + //create forward zone + _authoritativeZoneRoot.SetRecords(scope.DomainName, DnsResourceRecordType.SOA, 14400, new DnsResourceRecordData[] { new DnsSOARecord(_authoritativeZoneRoot.ServerDomain, "hostmaster." + scope.DomainName, uint.Parse(DateTime.UtcNow.ToString("yyyyMMddHH")), 28800, 7200, 604800, 600) }); + _authoritativeZoneRoot.SetRecords(scope.DomainName, DnsResourceRecordType.NS, 14400, new DnsResourceRecordData[] { new DnsNSRecord(_authoritativeZoneRoot.ServerDomain) }); + } + + _authoritativeZoneRoot.SetRecords(lease.HostName, DnsResourceRecordType.A, scope.DnsTtl, new DnsResourceRecordData[] { new DnsARecord(lease.Address) }); + } + + //update reverse zone + { + if (!_authoritativeZoneRoot.ZoneExists(scope.ReverseZone)) + { + //create reverse zone + _authoritativeZoneRoot.SetRecords(scope.ReverseZone, DnsResourceRecordType.SOA, 14400, new DnsResourceRecordData[] { new DnsSOARecord(_authoritativeZoneRoot.ServerDomain, "hostmaster." + scope.ReverseZone, uint.Parse(DateTime.UtcNow.ToString("yyyyMMddHH")), 28800, 7200, 604800, 600) }); + _authoritativeZoneRoot.SetRecords(scope.ReverseZone, DnsResourceRecordType.NS, 14400, new DnsResourceRecordData[] { new DnsNSRecord(_authoritativeZoneRoot.ServerDomain) }); + } + + _authoritativeZoneRoot.SetRecords(Scope.GetReverseZone(lease.Address, 32), DnsResourceRecordType.PTR, scope.DnsTtl, new DnsResourceRecordData[] { new DnsPTRRecord(lease.HostName) }); + } + } + else + { + //remove from forward zone + _authoritativeZoneRoot.DeleteRecords(lease.HostName, DnsResourceRecordType.A); + + //remove from reverse zone + _authoritativeZoneRoot.DeleteRecords(Scope.GetReverseZone(lease.Address, 32), DnsResourceRecordType.PTR); + } + } + private void BindUdpListener(IPEndPoint dhcpEP) { Socket udpListener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); @@ -533,6 +631,290 @@ namespace DnsServerCore.Dhcp } } + private bool UnbindUdpListener(IPEndPoint dhcpEP) + { + lock (_udpListeners) + { + Socket foundSocket = null; + + foreach (Socket udpListener in _udpListeners) + { + if (dhcpEP.Equals(udpListener.LocalEndPoint)) + { + foundSocket = udpListener; + break; + } + } + + if (foundSocket != null) + { + foundSocket.Dispose(); + return _udpListeners.Remove(foundSocket); + } + } + + return false; + } + + private bool ActivateScope(Scope scope) + { + IPEndPoint dhcpEP = null; + + try + { + IPAddress interfaceAddress = scope.InterfaceAddress; + dhcpEP = new IPEndPoint(interfaceAddress, 67); + + if (interfaceAddress.Equals(IPAddress.Any)) + { + if (_serverAnyAddressScopeCount < 1) + BindUdpListener(dhcpEP); + + _serverAnyAddressScopeCount++; + } + else + { + BindUdpListener(dhcpEP); + } + + + if (_authoritativeZoneRoot != null) + { + //update valid leases into dns + DateTime utcNow = DateTime.UtcNow; + + foreach (Lease lease in scope.Leases) + { + if (utcNow < lease.LeaseExpires) + UpdateDnsAuthZone(true, scope, lease); //lease valid + } + } + + LogManager log = _log; + if (log != null) + log.Write(dhcpEP, "DHCP Server successfully activated scope: " + scope.Name); + + return true; + } + catch (Exception ex) + { + LogManager log = _log; + if (log != null) + log.Write(dhcpEP, "DHCP Server failed to activate scope: " + scope.Name + "\r\n" + ex.ToString()); + } + + return false; + } + + private bool DeactivateScope(Scope scope) + { + IPEndPoint dhcpEP = null; + + try + { + IPAddress interfaceAddress = scope.InterfaceAddress; + dhcpEP = new IPEndPoint(interfaceAddress, 67); + + if (interfaceAddress.Equals(IPAddress.Any)) + { + if (_serverAnyAddressScopeCount < 2) + { + UnbindUdpListener(dhcpEP); + _serverAnyAddressScopeCount = 0; + } + else + { + _serverAnyAddressScopeCount--; + } + } + else + { + UnbindUdpListener(dhcpEP); + } + + if (_authoritativeZoneRoot != null) + { + //remove all leases from dns + foreach (Lease lease in scope.Leases) + UpdateDnsAuthZone(false, scope, lease); + } + + LogManager log = _log; + if (log != null) + log.Write(dhcpEP, "DHCP Server successfully deactivated scope: " + scope.Name); + + return true; + } + catch (Exception ex) + { + LogManager log = _log; + if (log != null) + log.Write(dhcpEP, "DHCP Server failed to deactivate scope: " + scope.Name + "\r\n" + ex.ToString()); + } + + return false; + } + + private void LoadScope(Scope scope) + { + foreach (KeyValuePair existingScope in _scopes) + { + if (existingScope.Value.Equals(scope)) + throw new DhcpServerException("Scope with same range already exists."); + } + + if (!_scopes.TryAdd(scope.Name, scope)) + throw new DhcpServerException("Scope with same name already exists."); + + if (scope.Enabled) + ActivateScope(scope); + + LogManager log = _log; + if (log != null) + log.Write("DHCP Server successfully loaded scope: " + scope.Name); + } + + private void UnloadScope(Scope scope) + { + DeactivateScope(scope); + + if (_scopes.TryRemove(scope.Name, out _)) + { + LogManager log = _log; + if (log != null) + log.Write("DHCP Server successfully unloaded scope: " + scope.Name); + } + } + + private void LoadAllScopeFiles() + { + string[] scopeFiles = Directory.GetFiles(_configFolder, "*.scope"); + + foreach (string scopeFile in scopeFiles) + LoadScopeFile(scopeFile); + + _lastModifiedScopesSavedOn = DateTime.UtcNow; + } + + private void LoadScopeFile(string scopeFile) + { + try + { + using (FileStream fS = new FileStream(scopeFile, FileMode.Open, FileAccess.Read)) + { + LoadScope(new Scope(new BinaryReader(fS))); + } + + LogManager log = _log; + if (log != null) + log.Write("DHCP Server successfully loaded scope file: " + scopeFile); + } + catch (Exception ex) + { + LogManager log = _log; + if (log != null) + log.Write("DHCP Server failed to load scope file: " + scopeFile + "\r\n" + ex.ToString()); + } + } + + private void SaveScopeFile(Scope scope) + { + string scopeFile = Path.Combine(_configFolder, scope.Name + ".scope"); + + try + { + using (FileStream fS = new FileStream(scopeFile, FileMode.Create, FileAccess.Write)) + { + scope.WriteTo(new BinaryWriter(fS)); + } + + LogManager log = _log; + if (log != null) + log.Write("DHCP Server successfully saved scope file: " + scopeFile); + } + catch (Exception ex) + { + LogManager log = _log; + if (log != null) + log.Write("DHCP Server failed to save scope file: " + scopeFile + "\r\n" + ex.ToString()); + } + } + + private void DeleteScopeFile(Scope scope) + { + string scopeFile = Path.Combine(_configFolder, scope.Name + ".scope"); + + try + { + File.Delete(scopeFile); + + LogManager log = _log; + if (log != null) + log.Write("DHCP Server successfully deleted scope file: " + scopeFile); + } + catch (Exception ex) + { + LogManager log = _log; + if (log != null) + log.Write("DHCP Server failed to delete scope file: " + scopeFile + "\r\n" + ex.ToString()); + } + } + + private void SaveModifiedScopes() + { + DateTime currentDateTime = DateTime.UtcNow; + + foreach (KeyValuePair scope in _scopes) + { + if (scope.Value.LastModified > _lastModifiedScopesSavedOn) + SaveScopeFile(scope.Value); + } + + _lastModifiedScopesSavedOn = currentDateTime; + } + + private void StartMaintenanceTimer() + { + if (_maintenanceTimer == null) + { + _maintenanceTimer = new Timer(delegate (object state) + { + try + { + foreach (KeyValuePair scope in _scopes) + { + scope.Value.RemoveExpiredOffers(); + + List expiredLeases = scope.Value.RemoveExpiredLeases(); + + foreach (Lease expiredLease in expiredLeases) + UpdateDnsAuthZone(false, scope.Value, expiredLease); + } + + SaveModifiedScopes(); + } + catch (Exception ex) + { + LogManager log = _log; + if (log != null) + log.Write(ex); + } + finally + { + if (!_disposed) + _maintenanceTimer.Change(MAINTENANCE_TIMER_INTERVAL, Timeout.Infinite); + } + }, null, Timeout.Infinite, Timeout.Infinite); + } + + _maintenanceTimer.Change(MAINTENANCE_TIMER_INTERVAL, Timeout.Infinite); + } + + private void StopMaintenanceTimer() + { + _maintenanceTimer.Change(Timeout.Infinite, Timeout.Infinite); + } + #endregion #region public @@ -547,31 +929,8 @@ namespace DnsServerCore.Dhcp _state = ServiceState.Starting; - IPEndPoint dhcpEP = new IPEndPoint(IPAddress.Any, 67); - - try - { - BindUdpListener(dhcpEP); - - LogManager log = _log; - if (log != null) - log.Write(dhcpEP, "DHCP Server was bound successfully."); - } - catch (Exception ex) - { - LogManager log = _log; - if (log != null) - log.Write(dhcpEP, "DHCP Server failed bind.\r\n" + ex.ToString()); - } - - lock (_scopes) - { - foreach (Scope scope in _scopes) - { - if (scope.Enabled) - ActivateScope(scope); - } - } + LoadAllScopeFiles(); + StartMaintenanceTimer(); _state = ServiceState.Running; } @@ -583,139 +942,72 @@ namespace DnsServerCore.Dhcp _state = ServiceState.Stopping; - lock (_udpListeners) - { - foreach (Socket udpListener in _udpListeners) - udpListener.Dispose(); - } + StopMaintenanceTimer(); + + foreach (KeyValuePair scope in _scopes) + UnloadScope(scope.Value); _listenerThreads.Clear(); _udpListeners.Clear(); - lock (_scopes) - { - foreach (Scope scope in _scopes) - scope.Dispose(); - } - _state = ServiceState.Stopped; } - public Scope[] GetScopes() - { - lock (_scopes) - { - return _scopes.ToArray(); - } - } - public void AddScope(Scope scope) { - lock (_scopes) + LoadScope(scope); + SaveScopeFile(scope); + } + + public Scope GetScope(string name) + { + if (_scopes.TryGetValue(name, out Scope scope)) + return scope; + + return null; + } + + public void RenameScope(string name, string newName) + { + if (_scopes.TryGetValue(name, out Scope scope)) + throw new DhcpServerException("Scope with name '" + name + "' does not exists."); + + if (!_scopes.TryAdd(newName, scope)) + throw new DhcpServerException("Scope with name '" + newName + "' already exists."); + + scope.Name = newName; + _scopes.TryRemove(name, out _); + } + + public void DeleteScope(string name) + { + if (_scopes.TryGetValue(name, out Scope scope)) { - foreach (Scope existingScope in _scopes) - { - if (existingScope.Equals(scope)) - return; - } - - scope.LogManager = _log; - - if (scope.Enabled) - ActivateScope(scope); - - _scopes.Add(scope); + UnloadScope(scope); + DeleteScopeFile(scope); } } - public void RemoveScope(Scope scope) + public void EnableScope(string name) { - lock (_scopes) + if (_scopes.TryGetValue(name, out Scope scope)) { - DeactivateScope(scope); - _scopes.Remove(scope); - } - } - - public void ActivateScope(Scope scope) - { - if (scope.IsActive) - return; - - IPAddress interfaceAddress = scope.InterfaceAddress; - IPEndPoint dhcpEP = new IPEndPoint(interfaceAddress, 67); - - if (interfaceAddress.Equals(IPAddress.Any)) - { - scope.SetActive(true); - - LogManager log = _log; - if (log != null) - log.Write(dhcpEP, "DHCP Server successfully activated scope '" + scope.Name + "'"); - } - else - { - try + if (ActivateScope(scope)) { - BindUdpListener(dhcpEP); - scope.SetActive(true); - - LogManager log = _log; - if (log != null) - log.Write(dhcpEP, "DHCP Server successfully activated scope '" + scope.Name + "'"); - } - catch (Exception ex) - { - LogManager log = _log; - if (log != null) - log.Write(dhcpEP, "DHCP Server failed to activate scope '" + scope.Name + "'.\r\n" + ex.ToString()); + scope.SetEnabled(true); + SaveScopeFile(scope); } } } - public void DeactivateScope(Scope scope) + public void DisableScope(string name) { - if (!scope.IsActive) - return; - - IPAddress interfaceAddress = scope.InterfaceAddress; - IPEndPoint dhcpEP = new IPEndPoint(interfaceAddress, 67); - - if (interfaceAddress.Equals(IPAddress.Any)) + if (_scopes.TryGetValue(name, out Scope scope)) { - scope.SetActive(false); - - LogManager log = _log; - if (log != null) - log.Write(dhcpEP, "DHCP Server successfully deactivated scope '" + scope.Name + "'"); - } - else - { - lock (_udpListeners) + if (DeactivateScope(scope)) { - foreach (Socket udpListener in _udpListeners) - { - if (dhcpEP.Equals(udpListener.LocalEndPoint)) - { - try - { - udpListener.Dispose(); - scope.SetActive(false); - - LogManager log = _log; - if (log != null) - log.Write(dhcpEP, "DHCP Server successfully deactivated scope '" + scope.Name + "'"); - } - catch (Exception ex) - { - LogManager log = _log; - if (log != null) - log.Write(dhcpEP, "DHCP Server failed to deactivated scope '" + scope.Name + "'.\r\n" + ex.ToString()); - } - - return; - } - } + scope.SetEnabled(false); + SaveScopeFile(scope); } } } @@ -724,6 +1016,15 @@ namespace DnsServerCore.Dhcp #region properties + public ICollection Scopes + { get { return _scopes.Values; } } + + public Zone AuthoritativeZoneRoot + { + get { return _authoritativeZoneRoot; } + set { _authoritativeZoneRoot = value; } + } + public LogManager LogManager { get { return _log; }