1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185 |
- /*
- 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 <http://www.gnu.org/licenses/>.
- */
- 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<Scope>, 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<string> _domainSearchList;
- bool _dnsUpdates = true;
- uint _dnsTtl = 900;
- IPAddress _serverAddress;
- string _serverHostName;
- string _bootFileName;
- IPAddress _routerAddress;
- bool _useThisDnsServer;
- IReadOnlyCollection<IPAddress> _dnsServers;
- IReadOnlyCollection<IPAddress> _winsServers;
- IReadOnlyCollection<IPAddress> _ntpServers;
- IReadOnlyCollection<string> _ntpServerDomainNames;
- IReadOnlyCollection<ClasslessStaticRouteOption.Route> _staticRoutes;
- IReadOnlyDictionary<string, VendorSpecificInformationOption> _vendorInfo;
- IReadOnlyCollection<IPAddress> _capwapAcIpAddresses;
- IReadOnlyCollection<IPAddress> _tftpServerAddreses;
- //advanced options
- IReadOnlyCollection<DhcpOption> _genericOptions;
- IReadOnlyCollection<Exclusion> _exclusions;
- readonly ConcurrentDictionary<ClientIdentifierOption, Lease> _reservedLeases = new ConcurrentDictionary<ClientIdentifierOption, Lease>();
- bool _allowOnlyReservedLeases;
- bool _blockLocallyAdministeredMacAddresses;
- bool _ignoreClientIdentifierOption;
- //leases
- readonly ConcurrentDictionary<ClientIdentifierOption, Lease> _leases = new ConcurrentDictionary<ClientIdentifierOption, Lease>();
- //internal computed parameters
- IPAddress _networkAddress;
- IPAddress _broadcastAddress;
- //internal parameters
- const int OFFER_EXPIRY_SECONDS = 60; //1 mins offer expiry
- readonly ConcurrentDictionary<ClientIdentifierOption, Lease> _offers = new ConcurrentDictionary<ClientIdentifierOption, Lease>();
- 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<string, VendorSpecificInformationOption> vendorInfo = new Dictionary<string, VendorSpecificInformationOption>(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<IPAddress> 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<AddressStatus> 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<ClientIdentifierOption, Lease> reservedLease in _reservedLeases)
- {
- if (address.Equals(reservedLease.Value.Address))
- return AddressStatus.FALSE;
- }
- foreach (KeyValuePair<ClientIdentifierOption, Lease> lease in _leases)
- {
- if (address.Equals(lease.Value.Address))
- return AddressStatus.FALSE;
- }
- foreach (KeyValuePair<ClientIdentifierOption, Lease> 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<ClientIdentifierOption, Lease> lease in _leases)
- {
- if (address.Equals(lease.Value.Address))
- return !lease.Key.Equals(clientIdentifier);
- }
- foreach (KeyValuePair<ClientIdentifierOption, Lease> 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 = DhcpServer.GetSanitizedHostName(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 = DhcpServer.GetSanitizedHostName(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<Exclusion> exclusions = new List<Exclusion>();
- 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<ClientIdentifierOption, Lease> 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<Lease> 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<List<DhcpOption>> GetOptionsAsync(DhcpMessage request, IPAddress serverIdentifierAddress, string reservedLeaseHostName, DnsServer dnsServer)
- {
- List<DhcpOption> options = new List<DhcpOption>();
- 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<string, VendorSpecificInformationOption> 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<NetworkTimeProtocolServersOption> GetNetworkTimeProtocolServersOptionAsync(DnsServer dnsServer)
- {
- if (_ntpServerDomainNames is not null)
- {
- Task<DnsDatagram>[] tasks = new Task<DnsDatagram>[_ntpServerDomainNames.Count];
- int i = 0;
- foreach (string ntpServerDomainName in _ntpServerDomainNames)
- tasks[i++] = dnsServer.DirectQueryAsync(new DnsQuestionRecord(ntpServerDomainName, DnsResourceRecordType.A, DnsClass.IN), 1000);
- List<IPAddress> ntpServers = new List<IPAddress>(_ntpServerDomainNames.Count + (_ntpServers is null ? 0 : _ntpServers.Count));
- if (_ntpServers is not null)
- ntpServers.AddRange(_ntpServers);
- foreach (Task<DnsDatagram> 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<ClientIdentifierOption, Lease> offer in _offers)
- {
- if (utcNow > offer.Value.LeaseObtained.AddSeconds(OFFER_EXPIRY_SECONDS))
- {
- //offer expired
- _offers.TryRemove(offer.Key, out _);
- }
- }
- }
- internal List<Lease> RemoveExpiredLeases()
- {
- List<Lease> expiredLeases = new List<Lease>();
- DateTime utcNow = DateTime.UtcNow;
- foreach (KeyValuePair<ClientIdentifierOption, Lease> 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<ClientIdentifierOption, Lease> 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<ClientIdentifierOption, Lease> 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<ClientIdentifierOption, Lease> 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<string, VendorSpecificInformationOption> 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<ClientIdentifierOption, Lease> reservedLease in _reservedLeases)
- reservedLease.Value.WriteTo(bW);
- bW.Write(_allowOnlyReservedLeases);
- bW.Write(_blockLocallyAdministeredMacAddresses);
- bW.Write(_ignoreClientIdentifierOption);
- {
- bW.Write(_leases.Count);
- foreach (KeyValuePair<ClientIdentifierOption, Lease> 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<string> 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<IPAddress> DnsServers
- {
- get { return _dnsServers; }
- set
- {
- ValidateIpv4(value, nameof(DnsServers));
- _dnsServers = value;
- if ((_dnsServers != null) && _dnsServers.Count > 0)
- _useThisDnsServer = false;
- }
- }
- public IReadOnlyCollection<IPAddress> WinsServers
- {
- get { return _winsServers; }
- set
- {
- ValidateIpv4(value, nameof(WinsServers));
- _winsServers = value;
- }
- }
- public IReadOnlyCollection<IPAddress> NtpServers
- {
- get { return _ntpServers; }
- set
- {
- ValidateIpv4(value, nameof(NtpServers));
- _ntpServers = value;
- }
- }
- public IReadOnlyCollection<string> NtpServerDomainNames
- {
- get { return _ntpServerDomainNames; }
- set
- {
- if (value is not null)
- {
- foreach (string ntpServerDomainName in value)
- DnsClient.IsDomainNameValid(ntpServerDomainName, true);
- }
- _ntpServerDomainNames = value;
- }
- }
- public IReadOnlyCollection<ClasslessStaticRouteOption.Route> StaticRoutes
- {
- get { return _staticRoutes; }
- set { _staticRoutes = value; }
- }
- public IReadOnlyDictionary<string, VendorSpecificInformationOption> VendorInfo
- {
- get { return _vendorInfo; }
- set { _vendorInfo = value; }
- }
- public IReadOnlyCollection<IPAddress> CAPWAPAcIpAddresses
- {
- get { return _capwapAcIpAddresses; }
- set
- {
- ValidateIpv4(value, nameof(CAPWAPAcIpAddresses));
- _capwapAcIpAddresses = value;
- }
- }
- public IReadOnlyCollection<IPAddress> TftpServerAddresses
- {
- get { return _tftpServerAddreses; }
- set
- {
- ValidateIpv4(value, nameof(TftpServerAddresses));
- _tftpServerAddreses = value;
- }
- }
- public IReadOnlyCollection<DhcpOption> GenericOptions
- {
- get { return _genericOptions; }
- set { _genericOptions = value; }
- }
- public IReadOnlyCollection<Exclusion> 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<Lease> ReservedLeases
- {
- get
- {
- List<Lease> leases = new List<Lease>(_reservedLeases.Count);
- foreach (KeyValuePair<ClientIdentifierOption, Lease> 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<ClientIdentifierOption, Lease> 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;
- }
- }
- }
- }
|