CacheZoneManager.cs 52 KB

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