From 06282fc8c06192cc660ef4c92c88646bddaa4485 Mon Sep 17 00:00:00 2001 From: Shreyas Zare Date: Sun, 12 Feb 2023 12:56:09 +0530 Subject: [PATCH] AuthZoneManager: Implemented zone index to allow paginated access to zones list. --- .../Dns/ZoneManagers/AuthZoneManager.cs | 463 ++++++++++++------ 1 file changed, 305 insertions(+), 158 deletions(-) diff --git a/DnsServerCore/Dns/ZoneManagers/AuthZoneManager.cs b/DnsServerCore/Dns/ZoneManagers/AuthZoneManager.cs index a504b7e5..c444975f 100644 --- a/DnsServerCore/Dns/ZoneManagers/AuthZoneManager.cs +++ b/DnsServerCore/Dns/ZoneManagers/AuthZoneManager.cs @@ -45,7 +45,8 @@ namespace DnsServerCore.Dns.ZoneManagers readonly AuthZoneTree _root = new AuthZoneTree(); - int _totalZones; + readonly List _zoneIndex = new List(10); + readonly ReaderWriterLockSlim _zoneIndexLock = new ReaderWriterLockSlim(); #endregion @@ -94,7 +95,7 @@ namespace DnsServerCore.Dns.ZoneManagers //update authoritative zone SOA and NS records try { - List zones = GetAllZones(); + IReadOnlyList zones = GetAllZones(); foreach (AuthZoneInfo zone in zones) { @@ -179,10 +180,7 @@ namespace DnsServerCore.Dns.ZoneManagers } if (_root.TryAdd(zone)) - { - _totalZones++; return zone; - } throw new DnsServerException("Zone already exists: " + zoneInfo.Name); } @@ -462,7 +460,16 @@ namespace DnsServerCore.Dns.ZoneManagers internal void Flush() { - _root.Clear(); + _zoneIndexLock.EnterWriteLock(); + try + { + _root.Clear(); + _zoneIndex.Clear(); + } + finally + { + _zoneIndexLock.ExitWriteLock(); + } } private static IReadOnlyList CondenseIncrementalZoneTransferRecords(string zoneName, DnsResourceRecord currentSoaRecord, IReadOnlyList xfrRecords) @@ -652,7 +659,7 @@ namespace DnsServerCore.Dns.ZoneManagers public void LoadAllZoneFiles() { - _root.Clear(); + Flush(); string zonesFolder = Path.Combine(_dnsServer.ConfigFolder, "zones"); if (!Directory.Exists(zonesFolder)) @@ -666,7 +673,7 @@ namespace DnsServerCore.Dns.ZoneManagers File.Move(oldZoneFile, Path.Combine(zonesFolder, Path.GetFileName(oldZoneFile))); } - //remove old internal zones + //remove old internal zones files { string[] oldZoneFiles = new string[] { "localhost.zone", "1.0.0.127.in-addr.arpa.zone", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.zone" }; @@ -722,27 +729,38 @@ namespace DnsServerCore.Dns.ZoneManagers } //load zone files - string[] zoneFiles = Directory.GetFiles(zonesFolder, "*.zone"); - - foreach (string zoneFile in zoneFiles) + _zoneIndexLock.EnterWriteLock(); + try { - try - { - using (FileStream fS = new FileStream(zoneFile, FileMode.Open, FileAccess.Read)) - { - LoadZoneFrom(fS); - } + string[] zoneFiles = Directory.GetFiles(zonesFolder, "*.zone"); - LogManager log = _dnsServer.LogManager; - if (log != null) - log.Write("DNS Server successfully loaded zone file: " + zoneFile); - } - catch (Exception ex) + foreach (string zoneFile in zoneFiles) { - LogManager log = _dnsServer.LogManager; - if (log != null) - log.Write("DNS Server failed to load zone file: " + zoneFile + "\r\n" + ex.ToString()); + try + { + using (FileStream fS = new FileStream(zoneFile, FileMode.Open, FileAccess.Read)) + { + AuthZoneInfo zoneInfo = LoadZoneFrom(fS); + _zoneIndex.Add(zoneInfo); + } + + LogManager log = _dnsServer.LogManager; + if (log != null) + log.Write("DNS Server successfully loaded zone file: " + zoneFile); + } + catch (Exception ex) + { + LogManager log = _dnsServer.LogManager; + if (log != null) + log.Write("DNS Server failed to load zone file: " + zoneFile + "\r\n" + ex.ToString()); + } } + + _zoneIndex.Sort(); + } + finally + { + _zoneIndexLock.ExitWriteLock(); } } @@ -750,10 +768,21 @@ namespace DnsServerCore.Dns.ZoneManagers { PrimaryZone apexZone = new PrimaryZone(_dnsServer, zoneName, soaRecord, ns); - if (_root.TryAdd(apexZone)) + _zoneIndexLock.EnterWriteLock(); + try { - _totalZones++; - return new AuthZoneInfo(apexZone); + if (_root.TryAdd(apexZone)) + { + AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone); + _zoneIndex.Add(zoneInfo); + _zoneIndex.Sort(); + + return zoneInfo; + } + } + finally + { + _zoneIndexLock.ExitWriteLock(); } return null; @@ -763,10 +792,21 @@ namespace DnsServerCore.Dns.ZoneManagers { PrimaryZone apexZone = new PrimaryZone(_dnsServer, zoneName, primaryNameServer, @internal); - if (_root.TryAdd(apexZone)) + _zoneIndexLock.EnterWriteLock(); + try { - _totalZones++; - return new AuthZoneInfo(apexZone); + if (_root.TryAdd(apexZone)) + { + AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone); + _zoneIndex.Add(zoneInfo); + _zoneIndex.Sort(); + + return zoneInfo; + } + } + finally + { + _zoneIndexLock.ExitWriteLock(); } return null; @@ -776,11 +816,23 @@ namespace DnsServerCore.Dns.ZoneManagers { SecondaryZone apexZone = await SecondaryZone.CreateAsync(_dnsServer, zoneName, primaryNameServerAddresses, zoneTransferProtocol, tsigKeyName); - if (_root.TryAdd(apexZone)) + _zoneIndexLock.EnterWriteLock(); + try { - apexZone.TriggerRefresh(0); - _totalZones++; - return new AuthZoneInfo(apexZone); + if (_root.TryAdd(apexZone)) + { + apexZone.TriggerRefresh(0); + + AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone); + _zoneIndex.Add(zoneInfo); + _zoneIndex.Sort(); + + return zoneInfo; + } + } + finally + { + _zoneIndexLock.ExitWriteLock(); } return null; @@ -790,11 +842,23 @@ namespace DnsServerCore.Dns.ZoneManagers { StubZone apexZone = await StubZone.CreateAsync(_dnsServer, zoneName, primaryNameServerAddresses); - if (_root.TryAdd(apexZone)) + _zoneIndexLock.EnterWriteLock(); + try { - apexZone.TriggerRefresh(0); - _totalZones++; - return new AuthZoneInfo(apexZone); + if (_root.TryAdd(apexZone)) + { + apexZone.TriggerRefresh(0); + + AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone); + _zoneIndex.Add(zoneInfo); + _zoneIndex.Sort(); + + return zoneInfo; + } + } + finally + { + _zoneIndexLock.ExitWriteLock(); } return null; @@ -804,10 +868,21 @@ namespace DnsServerCore.Dns.ZoneManagers { ForwarderZone apexZone = new ForwarderZone(zoneName, forwarderProtocol, forwarder, dnssecValidation, proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword, fwdRecordComments); - if (_root.TryAdd(apexZone)) + _zoneIndexLock.EnterWriteLock(); + try { - _totalZones++; - return new AuthZoneInfo(apexZone); + if (_root.TryAdd(apexZone)) + { + AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone); + _zoneIndex.Add(zoneInfo); + _zoneIndex.Sort(); + + return zoneInfo; + } + } + finally + { + _zoneIndexLock.ExitWriteLock(); } return null; @@ -943,12 +1018,23 @@ namespace DnsServerCore.Dns.ZoneManagers public bool DeleteZone(string zoneName) { - if (_root.TryRemove(zoneName, out ApexZone apexZone)) + _zoneIndexLock.EnterWriteLock(); + try { - apexZone.Dispose(); + if (_root.TryRemove(zoneName, out ApexZone apexZone)) + { + apexZone.Dispose(); - _totalZones--; - return true; + AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone); + if (!_zoneIndex.Remove(zoneInfo)) + throw new InvalidOperationException("Zone deleted from tree but failed to remove from zone index."); + + return true; + } + } + finally + { + _zoneIndexLock.ExitWriteLock(); } return false; @@ -1698,31 +1784,49 @@ namespace DnsServerCore.Dns.ZoneManagers } } - public List GetAllZones() + public IReadOnlyList GetAllZones() { - List zones = new List(); - - foreach (AuthZoneNode zoneNode in _root) + _zoneIndexLock.EnterReadLock(); + try { - ApexZone apexZone = zoneNode.ApexZone; - if (apexZone is null) - continue; - - AuthZoneInfo zoneInfo = new AuthZoneInfo(apexZone); - switch (zoneInfo.Type) - { - case AuthZoneType.Primary: - case AuthZoneType.Secondary: - case AuthZoneType.Stub: - case AuthZoneType.Forwarder: - zones.Add(zoneInfo); - break; - } + return new List(_zoneIndex); } + finally + { + _zoneIndexLock.ExitReadLock(); + } + } - _totalZones = zones.Count; + public ZonesPage GetZonesPage(int pageNumber, int zonesPerPage) + { + _zoneIndexLock.EnterReadLock(); + try + { + if (pageNumber < 0) + pageNumber = int.MaxValue; + else if (pageNumber == 0) + pageNumber = 1; - return zones; + int totalZones = _zoneIndex.Count; + int totalPages = (totalZones / zonesPerPage) + (totalZones % zonesPerPage > 0 ? 1 : 0); + + if (pageNumber > totalPages) + pageNumber = totalPages; + + int start = (pageNumber - 1) * zonesPerPage; + int end = Math.Min(start + zonesPerPage, totalZones); + + List zones = new List(end - start); + + for (int i = start; i < end; i++) + zones.Add(_zoneIndex[i]); + + return new ZonesPage(pageNumber, totalPages, totalZones, zones); + } + finally + { + _zoneIndexLock.ExitReadLock(); + } } public void ListSubDomains(string domain, List subDomains) @@ -2018,7 +2122,7 @@ namespace DnsServerCore.Dns.ZoneManagers } } - public void LoadZoneFrom(Stream s) + public AuthZoneInfo LoadZoneFrom(Stream s) { BinaryReader bR = new BinaryReader(s); @@ -2030,108 +2134,110 @@ namespace DnsServerCore.Dns.ZoneManagers case 2: { DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()]; - if (records.Length > 0) + if (records.Length == 0) + throw new InvalidDataException("Zone does not contain SOA record."); + + DnsResourceRecord soaRecord = null; + + for (int i = 0; i < records.Length; i++) { - DnsResourceRecord soaRecord = null; + records[i] = new DnsResourceRecord(s); - for (int i = 0; i < records.Length; i++) - { - records[i] = new DnsResourceRecord(s); - - if (records[i].Type == DnsResourceRecordType.SOA) - soaRecord = records[i]; - } - - if (soaRecord == null) - throw new InvalidDataException("Zone does not contain SOA record."); - - //make zone info - AuthZoneType zoneType; - if (_dnsServer.ServerDomain.Equals((soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer, StringComparison.OrdinalIgnoreCase)) - zoneType = AuthZoneType.Primary; - else - zoneType = AuthZoneType.Stub; - - AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, zoneType, false); - - //create zone - ApexZone apexZone = CreateEmptyZone(zoneInfo); - - try - { - //load records - LoadRecords(apexZone, records); - } - catch - { - DeleteZone(zoneInfo.Name); - throw; - } - - //init zone - switch (zoneInfo.Type) - { - case AuthZoneType.Primary: - (apexZone as PrimaryZone).TriggerNotify(); - break; - } + if (records[i].Type == DnsResourceRecordType.SOA) + soaRecord = records[i]; } + + if (soaRecord == null) + throw new InvalidDataException("Zone does not contain SOA record."); + + //make zone info + AuthZoneType zoneType; + if (_dnsServer.ServerDomain.Equals((soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer, StringComparison.OrdinalIgnoreCase)) + zoneType = AuthZoneType.Primary; + else + zoneType = AuthZoneType.Stub; + + AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, zoneType, false); + + //create zone + ApexZone apexZone = CreateEmptyZone(zoneInfo); + + try + { + //load records + LoadRecords(apexZone, records); + } + catch + { + DeleteZone(zoneInfo.Name); + throw; + } + + //init zone + switch (zoneInfo.Type) + { + case AuthZoneType.Primary: + (apexZone as PrimaryZone).TriggerNotify(); + break; + } + + return new AuthZoneInfo(apexZone); } - break; case 3: { bool zoneDisabled = bR.ReadBoolean(); DnsResourceRecord[] records = new DnsResourceRecord[bR.ReadInt32()]; - if (records.Length > 0) + if (records.Length == 0) + throw new InvalidDataException("Zone does not contain SOA record."); + + DnsResourceRecord soaRecord = null; + + for (int i = 0; i < records.Length; i++) { - DnsResourceRecord soaRecord = null; + records[i] = new DnsResourceRecord(s); + records[i].Tag = new AuthRecordInfo(bR, records[i].Type == DnsResourceRecordType.SOA); - for (int i = 0; i < records.Length; i++) - { - records[i] = new DnsResourceRecord(s); - records[i].Tag = new AuthRecordInfo(bR, records[i].Type == DnsResourceRecordType.SOA); - - if (records[i].Type == DnsResourceRecordType.SOA) - soaRecord = records[i]; - } - - if (soaRecord == null) - throw new InvalidDataException("Zone does not contain SOA record."); - - //make zone info - AuthZoneType zoneType; - if (_dnsServer.ServerDomain.Equals((soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer, StringComparison.OrdinalIgnoreCase)) - zoneType = AuthZoneType.Primary; - else - zoneType = AuthZoneType.Stub; - - AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, zoneType, zoneDisabled); - - //create zone - ApexZone apexZone = CreateEmptyZone(zoneInfo); - - try - { - //load records - LoadRecords(apexZone, records); - } - catch - { - DeleteZone(zoneInfo.Name); - throw; - } - - //init zone - switch (zoneInfo.Type) - { - case AuthZoneType.Primary: - (apexZone as PrimaryZone).TriggerNotify(); - break; - } + if (records[i].Type == DnsResourceRecordType.SOA) + soaRecord = records[i]; } + + if (soaRecord == null) + throw new InvalidDataException("Zone does not contain SOA record."); + + //make zone info + AuthZoneType zoneType; + if (_dnsServer.ServerDomain.Equals((soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer, StringComparison.OrdinalIgnoreCase)) + zoneType = AuthZoneType.Primary; + else + zoneType = AuthZoneType.Stub; + + AuthZoneInfo zoneInfo = new AuthZoneInfo(records[0].Name, zoneType, zoneDisabled); + + //create zone + ApexZone apexZone = CreateEmptyZone(zoneInfo); + + try + { + //load records + LoadRecords(apexZone, records); + } + catch + { + DeleteZone(zoneInfo.Name); + throw; + } + + //init zone + switch (zoneInfo.Type) + { + case AuthZoneType.Primary: + (apexZone as PrimaryZone).TriggerNotify(); + break; + } + + return new AuthZoneInfo(apexZone); } - break; case 4: { @@ -2181,8 +2287,9 @@ namespace DnsServerCore.Dns.ZoneManagers break; } } + + return new AuthZoneInfo(apexZone); } - break; default: throw new InvalidDataException("DNS Zone file version not supported."); @@ -2269,8 +2376,48 @@ namespace DnsServerCore.Dns.ZoneManagers } public int TotalZones - { get { return _totalZones; } } + { get { return _zoneIndex.Count; } } #endregion + + public class ZonesPage + { + #region variables + + readonly long _pageNumber; + readonly long _totalPages; + readonly long _totalZones; + readonly IReadOnlyList _zones; + + #endregion + + #region constructor + + public ZonesPage(long pageNumber, long totalPages, long totalZones, IReadOnlyList zones) + { + _pageNumber = pageNumber; + _totalPages = totalPages; + _totalZones = totalZones; + _zones = zones; + } + + #endregion + + #region properties + + public long PageNumber + { get { return _pageNumber; } } + + public long TotalPages + { get { return _totalPages; } } + + public long TotalZones + { get { return _totalZones; } } + + public IReadOnlyList Zones + { get { return _zones; } } + + #endregion + } } }