CacheZoneManager.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2020 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using DnsServerCore.Dns.Zones;
  16. using System.Collections.Generic;
  17. using TechnitiumLibrary.Net.Dns;
  18. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  19. namespace DnsServerCore.Dns.ZoneManagers
  20. {
  21. public sealed class CacheZoneManager : DnsCache
  22. {
  23. #region variables
  24. const uint FAILURE_RECORD_TTL = 30u;
  25. const uint NEGATIVE_RECORD_TTL = 300u;
  26. const uint MINIMUM_RECORD_TTL = 10u;
  27. const uint SERVE_STALE_TTL = 7 * 24 * 60 * 60; //7 days serve stale ttl as per draft-ietf-dnsop-serve-stale-04
  28. readonly ZoneTree<CacheZone> _root = new ZoneTree<CacheZone>();
  29. #endregion
  30. #region constructor
  31. public CacheZoneManager()
  32. : base(FAILURE_RECORD_TTL, NEGATIVE_RECORD_TTL, MINIMUM_RECORD_TTL, SERVE_STALE_TTL)
  33. { }
  34. #endregion
  35. #region protected
  36. protected override void CacheRecords(IReadOnlyList<DnsResourceRecord> resourceRecords)
  37. {
  38. if (resourceRecords.Count == 1)
  39. {
  40. CacheZone zone = _root.GetOrAdd(resourceRecords[0].Name, delegate (string key)
  41. {
  42. return new CacheZone(resourceRecords[0].Name);
  43. });
  44. zone.SetRecords(resourceRecords[0].Type, resourceRecords);
  45. }
  46. else
  47. {
  48. Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> groupedByDomainRecords = DnsResourceRecord.GroupRecords(resourceRecords);
  49. //add grouped records
  50. foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> groupedByTypeRecords in groupedByDomainRecords)
  51. {
  52. CacheZone zone = _root.GetOrAdd(groupedByTypeRecords.Key, delegate (string key)
  53. {
  54. return new CacheZone(groupedByTypeRecords.Key);
  55. });
  56. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> groupedRecords in groupedByTypeRecords.Value)
  57. zone.SetRecords(groupedRecords.Key, groupedRecords.Value);
  58. }
  59. }
  60. }
  61. #endregion
  62. #region private
  63. private List<DnsResourceRecord> GetAdditionalRecords(IReadOnlyCollection<DnsResourceRecord> nsRecords, bool serveStale)
  64. {
  65. List<DnsResourceRecord> additionalRecords = new List<DnsResourceRecord>();
  66. foreach (DnsResourceRecord nsRecord in nsRecords)
  67. {
  68. if (nsRecord.Type != DnsResourceRecordType.NS)
  69. continue;
  70. CacheZone cacheZone = _root.FindZone((nsRecord.RDATA as DnsNSRecord).NameServer, out _, out _, out _);
  71. if (cacheZone != null)
  72. {
  73. {
  74. IReadOnlyList<DnsResourceRecord> records = cacheZone.QueryRecords(DnsResourceRecordType.A, serveStale);
  75. if ((records.Count > 0) && (records[0].RDATA is DnsARecord))
  76. additionalRecords.AddRange(records);
  77. }
  78. {
  79. IReadOnlyList<DnsResourceRecord> records = cacheZone.QueryRecords(DnsResourceRecordType.AAAA, serveStale);
  80. if ((records.Count > 0) && (records[0].RDATA is DnsAAAARecord))
  81. additionalRecords.AddRange(records);
  82. }
  83. }
  84. }
  85. return additionalRecords;
  86. }
  87. #endregion
  88. #region public
  89. public void DoMaintenance()
  90. {
  91. foreach (CacheZone zone in _root)
  92. {
  93. zone.RemoveExpiredRecords();
  94. if (zone.IsEmpty)
  95. _root.TryRemove(zone.Name, out _); //remove empty zone
  96. }
  97. }
  98. public void Flush()
  99. {
  100. _root.Clear();
  101. }
  102. public bool DeleteZone(string domain)
  103. {
  104. return _root.TryRemove(domain, out _);
  105. }
  106. public List<string> ListSubDomains(string domain)
  107. {
  108. return _root.ListSubDomains(domain);
  109. }
  110. public List<DnsResourceRecord> ListAllRecords(string domain)
  111. {
  112. if (_root.TryGet(domain, out CacheZone zone))
  113. return zone.ListAllRecords();
  114. return new List<DnsResourceRecord>(0);
  115. }
  116. public DnsDatagram QueryClosestDelegation(DnsDatagram request)
  117. {
  118. _ = _root.FindZone(request.Question[0].Name, out CacheZone delegation, out _, out _);
  119. if (delegation == null)
  120. {
  121. //no cached delegation found
  122. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.Refused, request.Question);
  123. }
  124. //return closest name servers in delegation
  125. IReadOnlyList<DnsResourceRecord> authority = delegation.QueryRecords(DnsResourceRecordType.NS, false);
  126. List<DnsResourceRecord> additional = GetAdditionalRecords(authority, false);
  127. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, authority, additional);
  128. }
  129. public override DnsDatagram Query(DnsDatagram request, bool serveStale = false)
  130. {
  131. CacheZone zone = _root.FindZone(request.Question[0].Name, out CacheZone delegation, out _, out _);
  132. if (zone == null)
  133. {
  134. //zone not found
  135. if (delegation == null)
  136. {
  137. //no cached delegation found
  138. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.Refused, request.Question);
  139. }
  140. //return closest name servers in delegation
  141. IReadOnlyList<DnsResourceRecord> authority = delegation.QueryRecords(DnsResourceRecordType.NS, serveStale);
  142. List<DnsResourceRecord> additional = GetAdditionalRecords(authority, serveStale);
  143. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, authority, additional);
  144. }
  145. //zone found
  146. IReadOnlyList<DnsResourceRecord> answers = zone.QueryRecords(request.Question[0].Type, serveStale);
  147. if (answers.Count > 0)
  148. {
  149. if (answers[0].RDATA is DnsEmptyRecord)
  150. {
  151. DnsResourceRecord[] authority = null;
  152. DnsResourceRecord soaRecord = (answers[0].RDATA as DnsEmptyRecord).Authority;
  153. if (soaRecord != null)
  154. authority = new DnsResourceRecord[] { soaRecord };
  155. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, authority);
  156. }
  157. if (answers[0].RDATA is DnsNXRecord)
  158. {
  159. DnsResourceRecord[] authority = null;
  160. DnsResourceRecord soaRecord = (answers[0].RDATA as DnsNXRecord).Authority;
  161. if (soaRecord != null)
  162. authority = new DnsResourceRecord[] { soaRecord };
  163. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NameError, request.Question, null, authority);
  164. }
  165. if (answers[0].RDATA is DnsANYRecord)
  166. {
  167. DnsANYRecord anyRR = answers[0].RDATA as DnsANYRecord;
  168. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, anyRR.Records);
  169. }
  170. if (answers[0].RDATA is DnsFailureRecord)
  171. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, (answers[0].RDATA as DnsFailureRecord).RCODE, request.Question);
  172. IReadOnlyList<DnsResourceRecord> additional = null;
  173. if (request.Question[0].Type == DnsResourceRecordType.NS)
  174. additional = GetAdditionalRecords(answers, serveStale);
  175. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, answers, null, additional);
  176. }
  177. //found nothing in cache
  178. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.Refused, request.Question);
  179. }
  180. #endregion
  181. }
  182. }