diff --git a/DnsServerCore/Dhcp/DhcpServer.cs b/DnsServerCore/Dhcp/DhcpServer.cs
new file mode 100644
index 00000000..f0a3c170
--- /dev/null
+++ b/DnsServerCore/Dhcp/DhcpServer.cs
@@ -0,0 +1,735 @@
+/*
+Technitium DNS Server
+Copyright (C) 2019 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using DnsServerCore.Dhcp.Options;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace DnsServerCore.Dhcp
+{
+ //Dynamic Host Configuration Protocol
+ //https://tools.ietf.org/html/rfc2131
+
+ //DHCP Options and BOOTP Vendor Extensions
+ //https://tools.ietf.org/html/rfc2132
+
+ //Encoding Long Options in the Dynamic Host Configuration Protocol (DHCPv4)
+ //https://tools.ietf.org/html/rfc3396
+
+ //Client Fully Qualified Domain Name(FQDN) Option
+ //https://tools.ietf.org/html/rfc4702
+
+ public class DhcpServer : IDisposable
+ {
+ #region enum
+
+ enum ServiceState
+ {
+ Stopped = 0,
+ Starting = 1,
+ Running = 2,
+ Stopping = 3
+ }
+
+ #endregion
+
+ #region variables
+
+ readonly List _udpListeners = new List();
+ readonly List _listenerThreads = new List();
+
+ readonly List _scopes = new List();
+
+ LogManager _log;
+
+ volatile ServiceState _state = ServiceState.Stopped;
+
+ #endregion
+
+ #region constructor
+
+ public DhcpServer()
+ { }
+
+ public DhcpServer(ICollection scopes)
+ {
+ _scopes.AddRange(scopes);
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ private bool _disposed = false;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ if (disposing)
+ {
+ Stop();
+
+ if (_log != null)
+ _log.Dispose();
+ }
+
+ _disposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ #endregion
+
+ #region private
+
+ private void ReadUdpRequestAsync(object parameter)
+ {
+ Socket udpListener = parameter as Socket;
+ EndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
+ byte[] recvBuffer = new byte[576];
+ int bytesRecv;
+
+ try
+ {
+ while (true)
+ {
+ remoteEP = new IPEndPoint(IPAddress.Any, 0);
+
+ try
+ {
+ bytesRecv = udpListener.ReceiveFrom(recvBuffer, ref remoteEP);
+ }
+ catch (SocketException ex)
+ {
+ switch (ex.SocketErrorCode)
+ {
+ case SocketError.ConnectionReset:
+ case SocketError.HostUnreachable:
+ case SocketError.MessageSize:
+ case SocketError.NetworkReset:
+ bytesRecv = 0;
+ break;
+
+ default:
+ throw;
+ }
+ }
+
+ if (bytesRecv > 0)
+ {
+ switch ((remoteEP as IPEndPoint).Port)
+ {
+ case 67:
+ case 68:
+ try
+ {
+ ThreadPool.QueueUserWorkItem(ProcessUdpRequestAsync, new object[] { udpListener, remoteEP, new DhcpMessage(new MemoryStream(recvBuffer, 0, bytesRecv, false)) });
+ }
+ catch (Exception ex)
+ {
+ LogManager log = _log;
+ if (log != null)
+ log.Write(remoteEP as IPEndPoint, ex);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
+ return; //server stopping
+
+ LogManager log = _log;
+ if (log != null)
+ log.Write(remoteEP as IPEndPoint, ex);
+
+ throw;
+ }
+ }
+
+ private void ProcessUdpRequestAsync(object parameter)
+ {
+ object[] parameters = parameter as object[];
+
+ Socket udpListener = parameters[0] as Socket;
+ EndPoint remoteEP = parameters[1] as EndPoint;
+ DhcpMessage request = parameters[2] as DhcpMessage;
+
+ try
+ {
+ DhcpMessage response = ProcessDhcpMessage(request, remoteEP as IPEndPoint, udpListener.LocalEndPoint as IPEndPoint);
+
+ //send response
+ if (response != null)
+ {
+ byte[] sendBuffer = new byte[512];
+ MemoryStream sendBufferStream = new MemoryStream(sendBuffer);
+
+ response.WriteTo(sendBufferStream);
+
+ //send dns datagram
+ if (!request.RelayAgentIpAddress.Equals(IPAddress.Any))
+ {
+ //received request via relay agent so send unicast response to relay agent on port 67
+ udpListener.SendTo(sendBuffer, 0, (int)sendBufferStream.Position, SocketFlags.None, new IPEndPoint(request.RelayAgentIpAddress, 67));
+ }
+ else if (!request.ClientIpAddress.Equals(IPAddress.Any))
+ {
+ //client is already configured and renewing lease so send unicast response on port 68
+ udpListener.SendTo(sendBuffer, 0, (int)sendBufferStream.Position, SocketFlags.None, new IPEndPoint(request.ClientIpAddress, 68));
+ }
+ else
+ {
+ //send response as broadcast on port 68
+ udpListener.SendTo(sendBuffer, 0, (int)sendBufferStream.Position, SocketFlags.None, new IPEndPoint(IPAddress.Broadcast, 68));
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
+ return; //server stopping
+
+ LogManager log = _log;
+ if (log != null)
+ log.Write(remoteEP as IPEndPoint, ex);
+ }
+ }
+
+ private DhcpMessage ProcessDhcpMessage(DhcpMessage request, IPEndPoint remoteEP, IPEndPoint interfaceEP)
+ {
+ if (request.OpCode != DhcpMessageOpCode.BootRequest)
+ return null;
+
+ switch (request.DhcpMessageType?.Type)
+ {
+ case DhcpMessageType.Discover:
+ {
+ Scope scope = FindScope(request, remoteEP.Address, interfaceEP.Address);
+ if (scope == null)
+ return null; //no scope available; do nothing
+
+ if (scope.DelayTime > 0)
+ Thread.Sleep(scope.DelayTime * 1000); //delay sending offer
+
+ Lease offer = scope.GetOffer(request);
+ if (offer == null)
+ throw new DhcpServerException("DHCP Server failed to offer address: address unavailable.");
+
+ List options = scope.GetOptions(request, interfaceEP.Address);
+ if (options == null)
+ return null;
+
+ return new DhcpMessage(request, offer.Address, interfaceEP.Address, options);
+ }
+
+ case DhcpMessageType.Request:
+ {
+ //request ip address lease or extend existing lease
+ Scope scope;
+ Lease leaseOffer;
+
+ if (request.ServerIdentifier == null)
+ {
+ if (request.RequestedIpAddress == null)
+ {
+ //renewing or rebinding
+
+ if (request.ClientIpAddress.Equals(IPAddress.Any))
+ return null; //client must set IP address in ciaddr; do nothing
+
+ scope = FindScope(request, remoteEP.Address, interfaceEP.Address);
+ if (scope == null)
+ {
+ //no scope available; do nothing
+ return null;
+ }
+
+ leaseOffer = scope.GetExistingLeaseOrOffer(request);
+ if (leaseOffer == null)
+ {
+ //no existing lease or offer available for client
+ //send nak
+ return new DhcpMessage(request, IPAddress.Any, interfaceEP.Address, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(interfaceEP.Address), DhcpOption.CreateEndOption() });
+ }
+
+ if (!request.ClientIpAddress.Equals(leaseOffer.Address))
+ {
+ //client ip is incorrect
+ //send nak
+ return new DhcpMessage(request, IPAddress.Any, interfaceEP.Address, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(interfaceEP.Address), DhcpOption.CreateEndOption() });
+ }
+ }
+ else
+ {
+ //init-reboot
+ scope = FindScope(request, remoteEP.Address, interfaceEP.Address);
+ if (scope == null)
+ {
+ //no scope available; do nothing
+ return null;
+ }
+
+ leaseOffer = scope.GetExistingLeaseOrOffer(request);
+ if (leaseOffer == null)
+ {
+ //no existing lease or offer available for client
+ //send nak
+ return new DhcpMessage(request, IPAddress.Any, interfaceEP.Address, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(interfaceEP.Address), DhcpOption.CreateEndOption() });
+ }
+
+ if (!request.RequestedIpAddress.Address.Equals(leaseOffer.Address))
+ {
+ //the client's notion of its IP address is not correct - RFC 2131
+ //send nak
+ return new DhcpMessage(request, IPAddress.Any, interfaceEP.Address, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(interfaceEP.Address), DhcpOption.CreateEndOption() });
+ }
+ }
+ }
+ else
+ {
+ //selecting offer
+
+ if (request.RequestedIpAddress == null)
+ return null; //client MUST include this option; do nothing
+
+ if (!request.ServerIdentifier.Address.Equals(interfaceEP.Address))
+ return null; //offer declined by client; do nothing
+
+ scope = FindScope(request, remoteEP.Address, interfaceEP.Address);
+ if (scope == null)
+ {
+ //no scope available
+ //send nak
+ return new DhcpMessage(request, IPAddress.Any, interfaceEP.Address, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(interfaceEP.Address), DhcpOption.CreateEndOption() });
+ }
+
+ leaseOffer = scope.GetExistingLeaseOrOffer(request);
+ if (leaseOffer == null)
+ {
+ //no existing lease or offer available for client
+ //send nak
+ return new DhcpMessage(request, IPAddress.Any, interfaceEP.Address, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(interfaceEP.Address), DhcpOption.CreateEndOption() });
+ }
+
+ if (!request.RequestedIpAddress.Address.Equals(leaseOffer.Address))
+ {
+ //requested ip is incorrect
+ //send nak
+ return new DhcpMessage(request, IPAddress.Any, interfaceEP.Address, new DhcpOption[] { new DhcpMessageTypeOption(DhcpMessageType.Nak), new ServerIdentifierOption(interfaceEP.Address), DhcpOption.CreateEndOption() });
+ }
+ }
+
+ List options = scope.GetOptions(request, interfaceEP.Address);
+ if (options == null)
+ return null;
+
+ scope.CommitLease(leaseOffer);
+
+ //log ip lease
+ LogManager log = _log;
+ if (log != null)
+ log.Write(remoteEP as IPEndPoint, "DHCP Server leased IP address [" + leaseOffer.Address.ToString() + "] to " + request.GetClientFullIdentifier() + ".");
+
+ return new DhcpMessage(request, leaseOffer.Address, interfaceEP.Address, options);
+ }
+
+ case DhcpMessageType.Decline:
+ {
+ //ip address is already in use as detected by client via ARP
+
+ if ((request.ServerIdentifier == null) || (request.RequestedIpAddress == null))
+ return null; //client MUST include these option; do nothing
+
+ if (!request.ServerIdentifier.Address.Equals(interfaceEP.Address))
+ return null; //request not for this server; do nothing
+
+ Scope scope = FindScope(request, remoteEP.Address, interfaceEP.Address);
+ if (scope == null)
+ return null; //no scope available; do nothing
+
+ Lease lease = scope.GetExistingLeaseOrOffer(request);
+ if (lease == null)
+ return null; //no existing lease or offer available for client; do nothing
+
+ if (!lease.Address.Equals(request.RequestedIpAddress.Address))
+ return null; //the client's notion of its IP address is not correct; do nothing
+
+ //remove lease since the IP address is used by someone else
+ scope.ReleaseLease(lease);
+
+ //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.");
+
+ return null;
+ }
+
+ case DhcpMessageType.Release:
+ {
+ //cancel ip address lease
+
+ if (request.ServerIdentifier == null)
+ return null; //client MUST include this option; do nothing
+
+ if (!request.ServerIdentifier.Address.Equals(interfaceEP.Address))
+ return null; //request not for this server; do nothing
+
+ Scope scope = FindScope(request, remoteEP.Address, interfaceEP.Address);
+ if (scope == null)
+ return null; //no scope available; do nothing
+
+ Lease lease = scope.GetExistingLeaseOrOffer(request);
+ if (lease == null)
+ return null; //no existing lease or offer available for client; do nothing
+
+ if (!lease.Address.Equals(request.ClientIpAddress))
+ return null; //the client's notion of its IP address is not correct; do nothing
+
+ //release lease
+ scope.ReleaseLease(lease);
+
+ //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() + ".");
+
+ //do nothing
+ return null;
+ }
+
+ case DhcpMessageType.Inform:
+ {
+ //need only local config; already has ip address assigned externally/manually
+
+ Scope scope = FindScope(request, remoteEP.Address, interfaceEP.Address);
+ if (scope == null)
+ return null; //no scope available; do nothing
+
+ List options = scope.GetOptions(request, interfaceEP.Address);
+ if (options == null)
+ return null;
+
+ return new DhcpMessage(request, IPAddress.Any, interfaceEP.Address, options);
+ }
+
+ default:
+ return null;
+ }
+ }
+
+ private Scope FindScope(DhcpMessage request, IPAddress remoteAddress, IPAddress interfaceAddress)
+ {
+ IPAddress address;
+
+ if (request.RelayAgentIpAddress.Equals(IPAddress.Any))
+ {
+ //no relay agent
+ if (request.ClientIpAddress.Equals(IPAddress.Any))
+ {
+ address = interfaceAddress; //broadcast request
+ }
+ else
+ {
+ if (!remoteAddress.Equals(request.ClientIpAddress))
+ return null; //client ip must match udp src addr
+
+ address = request.ClientIpAddress; //unicast request
+ }
+ }
+ else
+ {
+ //relay agent unicast
+
+ if (!remoteAddress.Equals(request.RelayAgentIpAddress))
+ return null; //relay ip must match udp src addr
+
+ address = request.RelayAgentIpAddress;
+ }
+
+ lock (_scopes)
+ {
+ foreach (Scope scope in _scopes)
+ {
+ if (scope.InterfaceAddress.Equals(interfaceAddress) && scope.IsAddressInRange(address))
+ return scope;
+ }
+ }
+
+ return null;
+ }
+
+ private void BindUdpListener(IPEndPoint dhcpEP)
+ {
+ Socket udpListener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+
+ try
+ {
+ #region this code ignores ICMP port unreachable responses which creates SocketException in ReceiveFrom()
+
+ if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ {
+ const uint IOC_IN = 0x80000000;
+ const uint IOC_VENDOR = 0x18000000;
+ const uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
+
+ udpListener.IOControl((IOControlCode)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null);
+ }
+
+ #endregion
+
+ //bind to interface address
+ udpListener.EnableBroadcast = true;
+ udpListener.Bind(dhcpEP);
+
+ lock (_udpListeners)
+ {
+ _udpListeners.Add(udpListener);
+ }
+
+ //start reading dhcp packets
+ Thread listenerThread = new Thread(ReadUdpRequestAsync);
+ listenerThread.IsBackground = true;
+ listenerThread.Start(udpListener);
+
+ lock (_listenerThreads)
+ {
+ _listenerThreads.Add(listenerThread);
+ }
+ }
+ catch
+ {
+ udpListener.Dispose();
+ throw;
+ }
+ }
+
+ #endregion
+
+ #region public
+
+ public void Start()
+ {
+ if (_disposed)
+ throw new ObjectDisposedException("DhcpServer");
+
+ if (_state != ServiceState.Stopped)
+ throw new InvalidOperationException("DHCP Server is already running.");
+
+ _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);
+ }
+ }
+
+ _state = ServiceState.Running;
+ }
+
+ public void Stop()
+ {
+ if (_state != ServiceState.Running)
+ return;
+
+ _state = ServiceState.Stopping;
+
+ lock (_udpListeners)
+ {
+ foreach (Socket udpListener in _udpListeners)
+ udpListener.Dispose();
+ }
+
+ _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)
+ {
+ foreach (Scope existingScope in _scopes)
+ {
+ if (existingScope.Equals(scope))
+ return;
+ }
+
+ scope.LogManager = _log;
+
+ if (scope.Enabled)
+ ActivateScope(scope);
+
+ _scopes.Add(scope);
+ }
+ }
+
+ public void RemoveScope(Scope scope)
+ {
+ lock (_scopes)
+ {
+ 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
+ {
+ 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());
+ }
+ }
+ }
+
+ public void DeactivateScope(Scope scope)
+ {
+ if (!scope.IsActive)
+ return;
+
+ IPAddress interfaceAddress = scope.InterfaceAddress;
+ IPEndPoint dhcpEP = new IPEndPoint(interfaceAddress, 67);
+
+ if (interfaceAddress.Equals(IPAddress.Any))
+ {
+ scope.SetActive(false);
+
+ LogManager log = _log;
+ if (log != null)
+ log.Write(dhcpEP, "DHCP Server successfully deactivated scope '" + scope.Name + "'");
+ }
+ else
+ {
+ lock (_udpListeners)
+ {
+ 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;
+ }
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region properties
+
+ public LogManager LogManager
+ {
+ get { return _log; }
+ set { _log = value; }
+ }
+
+ #endregion
+ }
+}
diff --git a/DnsServerCore/Dhcp/DhcpServerException.cs b/DnsServerCore/Dhcp/DhcpServerException.cs
new file mode 100644
index 00000000..b6b3979d
--- /dev/null
+++ b/DnsServerCore/Dhcp/DhcpServerException.cs
@@ -0,0 +1,46 @@
+/*
+Technitium DNS Server
+Copyright (C) 2017 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using System;
+
+namespace DnsServerCore.Dhcp
+{
+ public class DhcpServerException : Exception
+ {
+ #region constructors
+
+ public DhcpServerException()
+ : base()
+ { }
+
+ public DhcpServerException(string message)
+ : base(message)
+ { }
+
+ public DhcpServerException(string message, Exception innerException)
+ : base(message, innerException)
+ { }
+
+ protected DhcpServerException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+ : base(info, context)
+ { }
+
+ #endregion
+ }
+}
diff --git a/DnsServerCore/Dhcp/Exclusion.cs b/DnsServerCore/Dhcp/Exclusion.cs
new file mode 100644
index 00000000..c48f2aee
--- /dev/null
+++ b/DnsServerCore/Dhcp/Exclusion.cs
@@ -0,0 +1,53 @@
+/*
+Technitium DNS Server
+Copyright (C) 2019 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using System.Net;
+
+namespace DnsServerCore.Dhcp
+{
+ public class Exclusion
+ {
+ #region variables
+
+ readonly IPAddress _startingAddress;
+ readonly IPAddress _endingAddress;
+
+ #endregion
+
+ #region constructor
+
+ public Exclusion(IPAddress startingAddress, IPAddress endingAddress)
+ {
+ _startingAddress = startingAddress;
+ _endingAddress = endingAddress;
+ }
+
+ #endregion
+
+ #region properties
+
+ public IPAddress StartingAddress
+ { get { return _startingAddress; } }
+
+ public IPAddress EndingAddress
+ { get { return _endingAddress; } }
+
+ #endregion
+ }
+}
diff --git a/DnsServerCore/Dhcp/Lease.cs b/DnsServerCore/Dhcp/Lease.cs
new file mode 100644
index 00000000..433668ba
--- /dev/null
+++ b/DnsServerCore/Dhcp/Lease.cs
@@ -0,0 +1,89 @@
+/*
+Technitium DNS Server
+Copyright (C) 2019 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using DnsServerCore.Dhcp.Options;
+using System;
+using System.Net;
+
+namespace DnsServerCore.Dhcp
+{
+ public class Lease
+ {
+ #region variables
+
+ readonly ClientIdentifierOption _clientIdentifier;
+ readonly string _hostName;
+ readonly byte[] _hardwareAddress;
+ readonly IPAddress _address;
+ DateTime _leaseObtained;
+ DateTime _leaseExpires;
+
+ #endregion
+
+ #region constructor
+
+ internal Lease(ClientIdentifierOption clientIdentifier, string hostName, byte[] hardwareAddress, IPAddress address, uint leaseTime)
+ {
+ _clientIdentifier = clientIdentifier;
+ _hostName = hostName;
+ _hardwareAddress = hardwareAddress;
+ _address = address;
+
+ ResetLeaseTime(leaseTime);
+ }
+
+ internal Lease(byte[] hardwareAddress, IPAddress address, uint leaseTime)
+ : this(new ClientIdentifierOption(1, hardwareAddress), null, hardwareAddress, address, leaseTime)
+ { }
+
+ #endregion
+
+ #region public
+
+ public void ResetLeaseTime(uint leaseTime)
+ {
+ _leaseObtained = DateTime.UtcNow;
+ _leaseExpires = DateTime.UtcNow.AddSeconds(leaseTime);
+ }
+
+ #endregion
+
+ #region properties
+
+ internal ClientIdentifierOption ClientIdentifier
+ { get { return _clientIdentifier; } }
+
+ public string HostName
+ { get { return _hostName; } }
+
+ public byte[] HardwareAddress
+ { get { return _hardwareAddress; } }
+
+ public IPAddress Address
+ { get { return _address; } }
+
+ public DateTime LeaseObtained
+ { get { return _leaseObtained; } }
+
+ public DateTime LeaseExpires
+ { get { return _leaseExpires; } }
+
+ #endregion
+ }
+}
diff --git a/DnsServerCore/Dhcp/Scope.cs b/DnsServerCore/Dhcp/Scope.cs
new file mode 100644
index 00000000..35b9d454
--- /dev/null
+++ b/DnsServerCore/Dhcp/Scope.cs
@@ -0,0 +1,843 @@
+/*
+Technitium DNS Server
+Copyright (C) 2019 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using DnsServerCore.Dhcp.Options;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace DnsServerCore.Dhcp
+{
+ public class Scope : IDisposable, IEquatable
+ {
+ #region variables
+
+ //required parameters
+ string _name;
+ bool _enabled;
+ IPAddress _startingAddress;
+ IPAddress _endingAddress;
+ IPAddress _subnetMask;
+
+ //optional parameters
+ string _domainName;
+ IPAddress _routerAddress;
+ IPAddress[] _dnsServers;
+ IPAddress[] _winsServers;
+ IPAddress[] _ntpServers;
+ ClasslessStaticRouteOption.Route[] _staticRoutes;
+ uint _leaseTime = 86400; //default 1 day lease
+ ushort _delayTime;
+ bool _autoRouter;
+ bool _autoDnsServer;
+ bool _reservedAddressOffersOnly;
+ readonly List _exclusions = new List();
+ readonly ConcurrentDictionary _reservedAddresses = new ConcurrentDictionary();
+
+ //leases
+ readonly ConcurrentDictionary _leases = new ConcurrentDictionary();
+
+ //computed parameters
+ IPAddress _networkAddress;
+ IPAddress _broadcastAddress;
+ uint _renewTime;
+ uint _rebindTime;
+
+ //internal parameters
+ readonly ConcurrentDictionary _offers = new ConcurrentDictionary();
+ IPAddress _lastAddressOffered;
+ const int OFFER_EXPIRY_SECONDS = 120; //2 mins offer expiry
+
+ bool _isActive;
+ IPAddress _interfaceAddress;
+ LogManager _log;
+
+ Timer _maintenanceTimer;
+ const int MAINTENANCE_TIMER_INTERVAL = 60000;
+
+ #endregion
+
+ #region constructor
+
+ public Scope(string name, IPAddress startingAddress, IPAddress endingAddress, IPAddress subnetMask, bool enabled)
+ {
+ _name = name;
+ _enabled = enabled;
+
+ ChangeNetwork(startingAddress, endingAddress, subnetMask);
+
+ _renewTime = _leaseTime / 2;
+ _rebindTime = Convert.ToUInt32(_leaseTime * 0.875);
+
+ StartMaintenanceTimer();
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ bool _disposed = false;
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ if (disposing)
+ {
+ if (_maintenanceTimer != null)
+ _maintenanceTimer.Dispose();
+ }
+
+ _disposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ #endregion
+
+ #region static
+
+ public static bool IsAddressInRange(IPAddress address, IPAddress startingAddress, IPAddress endingAddress)
+ {
+ uint addressNumber = ConvertIpToNumber(address);
+ uint startingAddressNumber = ConvertIpToNumber(startingAddress);
+ uint endingAddressNumber = ConvertIpToNumber(endingAddress);
+
+ return (startingAddressNumber <= addressNumber) && (addressNumber <= endingAddressNumber);
+ }
+
+ #endregion
+
+ #region private
+
+ private static uint ConvertIpToNumber(IPAddress address)
+ {
+ byte[] addr = address.GetAddressBytes();
+ Array.Reverse(addr);
+ return BitConverter.ToUInt32(addr, 0);
+ }
+
+ private static IPAddress ConvertNumberToIp(uint address)
+ {
+ byte[] addr = BitConverter.GetBytes(address);
+ Array.Reverse(addr);
+ return new IPAddress(addr);
+ }
+
+ private bool IsAddressAvailable(ref IPAddress address)
+ {
+ if (address.Equals(_routerAddress))
+ return false;
+
+ if ((_dnsServers != null) && _dnsServers.Contains(address))
+ return false;
+
+ if ((_winsServers != null) && _winsServers.Contains(address))
+ return false;
+
+ if ((_ntpServers != null) && _ntpServers.Contains(address))
+ return false;
+
+ lock (_exclusions)
+ {
+ foreach (Exclusion exclusion in _exclusions)
+ {
+ if (IsAddressInRange(address, exclusion.StartingAddress, exclusion.EndingAddress))
+ {
+ address = exclusion.EndingAddress;
+ return false;
+ }
+ }
+ }
+
+ foreach (KeyValuePair reservedAddress in _reservedAddresses)
+ {
+ if (address.Equals(reservedAddress.Value.Address))
+ return false;
+ }
+
+ foreach (KeyValuePair lease in _leases)
+ {
+ if (address.Equals(lease.Value.Address))
+ return false;
+ }
+
+ return true;
+ }
+
+ private ClientFullyQualifiedDomainNameOption GetClientFullyQualifiedDomainNameOption(DhcpMessage request)
+ {
+ ClientFullyQualifiedDomainNameFlags responseFlags = ClientFullyQualifiedDomainNameFlags.None;
+
+ if (request.ClientFullyQualifiedDomainName.Flags.HasFlag(ClientFullyQualifiedDomainNameFlags.EncodeUsingCanonicalWireFormat))
+ responseFlags |= ClientFullyQualifiedDomainNameFlags.EncodeUsingCanonicalWireFormat;
+
+ if (request.ClientFullyQualifiedDomainName.Flags.HasFlag(ClientFullyQualifiedDomainNameFlags.NoDnsUpdate))
+ {
+ responseFlags |= ClientFullyQualifiedDomainNameFlags.ShouldUpdateDns;
+ responseFlags |= ClientFullyQualifiedDomainNameFlags.OverrideByServer;
+ }
+ else if (request.ClientFullyQualifiedDomainName.Flags.HasFlag(ClientFullyQualifiedDomainNameFlags.ShouldUpdateDns))
+ {
+ responseFlags |= ClientFullyQualifiedDomainNameFlags.ShouldUpdateDns;
+ }
+ else
+ {
+ responseFlags |= ClientFullyQualifiedDomainNameFlags.ShouldUpdateDns;
+ responseFlags |= ClientFullyQualifiedDomainNameFlags.OverrideByServer;
+ }
+
+ string responseDomainName;
+
+ if (request.ClientFullyQualifiedDomainName.DomainName == "")
+ {
+ //client domain empty and expects server for a fqdn domain name
+ if (request.HostName == null)
+ return null; //server unable to decide a name for client
+
+ responseDomainName = request.HostName.HostName + "." + _domainName;
+ }
+ else if (request.ClientFullyQualifiedDomainName.DomainName.Contains("."))
+ {
+ //client domain is fqdn
+ if (request.ClientFullyQualifiedDomainName.DomainName.EndsWith("." + _domainName, StringComparison.OrdinalIgnoreCase))
+ {
+ responseDomainName = request.ClientFullyQualifiedDomainName.DomainName;
+ }
+ else
+ {
+ string[] parts = request.ClientFullyQualifiedDomainName.DomainName.Split('.');
+ responseDomainName = parts[0] + "." + _domainName;
+ }
+ }
+ else
+ {
+ //client domain is just hostname
+ responseDomainName = request.ClientFullyQualifiedDomainName.DomainName + "." + _domainName;
+ }
+
+ return new ClientFullyQualifiedDomainNameOption(responseFlags, 255, 255, responseDomainName);
+ }
+
+ private void StartMaintenanceTimer()
+ {
+ if (_maintenanceTimer == null)
+ {
+ _maintenanceTimer = new Timer(delegate (object state)
+ {
+ try
+ {
+ List expiredOffers = new List();
+ DateTime utcNow = DateTime.UtcNow;
+
+ foreach (KeyValuePair offer in _offers)
+ {
+ if (offer.Value.LeaseObtained.AddSeconds(OFFER_EXPIRY_SECONDS) > utcNow)
+ {
+ //offer expired
+ expiredOffers.Add(offer.Key);
+ }
+ }
+
+ foreach (ClientIdentifierOption expiredOffer in expiredOffers)
+ _offers.TryRemove(expiredOffer, out _);
+ }
+ catch (Exception ex)
+ {
+ LogManager log = _log;
+ if (log != null)
+ log.Write(ex);
+ }
+ finally
+ {
+ if (!_disposed)
+ _maintenanceTimer.Change(MAINTENANCE_TIMER_INTERVAL, Timeout.Infinite);
+ }
+ }, null, MAINTENANCE_TIMER_INTERVAL, Timeout.Infinite);
+ }
+ }
+
+ #endregion
+
+ #region internal
+
+ internal bool IsAddressInRange(IPAddress address)
+ {
+ return IsAddressInRange(address, _startingAddress, _endingAddress);
+ }
+
+ internal Lease GetOffer(DhcpMessage request)
+ {
+ if (_leases.TryGetValue(request.ClientIdentifier, out Lease existingLease))
+ {
+ //lease already exists
+ return existingLease;
+ }
+
+ if (_reservedAddresses.TryGetValue(request.ClientIdentifier, out Lease existingReservedAddress))
+ {
+ //reserved address exists
+ Lease reservedOffer = new Lease(request.ClientIdentifier, request.HostName?.HostName, request.ClientHardwareAddress, existingReservedAddress.Address, _leaseTime);
+
+ return _offers.AddOrUpdate(request.ClientIdentifier, reservedOffer, delegate (ClientIdentifierOption key, Lease existingValue)
+ {
+ return reservedOffer;
+ });
+ }
+
+ if (_reservedAddressOffersOnly)
+ return null; //client does not have reserved address as per scope requirements
+
+ Lease dummyOffer = new Lease(request.ClientIdentifier, request.HostName?.HostName, request.ClientHardwareAddress, null, _leaseTime);
+ Lease existingOffer = _offers.GetOrAdd(request.ClientIdentifier, dummyOffer);
+
+ if (dummyOffer != existingOffer)
+ {
+ if (existingOffer.Address == null)
+ return null; //dummy offer so another thread is handling offer; do nothing
+
+ //offer already exists
+ existingOffer.ResetLeaseTime(_leaseTime);
+
+ return existingOffer;
+ }
+
+ //find offer ip address
+ IPAddress offerAddress = null;
+
+ if (request.RequestedIpAddress != null)
+ {
+ //client wish to get this address
+ IPAddress requestedAddress = request.RequestedIpAddress.Address;
+
+ if (IsAddressInRange(requestedAddress) && IsAddressAvailable(ref requestedAddress))
+ offerAddress = requestedAddress;
+ }
+
+ if (offerAddress == null)
+ {
+ //find free address from scope
+ offerAddress = _lastAddressOffered;
+ bool offerAddressWasResetFromEnd = false;
+
+ while (true)
+ {
+ offerAddress = ConvertNumberToIp(ConvertIpToNumber(offerAddress) + 1u);
+
+ if (offerAddress.Equals(_endingAddress))
+ {
+ if (offerAddressWasResetFromEnd)
+ return null; //ip pool exhausted
+
+ offerAddress = _startingAddress;
+ offerAddressWasResetFromEnd = true;
+ continue;
+ }
+
+ if (IsAddressAvailable(ref offerAddress))
+ break;
+ }
+
+ _lastAddressOffered = offerAddress;
+ }
+
+ Lease offerLease = new Lease(request.ClientIdentifier, request.HostName?.HostName, request.ClientHardwareAddress, offerAddress, _leaseTime);
+
+ return _offers.AddOrUpdate(request.ClientIdentifier, offerLease, delegate (ClientIdentifierOption key, Lease existingValue)
+ {
+ return offerLease;
+ });
+ }
+
+ internal Lease GetExistingLeaseOrOffer(DhcpMessage request)
+ {
+ if (_leases.TryGetValue(request.ClientIdentifier, out Lease existingLease))
+ return existingLease;
+
+ if (_offers.TryGetValue(request.ClientIdentifier, out Lease existingOffer))
+ return existingOffer;
+
+ return null;
+ }
+
+ internal List GetOptions(DhcpMessage request, IPAddress interfaceAddress)
+ {
+ List options = new List();
+
+ switch (request.DhcpMessageType.Type)
+ {
+ case DhcpMessageType.Discover:
+ options.Add(new DhcpMessageTypeOption(DhcpMessageType.Offer));
+ break;
+
+ case DhcpMessageType.Request:
+ case DhcpMessageType.Inform:
+ options.Add(new DhcpMessageTypeOption(DhcpMessageType.Ack));
+ break;
+
+ default:
+ return null;
+ }
+
+ options.Add(new ServerIdentifierOption(interfaceAddress));
+
+ switch (request.DhcpMessageType.Type)
+ {
+ case DhcpMessageType.Discover:
+ case DhcpMessageType.Request:
+ options.Add(new IpAddressLeaseTimeOption(_leaseTime));
+ options.Add(new RenewalTimeValueOption(_renewTime));
+ options.Add(new RebindingTimeValueOption(_rebindTime));
+ break;
+ }
+
+ if (request.ParameterRequestList == null)
+ {
+ options.Add(new SubnetMaskOption(_subnetMask));
+ options.Add(new BroadcastAddressOption(_broadcastAddress));
+
+ if (!string.IsNullOrEmpty(_domainName))
+ {
+ options.Add(new DomainNameOption(_domainName));
+
+ if (request.ClientFullyQualifiedDomainName != null)
+ options.Add(GetClientFullyQualifiedDomainNameOption(request));
+ }
+
+ if (_autoRouter)
+ options.Add(new RouterOption(new IPAddress[] { interfaceAddress }));
+ else if (_routerAddress != null)
+ options.Add(new RouterOption(new IPAddress[] { _routerAddress }));
+
+ if (_autoDnsServer)
+ options.Add(new DomainNameServerOption(new IPAddress[] { interfaceAddress }));
+ else if (_dnsServers != null)
+ options.Add(new DomainNameServerOption(_dnsServers));
+
+ if (_winsServers != null)
+ options.Add(new NetBiosNameServerOption(_winsServers));
+
+ if (_ntpServers != null)
+ options.Add(new NetworkTimeProtocolServersOption(_ntpServers));
+
+ if (_staticRoutes != null)
+ options.Add(new ClasslessStaticRouteOption(_staticRoutes));
+ }
+ else
+ {
+ foreach (DhcpOptionCode optionCode in request.ParameterRequestList.OptionCodes)
+ {
+ switch (optionCode)
+ {
+ case DhcpOptionCode.SubnetMask:
+ options.Add(new SubnetMaskOption(_subnetMask));
+ options.Add(new BroadcastAddressOption(_broadcastAddress));
+ break;
+
+ case DhcpOptionCode.DomainName:
+ if (!string.IsNullOrEmpty(_domainName))
+ {
+ options.Add(new DomainNameOption(_domainName));
+
+ if (request.ClientFullyQualifiedDomainName != null)
+ options.Add(GetClientFullyQualifiedDomainNameOption(request));
+ }
+
+ break;
+
+ case DhcpOptionCode.Router:
+ if (_autoRouter)
+ options.Add(new RouterOption(new IPAddress[] { interfaceAddress }));
+ else if (_routerAddress != null)
+ options.Add(new RouterOption(new IPAddress[] { _routerAddress }));
+
+ break;
+
+ case DhcpOptionCode.DomainNameServer:
+ if (_autoDnsServer)
+ options.Add(new DomainNameServerOption(new IPAddress[] { interfaceAddress }));
+ else if (_dnsServers != null)
+ options.Add(new DomainNameServerOption(_dnsServers));
+
+ break;
+
+ case DhcpOptionCode.NetBiosOverTcpIpNameServer:
+ if (_winsServers != null)
+ options.Add(new NetBiosNameServerOption(_winsServers));
+
+ break;
+
+ case DhcpOptionCode.NetworkTimeProtocolServers:
+ if (_ntpServers != null)
+ options.Add(new NetworkTimeProtocolServersOption(_ntpServers));
+
+ break;
+
+ case DhcpOptionCode.ClasslessStaticRoute:
+ if (_staticRoutes != null)
+ options.Add(new ClasslessStaticRouteOption(_staticRoutes));
+
+ break;
+ }
+ }
+ }
+
+ options.Add(DhcpOption.CreateEndOption());
+
+ return options;
+ }
+
+ internal void CommitLease(Lease lease)
+ {
+ lease.ResetLeaseTime(_leaseTime);
+
+ _leases.AddOrUpdate(lease.ClientIdentifier, lease, delegate (ClientIdentifierOption key, Lease existingValue)
+ {
+ return lease;
+ });
+ }
+
+ internal void ReleaseLease(Lease lease)
+ {
+ _leases.TryRemove(lease.ClientIdentifier, out _);
+ }
+
+ internal void SetActive(bool isActive)
+ {
+ _isActive = isActive;
+
+ if (!_isActive)
+ _interfaceAddress = null; //remove interface address on deactivation to allow finding it back on activation
+ }
+
+ #endregion
+
+ #region public
+
+ public void ChangeNetwork(IPAddress startingAddress, IPAddress endingAddress, IPAddress subnetMask)
+ {
+ if (startingAddress.AddressFamily != AddressFamily.InterNetwork)
+ throw new ArgumentException("Address family not supported.", "startingAddress");
+
+ if (endingAddress.AddressFamily != AddressFamily.InterNetwork)
+ throw new ArgumentException("Address family not supported.", "endingAddress");
+
+ if (subnetMask.AddressFamily != AddressFamily.InterNetwork)
+ throw new ArgumentException("Address family not supported.", "subnetMask");
+
+ uint startingAddressNumber = ConvertIpToNumber(startingAddress);
+ uint endingAddressNumber = ConvertIpToNumber(endingAddress);
+
+ if (startingAddressNumber >= endingAddressNumber)
+ throw new ArgumentException("Ending address must be greater than starting address.", "endingAddress");
+
+ _startingAddress = startingAddress;
+ _endingAddress = endingAddress;
+ _subnetMask = subnetMask;
+
+ //compute other parameters
+ uint subnetMaskNumber = ConvertIpToNumber(_subnetMask);
+ uint networkAddressNumber = startingAddressNumber & subnetMaskNumber;
+
+ _networkAddress = ConvertNumberToIp(networkAddressNumber);
+ _broadcastAddress = ConvertNumberToIp(networkAddressNumber | ~subnetMaskNumber);
+
+ _lastAddressOffered = _startingAddress;
+ }
+
+ public void AddExclusion(IPAddress startingAddress, IPAddress endingAddress)
+ {
+ if (!IsAddressInRange(startingAddress))
+ throw new ArgumentOutOfRangeException("startingAddress", "Exclusion address must be in scope range.");
+
+ if (!IsAddressInRange(endingAddress))
+ throw new ArgumentOutOfRangeException("endingAddress", "Exclusion address must be in scope range.");
+
+ lock (_exclusions)
+ {
+ foreach (Exclusion exclusion in _exclusions)
+ {
+ if (IsAddressInRange(startingAddress, exclusion.StartingAddress, exclusion.EndingAddress))
+ throw new ArgumentException("Exclusion range overlaps existing exclusion.");
+
+ if (IsAddressInRange(endingAddress, exclusion.StartingAddress, exclusion.EndingAddress))
+ throw new ArgumentException("Exclusion range overlaps existing exclusion.");
+ }
+
+ _exclusions.Add(new Exclusion(startingAddress, endingAddress));
+ }
+ }
+
+ public bool RemoveExclusion(IPAddress startingAddress, IPAddress endingAddress)
+ {
+ lock (_exclusions)
+ {
+ Exclusion exclusionFound = null;
+
+ foreach (Exclusion exclusion in _exclusions)
+ {
+ if (exclusion.StartingAddress.Equals(startingAddress) && exclusion.EndingAddress.Equals(endingAddress))
+ {
+ exclusionFound = exclusion;
+ break;
+ }
+ }
+
+ if (exclusionFound == null)
+ return false;
+
+ return _exclusions.Remove(exclusionFound);
+ }
+ }
+
+ public void AddReservedAddress(byte[] hardwareAddress, IPAddress address)
+ {
+ if (!IsAddressInRange(address))
+ throw new ArgumentOutOfRangeException("address", "Reserved address must be in scope range.");
+
+ Lease reservedLease = new Lease(hardwareAddress, address, _leaseTime);
+
+ _reservedAddresses.AddOrUpdate(new ClientIdentifierOption(1, hardwareAddress), reservedLease, delegate (ClientIdentifierOption key, Lease existingValue)
+ {
+ return reservedLease;
+ });
+ }
+
+ public bool RemoveReservedAddress(byte[] hardwareAddress)
+ {
+ return _reservedAddresses.TryRemove(new ClientIdentifierOption(1, hardwareAddress), out _);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is null)
+ return false;
+
+ if (ReferenceEquals(this, obj))
+ return true;
+
+ return Equals(obj as Scope);
+ }
+
+ public bool Equals(Scope other)
+ {
+ if (other is null)
+ return false;
+
+ if (!_startingAddress.Equals(other._startingAddress))
+ return false;
+
+ if (!_endingAddress.Equals(other._endingAddress))
+ return false;
+
+ if (!_subnetMask.Equals(other._subnetMask))
+ return false;
+
+ return true;
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = 206027136;
+ hashCode = hashCode * -1521134295 + _startingAddress.GetHashCode();
+ hashCode = hashCode * -1521134295 + _endingAddress.GetHashCode();
+ hashCode = hashCode * -1521134295 + _subnetMask.GetHashCode();
+ return hashCode;
+ }
+
+ public override string ToString()
+ {
+ return _name;
+ }
+
+ #endregion
+
+ #region properties
+
+ public string Name
+ {
+ get { return _name; }
+ set { _name = value; }
+ }
+
+ public bool Enabled
+ {
+ get { return _enabled; }
+ set { _enabled = value; }
+ }
+
+ public IPAddress StartingAddress
+ { get { return _startingAddress; } }
+
+ public IPAddress EndingAddress
+ { get { return _endingAddress; } }
+
+ public IPAddress SubnetMask
+ { get { return _subnetMask; } }
+
+ public string DomainName
+ {
+ get { return _domainName; }
+ set { _domainName = value; }
+ }
+
+ public IPAddress RouterAddress
+ {
+ get { return _routerAddress; }
+ set { _routerAddress = value; }
+ }
+
+ public IPAddress[] DnsServers
+ {
+ get { return _dnsServers; }
+ set { _dnsServers = value; }
+ }
+
+ public IPAddress[] WinsServers
+ {
+ get { return _winsServers; }
+ set { _winsServers = value; }
+ }
+
+ public IPAddress[] NtpServers
+ {
+ get { return _ntpServers; }
+ set { _ntpServers = value; }
+ }
+
+ public ClasslessStaticRouteOption.Route[] StaticRoutes
+ {
+ get { return _staticRoutes; }
+ set { _staticRoutes = value; }
+ }
+
+ public uint LeaseTime
+ {
+ get { return _leaseTime; }
+ set
+ {
+ _leaseTime = value;
+ _renewTime = _leaseTime / 2;
+ _rebindTime = Convert.ToUInt32(_leaseTime * 0.875);
+ }
+ }
+
+ public ushort DelayTime
+ {
+ get { return _delayTime; }
+ set { _delayTime = value; }
+ }
+
+ public bool AutoRouter
+ {
+ get { return _autoRouter; }
+ set { _autoRouter = value; }
+ }
+
+ public bool AutoDnsServer
+ {
+ get { return _autoDnsServer; }
+ set { _autoDnsServer = value; }
+ }
+
+ public bool ReservedAddressOffersOnly
+ {
+ get { return _reservedAddressOffersOnly; }
+ set { _reservedAddressOffersOnly = value; }
+ }
+
+ public Exclusion[] Exclusions
+ {
+ get
+ {
+ lock (_exclusions)
+ {
+ return _exclusions.ToArray();
+ }
+ }
+ }
+
+ public ICollection ReservedAddresses
+ { get { return _reservedAddresses.Values; } }
+
+ public ICollection Leases
+ { get { return _leases.Values; } }
+
+ public IPAddress NetworkAddress
+ { get { return _networkAddress; } }
+
+ public IPAddress BroadcastAddress
+ { get { return _broadcastAddress; } }
+
+ public bool IsActive
+ { get { return _isActive; } }
+
+ public IPAddress InterfaceAddress
+ {
+ get
+ {
+ if (_interfaceAddress == null)
+ {
+ uint networkAddressNumber = ConvertIpToNumber(_networkAddress);
+ uint subnetMaskNumber = ConvertIpToNumber(_subnetMask);
+
+ foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
+ {
+ if (nic.OperationalStatus != OperationalStatus.Up)
+ continue;
+
+ IPInterfaceProperties ipInterface = nic.GetIPProperties();
+
+ foreach (UnicastIPAddressInformation ip in ipInterface.UnicastAddresses)
+ {
+ if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ uint addressNumber = ConvertIpToNumber(ip.Address);
+
+ if ((addressNumber & subnetMaskNumber) == networkAddressNumber)
+ return ip.Address;
+ }
+ }
+ }
+
+ _interfaceAddress = IPAddress.Any;
+ }
+
+ return _interfaceAddress;
+ }
+ }
+
+ internal LogManager LogManager
+ {
+ get { return _log; }
+ set { _log = value; }
+ }
+
+ #endregion
+ }
+}