CacheZoneManager.cs 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2024 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.ResourceRecords;
  16. using DnsServerCore.Dns.Trees;
  17. using DnsServerCore.Dns.Zones;
  18. using System;
  19. using System.Collections.Generic;
  20. using System.IO;
  21. using System.Text;
  22. using System.Threading;
  23. using TechnitiumLibrary.Net;
  24. using TechnitiumLibrary.Net.Dns;
  25. using TechnitiumLibrary.Net.Dns.EDnsOptions;
  26. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  27. namespace DnsServerCore.Dns.ZoneManagers
  28. {
  29. public sealed class CacheZoneManager : DnsCache
  30. {
  31. #region variables
  32. public const uint FAILURE_RECORD_TTL = 10u;
  33. public const uint NEGATIVE_RECORD_TTL = 300u;
  34. public const uint MINIMUM_RECORD_TTL = 10u;
  35. public const uint MAXIMUM_RECORD_TTL = 7 * 24 * 60 * 60;
  36. public const uint SERVE_STALE_TTL = 3 * 24 * 60 * 60; //3 days serve stale ttl as per https://www.rfc-editor.org/rfc/rfc8767.html suggestion
  37. public const uint SERVE_STALE_ANSWER_TTL = 30; //as per https://www.rfc-editor.org/rfc/rfc8767.html suggestion
  38. public const uint SERVE_STALE_RESET_TTL = 30; //as per https://www.rfc-editor.org/rfc/rfc8767.html suggestion
  39. const uint SERVE_STALE_MIN_RESET_TTL = 10;
  40. const uint SERVE_STALE_MAX_RESET_TTL = 900;
  41. readonly DnsServer _dnsServer;
  42. readonly CacheZoneTree _root = new CacheZoneTree();
  43. uint _serveStaleResetTtl = SERVE_STALE_RESET_TTL;
  44. long _maximumEntries;
  45. long _totalEntries;
  46. #endregion
  47. #region constructor
  48. public CacheZoneManager(DnsServer dnsServer)
  49. : base(FAILURE_RECORD_TTL, NEGATIVE_RECORD_TTL, MINIMUM_RECORD_TTL, MAXIMUM_RECORD_TTL, SERVE_STALE_TTL, SERVE_STALE_ANSWER_TTL)
  50. {
  51. _dnsServer = dnsServer;
  52. }
  53. #endregion
  54. #region protected
  55. protected override void CacheRecords(IReadOnlyList<DnsResourceRecord> resourceRecords, NetworkAddress eDnsClientSubnet, DnsDatagramMetadata responseMetadata)
  56. {
  57. List<DnsResourceRecord> dnameRecords = null;
  58. //read and set glue records from base class; also collect any DNAME records found
  59. foreach (DnsResourceRecord resourceRecord in resourceRecords)
  60. {
  61. IReadOnlyList<DnsResourceRecord> glueRecords = GetGlueRecordsFrom(resourceRecord);
  62. IReadOnlyList<DnsResourceRecord> rrsigRecords = GetRRSIGRecordsFrom(resourceRecord);
  63. IReadOnlyList<DnsResourceRecord> nsecRecords = GetNSECRecordsFrom(resourceRecord);
  64. CacheRecordInfo rrInfo = resourceRecord.GetCacheRecordInfo();
  65. rrInfo.GlueRecords = glueRecords;
  66. rrInfo.RRSIGRecords = rrsigRecords;
  67. rrInfo.NSECRecords = nsecRecords;
  68. rrInfo.EDnsClientSubnet = eDnsClientSubnet;
  69. rrInfo.ResponseMetadata = responseMetadata;
  70. if (glueRecords is not null)
  71. {
  72. foreach (DnsResourceRecord glueRecord in glueRecords)
  73. {
  74. IReadOnlyList<DnsResourceRecord> glueRRSIGRecords = GetRRSIGRecordsFrom(glueRecord);
  75. if (glueRRSIGRecords is not null)
  76. glueRecord.GetCacheRecordInfo().RRSIGRecords = glueRRSIGRecords;
  77. }
  78. }
  79. if (nsecRecords is not null)
  80. {
  81. foreach (DnsResourceRecord nsecRecord in nsecRecords)
  82. {
  83. IReadOnlyList<DnsResourceRecord> nsecRRSIGRecords = GetRRSIGRecordsFrom(nsecRecord);
  84. if (nsecRRSIGRecords is not null)
  85. nsecRecord.GetCacheRecordInfo().RRSIGRecords = nsecRRSIGRecords;
  86. }
  87. }
  88. if (resourceRecord.Type == DnsResourceRecordType.DNAME)
  89. {
  90. if (dnameRecords is null)
  91. dnameRecords = new List<DnsResourceRecord>(1);
  92. dnameRecords.Add(resourceRecord);
  93. }
  94. }
  95. if (resourceRecords.Count == 1)
  96. {
  97. DnsResourceRecord resourceRecord = resourceRecords[0];
  98. CacheZone zone = _root.GetOrAdd(resourceRecord.Name, delegate (string key)
  99. {
  100. return new CacheZone(resourceRecord.Name, 1);
  101. });
  102. if (zone.SetRecords(resourceRecord.Type, resourceRecords, _dnsServer.ServeStale))
  103. Interlocked.Increment(ref _totalEntries);
  104. }
  105. else
  106. {
  107. Dictionary<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> groupedByDomainRecords = DnsResourceRecord.GroupRecords(resourceRecords);
  108. bool serveStale = _dnsServer.ServeStale;
  109. int addedEntries = 0;
  110. //add grouped records
  111. foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> groupedByTypeRecords in groupedByDomainRecords)
  112. {
  113. if (dnameRecords is not null)
  114. {
  115. bool foundSynthesizedCNAME = false;
  116. foreach (DnsResourceRecord dnameRecord in dnameRecords)
  117. {
  118. if (groupedByTypeRecords.Key.EndsWith("." + dnameRecord.Name, StringComparison.OrdinalIgnoreCase))
  119. {
  120. foundSynthesizedCNAME = true;
  121. break;
  122. }
  123. }
  124. if (foundSynthesizedCNAME)
  125. continue; //do not cache synthesized CNAME
  126. }
  127. CacheZone zone = _root.GetOrAdd(groupedByTypeRecords.Key, delegate (string key)
  128. {
  129. return new CacheZone(groupedByTypeRecords.Key, groupedByTypeRecords.Value.Count);
  130. });
  131. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> groupedRecords in groupedByTypeRecords.Value)
  132. {
  133. if (zone.SetRecords(groupedRecords.Key, groupedRecords.Value, serveStale))
  134. addedEntries++;
  135. }
  136. }
  137. if (addedEntries > 0)
  138. Interlocked.Add(ref _totalEntries, addedEntries);
  139. }
  140. }
  141. #endregion
  142. #region private
  143. private static IReadOnlyList<DnsResourceRecord> AddDSRecordsTo(CacheZone delegation, bool serveStale, IReadOnlyList<DnsResourceRecord> nsRecords, NetworkAddress eDnsClientSubnet, bool advancedForwardingClientSubnet)
  144. {
  145. IReadOnlyList<DnsResourceRecord> records = delegation.QueryRecords(DnsResourceRecordType.DS, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet);
  146. if ((records.Count > 0) && (records[0].Type == DnsResourceRecordType.DS))
  147. {
  148. List<DnsResourceRecord> newNSRecords = new List<DnsResourceRecord>(nsRecords.Count + records.Count);
  149. newNSRecords.AddRange(nsRecords);
  150. newNSRecords.AddRange(records);
  151. return newNSRecords;
  152. }
  153. //no DS records found check for NSEC records
  154. IReadOnlyList<DnsResourceRecord> nsecRecords = nsRecords[0].GetCacheRecordInfo().NSECRecords;
  155. if (nsecRecords is not null)
  156. {
  157. List<DnsResourceRecord> newNSRecords = new List<DnsResourceRecord>(nsRecords.Count + nsecRecords.Count);
  158. newNSRecords.AddRange(nsRecords);
  159. newNSRecords.AddRange(nsecRecords);
  160. return newNSRecords;
  161. }
  162. //found nothing; return original NS records
  163. return nsRecords;
  164. }
  165. private static void AddRRSIGRecords(IReadOnlyList<DnsResourceRecord> answer, out IReadOnlyList<DnsResourceRecord> newAnswer, out IReadOnlyList<DnsResourceRecord> newAuthority)
  166. {
  167. List<DnsResourceRecord> newAnswerList = new List<DnsResourceRecord>(answer.Count * 2);
  168. List<DnsResourceRecord> newAuthorityList = null;
  169. foreach (DnsResourceRecord record in answer)
  170. {
  171. if (record.Type == DnsResourceRecordType.RRSIG)
  172. continue; //skip RRSIG to avoid duplicates
  173. newAnswerList.Add(record);
  174. CacheRecordInfo rrInfo = record.GetCacheRecordInfo();
  175. IReadOnlyList<DnsResourceRecord> rrsigRecords = rrInfo.RRSIGRecords;
  176. if (rrsigRecords is not null)
  177. {
  178. newAnswerList.AddRange(rrsigRecords);
  179. foreach (DnsResourceRecord rrsigRecord in rrsigRecords)
  180. {
  181. if (!DnsRRSIGRecordData.IsWildcard(rrsigRecord))
  182. continue;
  183. //add NSEC/NSEC3 for the wildcard proof
  184. if (newAuthorityList is null)
  185. newAuthorityList = new List<DnsResourceRecord>(2);
  186. IReadOnlyList<DnsResourceRecord> nsecRecords = rrInfo.NSECRecords;
  187. if (nsecRecords is not null)
  188. {
  189. foreach (DnsResourceRecord nsecRecord in nsecRecords)
  190. {
  191. newAuthorityList.Add(nsecRecord);
  192. IReadOnlyList<DnsResourceRecord> nsecRRSIGRecords = nsecRecord.GetCacheRecordInfo().RRSIGRecords;
  193. if (nsecRRSIGRecords is not null)
  194. newAuthorityList.AddRange(nsecRRSIGRecords);
  195. }
  196. }
  197. }
  198. }
  199. }
  200. newAnswer = newAnswerList;
  201. newAuthority = newAuthorityList;
  202. }
  203. private void ResolveCNAME(DnsQuestionRecord question, DnsResourceRecord lastCNAME, bool serveStale, NetworkAddress eDnsClientSubnet, bool advancedForwardingClientSubnet, List<DnsResourceRecord> answerRecords)
  204. {
  205. int queryCount = 0;
  206. do
  207. {
  208. string cnameDomain = (lastCNAME.RDATA as DnsCNAMERecordData).Domain;
  209. if (lastCNAME.Name.Equals(cnameDomain, StringComparison.OrdinalIgnoreCase))
  210. break; //loop detected
  211. if (!_root.TryGet(cnameDomain, out CacheZone cacheZone))
  212. break;
  213. IReadOnlyList<DnsResourceRecord> records = cacheZone.QueryRecords(question.Type, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet);
  214. if (records.Count < 1)
  215. break;
  216. DnsResourceRecord lastRR = records[records.Count - 1];
  217. if (lastRR.Type != DnsResourceRecordType.CNAME)
  218. {
  219. answerRecords.AddRange(records);
  220. break;
  221. }
  222. foreach (DnsResourceRecord answerRecord in answerRecords)
  223. {
  224. if (answerRecord.Type != DnsResourceRecordType.CNAME)
  225. continue;
  226. if (answerRecord.RDATA.Equals(lastRR.RDATA))
  227. return; //loop detected
  228. }
  229. answerRecords.AddRange(records);
  230. lastCNAME = lastRR;
  231. }
  232. while (++queryCount < DnsServer.MAX_CNAME_HOPS);
  233. }
  234. private bool DoDNAMESubstitution(DnsQuestionRecord question, IReadOnlyList<DnsResourceRecord> answer, bool serveStale, NetworkAddress eDnsClientSubnet, bool advancedForwardingClientSubnet, out IReadOnlyList<DnsResourceRecord> newAnswer)
  235. {
  236. DnsResourceRecord dnameRR = answer[0];
  237. string result = (dnameRR.RDATA as DnsDNAMERecordData).Substitute(question.Name, dnameRR.Name);
  238. if (DnsClient.IsDomainNameValid(result))
  239. {
  240. DnsResourceRecord cnameRR = new DnsResourceRecord(question.Name, DnsResourceRecordType.CNAME, question.Class, dnameRR.TTL, new DnsCNAMERecordData(result));
  241. List<DnsResourceRecord> list = new List<DnsResourceRecord>(5)
  242. {
  243. dnameRR,
  244. cnameRR
  245. };
  246. ResolveCNAME(question, cnameRR, serveStale, eDnsClientSubnet, advancedForwardingClientSubnet, list);
  247. newAnswer = list;
  248. return true;
  249. }
  250. else
  251. {
  252. newAnswer = answer;
  253. return false;
  254. }
  255. }
  256. private List<DnsResourceRecord> GetAdditionalRecords(IReadOnlyList<DnsResourceRecord> refRecords, bool serveStale, bool dnssecOk, NetworkAddress eDnsClientSubnet, bool advancedForwardingClientSubnet)
  257. {
  258. List<DnsResourceRecord> additionalRecords = new List<DnsResourceRecord>();
  259. foreach (DnsResourceRecord refRecord in refRecords)
  260. {
  261. switch (refRecord.Type)
  262. {
  263. case DnsResourceRecordType.NS:
  264. if (refRecord.RDATA is DnsNSRecordData ns)
  265. ResolveAdditionalRecords(refRecord, ns.NameServer, serveStale, dnssecOk, eDnsClientSubnet, advancedForwardingClientSubnet, additionalRecords);
  266. break;
  267. case DnsResourceRecordType.MX:
  268. if (refRecord.RDATA is DnsMXRecordData mx)
  269. ResolveAdditionalRecords(refRecord, mx.Exchange, serveStale, dnssecOk, eDnsClientSubnet, advancedForwardingClientSubnet, additionalRecords);
  270. break;
  271. case DnsResourceRecordType.SRV:
  272. if (refRecord.RDATA is DnsSRVRecordData srv)
  273. ResolveAdditionalRecords(refRecord, srv.Target, serveStale, dnssecOk, eDnsClientSubnet, advancedForwardingClientSubnet, additionalRecords);
  274. break;
  275. case DnsResourceRecordType.SVCB:
  276. case DnsResourceRecordType.HTTPS:
  277. if (refRecord.RDATA is DnsSVCBRecordData svcb)
  278. {
  279. string targetName = svcb.TargetName;
  280. if (svcb.SvcPriority == 0)
  281. {
  282. //For AliasMode SVCB RRs, a TargetName of "." indicates that the service is not available or does not exist [draft-ietf-dnsop-svcb-https-12]
  283. if ((targetName.Length == 0) || targetName.Equals(refRecord.Name, StringComparison.OrdinalIgnoreCase))
  284. break;
  285. }
  286. else
  287. {
  288. //For ServiceMode SVCB RRs, if TargetName has the value ".", then the owner name of this record MUST be used as the effective TargetName [draft-ietf-dnsop-svcb-https-12]
  289. if (targetName.Length == 0)
  290. targetName = refRecord.Name;
  291. }
  292. ResolveAdditionalRecords(refRecord, targetName, serveStale, dnssecOk, eDnsClientSubnet, advancedForwardingClientSubnet, additionalRecords);
  293. }
  294. break;
  295. }
  296. }
  297. return additionalRecords;
  298. }
  299. private void ResolveAdditionalRecords(DnsResourceRecord refRecord, string domain, bool serveStale, bool dnssecOk, NetworkAddress eDnsClientSubnet, bool advancedForwardingClientSubnet, List<DnsResourceRecord> additionalRecords)
  300. {
  301. IReadOnlyList<DnsResourceRecord> glueRecords = refRecord.GetCacheRecordInfo().GlueRecords;
  302. if (glueRecords is not null)
  303. {
  304. bool added = false;
  305. foreach (DnsResourceRecord glueRecord in glueRecords)
  306. {
  307. if (!glueRecord.IsStale)
  308. {
  309. added = true;
  310. additionalRecords.Add(glueRecord);
  311. if (dnssecOk)
  312. {
  313. IReadOnlyList<DnsResourceRecord> rrsigRecords = glueRecord.GetCacheRecordInfo().RRSIGRecords;
  314. if (rrsigRecords is not null)
  315. additionalRecords.AddRange(rrsigRecords);
  316. }
  317. }
  318. }
  319. if (added)
  320. return;
  321. }
  322. int count = 0;
  323. while ((count++ < DnsServer.MAX_CNAME_HOPS) && _root.TryGet(domain, out CacheZone cacheZone))
  324. {
  325. if (((refRecord.Type == DnsResourceRecordType.SVCB) || (refRecord.Type == DnsResourceRecordType.HTTPS)) && ((refRecord.RDATA as DnsSVCBRecordData).SvcPriority == 0))
  326. {
  327. //resolve SVCB/HTTPS for Alias mode refRecord
  328. IReadOnlyList<DnsResourceRecord> records = cacheZone.QueryRecords(refRecord.Type, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet);
  329. if ((records.Count > 0) && (records[0].Type == refRecord.Type) && (records[0].RDATA is DnsSVCBRecordData svcb))
  330. {
  331. additionalRecords.AddRange(records);
  332. string targetName = svcb.TargetName;
  333. if (svcb.SvcPriority == 0)
  334. {
  335. //Alias mode
  336. if ((targetName.Length == 0) || targetName.Equals(records[0].Name, StringComparison.OrdinalIgnoreCase))
  337. break; //For AliasMode SVCB RRs, a TargetName of "." indicates that the service is not available or does not exist [draft-ietf-dnsop-svcb-https-12]
  338. foreach (DnsResourceRecord additionalRecord in additionalRecords)
  339. {
  340. if (additionalRecord.Name.Equals(targetName, StringComparison.OrdinalIgnoreCase))
  341. return; //loop detected
  342. }
  343. //continue to resolve SVCB/HTTPS further
  344. domain = targetName;
  345. refRecord = records[0];
  346. continue;
  347. }
  348. else
  349. {
  350. //Service mode
  351. if (targetName.Length > 0)
  352. {
  353. //continue to resolve A/AAAA for target name
  354. domain = targetName;
  355. refRecord = records[0];
  356. continue;
  357. }
  358. //resolve A/AAAA below
  359. }
  360. }
  361. }
  362. bool hasA = false;
  363. bool hasAAAA = false;
  364. if ((refRecord.Type == DnsResourceRecordType.SRV) || (refRecord.Type == DnsResourceRecordType.SVCB) || (refRecord.Type == DnsResourceRecordType.HTTPS))
  365. {
  366. foreach (DnsResourceRecord additionalRecord in additionalRecords)
  367. {
  368. if (additionalRecord.Name.Equals(domain, StringComparison.OrdinalIgnoreCase))
  369. {
  370. switch (additionalRecord.Type)
  371. {
  372. case DnsResourceRecordType.A:
  373. hasA = true;
  374. break;
  375. case DnsResourceRecordType.AAAA:
  376. hasAAAA = true;
  377. break;
  378. }
  379. }
  380. if (hasA && hasAAAA)
  381. break;
  382. }
  383. }
  384. if (!hasA)
  385. {
  386. IReadOnlyList<DnsResourceRecord> records = cacheZone.QueryRecords(DnsResourceRecordType.A, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet);
  387. if ((records.Count > 0) && (records[0].Type == DnsResourceRecordType.A))
  388. additionalRecords.AddRange(records);
  389. }
  390. if (!hasAAAA)
  391. {
  392. IReadOnlyList<DnsResourceRecord> records = cacheZone.QueryRecords(DnsResourceRecordType.AAAA, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet);
  393. if ((records.Count > 0) && (records[0].Type == DnsResourceRecordType.AAAA))
  394. additionalRecords.AddRange(records);
  395. }
  396. break;
  397. }
  398. }
  399. private int RemoveExpiredRecordsInternal(bool serveStale, long minimumEntriesToRemove)
  400. {
  401. int removedEntries = 0;
  402. foreach (CacheZone zone in _root)
  403. {
  404. removedEntries += zone.RemoveExpiredRecords(serveStale);
  405. if (zone.IsEmpty)
  406. _root.TryRemove(zone.Name, out _); //remove empty zone
  407. if ((minimumEntriesToRemove > 0) && (removedEntries >= minimumEntriesToRemove))
  408. break;
  409. }
  410. if (removedEntries > 0)
  411. {
  412. long totalEntries = Interlocked.Add(ref _totalEntries, -removedEntries);
  413. if (totalEntries < 0)
  414. Interlocked.Add(ref _totalEntries, -totalEntries);
  415. }
  416. return removedEntries;
  417. }
  418. private int RemoveLeastUsedRecordsInternal(DateTime cutoff, long minimumEntriesToRemove)
  419. {
  420. int removedEntries = 0;
  421. foreach (CacheZone zone in _root)
  422. {
  423. removedEntries += zone.RemoveLeastUsedRecords(cutoff);
  424. if (zone.IsEmpty)
  425. _root.TryRemove(zone.Name, out _); //remove empty zone
  426. if ((minimumEntriesToRemove > 0) && (removedEntries >= minimumEntriesToRemove))
  427. break;
  428. }
  429. if (removedEntries > 0)
  430. {
  431. long totalEntries = Interlocked.Add(ref _totalEntries, -removedEntries);
  432. if (totalEntries < 0)
  433. Interlocked.Add(ref _totalEntries, -totalEntries);
  434. }
  435. return removedEntries;
  436. }
  437. #endregion
  438. #region public
  439. public override void RemoveExpiredRecords()
  440. {
  441. bool serveStale = _dnsServer.ServeStale;
  442. //remove expired records/expired stale records
  443. RemoveExpiredRecordsInternal(serveStale, 0);
  444. if (_maximumEntries < 1)
  445. return; //cache limit feature disabled
  446. //find minimum entries to remove
  447. long minimumEntriesToRemove = _totalEntries - _maximumEntries;
  448. if (minimumEntriesToRemove < 1)
  449. return; //no need to remove
  450. //remove stale records if they exist
  451. if (serveStale)
  452. minimumEntriesToRemove -= RemoveExpiredRecordsInternal(false, minimumEntriesToRemove);
  453. if (minimumEntriesToRemove < 1)
  454. return; //task completed
  455. //remove least recently used records
  456. for (int seconds = 86400; seconds > 0; seconds /= 2)
  457. {
  458. DateTime cutoff = DateTime.UtcNow.AddSeconds(-seconds);
  459. minimumEntriesToRemove -= RemoveLeastUsedRecordsInternal(cutoff, minimumEntriesToRemove);
  460. if (minimumEntriesToRemove < 1)
  461. break; //task completed
  462. }
  463. }
  464. public void DeleteEDnsClientSubnetData()
  465. {
  466. int removedEntries = 0;
  467. foreach (CacheZone zone in _root)
  468. {
  469. removedEntries += zone.DeleteEDnsClientSubnetData();
  470. if (zone.IsEmpty)
  471. _root.TryRemove(zone.Name, out _); //remove empty zone
  472. }
  473. if (removedEntries > 0)
  474. {
  475. long totalEntries = Interlocked.Add(ref _totalEntries, -removedEntries);
  476. if (totalEntries < 0)
  477. Interlocked.Add(ref _totalEntries, -totalEntries);
  478. }
  479. }
  480. public override void Flush()
  481. {
  482. _root.Clear();
  483. long totalEntries = _totalEntries;
  484. totalEntries = Interlocked.Add(ref _totalEntries, -totalEntries);
  485. if (totalEntries < 0)
  486. Interlocked.Add(ref _totalEntries, -totalEntries);
  487. }
  488. public bool DeleteZone(string domain)
  489. {
  490. if (_root.TryRemoveTree(domain, out _, out int removedEntries))
  491. {
  492. if (removedEntries > 0)
  493. {
  494. long totalEntries = Interlocked.Add(ref _totalEntries, -removedEntries);
  495. if (totalEntries < 0)
  496. Interlocked.Add(ref _totalEntries, -totalEntries);
  497. }
  498. return true;
  499. }
  500. return false;
  501. }
  502. public void ListSubDomains(string domain, List<string> subDomains)
  503. {
  504. _root.ListSubDomains(domain, subDomains);
  505. }
  506. public void ListAllRecords(string domain, List<DnsResourceRecord> records)
  507. {
  508. if (_root.TryGet(domain, out CacheZone zone))
  509. zone.ListAllRecords(records);
  510. }
  511. public override DnsDatagram QueryClosestDelegation(DnsDatagram request)
  512. {
  513. string domain = request.Question[0].Name;
  514. NetworkAddress eDnsClientSubnet = null;
  515. bool advancedForwardingClientSubnet = false;
  516. {
  517. EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption();
  518. if (requestECS is not null)
  519. {
  520. eDnsClientSubnet = new NetworkAddress(requestECS.Address, requestECS.SourcePrefixLength);
  521. advancedForwardingClientSubnet = requestECS.AdvancedForwardingClientSubnet;
  522. }
  523. }
  524. do
  525. {
  526. _ = _root.FindZone(domain, out _, out CacheZone delegation);
  527. if (delegation is null)
  528. return null;
  529. //return closest name servers in delegation
  530. IReadOnlyList<DnsResourceRecord> closestAuthority = delegation.QueryRecords(DnsResourceRecordType.NS, false, true, eDnsClientSubnet, advancedForwardingClientSubnet);
  531. if ((closestAuthority.Count > 0) && (closestAuthority[0].Type == DnsResourceRecordType.NS))
  532. {
  533. if (request.DnssecOk)
  534. {
  535. if (closestAuthority[0].DnssecStatus != DnssecStatus.Disabled) //dont return records with disabled status
  536. {
  537. closestAuthority = AddDSRecordsTo(delegation, false, closestAuthority, eDnsClientSubnet, advancedForwardingClientSubnet);
  538. IReadOnlyList<DnsResourceRecord> additional = GetAdditionalRecords(closestAuthority, false, true, eDnsClientSubnet, advancedForwardingClientSubnet);
  539. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional);
  540. }
  541. }
  542. else
  543. {
  544. IReadOnlyList<DnsResourceRecord> additional = GetAdditionalRecords(closestAuthority, false, false, eDnsClientSubnet, advancedForwardingClientSubnet);
  545. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional);
  546. }
  547. }
  548. domain = AuthZoneManager.GetParentZone(delegation.Name);
  549. }
  550. while (domain is not null);
  551. //no cached delegation found
  552. return null;
  553. }
  554. public override DnsDatagram Query(DnsDatagram request, bool serveStale = false, bool findClosestNameServers = false, bool resetExpiry = false)
  555. {
  556. DnsQuestionRecord question = request.Question[0];
  557. NetworkAddress eDnsClientSubnet = null;
  558. bool advancedForwardingClientSubnet = false;
  559. {
  560. EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption();
  561. if (requestECS is not null)
  562. {
  563. eDnsClientSubnet = new NetworkAddress(requestECS.Address, requestECS.SourcePrefixLength);
  564. advancedForwardingClientSubnet = requestECS.AdvancedForwardingClientSubnet;
  565. }
  566. }
  567. CacheZone zone;
  568. CacheZone closest = null;
  569. CacheZone delegation = null;
  570. if (findClosestNameServers)
  571. {
  572. zone = _root.FindZone(question.Name, out closest, out delegation);
  573. }
  574. else
  575. {
  576. if (!_root.TryGet(question.Name, out zone))
  577. _ = _root.FindZone(question.Name, out closest, out _); //zone not found; attempt to find closest
  578. }
  579. bool dnssecOk = request.DnssecOk;
  580. if (zone is not null)
  581. {
  582. //zone found
  583. IReadOnlyList<DnsResourceRecord> answer = zone.QueryRecords(question.Type, serveStale, false, eDnsClientSubnet, advancedForwardingClientSubnet);
  584. if (answer.Count > 0)
  585. {
  586. //answer found in cache
  587. DnsResourceRecord firstRR = answer[0];
  588. if (firstRR.RDATA is DnsSpecialCacheRecordData dnsSpecialCacheRecord)
  589. {
  590. if (dnssecOk)
  591. {
  592. foreach (DnsResourceRecord originalAuthority in dnsSpecialCacheRecord.OriginalAuthority)
  593. {
  594. if (originalAuthority.DnssecStatus == DnssecStatus.Disabled)
  595. goto beforeFindClosestNameServers; //dont return answer with disabled status
  596. }
  597. }
  598. if (resetExpiry)
  599. {
  600. if (firstRR.IsStale)
  601. firstRR.ResetExpiry(_serveStaleResetTtl); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767
  602. if (dnsSpecialCacheRecord.Authority is not null)
  603. {
  604. foreach (DnsResourceRecord record in dnsSpecialCacheRecord.Authority)
  605. {
  606. if (record.IsStale)
  607. record.ResetExpiry(_serveStaleResetTtl); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767
  608. }
  609. }
  610. }
  611. IReadOnlyList<EDnsOption> specialOptions;
  612. if (firstRR.WasExpiryReset || firstRR.IsStale)
  613. {
  614. List<EDnsOption> newOptions = new List<EDnsOption>(dnsSpecialCacheRecord.EDnsOptions.Count + 1);
  615. newOptions.AddRange(dnsSpecialCacheRecord.EDnsOptions);
  616. if (dnsSpecialCacheRecord.RCODE == DnsResponseCode.NxDomain)
  617. newOptions.Add(new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.StaleNxDomainAnswer, firstRR.Name.ToLowerInvariant() + " " + firstRR.Type.ToString() + " " + firstRR.Class.ToString())));
  618. else
  619. newOptions.Add(new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.StaleAnswer, firstRR.Name.ToLowerInvariant() + " " + firstRR.Type.ToString() + " " + firstRR.Class.ToString())));
  620. specialOptions = newOptions;
  621. }
  622. else
  623. {
  624. specialOptions = dnsSpecialCacheRecord.EDnsOptions;
  625. }
  626. if (eDnsClientSubnet is not null)
  627. {
  628. EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption(true);
  629. if (requestECS is not null)
  630. {
  631. NetworkAddress recordECS = firstRR.GetCacheRecordInfo().EDnsClientSubnet;
  632. if (recordECS is not null)
  633. {
  634. EDnsOption[] ecsOption = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, recordECS.PrefixLength, requestECS.Address);
  635. if ((specialOptions is null) || (specialOptions.Count == 0))
  636. {
  637. specialOptions = ecsOption;
  638. }
  639. else
  640. {
  641. List<EDnsOption> newOptions = new List<EDnsOption>(specialOptions.Count + 1);
  642. newOptions.AddRange(specialOptions);
  643. newOptions.Add(ecsOption[0]);
  644. specialOptions = newOptions;
  645. }
  646. }
  647. }
  648. }
  649. if (dnssecOk)
  650. {
  651. bool authenticData;
  652. switch (dnsSpecialCacheRecord.Type)
  653. {
  654. case DnsSpecialCacheRecordType.NegativeCache:
  655. authenticData = true;
  656. break;
  657. default:
  658. authenticData = false;
  659. break;
  660. }
  661. if (request.CheckingDisabled)
  662. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, authenticData, true, dnsSpecialCacheRecord.OriginalRCODE, request.Question, dnsSpecialCacheRecord.OriginalAnswer, dnsSpecialCacheRecord.OriginalAuthority, dnsSpecialCacheRecord.Additional, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.DNSSEC_OK, specialOptions);
  663. else
  664. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, authenticData, false, dnsSpecialCacheRecord.RCODE, request.Question, null, dnsSpecialCacheRecord.Authority, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.DNSSEC_OK, specialOptions);
  665. }
  666. else
  667. {
  668. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, request.CheckingDisabled, dnsSpecialCacheRecord.RCODE, request.Question, null, dnsSpecialCacheRecord.NoDnssecAuthority, null, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, specialOptions);
  669. }
  670. }
  671. DnsResourceRecord lastRR = answer[answer.Count - 1];
  672. if ((lastRR.Type != question.Type) && (lastRR.Type == DnsResourceRecordType.CNAME) && (question.Type != DnsResourceRecordType.ANY))
  673. {
  674. List<DnsResourceRecord> newAnswers = new List<DnsResourceRecord>(answer.Count + 3);
  675. newAnswers.AddRange(answer);
  676. ResolveCNAME(question, lastRR, serveStale, eDnsClientSubnet, advancedForwardingClientSubnet, newAnswers);
  677. answer = newAnswers;
  678. }
  679. IReadOnlyList<DnsResourceRecord> authority = null;
  680. EDnsHeaderFlags ednsFlags = EDnsHeaderFlags.None;
  681. if (dnssecOk)
  682. {
  683. //DNSSEC enabled
  684. foreach (DnsResourceRecord record in answer)
  685. {
  686. if (record.DnssecStatus == DnssecStatus.Disabled)
  687. goto beforeFindClosestNameServers; //dont return answer when status is disabled
  688. }
  689. //add RRSIG records
  690. AddRRSIGRecords(answer, out answer, out authority);
  691. ednsFlags = EDnsHeaderFlags.DNSSEC_OK;
  692. }
  693. IReadOnlyList<DnsResourceRecord> additional = null;
  694. switch (question.Type)
  695. {
  696. case DnsResourceRecordType.NS:
  697. case DnsResourceRecordType.MX:
  698. case DnsResourceRecordType.SRV:
  699. case DnsResourceRecordType.SVCB:
  700. case DnsResourceRecordType.HTTPS:
  701. additional = GetAdditionalRecords(answer, serveStale, dnssecOk, eDnsClientSubnet, advancedForwardingClientSubnet);
  702. break;
  703. }
  704. if (resetExpiry)
  705. {
  706. foreach (DnsResourceRecord record in answer)
  707. {
  708. if (record.IsStale)
  709. record.ResetExpiry(_serveStaleResetTtl); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767
  710. }
  711. if (additional is not null)
  712. {
  713. foreach (DnsResourceRecord record in additional)
  714. {
  715. if (record.IsStale)
  716. record.ResetExpiry(_serveStaleResetTtl); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767
  717. }
  718. }
  719. }
  720. IReadOnlyList<EDnsOption> options = null;
  721. foreach (DnsResourceRecord record in answer)
  722. {
  723. if (record.WasExpiryReset || record.IsStale)
  724. options = [new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.StaleAnswer, record.Name.ToLowerInvariant() + " " + record.Type.ToString() + " " + record.Class.ToString()))];
  725. }
  726. if (eDnsClientSubnet is not null)
  727. {
  728. EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption(true);
  729. if (requestECS is not null)
  730. {
  731. NetworkAddress suitableECS = null;
  732. foreach (DnsResourceRecord record in answer)
  733. {
  734. NetworkAddress recordECS = record.GetCacheRecordInfo().EDnsClientSubnet;
  735. if (recordECS is not null)
  736. {
  737. if ((suitableECS is null) || (recordECS.PrefixLength > suitableECS.PrefixLength))
  738. suitableECS = recordECS;
  739. }
  740. }
  741. if (suitableECS is not null)
  742. {
  743. EDnsOption[] ecsOption = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, suitableECS.PrefixLength, requestECS.Address);
  744. if (options is null)
  745. {
  746. options = ecsOption;
  747. }
  748. else
  749. {
  750. List<EDnsOption> newOptions = new List<EDnsOption>(options.Count + 1);
  751. newOptions.AddRange(options);
  752. newOptions.Add(ecsOption[0]);
  753. options = newOptions;
  754. }
  755. }
  756. }
  757. }
  758. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, dnssecOk && (answer.Count > 0) && (answer[0].DnssecStatus == DnssecStatus.Secure), request.CheckingDisabled, DnsResponseCode.NoError, request.Question, answer, authority, additional, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, ednsFlags, options);
  759. }
  760. }
  761. else
  762. {
  763. //zone not found
  764. //check for DNAME in closest zone
  765. if (closest is not null)
  766. {
  767. IReadOnlyList<DnsResourceRecord> answer = closest.QueryRecords(DnsResourceRecordType.DNAME, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet);
  768. if ((answer.Count > 0) && (answer[0].Type == DnsResourceRecordType.DNAME))
  769. {
  770. DnsResponseCode rCode;
  771. if (DoDNAMESubstitution(question, answer, serveStale, eDnsClientSubnet, advancedForwardingClientSubnet, out answer))
  772. rCode = DnsResponseCode.NoError;
  773. else
  774. rCode = DnsResponseCode.YXDomain;
  775. IReadOnlyList<DnsResourceRecord> authority = null;
  776. EDnsHeaderFlags ednsFlags = EDnsHeaderFlags.None;
  777. if (dnssecOk)
  778. {
  779. //DNSSEC enabled
  780. foreach (DnsResourceRecord record in answer)
  781. {
  782. if (record.DnssecStatus == DnssecStatus.Disabled)
  783. goto beforeFindClosestNameServers; //dont return answer when status is disabled
  784. }
  785. //add RRSIG records
  786. AddRRSIGRecords(answer, out answer, out authority);
  787. ednsFlags = EDnsHeaderFlags.DNSSEC_OK;
  788. }
  789. if (resetExpiry)
  790. {
  791. foreach (DnsResourceRecord record in answer)
  792. {
  793. if (record.IsStale)
  794. record.ResetExpiry(_serveStaleResetTtl); //reset expiry by 30 seconds so that resolver tries again only after 30 seconds as per RFC 8767
  795. }
  796. }
  797. EDnsOption[] options = null;
  798. foreach (DnsResourceRecord record in answer)
  799. {
  800. if (record.WasExpiryReset || record.IsStale)
  801. options = [new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.StaleAnswer, record.Name.ToLowerInvariant() + " " + record.Type.ToString() + " " + record.Class.ToString()))];
  802. }
  803. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, dnssecOk && (answer.Count > 0) && (answer[0].DnssecStatus == DnssecStatus.Secure), request.CheckingDisabled, rCode, request.Question, answer, authority, null, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, ednsFlags, options);
  804. }
  805. }
  806. }
  807. //no answer in cache
  808. beforeFindClosestNameServers:
  809. //check for closest delegation if any
  810. if (findClosestNameServers && (delegation is not null))
  811. {
  812. //return closest name servers in delegation
  813. if (question.Type == DnsResourceRecordType.DS)
  814. {
  815. //find parent delegation
  816. string domain = AuthZoneManager.GetParentZone(question.Name);
  817. if (domain is null)
  818. return null; //dont find NS for root
  819. _ = _root.FindZone(domain, out _, out delegation);
  820. if (delegation is null)
  821. return null; //no cached delegation found
  822. }
  823. while (true)
  824. {
  825. IReadOnlyList<DnsResourceRecord> closestAuthority = delegation.QueryRecords(DnsResourceRecordType.NS, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet);
  826. if ((closestAuthority.Count > 0) && (closestAuthority[0].Type == DnsResourceRecordType.NS))
  827. {
  828. if (dnssecOk)
  829. {
  830. if (closestAuthority[0].DnssecStatus != DnssecStatus.Disabled) //dont return records with disabled status
  831. {
  832. closestAuthority = AddDSRecordsTo(delegation, serveStale, closestAuthority, eDnsClientSubnet, advancedForwardingClientSubnet);
  833. IReadOnlyList<DnsResourceRecord> additional = GetAdditionalRecords(closestAuthority, serveStale, true, eDnsClientSubnet, advancedForwardingClientSubnet);
  834. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, closestAuthority[0].DnssecStatus == DnssecStatus.Secure, request.CheckingDisabled, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional);
  835. }
  836. }
  837. else
  838. {
  839. IReadOnlyList<DnsResourceRecord> additional = GetAdditionalRecords(closestAuthority, serveStale, false, eDnsClientSubnet, advancedForwardingClientSubnet);
  840. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, request.CheckingDisabled, DnsResponseCode.NoError, request.Question, null, closestAuthority, additional);
  841. }
  842. }
  843. string domain = AuthZoneManager.GetParentZone(delegation.Name);
  844. if (domain is null)
  845. return null; //dont find NS for root
  846. _ = _root.FindZone(domain, out _, out delegation);
  847. if (delegation is null)
  848. return null; //no cached delegation found
  849. }
  850. }
  851. //no cached delegation found
  852. return null;
  853. }
  854. public void LoadCacheZoneFile()
  855. {
  856. string cacheZoneFile = Path.Combine(_dnsServer.ConfigFolder, "cache.bin");
  857. if (!File.Exists(cacheZoneFile))
  858. return;
  859. _dnsServer.LogManager?.Write("Loading DNS Cache from disk...");
  860. using (FileStream fS = new FileStream(cacheZoneFile, FileMode.Open, FileAccess.Read))
  861. {
  862. BinaryReader bR = new BinaryReader(fS);
  863. if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "CZ")
  864. throw new InvalidDataException("CacheZoneManager format is invalid.");
  865. int version = bR.ReadByte();
  866. switch (version)
  867. {
  868. case 1:
  869. int addedEntries = 0;
  870. try
  871. {
  872. bool serveStale = _dnsServer.ServeStale;
  873. while (bR.BaseStream.Position < bR.BaseStream.Length)
  874. {
  875. CacheZone zone = CacheZone.ReadFrom(bR, serveStale);
  876. if (!zone.IsEmpty)
  877. {
  878. if (_root.TryAdd(zone.Name, zone))
  879. addedEntries += zone.TotalEntries;
  880. }
  881. }
  882. }
  883. finally
  884. {
  885. if (addedEntries > 0)
  886. Interlocked.Add(ref _totalEntries, addedEntries);
  887. }
  888. break;
  889. default:
  890. throw new InvalidDataException("CacheZoneManager format version not supported: " + version);
  891. }
  892. }
  893. _dnsServer.LogManager?.Write("DNS Cache was loaded from disk successfully.");
  894. }
  895. public void SaveCacheZoneFile()
  896. {
  897. _dnsServer.LogManager?.Write("Saving DNS Cache to disk...");
  898. string cacheZoneFile = Path.Combine(_dnsServer.ConfigFolder, "cache.bin");
  899. using (FileStream fS = new FileStream(cacheZoneFile, FileMode.Create, FileAccess.Write))
  900. {
  901. BinaryWriter bW = new BinaryWriter(fS);
  902. bW.Write(Encoding.ASCII.GetBytes("CZ")); //format
  903. bW.Write((byte)1); //version
  904. foreach (CacheZone zone in _root)
  905. zone.WriteTo(bW);
  906. }
  907. _dnsServer.LogManager?.Write("DNS Cache was saved to disk successfully.");
  908. }
  909. public void DeleteCacheZoneFile()
  910. {
  911. string cacheZoneFile = Path.Combine(_dnsServer.ConfigFolder, "cache.bin");
  912. if (File.Exists(cacheZoneFile))
  913. File.Delete(cacheZoneFile);
  914. }
  915. #endregion
  916. #region properties
  917. public uint ServeStaleResetTtl
  918. {
  919. get { return _serveStaleResetTtl; }
  920. set
  921. {
  922. if ((value < SERVE_STALE_MIN_RESET_TTL) || (value > SERVE_STALE_MAX_RESET_TTL))
  923. throw new ArgumentOutOfRangeException(nameof(ServeStaleResetTtl), "Serve stale reset TTL must be between " + SERVE_STALE_MIN_RESET_TTL + " and " + SERVE_STALE_MAX_RESET_TTL + " seconds. Recommended value is 30 seconds.");
  924. _serveStaleResetTtl = value;
  925. }
  926. }
  927. public long MaximumEntries
  928. {
  929. get { return _maximumEntries; }
  930. set
  931. {
  932. if (value < 0)
  933. throw new ArgumentOutOfRangeException(nameof(MaximumEntries), "Invalid cache maximum entries value. Valid range is 0 and above.");
  934. _maximumEntries = value;
  935. }
  936. }
  937. public long TotalEntries
  938. { get { return _totalEntries; } }
  939. #endregion
  940. }
  941. }