/* Technitium DNS Server Copyright (C) 2024 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 DnsServerCore.Dns; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using TechnitiumLibrary; using TechnitiumLibrary.IO; using TechnitiumLibrary.Net; using TechnitiumLibrary.Net.Dns; using TechnitiumLibrary.Net.Dns.ResourceRecords; namespace DnsServerCore.Dhcp { public sealed class Scope : IComparable, IDisposable { #region variables //required parameters string _name; bool _enabled; IPAddress _startingAddress; IPAddress _endingAddress; IPAddress _subnetMask; ushort _leaseTimeDays = 1; //default 1 day lease byte _leaseTimeHours = 0; byte _leaseTimeMinutes = 0; ushort _offerDelayTime; readonly LogManager _log; readonly DhcpServer _dhcpServer; bool _pingCheckEnabled; ushort _pingCheckTimeout = 1000; byte _pingCheckRetries = 2; //dhcp options string _domainName; IReadOnlyCollection _domainSearchList; bool _dnsUpdates = true; uint _dnsTtl = 900; IPAddress _serverAddress; string _serverHostName; string _bootFileName; IPAddress _routerAddress; bool _useThisDnsServer; IReadOnlyCollection _dnsServers; IReadOnlyCollection _winsServers; IReadOnlyCollection _ntpServers; IReadOnlyCollection _ntpServerDomainNames; IReadOnlyCollection _staticRoutes; IReadOnlyDictionary _vendorInfo; IReadOnlyCollection _capwapAcIpAddresses; IReadOnlyCollection _tftpServerAddreses; //advanced options IReadOnlyCollection _genericOptions; IReadOnlyCollection _exclusions; readonly ConcurrentDictionary _reservedLeases = new ConcurrentDictionary(); bool _allowOnlyReservedLeases; bool _blockLocallyAdministeredMacAddresses; bool _ignoreClientIdentifierOption; //leases readonly ConcurrentDictionary _leases = new ConcurrentDictionary(); //internal computed parameters IPAddress _networkAddress; IPAddress _broadcastAddress; //internal parameters const int OFFER_EXPIRY_SECONDS = 60; //1 mins offer expiry readonly ConcurrentDictionary _offers = new ConcurrentDictionary(); IPAddress _lastAddressOffered; readonly SemaphoreSlim _lastAddressOfferedLock = new SemaphoreSlim(1, 1); IPAddress _interfaceAddress; int _interfaceIndex; DateTime _lastModified = DateTime.UtcNow; #endregion #region constructor public Scope(string name, bool enabled, IPAddress startingAddress, IPAddress endingAddress, IPAddress subnetMask, LogManager log, DhcpServer dhcpServer) { ValidateScopeName(name); _name = name; _enabled = enabled; ChangeNetwork(startingAddress, endingAddress, subnetMask); _log = log; _dhcpServer = dhcpServer; } public Scope(BinaryReader bR, LogManager log, DhcpServer dhcpServer) { if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "SC") throw new InvalidDataException("DhcpServer scope file format is invalid."); _log = log; _dhcpServer = dhcpServer; byte version = bR.ReadByte(); switch (version) { case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: _name = bR.ReadShortString(); _enabled = bR.ReadBoolean(); ChangeNetwork(IPAddressExtensions.ReadFrom(bR), IPAddressExtensions.ReadFrom(bR), IPAddressExtensions.ReadFrom(bR)); _leaseTimeDays = bR.ReadUInt16(); _leaseTimeHours = bR.ReadByte(); _leaseTimeMinutes = bR.ReadByte(); _offerDelayTime = bR.ReadUInt16(); if (version >= 5) { _pingCheckEnabled = bR.ReadBoolean(); _pingCheckTimeout = bR.ReadUInt16(); _pingCheckRetries = bR.ReadByte(); } _domainName = bR.ReadShortString(); if (string.IsNullOrWhiteSpace(_domainName)) _domainName = null; if (version >= 7) { int count = bR.ReadByte(); if (count > 0) { string[] domainSearchStrings = new string[count]; for (int i = 0; i < count; i++) domainSearchStrings[i] = bR.ReadShortString(); _domainSearchList = domainSearchStrings; } _dnsUpdates = bR.ReadBoolean(); } _dnsTtl = bR.ReadUInt32(); if (version >= 2) { _serverAddress = IPAddressExtensions.ReadFrom(bR); if (_serverAddress.Equals(IPAddress.Any)) _serverAddress = null; } if (version >= 3) { _serverHostName = bR.ReadShortString(); if (string.IsNullOrEmpty(_serverHostName)) _serverHostName = null; _bootFileName = bR.ReadShortString(); if (string.IsNullOrEmpty(_bootFileName)) _bootFileName = null; } _routerAddress = IPAddressExtensions.ReadFrom(bR); if (_routerAddress.Equals(IPAddress.Any)) _routerAddress = null; { int count = bR.ReadByte(); if (count > 0) { if (count == 255) { _useThisDnsServer = true; FindThisDnsServerAddress(); } else { IPAddress[] dnsServers = new IPAddress[count]; for (int i = 0; i < count; i++) dnsServers[i] = IPAddressExtensions.ReadFrom(bR); _dnsServers = dnsServers; } } } { int count = bR.ReadByte(); if (count > 0) { IPAddress[] winsServers = new IPAddress[count]; for (int i = 0; i < count; i++) winsServers[i] = IPAddressExtensions.ReadFrom(bR); _winsServers = winsServers; } } { int count = bR.ReadByte(); if (count > 0) { IPAddress[] ntpServers = new IPAddress[count]; for (int i = 0; i < count; i++) ntpServers[i] = IPAddressExtensions.ReadFrom(bR); _ntpServers = ntpServers; } } if (version >= 7) { int count = bR.ReadByte(); if (count > 0) { string[] ntpServerDomainNames = new string[count]; for (int i = 0; i < count; i++) ntpServerDomainNames[i] = bR.ReadShortString(); _ntpServerDomainNames = ntpServerDomainNames; } } { int count = bR.ReadByte(); if (count > 0) { ClasslessStaticRouteOption.Route[] staticRoutes = new ClasslessStaticRouteOption.Route[count]; for (int i = 0; i < count; i++) staticRoutes[i] = new ClasslessStaticRouteOption.Route(bR.BaseStream); _staticRoutes = staticRoutes; } } if (version >= 4) { int count = bR.ReadByte(); if (count > 0) { Dictionary vendorInfo = new Dictionary(count); for (int i = 0; i < count; i++) { string vendorClassIdentifier = bR.ReadShortString(); VendorSpecificInformationOption vendorSpecificInformation = new VendorSpecificInformationOption(bR.ReadBuffer()); vendorInfo.Add(vendorClassIdentifier, vendorSpecificInformation); } _vendorInfo = vendorInfo; } } if (version >= 7) { int count = bR.ReadByte(); if (count > 0) { IPAddress[] capwapAcIpAddresses = new IPAddress[count]; for (int i = 0; i < count; i++) capwapAcIpAddresses[i] = IPAddressExtensions.ReadFrom(bR); _capwapAcIpAddresses = capwapAcIpAddresses; } } if (version >= 8) { int count = bR.ReadByte(); if (count > 0) { IPAddress[] tftpServerAddreses = new IPAddress[count]; for (int i = 0; i < count; i++) tftpServerAddreses[i] = IPAddressExtensions.ReadFrom(bR); _tftpServerAddreses = tftpServerAddreses; } } if (version >= 8) { int count = bR.ReadByte(); if (count > 0) { DhcpOption[] genericOptions = new DhcpOption[count]; for (int i = 0; i < count; i++) { DhcpOptionCode code = (DhcpOptionCode)bR.ReadByte(); short length = bR.ReadInt16(); byte[] value = bR.ReadBytes(length); genericOptions[i] = new DhcpOption(code, value); } _genericOptions = genericOptions; } } { int count = bR.ReadByte(); if (count > 0) { Exclusion[] exclusions = new Exclusion[count]; for (int i = 0; i < count; i++) exclusions[i] = new Exclusion(IPAddressExtensions.ReadFrom(bR), IPAddressExtensions.ReadFrom(bR)); _exclusions = exclusions; } } { int count = bR.ReadInt32(); if (count > 0) { for (int i = 0; i < count; i++) { Lease reservedLease = new Lease(bR); _reservedLeases.TryAdd(reservedLease.ClientIdentifier, reservedLease); } } _allowOnlyReservedLeases = bR.ReadBoolean(); } if (version >= 6) _blockLocallyAdministeredMacAddresses = bR.ReadBoolean(); else _blockLocallyAdministeredMacAddresses = false; if (version >= 9) _ignoreClientIdentifierOption = bR.ReadBoolean(); else _ignoreClientIdentifierOption = false; { int count = bR.ReadInt32(); if (count > 0) { for (int i = 0; i < count; i++) { Lease lease = new Lease(bR); _leases.TryAdd(lease.ClientIdentifier, lease); } } } break; default: throw new InvalidDataException("Scope data format version not supported."); } } #endregion #region IDisposable bool _disposed; public void Dispose() { if (_disposed) return; if (_lastAddressOfferedLock is not null) _lastAddressOfferedLock.Dispose(); _disposed = true; } #endregion #region static internal static void ValidateScopeName(string name) { foreach (char invalidChar in Path.GetInvalidFileNameChars()) { if (name.Contains(invalidChar)) throw new DhcpServerException("The scope name contains an invalid character: " + invalidChar); } } private static bool IsAddressInRange(IPAddress address, IPAddress startingAddress, IPAddress endingAddress) { uint addressNumber = address.ConvertIpToNumber(); uint startingAddressNumber = startingAddress.ConvertIpToNumber(); uint endingAddressNumber = endingAddress.ConvertIpToNumber(); return (startingAddressNumber <= addressNumber) && (addressNumber <= endingAddressNumber); } private static void ValidateIpv4(IReadOnlyCollection value, string paramName) { if (value is not null) { foreach (IPAddress ip in value) { if (ip.AddressFamily != AddressFamily.InterNetwork) throw new ArgumentException("The address must be an IPv4 address: " + ip.ToString(), paramName); } } } private static void ValidateIpv4(IPAddress value, string paramName) { if ((value is not null) && (value.AddressFamily != AddressFamily.InterNetwork)) throw new ArgumentException("The address must be an IPv4 address: " + value.ToString(), paramName); } #endregion #region private private uint GetLeaseTime() { return Convert.ToUInt32((_leaseTimeDays * 24 * 60 * 60) + (_leaseTimeHours * 60 * 60) + (_leaseTimeMinutes * 60)); } private async Task IsAddressAvailableAsync(IPAddress address) { if (address.Equals(_routerAddress)) return AddressStatus.FALSE; if ((_dnsServers != null) && _dnsServers.Contains(address)) return AddressStatus.FALSE; if ((_winsServers != null) && _winsServers.Contains(address)) return AddressStatus.FALSE; if ((_ntpServers != null) && _ntpServers.Contains(address)) return AddressStatus.FALSE; if (_exclusions != null) { foreach (Exclusion exclusion in _exclusions) { if (IsAddressInRange(address, exclusion.StartingAddress, exclusion.EndingAddress)) return new AddressStatus(false, exclusion.EndingAddress); } } foreach (KeyValuePair reservedLease in _reservedLeases) { if (address.Equals(reservedLease.Value.Address)) return AddressStatus.FALSE; } foreach (KeyValuePair lease in _leases) { if (address.Equals(lease.Value.Address)) return AddressStatus.FALSE; } foreach (KeyValuePair offer in _offers) { if (address.Equals(offer.Value.Address)) return AddressStatus.FALSE; } if (_pingCheckEnabled) { try { using (Ping ping = new Ping()) { int retry = 0; do { PingReply reply = await ping.SendPingAsync(address, _pingCheckTimeout); if (reply.Status == IPStatus.Success) return AddressStatus.FALSE; //address is in use } while (++retry < _pingCheckRetries); } } catch { } } return AddressStatus.TRUE; } private bool IsAddressAlreadyAllocated(IPAddress address, ClientIdentifierOption clientIdentifier) { foreach (KeyValuePair lease in _leases) { if (address.Equals(lease.Value.Address)) return !lease.Key.Equals(clientIdentifier); } foreach (KeyValuePair offer in _offers) { if (address.Equals(offer.Value.Address)) return !offer.Key.Equals(clientIdentifier); } return false; } private ClientFullyQualifiedDomainNameOption GetClientFullyQualifiedDomainNameOption(DhcpMessage request, string reservedLeaseHostName) { 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 clientDomainName; if (!string.IsNullOrWhiteSpace(reservedLeaseHostName)) { //domain name override by server clientDomainName = reservedLeaseHostName + "." + _domainName; } else if (string.IsNullOrWhiteSpace(request.ClientFullyQualifiedDomainName.DomainName)) { //client domain empty and expects server for a fqdn domain name if (request.HostName is null) return null; //server unable to decide a name for client clientDomainName = request.HostName.HostName + "." + _domainName; } else if (request.ClientFullyQualifiedDomainName.DomainName.Contains('.')) { //client domain is fqdn if (request.ClientFullyQualifiedDomainName.DomainName.EndsWith("." + _domainName, StringComparison.OrdinalIgnoreCase)) { clientDomainName = request.ClientFullyQualifiedDomainName.DomainName; } else { string[] parts = request.ClientFullyQualifiedDomainName.DomainName.Split('.'); clientDomainName = parts[0] + "." + _domainName; } } else { //client domain is just hostname clientDomainName = request.ClientFullyQualifiedDomainName.DomainName + "." + _domainName; } return new ClientFullyQualifiedDomainNameOption(responseFlags, 255, 255, clientDomainName); } private void ConvertToReservedLease(Lease lease) { //convert dynamic to reserved lease lease.ConvertToReserved(); //add reserved lease Lease reservedLease = new Lease(LeaseType.Reserved, null, DhcpMessageHardwareAddressType.Ethernet, lease.HardwareAddress, lease.Address, null); _reservedLeases[reservedLease.ClientIdentifier] = reservedLease; } private void ConvertToDynamicLease(Lease lease) { //convert reserved to dynamic lease lease.ConvertToDynamic(); //remove reserved lease Lease reservedLease = new Lease(LeaseType.Reserved, null, DhcpMessageHardwareAddressType.Ethernet, lease.HardwareAddress, lease.Address, null); _reservedLeases.TryRemove(reservedLease.ClientIdentifier, out _); //remove any old single address exclusion entry if (_exclusions != null) { foreach (Exclusion exclusion in _exclusions) { if (exclusion.StartingAddress.Equals(lease.Address) && exclusion.EndingAddress.Equals(lease.Address)) { //remove single address exclusion entry if (_exclusions.Count == 1) { _exclusions = null; } else { List exclusions = new List(); foreach (Exclusion exc in _exclusions) { if (exc.Equals(exclusion)) continue; exclusions.Add(exc); } _exclusions = exclusions; } break; } } } } #endregion #region internal internal bool FindInterface() { //find network with static ip address in scope range uint networkAddressNumber = _networkAddress.ConvertIpToNumber(); uint subnetMaskNumber = _subnetMask.ConvertIpToNumber(); 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 = ip.Address.ConvertIpToNumber(); if ((addressNumber & subnetMaskNumber) == networkAddressNumber) { //found interface for this scope range try { //check if interface has dynamic ipv4 address assigned via dhcp if (!OperatingSystem.IsMacOS()) { foreach (IPAddress dhcpServerAddress in ipInterface.DhcpServerAddresses) { if (dhcpServerAddress.AddressFamily == AddressFamily.InterNetwork) throw new DhcpServerException("DHCP Server requires static IP address to work correctly but the network interface was found to have a dynamic IP address [" + ip.Address.ToString() + "] assigned by another DHCP server: " + dhcpServerAddress.ToString()); } } } catch (PlatformNotSupportedException) { //DhcpServerAddresses() not supported on macOs //ignore the exception } _interfaceAddress = ip.Address; _interfaceIndex = ipInterface.GetIPv4Properties().Index; return true; } } } } try { if (!OperatingSystem.IsMacOS()) { //check if at least one interface has static ip address 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) { //check if address is static if (ipInterface.DhcpServerAddresses.Count < 1) { //found static ip address so this scope can be activated //using ANY ip address for this scope interface since we dont know the relay agent network _interfaceAddress = IPAddress.Any; _interfaceIndex = -1; return true; } } } } } } catch (PlatformNotSupportedException) { //DhcpServerAddresses() not supported on macOs //ignore the exception } //server has no static ip address configured return false; } internal void FindThisDnsServerAddress() { uint networkAddressNumber = _networkAddress.ConvertIpToNumber(); uint subnetMaskNumber = _subnetMask.ConvertIpToNumber(); DnsServer dnsServer = _dhcpServer.DnsServer; if (dnsServer is not null) { bool dnsOnAny = false; foreach (IPEndPoint localEP in dnsServer.LocalEndPoints) { if (localEP.Address.Equals(IPAddress.Any)) { dnsOnAny = true; break; } } if (!dnsOnAny) { //find local EP in scope network range foreach (IPEndPoint localEP in dnsServer.LocalEndPoints) { if (localEP.Address.AddressFamily == AddressFamily.InterNetwork) { uint addressNumber = localEP.Address.ConvertIpToNumber(); if ((addressNumber & subnetMaskNumber) == networkAddressNumber) { //found address in this scope range to use as dns server _dnsServers = new IPAddress[] { localEP.Address }; return; } } } //find any local EP available foreach (IPEndPoint localEP in dnsServer.LocalEndPoints) { if ((localEP.Address.AddressFamily == AddressFamily.InterNetwork) && !IPAddress.IsLoopback(localEP.Address)) { //found address to use as dns server _dnsServers = new IPAddress[] { localEP.Address }; return; } } //no useable address was found _dnsServers = null; return; } } NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); //find interface in current scope network range foreach (NetworkInterface nic in networkInterfaces) { if (nic.OperationalStatus != OperationalStatus.Up) continue; IPInterfaceProperties ipInterface = nic.GetIPProperties(); foreach (UnicastIPAddressInformation ip in ipInterface.UnicastAddresses) { if (ip.Address.AddressFamily == AddressFamily.InterNetwork) { uint addressNumber = ip.Address.ConvertIpToNumber(); if ((addressNumber & subnetMaskNumber) == networkAddressNumber) { //found address in this scope range to use as dns server _dnsServers = new IPAddress[] { ip.Address }; return; } } } } //find unicast ip address on an interface which has gateway foreach (NetworkInterface nic in networkInterfaces) { if (nic.OperationalStatus != OperationalStatus.Up) continue; IPInterfaceProperties ipInterface = nic.GetIPProperties(); if (ipInterface.GatewayAddresses.Count > 0) { foreach (UnicastIPAddressInformation ip in ipInterface.UnicastAddresses) { if (ip.Address.AddressFamily == AddressFamily.InterNetwork) { //use this address for dns _dnsServers = new IPAddress[] { ip.Address }; return; } } } } //find any unicast ip address available foreach (NetworkInterface nic in networkInterfaces) { if (nic.OperationalStatus != OperationalStatus.Up) continue; IPInterfaceProperties ipInterface = nic.GetIPProperties(); foreach (UnicastIPAddressInformation ip in ipInterface.UnicastAddresses) { if (ip.Address.AddressFamily == AddressFamily.InterNetwork) { //use this address for dns _dnsServers = new IPAddress[] { ip.Address }; return; } } } //no useable address was found _dnsServers = null; } internal bool IsAddressInRange(IPAddress address) { return IsAddressInRange(address, _startingAddress, _endingAddress); } internal bool IsAddressInNetwork(IPAddress address) { uint addressNumber = address.ConvertIpToNumber(); uint networkAddressNumber = _networkAddress.ConvertIpToNumber(); uint broadcastAddressNumber = _broadcastAddress.ConvertIpToNumber(); return (networkAddressNumber < addressNumber) && (addressNumber < broadcastAddressNumber); } internal bool IsAddressExcluded(IPAddress address) { if (_exclusions != null) { foreach (Exclusion exclusion in _exclusions) { if (IsAddressInRange(address, exclusion.StartingAddress, exclusion.EndingAddress)) return true; } } return false; } internal bool IsAddressReserved(IPAddress address) { foreach (KeyValuePair reservedLease in _reservedLeases) { if (address.Equals(reservedLease.Value.Address)) return true; } return false; } internal Lease GetReservedLease(DhcpMessage request) { return GetReservedLease(new ClientIdentifierOption((byte)request.HardwareAddressType, request.ClientHardwareAddress), request.GetClientIdentifier(_ignoreClientIdentifierOption)); } private Lease GetReservedLease(ClientIdentifierOption reservedLeasesClientIdentifier, ClientIdentifierOption clientIdentifier) { if (_reservedLeases.TryGetValue(reservedLeasesClientIdentifier, out Lease reservedLease)) { //reserved address exists if (IsAddressAlreadyAllocated(reservedLease.Address, clientIdentifier)) { //reserved lease address is already allocated so ignore reserved lease if (_log is not null) _log.Write("DHCP Server cannot allocate reserved lease [" + reservedLease.Address.ToString() + "] to " + BitConverter.ToString(reservedLeasesClientIdentifier.Identifier) + " for scope '" + _name + "': The IP address is already allocated."); return null; } return reservedLease; } return null; } internal async Task GetOfferAsync(DhcpMessage request) { ClientIdentifierOption clientIdentifier = request.GetClientIdentifier(_ignoreClientIdentifierOption); if (_leases.TryGetValue(clientIdentifier, out Lease existingLease)) { //lease already exists if (existingLease.Type == LeaseType.Reserved) { Lease existingReservedLease = GetReservedLease(request); if ((existingReservedLease is not null) && (existingReservedLease.Address == existingLease.Address)) return existingLease; //return existing reserved lease //reserved lease address was changed; proceed to offer new lease } else { //is dynamic lease if (IsAddressExcluded(existingLease.Address)) { //remove existing dynamic lease; proceed to offer new lease ReleaseLease(existingLease); } else { //return existing dynamic lease return existingLease; } } } Lease reservedLease = GetReservedLease(request); if (reservedLease != null) { Lease reservedOffer = new Lease(LeaseType.Reserved, clientIdentifier, null, request.ClientHardwareAddress, reservedLease.Address, null, GetLeaseTime()); _offers[clientIdentifier] = reservedOffer; return reservedOffer; } if (_allowOnlyReservedLeases) { if (_log is not null) _log.Write("DHCP Server failed to offer IP address to " + request.GetClientFullIdentifier() + " for scope '" + _name + "': the scope allows only reserved lease allocations."); return null; } if (_blockLocallyAdministeredMacAddresses) { if ((request.HardwareAddressType == DhcpMessageHardwareAddressType.Ethernet) && ((request.ClientHardwareAddress[0] & 0x02) > 0)) { if (_log is not null) _log.Write("DHCP Server failed to offer IP address to " + request.GetClientFullIdentifier() + " for scope '" + _name + "': the scope does not allow locally administered MAC addresses."); return null; } } Lease dummyOffer = new Lease(LeaseType.None, null, null, null, null, null, 0); Lease existingOffer = _offers.GetOrAdd(clientIdentifier, dummyOffer); if (dummyOffer != existingOffer) { if (existingOffer.Type == LeaseType.None) return null; //dummy offer so another thread is handling offer; do nothing //offer already exists existingOffer.ExtendLease(GetLeaseTime()); 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)) { AddressStatus addressStatus = await IsAddressAvailableAsync(requestedAddress); if (addressStatus.IsAddressAvailable) offerAddress = requestedAddress; } } if (offerAddress is null) { await _lastAddressOfferedLock.WaitAsync(); try { //find free address from scope offerAddress = _lastAddressOffered; uint endingAddressNumber = _endingAddress.ConvertIpToNumber(); bool offerAddressWasResetFromEnd = false; while (true) { uint nextOfferAddressNumber = offerAddress.ConvertIpToNumber() + 1u; if (nextOfferAddressNumber > endingAddressNumber) { if (offerAddressWasResetFromEnd) { if (_log is not null) _log.Write("DHCP Server failed to offer IP address to " + request.GetClientFullIdentifier() + " for scope '" + _name + "': address unavailable due to address pool exhaustion."); return null; } offerAddress = IPAddressExtensions.ConvertNumberToIp(_startingAddress.ConvertIpToNumber() - 1u); offerAddressWasResetFromEnd = true; continue; } offerAddress = IPAddressExtensions.ConvertNumberToIp(nextOfferAddressNumber); AddressStatus addressStatus = await IsAddressAvailableAsync(offerAddress); if (addressStatus.IsAddressAvailable) break; if (addressStatus.NewAddress is not null) offerAddress = addressStatus.NewAddress; } _lastAddressOffered = offerAddress; } finally { _lastAddressOfferedLock.Release(); } } Lease offerLease = new Lease(LeaseType.Dynamic, clientIdentifier, null, request.ClientHardwareAddress, offerAddress, null, GetLeaseTime()); return _offers[clientIdentifier] = offerLease; } internal Lease GetExistingLeaseOrOffer(DhcpMessage request) { ClientIdentifierOption clientIdentifier = request.GetClientIdentifier(_ignoreClientIdentifierOption); //check for lease offer first since it may have a different IP address to offer if (_offers.TryGetValue(clientIdentifier, out Lease existingOffer)) return existingOffer; if (_leases.TryGetValue(clientIdentifier, out Lease existingLease)) return existingLease; return null; } internal async Task> GetOptionsAsync(DhcpMessage request, IPAddress serverIdentifierAddress, string reservedLeaseHostName, DnsServer dnsServer) { 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(serverIdentifierAddress)); switch (request.DhcpMessageType.Type) { case DhcpMessageType.Discover: case DhcpMessageType.Request: uint leaseTime = GetLeaseTime(); options.Add(new IpAddressLeaseTimeOption(leaseTime)); options.Add(new RenewalTimeValueOption(leaseTime / 2)); options.Add(new RebindingTimeValueOption(Convert.ToUInt32(leaseTime * 0.875))); break; } if (request.ParameterRequestList is 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, reservedLeaseHostName)); } if (_domainSearchList is not null) options.Add(new DomainSearchOption(_domainSearchList)); if (_routerAddress is not null) options.Add(new RouterOption(new IPAddress[] { _routerAddress })); if (_dnsServers is not null) options.Add(new DomainNameServerOption(_dnsServers)); if (_winsServers is not null) options.Add(new NetBiosNameServerOption(_winsServers)); if ((_ntpServers is not null) || (_ntpServerDomainNames is not null)) options.Add(await GetNetworkTimeProtocolServersOptionAsync(dnsServer)); if (_staticRoutes is not 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.HostName: if (!string.IsNullOrWhiteSpace(reservedLeaseHostName)) options.Add(new HostNameOption(reservedLeaseHostName)); break; case DhcpOptionCode.DomainName: if (!string.IsNullOrEmpty(_domainName)) { options.Add(new DomainNameOption(_domainName)); if (request.ClientFullyQualifiedDomainName != null) options.Add(GetClientFullyQualifiedDomainNameOption(request, reservedLeaseHostName)); } break; case DhcpOptionCode.DomainSearch: if (_domainSearchList is not null) options.Add(new DomainSearchOption(_domainSearchList)); break; case DhcpOptionCode.Router: if (_routerAddress is not null) options.Add(new RouterOption(new IPAddress[] { _routerAddress })); break; case DhcpOptionCode.DomainNameServer: if (_dnsServers is not null) options.Add(new DomainNameServerOption(_dnsServers)); break; case DhcpOptionCode.NetBiosOverTcpIpNameServer: if (_winsServers is not null) options.Add(new NetBiosNameServerOption(_winsServers)); break; case DhcpOptionCode.NetworkTimeProtocolServers: if ((_ntpServers is not null) || (_ntpServerDomainNames is not null)) options.Add(await GetNetworkTimeProtocolServersOptionAsync(dnsServer)); break; case DhcpOptionCode.ClasslessStaticRoute: if (_staticRoutes is not null) options.Add(new ClasslessStaticRouteOption(_staticRoutes)); break; case DhcpOptionCode.CAPWAPAccessControllerAddresses: if (_capwapAcIpAddresses is not null) options.Add(new CAPWAPAccessControllerOption(_capwapAcIpAddresses)); break; case DhcpOptionCode.TftpServerAddress: if (_tftpServerAddreses is not null) options.Add(new TftpServerAddressOption(_tftpServerAddreses)); break; default: if (_genericOptions is not null) { foreach (DhcpOption genericOption in _genericOptions) { if (optionCode == genericOption.Code) { options.Add(genericOption); break; } } } break; } } } if ((_vendorInfo is not null) && (request.VendorClassIdentifier is not null)) { if (_vendorInfo.TryGetValue(request.VendorClassIdentifier.Identifier, out VendorSpecificInformationOption vendorSpecificInformationOption) || _vendorInfo.TryGetValue("", out vendorSpecificInformationOption)) { options.Add(new VendorClassIdentifierOption(request.VendorClassIdentifier.Identifier)); options.Add(vendorSpecificInformationOption); } else { string match = "substring(vendor-class-identifier,"; foreach (KeyValuePair entry in _vendorInfo) { if (entry.Key.StartsWith(match)) { int i = entry.Key.IndexOf(')', match.Length); if (i < match.Length) continue; string[] parts = entry.Key.Substring(match.Length, i - match.Length).Split(','); if (parts.Length != 2) continue; if (!int.TryParse(parts[0], out int startIndex)) continue; if (!int.TryParse(parts[1], out int length)) continue; if ((startIndex + length) > request.VendorClassIdentifier.Identifier.Length) continue; int j = entry.Key.IndexOf("==", i); if (j < i) continue; string value = entry.Key.Substring(j + 2); value = value.Trim(); value = value.Trim('"'); if (request.VendorClassIdentifier.Identifier.Substring(startIndex, length).Equals(value)) { options.Add(new VendorClassIdentifierOption(value)); options.Add(entry.Value); break; } } } } } options.Add(DhcpOption.CreateEndOption()); return options; } private async Task GetNetworkTimeProtocolServersOptionAsync(DnsServer dnsServer) { if (_ntpServerDomainNames is not null) { Task[] tasks = new Task[_ntpServerDomainNames.Count]; int i = 0; foreach (string ntpServerDomainName in _ntpServerDomainNames) tasks[i++] = dnsServer.DirectQueryAsync(new DnsQuestionRecord(ntpServerDomainName, DnsResourceRecordType.A, DnsClass.IN), 1000); List ntpServers = new List(_ntpServerDomainNames.Count + (_ntpServers is null ? 0 : _ntpServers.Count)); if (_ntpServers is not null) ntpServers.AddRange(_ntpServers); foreach (Task task in tasks) { try { ntpServers.AddRange(DnsClient.ParseResponseA(await task)); } catch { } } return new NetworkTimeProtocolServersOption(ntpServers); } else { return new NetworkTimeProtocolServersOption(_ntpServers); } } internal void CommitLease(Lease lease) { lease.ExtendLease(GetLeaseTime()); _leases[lease.ClientIdentifier] = lease; _offers.TryRemove(lease.ClientIdentifier, out _); _lastModified = DateTime.UtcNow; } internal void ReleaseLease(Lease lease) { _leases.TryRemove(lease.ClientIdentifier, out _); _lastModified = DateTime.UtcNow; } internal void SetEnabled(bool enabled) { _enabled = enabled; if (!enabled) { _interfaceAddress = null; _interfaceIndex = 0; } } internal void RemoveExpiredOffers() { DateTime utcNow = DateTime.UtcNow; foreach (KeyValuePair offer in _offers) { if (utcNow > offer.Value.LeaseObtained.AddSeconds(OFFER_EXPIRY_SECONDS)) { //offer expired _offers.TryRemove(offer.Key, out _); } } } internal List RemoveExpiredLeases() { List expiredLeases = new List(); DateTime utcNow = DateTime.UtcNow; foreach (KeyValuePair lease in _leases) { if (utcNow > lease.Value.LeaseExpires) { //lease expired if (_leases.TryRemove(lease.Key, out Lease expiredLease)) expiredLeases.Add(expiredLease); } } if (expiredLeases.Count > 0) _lastModified = DateTime.UtcNow; return expiredLeases; } #endregion #region public public void ChangeNetwork(IPAddress startingAddress, IPAddress endingAddress, IPAddress subnetMask) { if (startingAddress.AddressFamily != AddressFamily.InterNetwork) throw new ArgumentException("The address must be an IPv4 address: " + startingAddress.ToString(), nameof(startingAddress)); if (endingAddress.AddressFamily != AddressFamily.InterNetwork) throw new ArgumentException("The address must be an IPv4 address: " + endingAddress.ToString(), nameof(endingAddress)); if (subnetMask.AddressFamily != AddressFamily.InterNetwork) throw new ArgumentException("The address must be an IPv4 address: " + subnetMask.ToString(), nameof(subnetMask)); uint startingAddressNumber = startingAddress.ConvertIpToNumber(); uint endingAddressNumber = endingAddress.ConvertIpToNumber(); if (startingAddressNumber >= endingAddressNumber) throw new ArgumentException("Ending address must be greater than starting address."); _startingAddress = startingAddress; _endingAddress = endingAddress; _subnetMask = subnetMask; //compute other parameters uint subnetMaskNumber = _subnetMask.ConvertIpToNumber(); uint networkAddressNumber = startingAddressNumber & subnetMaskNumber; uint broadcastAddressNumber = networkAddressNumber | ~subnetMaskNumber; if (networkAddressNumber == startingAddressNumber) throw new ArgumentException("Starting address cannot be same as the network address."); if (broadcastAddressNumber == endingAddressNumber) throw new ArgumentException("Ending address cannot be same as the broadcast address."); _networkAddress = IPAddressExtensions.ConvertNumberToIp(networkAddressNumber); _broadcastAddress = IPAddressExtensions.ConvertNumberToIp(broadcastAddressNumber); _lastAddressOfferedLock.Wait(); try { _lastAddressOffered = IPAddressExtensions.ConvertNumberToIp(startingAddressNumber - 1u); } finally { _lastAddressOfferedLock.Release(); } } public bool AddReservedLease(Lease reservedLease) { return _reservedLeases.TryAdd(reservedLease.ClientIdentifier, reservedLease); } public bool RemoveReservedLease(string hardwareAddress) { byte[] hardwareAddressBytes = Lease.ParseHardwareAddress(hardwareAddress); ClientIdentifierOption reservedLeaseClientIdentifier = new ClientIdentifierOption((byte)DhcpMessageHardwareAddressType.Ethernet, hardwareAddressBytes); return _reservedLeases.TryRemove(reservedLeaseClientIdentifier, out _); } public Lease RemoveLease(string hardwareAddress) { byte[] hardwareAddressBytes = Lease.ParseHardwareAddress(hardwareAddress); foreach (KeyValuePair entry in _leases) { if (BinaryNumber.Equals(entry.Value.HardwareAddress, hardwareAddressBytes)) return RemoveLease(entry.Key); } throw new DhcpServerException("No lease was found for hardware address: " + hardwareAddress); } public Lease RemoveLease(ClientIdentifierOption clientIdentifier) { if (!_leases.TryRemove(clientIdentifier, out Lease removedLease)) throw new DhcpServerException("No lease was found for client identifier: " + clientIdentifier.ToString()); if (removedLease.Type == LeaseType.Reserved) { //remove reserved lease ClientIdentifierOption reservedLeaseClientIdentifier = new ClientIdentifierOption((byte)DhcpMessageHardwareAddressType.Ethernet, removedLease.HardwareAddress); if (_reservedLeases.TryGetValue(reservedLeaseClientIdentifier, out Lease existingReservedLease)) { //remove reserved lease only if the IP addresses match if (existingReservedLease.Address.Equals(removedLease.Address)) _reservedLeases.TryRemove(reservedLeaseClientIdentifier, out _); } } return removedLease; } public void ConvertToReservedLease(string hardwareAddress) { byte[] hardwareAddressBytes = Lease.ParseHardwareAddress(hardwareAddress); foreach (KeyValuePair entry in _leases) { Lease lease = entry.Value; if ((lease.Type == LeaseType.Dynamic) && BinaryNumber.Equals(lease.HardwareAddress, hardwareAddressBytes)) { ConvertToReservedLease(lease); return; } } throw new DhcpServerException("No dynamic lease was found for hardware address: " + hardwareAddress); } public void ConvertToReservedLease(ClientIdentifierOption clientIdentifier) { if (!_leases.TryGetValue(clientIdentifier, out Lease lease) || (lease.Type != LeaseType.Dynamic)) throw new DhcpServerException("No dynamic lease was found for client identifier: " + clientIdentifier.ToString()); ConvertToReservedLease(lease); } public void ConvertToDynamicLease(string hardwareAddress) { byte[] hardwareAddressBytes = Lease.ParseHardwareAddress(hardwareAddress); foreach (KeyValuePair entry in _leases) { Lease lease = entry.Value; if ((lease.Type == LeaseType.Reserved) && BinaryNumber.Equals(lease.HardwareAddress, hardwareAddressBytes)) { ConvertToDynamicLease(lease); return; } } throw new DhcpServerException("No reserved lease was found for hardware address: " + hardwareAddress); } public void ConvertToDynamicLease(ClientIdentifierOption clientIdentifier) { if (!_leases.TryGetValue(clientIdentifier, out Lease lease) || (lease.Type != LeaseType.Reserved)) throw new DhcpServerException("No reserved lease was found for client identifier: " + clientIdentifier.ToString()); ConvertToDynamicLease(lease); } public void WriteTo(BinaryWriter bW) { bW.Write(Encoding.ASCII.GetBytes("SC")); bW.Write((byte)9); //version bW.WriteShortString(_name); bW.Write(_enabled); _startingAddress.WriteTo(bW); _endingAddress.WriteTo(bW); _subnetMask.WriteTo(bW); bW.Write(_leaseTimeDays); bW.Write(_leaseTimeHours); bW.Write(_leaseTimeMinutes); bW.Write(_offerDelayTime); bW.Write(_pingCheckEnabled); bW.Write(_pingCheckTimeout); bW.Write(_pingCheckRetries); if (string.IsNullOrWhiteSpace(_domainName)) bW.Write((byte)0); else bW.WriteShortString(_domainName); if (_domainSearchList is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_domainSearchList.Count)); foreach (string domainSearchString in _domainSearchList) bW.WriteShortString(domainSearchString); } bW.Write(_dnsUpdates); bW.Write(_dnsTtl); if (_serverAddress is null) IPAddress.Any.WriteTo(bW); else _serverAddress.WriteTo(bW); if (string.IsNullOrEmpty(_serverHostName)) bW.Write((byte)0); else bW.WriteShortString(_serverHostName); if (string.IsNullOrEmpty(_bootFileName)) bW.Write((byte)0); else bW.WriteShortString(_bootFileName); if (_routerAddress is null) IPAddress.Any.WriteTo(bW); else _routerAddress.WriteTo(bW); if (_useThisDnsServer) { bW.Write((byte)255); } else if (_dnsServers is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_dnsServers.Count)); foreach (IPAddress dnsServer in _dnsServers) dnsServer.WriteTo(bW); } if (_winsServers is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_winsServers.Count)); foreach (IPAddress winsServer in _winsServers) winsServer.WriteTo(bW); } if (_ntpServers is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_ntpServers.Count)); foreach (IPAddress ntpServer in _ntpServers) ntpServer.WriteTo(bW); } if (_ntpServerDomainNames is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_ntpServerDomainNames.Count)); foreach (string ntpServerDomainName in _ntpServerDomainNames) bW.WriteShortString(ntpServerDomainName); } if (_staticRoutes is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_staticRoutes.Count)); foreach (ClasslessStaticRouteOption.Route route in _staticRoutes) route.WriteTo(bW.BaseStream); } if (_vendorInfo is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_vendorInfo.Count)); foreach (KeyValuePair entry in _vendorInfo) { bW.WriteShortString(entry.Key); bW.WriteBuffer(entry.Value.Information); } } if (_capwapAcIpAddresses is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_capwapAcIpAddresses.Count)); foreach (IPAddress capwapAcIpAddress in _capwapAcIpAddresses) capwapAcIpAddress.WriteTo(bW); } if (_tftpServerAddreses is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_tftpServerAddreses.Count)); foreach (IPAddress tftpServerAddress in _tftpServerAddreses) tftpServerAddress.WriteTo(bW); } if (_genericOptions is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_genericOptions.Count)); foreach (DhcpOption genericOption in _genericOptions) { bW.Write((byte)genericOption.Code); bW.Write(Convert.ToInt16(genericOption.RawValue.Length)); bW.Write(genericOption.RawValue); } } if (_exclusions is null) { bW.Write((byte)0); } else { bW.Write(Convert.ToByte(_exclusions.Count)); foreach (Exclusion exclusion in _exclusions) { exclusion.StartingAddress.WriteTo(bW); exclusion.EndingAddress.WriteTo(bW); } } bW.Write(_reservedLeases.Count); foreach (KeyValuePair reservedLease in _reservedLeases) reservedLease.Value.WriteTo(bW); bW.Write(_allowOnlyReservedLeases); bW.Write(_blockLocallyAdministeredMacAddresses); bW.Write(_ignoreClientIdentifierOption); { bW.Write(_leases.Count); foreach (KeyValuePair lease in _leases) lease.Value.WriteTo(bW); } } 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; return true; } public override int GetHashCode() { return HashCode.Combine(_startingAddress, _endingAddress, _subnetMask); } public override string ToString() { return _name; } public int CompareTo(Scope other) { return _name.CompareTo(other._name); } #endregion #region properties public string Name { get { return _name; } set { ValidateScopeName(value); _name = value; } } public bool Enabled { get { return _enabled; } } public IPAddress StartingAddress { get { return _startingAddress; } } public IPAddress EndingAddress { get { return _endingAddress; } } public IPAddress SubnetMask { get { return _subnetMask; } } public ushort LeaseTimeDays { get { return _leaseTimeDays; } set { if (value > 999) throw new ArgumentOutOfRangeException(nameof(LeaseTimeDays), "Lease time in days must be between 0 to 999."); _leaseTimeDays = value; } } public byte LeaseTimeHours { get { return _leaseTimeHours; } set { if (value > 23) throw new ArgumentOutOfRangeException(nameof(LeaseTimeHours), "Lease time in hours must be between 0 to 23."); _leaseTimeHours = value; } } public byte LeaseTimeMinutes { get { return _leaseTimeMinutes; } set { if (value > 59) throw new ArgumentOutOfRangeException(nameof(LeaseTimeMinutes), "Lease time in minutes must be between 0 to 59."); _leaseTimeMinutes = value; } } public ushort OfferDelayTime { get { return _offerDelayTime; } set { _offerDelayTime = value; } } public bool PingCheckEnabled { get { return _pingCheckEnabled; } set { _pingCheckEnabled = value; } } public ushort PingCheckTimeout { get { return _pingCheckTimeout; } set { _pingCheckTimeout = value; } } public byte PingCheckRetries { get { return _pingCheckRetries; } set { _pingCheckRetries = value; } } public string DomainName { get { return _domainName; } set { if (value != null) DnsClient.IsDomainNameValid(value, true); _domainName = value; } } public IReadOnlyCollection DomainSearchList { get { return _domainSearchList; } set { if (value is not null) { foreach (string domainSearchString in value) DnsClient.IsDomainNameValid(domainSearchString, true); } _domainSearchList = value; } } public bool DnsUpdates { get { return _dnsUpdates; } set { _dnsUpdates = value; } } public uint DnsTtl { get { return _dnsTtl; } set { _dnsTtl = value; } } public IPAddress ServerAddress { get { return _serverAddress; } set { ValidateIpv4(value, nameof(ServerAddress)); _serverAddress = value; } } public string ServerHostName { get { return _serverHostName; } set { if ((value != null) && (value.Length >= 64)) throw new ArgumentException("Server host name cannot exceed 63 bytes."); _serverHostName = value; } } public string BootFileName { get { return _bootFileName; } set { if ((value != null) && (value.Length >= 128)) throw new ArgumentException("Boot file name cannot exceed 127 bytes."); _bootFileName = value; } } public IPAddress RouterAddress { get { return _routerAddress; } set { ValidateIpv4(value, nameof(RouterAddress)); _routerAddress = value; } } public bool UseThisDnsServer { get { return _useThisDnsServer; } set { _useThisDnsServer = value; if (_useThisDnsServer) FindThisDnsServerAddress(); } } public IReadOnlyCollection DnsServers { get { return _dnsServers; } set { ValidateIpv4(value, nameof(DnsServers)); _dnsServers = value; if ((_dnsServers != null) && _dnsServers.Count > 0) _useThisDnsServer = false; } } public IReadOnlyCollection WinsServers { get { return _winsServers; } set { ValidateIpv4(value, nameof(WinsServers)); _winsServers = value; } } public IReadOnlyCollection NtpServers { get { return _ntpServers; } set { ValidateIpv4(value, nameof(NtpServers)); _ntpServers = value; } } public IReadOnlyCollection NtpServerDomainNames { get { return _ntpServerDomainNames; } set { if (value is not null) { foreach (string ntpServerDomainName in value) DnsClient.IsDomainNameValid(ntpServerDomainName, true); } _ntpServerDomainNames = value; } } public IReadOnlyCollection StaticRoutes { get { return _staticRoutes; } set { _staticRoutes = value; } } public IReadOnlyDictionary VendorInfo { get { return _vendorInfo; } set { _vendorInfo = value; } } public IReadOnlyCollection CAPWAPAcIpAddresses { get { return _capwapAcIpAddresses; } set { ValidateIpv4(value, nameof(CAPWAPAcIpAddresses)); _capwapAcIpAddresses = value; } } public IReadOnlyCollection TftpServerAddresses { get { return _tftpServerAddreses; } set { ValidateIpv4(value, nameof(TftpServerAddresses)); _tftpServerAddreses = value; } } public IReadOnlyCollection GenericOptions { get { return _genericOptions; } set { _genericOptions = value; } } public IReadOnlyCollection Exclusions { get { return _exclusions; } set { if (value is null) { _exclusions = null; } else { foreach (Exclusion exclusion in value) { if (!IsAddressInRange(exclusion.StartingAddress)) throw new ArgumentOutOfRangeException(nameof(Exclusions), "Exclusion starting address must be in scope range."); if (!IsAddressInRange(exclusion.EndingAddress)) throw new ArgumentOutOfRangeException(nameof(Exclusions), "Exclusion ending address must be in scope range."); } _exclusions = value; } } } public IReadOnlyCollection ReservedLeases { get { List leases = new List(_reservedLeases.Count); foreach (KeyValuePair entry in _reservedLeases) leases.Add(entry.Value); leases.Sort(); return leases; } set { if (value is null) { _reservedLeases.Clear(); } else { foreach (Lease reservedLease in value) { if (!IsAddressInRange(reservedLease.Address)) throw new ArgumentOutOfRangeException(nameof(ReservedLeases), "Reserved address must be in scope range."); } _reservedLeases.Clear(); foreach (Lease reservedLease in value) _reservedLeases.TryAdd(reservedLease.ClientIdentifier, reservedLease); } } } public bool AllowOnlyReservedLeases { get { return _allowOnlyReservedLeases; } set { _allowOnlyReservedLeases = value; } } public bool BlockLocallyAdministeredMacAddresses { get { return _blockLocallyAdministeredMacAddresses; } set { _blockLocallyAdministeredMacAddresses = value; } } public bool IgnoreClientIdentifierOption { get { return _ignoreClientIdentifierOption; } set { _ignoreClientIdentifierOption = value; } } public IReadOnlyDictionary Leases { get { return _leases; } } public IPAddress NetworkAddress { get { return _networkAddress; } } public IPAddress BroadcastAddress { get { return _broadcastAddress; } } public IPAddress InterfaceAddress { get { return _interfaceAddress; } } internal int InterfaceIndex { get { return _interfaceIndex; } } internal DateTime LastModified { get { return _lastModified; } } #endregion class AddressStatus { public static readonly AddressStatus TRUE = new AddressStatus(true, null); public static readonly AddressStatus FALSE = new AddressStatus(false, null); public readonly bool IsAddressAvailable; public readonly IPAddress NewAddress; public AddressStatus(bool isAddressAvailable, IPAddress newAddress) { IsAddressAvailable = isAddressAvailable; NewAddress = newAddress; } } } }