CacheZone.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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 System;
  16. using System.Collections.Generic;
  17. using TechnitiumLibrary.IO;
  18. using TechnitiumLibrary.Net.Dns;
  19. namespace DnsServerCore.Dns.Zones
  20. {
  21. class CacheZone : Zone
  22. {
  23. #region constructor
  24. public CacheZone(string name)
  25. : base(name)
  26. { }
  27. #endregion
  28. #region private
  29. private static IReadOnlyList<DnsResourceRecord> FilterExpiredRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records, bool serveStale)
  30. {
  31. if (records.Count == 1)
  32. {
  33. if (!serveStale && records[0].IsStale)
  34. return Array.Empty<DnsResourceRecord>(); //record is stale
  35. if (records[0].TtlValue < 1u)
  36. return Array.Empty<DnsResourceRecord>(); //ttl expired
  37. return records;
  38. }
  39. List<DnsResourceRecord> newRecords = new List<DnsResourceRecord>(records.Count);
  40. foreach (DnsResourceRecord record in records)
  41. {
  42. if (!serveStale && record.IsStale)
  43. continue; //record is stale
  44. if (record.TtlValue < 1u)
  45. continue; //ttl expired
  46. newRecords.Add(record);
  47. }
  48. if (newRecords.Count > 1)
  49. {
  50. switch (type)
  51. {
  52. case DnsResourceRecordType.A:
  53. case DnsResourceRecordType.AAAA:
  54. newRecords.Shuffle(); //shuffle records to allow load balancing
  55. break;
  56. }
  57. }
  58. return newRecords;
  59. }
  60. #endregion
  61. #region public
  62. public void SetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
  63. {
  64. if ((records.Count > 0) && (records[0].RDATA is DnsCache.DnsFailureRecord))
  65. {
  66. //call trying to cache failure record
  67. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
  68. {
  69. if ((existingRecords.Count > 0) && !(existingRecords[0].RDATA is DnsCache.DnsFailureRecord))
  70. return; //skip to avoid overwriting a useful stale record with a failure record to allow serve-stale to work as intended
  71. }
  72. }
  73. //set records
  74. _entries[type] = records;
  75. switch (type)
  76. {
  77. case DnsResourceRecordType.CNAME:
  78. case DnsResourceRecordType.SOA:
  79. case DnsResourceRecordType.NS:
  80. //do nothing
  81. break;
  82. default:
  83. //remove old CNAME entry since current new entry type overlaps any existing CNAME entry in cache
  84. //keeping both entries will create issue with serve stale implementation since stale CNAME entry will be always returned
  85. _entries.TryRemove(DnsResourceRecordType.CNAME, out _);
  86. break;
  87. }
  88. }
  89. public void RemoveExpiredRecords()
  90. {
  91. foreach (DnsResourceRecordType type in _entries.Keys)
  92. {
  93. IReadOnlyList<DnsResourceRecord> records = _entries[type];
  94. foreach (DnsResourceRecord record in records)
  95. {
  96. if (record.TtlValue < 1u)
  97. {
  98. //record is expired; update entry
  99. List<DnsResourceRecord> newRecords = new List<DnsResourceRecord>(records.Count);
  100. foreach (DnsResourceRecord existingRecord in records)
  101. {
  102. if (existingRecord.TtlValue < 1u)
  103. continue;
  104. newRecords.Add(existingRecord);
  105. }
  106. if (newRecords.Count > 0)
  107. {
  108. //try update entry with non-expired records
  109. _entries.TryUpdate(type, newRecords, records);
  110. }
  111. else
  112. {
  113. //all records expired; remove entry
  114. _entries.TryRemove(type, out _);
  115. }
  116. break;
  117. }
  118. }
  119. }
  120. }
  121. public IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool serveStale)
  122. {
  123. //check for CNAME
  124. if (_entries.TryGetValue(DnsResourceRecordType.CNAME, out IReadOnlyList<DnsResourceRecord> existingCNAMERecords))
  125. {
  126. IReadOnlyList<DnsResourceRecord> filteredRecords = FilterExpiredRecords(type, existingCNAMERecords, serveStale);
  127. if (filteredRecords.Count > 0)
  128. return filteredRecords;
  129. }
  130. if (_entries.TryGetValue(type, out IReadOnlyList<DnsResourceRecord> existingRecords))
  131. return FilterExpiredRecords(type, existingRecords, serveStale);
  132. return Array.Empty<DnsResourceRecord>();
  133. }
  134. public override bool ContainsNameServerRecords()
  135. {
  136. if (!_entries.TryGetValue(DnsResourceRecordType.NS, out IReadOnlyList<DnsResourceRecord> records))
  137. return false;
  138. foreach (DnsResourceRecord record in records)
  139. {
  140. if (record.IsStale)
  141. continue;
  142. if (record.TtlValue < 1u)
  143. continue;
  144. return true;
  145. }
  146. return false;
  147. }
  148. #endregion
  149. }
  150. }