AuthZoneTree.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using DnsServerCore.Dns.Zones;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Threading;
  19. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  20. namespace DnsServerCore.Dns.Trees
  21. {
  22. class AuthZoneTree : ZoneTree<AuthZoneNode, SubDomainZone, ApexZone>
  23. {
  24. #region private
  25. private static Node GetPreviousSubDomainZoneNode(byte[] key, Node currentNode, int baseDepth)
  26. {
  27. int k;
  28. NodeValue currentValue = currentNode.Value;
  29. if (currentValue is null)
  30. {
  31. //key value does not exists
  32. if (currentNode.Children is null)
  33. {
  34. //no children available; move to previous sibling
  35. k = currentNode.K - 1; //find previous node from sibling starting at k - 1
  36. currentNode = currentNode.Parent;
  37. }
  38. else
  39. {
  40. if (key.Length == currentNode.Depth)
  41. {
  42. //current node belongs to the key
  43. k = currentNode.K - 1; //find previous node from sibling starting at k - 1
  44. currentNode = currentNode.Parent;
  45. }
  46. else
  47. {
  48. //find the previous node for the given k in current node's children
  49. k = key[currentNode.Depth];
  50. }
  51. }
  52. }
  53. else
  54. {
  55. int x = DnsNSECRecordData.CanonicalComparison(currentValue.Key, key);
  56. if (x == 0)
  57. {
  58. //current node value matches the key
  59. k = currentNode.K - 1; //find previous node from sibling starting at k - 1
  60. currentNode = currentNode.Parent;
  61. }
  62. else if (x > 0)
  63. {
  64. //current node value is larger for the key
  65. k = currentNode.K - 1; //find previous node from sibling starting at k - 1
  66. currentNode = currentNode.Parent;
  67. }
  68. else
  69. {
  70. //current node value is smaller for the key
  71. if (currentNode.Children is null)
  72. {
  73. //the current node is previous node since no children exists and value is smaller for the key
  74. return currentNode;
  75. }
  76. else
  77. {
  78. //find the previous node for the given k in current node's children
  79. k = key[currentNode.Depth];
  80. }
  81. }
  82. }
  83. //start reverse tree traversal
  84. while ((currentNode is not null) && (currentNode.Depth >= baseDepth))
  85. {
  86. Node[] children = currentNode.Children;
  87. if (children is not null)
  88. {
  89. //find previous child node
  90. Node child = null;
  91. for (int i = k; i > -1; i--)
  92. {
  93. child = Volatile.Read(ref children[i]);
  94. if (child is not null)
  95. {
  96. bool childNodeHasApexZone = false;
  97. NodeValue childValue = child.Value;
  98. if (childValue is not null)
  99. {
  100. AuthZoneNode authZoneNode = childValue.Value;
  101. if (authZoneNode is not null)
  102. {
  103. if (authZoneNode.ApexZone is not null)
  104. childNodeHasApexZone = true; //must stop checking children of the apex of the sub zone
  105. }
  106. }
  107. if (!childNodeHasApexZone && child.Children is not null)
  108. break; //child has further children so check them first
  109. if (childValue is not null)
  110. {
  111. AuthZoneNode authZoneNode = childValue.Value;
  112. if (authZoneNode is not null)
  113. {
  114. if (authZoneNode.ParentSideZone is not null)
  115. {
  116. //is sub domain zone
  117. return child; //child has value so return it
  118. }
  119. if (authZoneNode.ApexZone is not null)
  120. {
  121. //is apex zone
  122. //skip to next child to avoid listing this auth zone's sub domains
  123. child = null; //set null to avoid child being set as current after the loop
  124. }
  125. }
  126. }
  127. }
  128. }
  129. if (child is not null)
  130. {
  131. //make found child as current
  132. k = children.Length - 1;
  133. currentNode = child;
  134. continue; //start over
  135. }
  136. }
  137. //no child node available; check for current node value
  138. {
  139. NodeValue value = currentNode.Value;
  140. if (value is not null)
  141. {
  142. AuthZoneNode authZoneNode = value.Value;
  143. if (authZoneNode is not null)
  144. {
  145. if ((authZoneNode.ApexZone is not null) && (currentNode.Depth == baseDepth))
  146. {
  147. //current node contains apex zone for the base depth i.e. current zone; return it
  148. return currentNode;
  149. }
  150. if (authZoneNode.ParentSideZone is not null)
  151. {
  152. //current node contains sub domain zone; return it
  153. return currentNode;
  154. }
  155. }
  156. }
  157. }
  158. //move up to parent node for previous sibling
  159. k = currentNode.K - 1;
  160. currentNode = currentNode.Parent;
  161. }
  162. return null;
  163. }
  164. private static Node GetNextSubDomainZoneNode(byte[] key, Node currentNode, int baseDepth)
  165. {
  166. int k;
  167. NodeValue currentValue = currentNode.Value;
  168. if (currentValue is null)
  169. {
  170. //key value does not exists
  171. if (currentNode.Children is null)
  172. {
  173. //no children available; move to next sibling
  174. k = currentNode.K + 1; //find next node from sibling starting at k + 1
  175. currentNode = currentNode.Parent;
  176. }
  177. else
  178. {
  179. if (key.Length == currentNode.Depth)
  180. {
  181. //current node belongs to the key
  182. k = 0; //find next node from first child of current node
  183. }
  184. else
  185. {
  186. //find next node for the given k in current node's children
  187. k = key[currentNode.Depth];
  188. }
  189. }
  190. }
  191. else
  192. {
  193. //check if node contains apex zone
  194. bool foundApexZone = false;
  195. if (currentNode.Depth > baseDepth)
  196. {
  197. AuthZoneNode authZoneNode = currentValue.Value;
  198. if (authZoneNode is not null)
  199. {
  200. ApexZone apexZone = authZoneNode.ApexZone;
  201. if (apexZone is not null)
  202. foundApexZone = true;
  203. }
  204. }
  205. if (foundApexZone)
  206. {
  207. //current contains apex for a sub zone; move up to parent node
  208. k = currentNode.K + 1; //find next node from sibling starting at k + 1
  209. currentNode = currentNode.Parent;
  210. }
  211. else
  212. {
  213. int x = DnsNSECRecordData.CanonicalComparison(currentValue.Key, key);
  214. if (x == 0)
  215. {
  216. //current node value matches the key
  217. k = 0; //find next node from children starting at k
  218. }
  219. else if (x > 0)
  220. {
  221. //current node value is larger for the key thus current is the next node
  222. return currentNode;
  223. }
  224. else
  225. {
  226. //current node value is smaller for the key
  227. k = key[currentNode.Depth]; //find next node from children starting at k = key[depth]
  228. }
  229. }
  230. }
  231. //start tree traversal
  232. while ((currentNode is not null) && (currentNode.Depth >= baseDepth))
  233. {
  234. Node[] children = currentNode.Children;
  235. if (children is not null)
  236. {
  237. //find next child node
  238. Node child = null;
  239. for (int i = k; i < children.Length; i++)
  240. {
  241. child = Volatile.Read(ref children[i]);
  242. if (child is not null)
  243. {
  244. NodeValue childValue = child.Value;
  245. if (childValue is not null)
  246. {
  247. AuthZoneNode authZoneNode = childValue.Value;
  248. if (authZoneNode is not null)
  249. {
  250. if (authZoneNode.ParentSideZone is not null)
  251. {
  252. //is sub domain zone
  253. return child; //child has value so return it
  254. }
  255. if (authZoneNode.ApexZone is not null)
  256. {
  257. //is apex zone
  258. //skip to next child to avoid listing this auth zone's sub domains
  259. child = null; //set null to avoid child being set as current after the loop
  260. continue;
  261. }
  262. }
  263. }
  264. if (child.Children is not null)
  265. break;
  266. }
  267. }
  268. if (child is not null)
  269. {
  270. //make found child as current
  271. k = 0;
  272. currentNode = child;
  273. continue; //start over
  274. }
  275. }
  276. //no child nodes available; move up to parent node
  277. k = currentNode.K + 1;
  278. currentNode = currentNode.Parent;
  279. }
  280. return null;
  281. }
  282. private static bool SubDomainExists(byte[] key, Node currentNode)
  283. {
  284. Node nextSubDomain = GetNextSubDomainZoneNode(key, currentNode, currentNode.Depth);
  285. if (nextSubDomain is null)
  286. return false;
  287. NodeValue value = nextSubDomain.Value;
  288. if (value is null)
  289. return false;
  290. return IsKeySubDomain(key, value.Key, false);
  291. }
  292. private static AuthZone GetAuthZoneFromNode(Node node, string zoneName)
  293. {
  294. NodeValue value = node.Value;
  295. if (value is not null)
  296. {
  297. AuthZoneNode authZoneNode = value.Value;
  298. if (authZoneNode is not null)
  299. return authZoneNode.GetAuthZone(zoneName);
  300. }
  301. return null;
  302. }
  303. #endregion
  304. #region protected
  305. protected override void GetClosestValuesForZone(AuthZoneNode zoneValue, out SubDomainZone closestSubDomain, out SubDomainZone closestDelegation, out ApexZone closestAuthority)
  306. {
  307. ApexZone apexZone = zoneValue.ApexZone;
  308. if (apexZone is not null)
  309. {
  310. //hosted primary/secondary/stub/forwarder zone found
  311. closestSubDomain = null;
  312. closestDelegation = zoneValue.ParentSideZone;
  313. closestAuthority = apexZone;
  314. }
  315. else
  316. {
  317. //hosted sub domain
  318. SubDomainZone subDomainZone = zoneValue.ParentSideZone;
  319. if (subDomainZone.ContainsNameServerRecords())
  320. {
  321. //delegated sub domain found
  322. closestSubDomain = null;
  323. closestDelegation = subDomainZone;
  324. }
  325. else
  326. {
  327. closestSubDomain = subDomainZone;
  328. closestDelegation = null;
  329. }
  330. closestAuthority = null;
  331. }
  332. }
  333. #endregion
  334. #region public
  335. public bool TryAdd(ApexZone zone)
  336. {
  337. AuthZoneNode zoneNode = GetOrAdd(zone.Name, delegate (string key)
  338. {
  339. return new AuthZoneNode(null, zone);
  340. });
  341. if (ReferenceEquals(zoneNode.ApexZone, zone))
  342. return true; //added successfully
  343. return zoneNode.TryAdd(zone);
  344. }
  345. public bool TryGet(string zoneName, string domain, out AuthZone authZone)
  346. {
  347. if (TryGet(domain, out AuthZoneNode authZoneNode))
  348. {
  349. authZone = authZoneNode.GetAuthZone(zoneName);
  350. return authZone is not null;
  351. }
  352. authZone = null;
  353. return false;
  354. }
  355. public bool TryGet(string zoneName, out ApexZone apexZone)
  356. {
  357. if (TryGet(zoneName, out AuthZoneNode authZoneNode) && (authZoneNode.ApexZone is not null))
  358. {
  359. apexZone = authZoneNode.ApexZone;
  360. return true;
  361. }
  362. apexZone = null;
  363. return false;
  364. }
  365. public bool TryRemove(string domain, out ApexZone apexZone)
  366. {
  367. if (!TryGet(domain, out AuthZoneNode authZoneNode, out Node currentNode) || (authZoneNode.ApexZone is null))
  368. {
  369. apexZone = null;
  370. return false;
  371. }
  372. apexZone = authZoneNode.ApexZone;
  373. if (authZoneNode.ParentSideZone is null)
  374. {
  375. //remove complete zone node
  376. if (!base.TryRemove(domain, out AuthZoneNode _))
  377. {
  378. apexZone = null;
  379. return false;
  380. }
  381. }
  382. else
  383. {
  384. //parent side sub domain exists; remove only apex zone from zone node
  385. if (!authZoneNode.TryRemove(out ApexZone _))
  386. {
  387. apexZone = null;
  388. return false;
  389. }
  390. }
  391. //remove all sub domains under current zone
  392. Node current = currentNode;
  393. byte[] currentKey = ConvertToByteKey(domain);
  394. do
  395. {
  396. current = GetNextSubDomainZoneNode(currentKey, current, currentNode.Depth);
  397. if (current is null)
  398. break;
  399. NodeValue v = current.Value;
  400. if (v is not null)
  401. {
  402. AuthZoneNode z = v.Value;
  403. if (z is not null)
  404. {
  405. if (z.ApexZone is null)
  406. {
  407. //no apex zone at this node; remove complete zone node
  408. current.RemoveNodeValue(v.Key, out _); //remove node value
  409. current.CleanThisBranch();
  410. }
  411. else
  412. {
  413. //apex node exists; remove parent size sub domain
  414. z.TryRemove(out SubDomainZone _);
  415. }
  416. }
  417. currentKey = v.Key;
  418. }
  419. }
  420. while (true);
  421. currentNode.CleanThisBranch();
  422. return true;
  423. }
  424. public bool TryRemove(string domain, out SubDomainZone subDomainZone)
  425. {
  426. if (!TryGet(domain, out AuthZoneNode zoneNode, out Node currentNode) || (zoneNode.ParentSideZone is null))
  427. {
  428. subDomainZone = null;
  429. return false;
  430. }
  431. subDomainZone = zoneNode.ParentSideZone;
  432. if (zoneNode.ApexZone is null)
  433. {
  434. //remove complete zone node
  435. if (!base.TryRemove(domain, out AuthZoneNode _))
  436. {
  437. subDomainZone = null;
  438. return false;
  439. }
  440. }
  441. else
  442. {
  443. //apex zone exists; remove only parent side sub domain from zone node
  444. if (!zoneNode.TryRemove(out SubDomainZone _))
  445. {
  446. subDomainZone = null;
  447. return false;
  448. }
  449. }
  450. currentNode.CleanThisBranch();
  451. return true;
  452. }
  453. public override bool TryRemove(string key, out AuthZoneNode authZoneNode)
  454. {
  455. throw new InvalidOperationException();
  456. }
  457. public IReadOnlyList<AuthZone> GetZoneWithSubDomainZones(string zoneName)
  458. {
  459. List<AuthZone> zones = new List<AuthZone>();
  460. byte[] key = ConvertToByteKey(zoneName);
  461. NodeValue nodeValue = _root.FindNodeValue(key, out Node currentNode);
  462. if (nodeValue is not null)
  463. {
  464. AuthZoneNode authZoneNode = nodeValue.Value;
  465. if (authZoneNode is not null)
  466. {
  467. ApexZone apexZone = authZoneNode.ApexZone;
  468. if (apexZone is not null)
  469. {
  470. zones.Add(apexZone);
  471. Node current = currentNode;
  472. byte[] currentKey = key;
  473. do
  474. {
  475. current = GetNextSubDomainZoneNode(currentKey, current, currentNode.Depth);
  476. if (current is null)
  477. break;
  478. NodeValue value = current.Value;
  479. if (value is not null)
  480. {
  481. authZoneNode = value.Value;
  482. if (authZoneNode is not null)
  483. zones.Add(authZoneNode.ParentSideZone);
  484. currentKey = value.Key;
  485. }
  486. }
  487. while (true);
  488. }
  489. }
  490. }
  491. return zones;
  492. }
  493. public AuthZone GetOrAddSubDomainZone(string zoneName, string domain, Func<SubDomainZone> valueFactory)
  494. {
  495. bool isApex = zoneName.Equals(domain, StringComparison.OrdinalIgnoreCase);
  496. AuthZoneNode authZoneNode = GetOrAdd(domain, delegate (string key)
  497. {
  498. if (isApex)
  499. throw new DnsServerException("Zone was not found for domain: " + key);
  500. return new AuthZoneNode(valueFactory(), null);
  501. });
  502. if (isApex)
  503. {
  504. if (authZoneNode.ApexZone is null)
  505. throw new DnsServerException("Zone was not found: " + zoneName);
  506. return authZoneNode.ApexZone;
  507. }
  508. else
  509. {
  510. return authZoneNode.GetOrAddParentSideZone(valueFactory);
  511. }
  512. }
  513. public AuthZone GetAuthZone(string zoneName, string domain)
  514. {
  515. if (TryGet(domain, out AuthZoneNode authZoneNode))
  516. return authZoneNode.GetAuthZone(zoneName);
  517. return null;
  518. }
  519. public AuthZone FindZone(string domain, out SubDomainZone closest, out SubDomainZone delegation, out ApexZone authority, out bool hasSubDomains)
  520. {
  521. byte[] key = ConvertToByteKey(domain);
  522. AuthZoneNode authZoneNode = FindZoneNode(key, true, out Node currentNode, out Node closestSubDomainNode, out _, out SubDomainZone closestSubDomain, out SubDomainZone closestDelegation, out ApexZone closestAuthority);
  523. if (authZoneNode is null)
  524. {
  525. //zone not found
  526. closest = closestSubDomain;
  527. delegation = closestDelegation;
  528. authority = closestAuthority;
  529. if (authority is null)
  530. {
  531. //no authority so no sub domains
  532. hasSubDomains = false;
  533. }
  534. else if ((closestSubDomainNode is not null) && !closestSubDomainNode.HasChildren)
  535. {
  536. //closest sub domain node does not have any children so no sub domains
  537. hasSubDomains = false;
  538. }
  539. else
  540. {
  541. //check if current node has sub domains
  542. hasSubDomains = SubDomainExists(key, currentNode);
  543. }
  544. return null;
  545. }
  546. else
  547. {
  548. //zone found
  549. AuthZone zone;
  550. ApexZone apexZone = authZoneNode.ApexZone;
  551. if (apexZone is not null)
  552. {
  553. zone = apexZone;
  554. closest = null;
  555. delegation = authZoneNode.ParentSideZone;
  556. authority = apexZone;
  557. }
  558. else
  559. {
  560. SubDomainZone subDomainZone = authZoneNode.ParentSideZone;
  561. zone = subDomainZone;
  562. closest = closestSubDomain;
  563. if (closestDelegation is not null)
  564. delegation = closestDelegation;
  565. else if (subDomainZone.ContainsNameServerRecords())
  566. delegation = subDomainZone;
  567. else
  568. delegation = null;
  569. authority = closestAuthority;
  570. }
  571. hasSubDomains = false; //since zone is found, it does not matter if subdomain exists or not
  572. return zone;
  573. }
  574. }
  575. public AuthZone FindPreviousSubDomainZone(string zoneName, string domain)
  576. {
  577. byte[] key = ConvertToByteKey(domain);
  578. AuthZoneNode authZoneNode = FindZoneNode(key, false, out Node currentNode, out _, out Node closestAuthorityNode, out _, out _, out _);
  579. if (authZoneNode is not null)
  580. {
  581. //zone exists
  582. ApexZone apexZone = authZoneNode.ApexZone;
  583. SubDomainZone parentSideZone = authZoneNode.ParentSideZone;
  584. if ((apexZone is not null) && (parentSideZone is not null))
  585. {
  586. //found ambiguity between apex zone and sub domain zone
  587. if (!apexZone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  588. {
  589. //zone name does not match with apex zone and thus not match with closest authority node
  590. //find the closest authority zone for given zone name
  591. if (!TryGet(zoneName, out _, out Node closestNodeForZoneName))
  592. throw new InvalidOperationException();
  593. closestAuthorityNode = closestNodeForZoneName;
  594. }
  595. }
  596. }
  597. Node previousNode = GetPreviousSubDomainZoneNode(key, currentNode, closestAuthorityNode.Depth);
  598. if (previousNode is not null)
  599. {
  600. AuthZone authZone = GetAuthZoneFromNode(previousNode, zoneName);
  601. if (authZone is not null)
  602. return authZone;
  603. }
  604. return null;
  605. }
  606. public AuthZone FindNextSubDomainZone(string zoneName, string domain)
  607. {
  608. byte[] key = ConvertToByteKey(domain);
  609. AuthZoneNode authZoneNode = FindZoneNode(key, false, out Node currentNode, out _, out Node closestAuthorityNode, out _, out _, out _);
  610. if (authZoneNode is not null)
  611. {
  612. //zone exists
  613. ApexZone apexZone = authZoneNode.ApexZone;
  614. SubDomainZone parentSideZone = authZoneNode.ParentSideZone;
  615. if ((apexZone is not null) && (parentSideZone is not null))
  616. {
  617. //found ambiguity between apex zone and sub domain zone
  618. if (!apexZone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
  619. {
  620. //zone name does not match with apex zone and thus not match with closest authority node
  621. //find the closest authority zone for given zone name
  622. if (!TryGet(zoneName, out _, out Node closestNodeForZoneName))
  623. throw new InvalidOperationException();
  624. closestAuthorityNode = closestNodeForZoneName;
  625. }
  626. }
  627. }
  628. Node nextNode = GetNextSubDomainZoneNode(key, currentNode, closestAuthorityNode.Depth);
  629. if (nextNode is not null)
  630. {
  631. AuthZone authZone = GetAuthZoneFromNode(nextNode, zoneName);
  632. if (authZone is not null)
  633. return authZone;
  634. }
  635. return null;
  636. }
  637. public bool SubDomainExists(string zoneName, string domain)
  638. {
  639. AuthZone nextAuthZone = FindNextSubDomainZone(zoneName, domain);
  640. if (nextAuthZone is null)
  641. return false;
  642. return nextAuthZone.Name.EndsWith("." + domain);
  643. }
  644. public IReadOnlyList<DnsResourceRecord> FindNSecProofOfNonExistenceNxDomain(string domain, bool isWildcardAnswer)
  645. {
  646. List<DnsResourceRecord> nsecRecords = new List<DnsResourceRecord>(2 * 2);
  647. void AddProofOfCoverFor(string domain)
  648. {
  649. byte[] key = ConvertToByteKey(domain);
  650. AuthZoneNode authZoneNode = FindZoneNode(key, false, out Node currentNode, out _, out Node closestAuthorityNode, out _, out _, out ApexZone closestAuthority);
  651. if (authZoneNode is not null)
  652. throw new InvalidOperationException(); //domain exists! cannot prove non-existence
  653. Node previousNode = GetPreviousSubDomainZoneNode(key, currentNode, closestAuthorityNode.Depth);
  654. if (previousNode is not null)
  655. {
  656. AuthZone authZone = GetAuthZoneFromNode(previousNode, closestAuthority.Name);
  657. if (authZone is not null)
  658. {
  659. IReadOnlyList<DnsResourceRecord> proofOfCoverRecords = authZone.QueryRecords(DnsResourceRecordType.NSEC, true);
  660. foreach (DnsResourceRecord proofOfCoverRecord in proofOfCoverRecords)
  661. {
  662. if (!nsecRecords.Contains(proofOfCoverRecord))
  663. nsecRecords.Add(proofOfCoverRecord);
  664. }
  665. }
  666. }
  667. }
  668. //add proof of cover for domain
  669. AddProofOfCoverFor(domain);
  670. if (isWildcardAnswer)
  671. return nsecRecords;
  672. //add proof of cover for wildcard
  673. if (nsecRecords.Count > 0)
  674. {
  675. //add wildcard proof to prove that a wildcard expansion was not possible
  676. DnsResourceRecord nsecRecord = nsecRecords[0];
  677. DnsNSECRecordData nsec = nsecRecord.RDATA as DnsNSECRecordData;
  678. string wildcardName = DnsNSECRecordData.GetWildcardFor(nsecRecord, domain);
  679. if (!DnsNSECRecordData.IsDomainCovered(nsecRecord.Name, nsec.NextDomainName, wildcardName))
  680. AddProofOfCoverFor(wildcardName);
  681. }
  682. return nsecRecords;
  683. }
  684. public IReadOnlyList<DnsResourceRecord> FindNSec3ProofOfNonExistenceNxDomain(string domain, bool isWildcardAnswer)
  685. {
  686. List<DnsResourceRecord> nsec3Records = new List<DnsResourceRecord>(3 * 2);
  687. void AddProofOfCoverFor(string hashedOwnerName, string zoneName)
  688. {
  689. //find previous NSEC3 for the hashed owner name
  690. IReadOnlyList<DnsResourceRecord> proofOfCoverRecords = null;
  691. string currentOwnerName = hashedOwnerName;
  692. while (true)
  693. {
  694. AuthZone previousNSec3Zone = FindPreviousSubDomainZone(zoneName, currentOwnerName);
  695. if (previousNSec3Zone is null)
  696. break;
  697. IReadOnlyList<DnsResourceRecord> previousNSec3Records = previousNSec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
  698. if (previousNSec3Records.Count > 0)
  699. {
  700. proofOfCoverRecords = previousNSec3Records;
  701. break;
  702. }
  703. currentOwnerName = previousNSec3Zone.Name;
  704. }
  705. if (proofOfCoverRecords is null)
  706. {
  707. //didnt find previous NSEC3; find the last NSEC3
  708. currentOwnerName = hashedOwnerName;
  709. while (true)
  710. {
  711. AuthZone nextNSec3Zone = GetAuthZone(zoneName, currentOwnerName);
  712. if (nextNSec3Zone is null)
  713. {
  714. nextNSec3Zone = FindNextSubDomainZone(zoneName, currentOwnerName);
  715. if (nextNSec3Zone is null)
  716. break;
  717. }
  718. IReadOnlyList<DnsResourceRecord> nextNSec3Records = nextNSec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
  719. if (nextNSec3Records.Count > 0)
  720. {
  721. proofOfCoverRecords = nextNSec3Records;
  722. DnsResourceRecord previousNSec3Record = nextNSec3Records[0];
  723. string nextHashedOwnerNameString = (previousNSec3Record.RDATA as DnsNSEC3RecordData).NextHashedOwnerName + (zoneName.Length > 0 ? "." + zoneName : "");
  724. if (DnsNSECRecordData.CanonicalComparison(previousNSec3Record.Name, nextHashedOwnerNameString) >= 0)
  725. break; //found last NSEC3
  726. //jump to next hashed owner
  727. currentOwnerName = nextHashedOwnerNameString;
  728. }
  729. else
  730. {
  731. currentOwnerName = nextNSec3Zone.Name;
  732. }
  733. }
  734. }
  735. if (proofOfCoverRecords is null)
  736. throw new InvalidOperationException();
  737. foreach (DnsResourceRecord proofOfCoverRecord in proofOfCoverRecords)
  738. {
  739. if (!nsec3Records.Contains(proofOfCoverRecord))
  740. nsec3Records.Add(proofOfCoverRecord);
  741. }
  742. }
  743. byte[] key = ConvertToByteKey(domain);
  744. string closestEncloser;
  745. AuthZoneNode authZoneNode = FindZoneNode(key, isWildcardAnswer, out _, out _, out _, out SubDomainZone closestSubDomain, out _, out ApexZone closestAuthority);
  746. if (authZoneNode is not null)
  747. {
  748. if (isWildcardAnswer && (closestSubDomain is not null) && closestSubDomain.Name.Contains('*'))
  749. {
  750. closestEncloser = closestSubDomain.Name.TrimStart(new char[] { '*', '.' });
  751. }
  752. else
  753. {
  754. //subdomain that contains only NSEC3 record does not really exists: RFC5155 section 7.2.8
  755. if ((authZoneNode.ApexZone is not null) || ((authZoneNode.ParentSideZone is not null) && !authZoneNode.ParentSideZone.HasOnlyNSec3Records()))
  756. throw new InvalidOperationException(); //domain exists! cannot prove non-existence
  757. //continue to prove non-existence of this nsec3 owner name
  758. closestEncloser = closestAuthority.Name;
  759. }
  760. }
  761. else
  762. {
  763. if (closestSubDomain is not null)
  764. closestEncloser = closestSubDomain.Name;
  765. else if (closestAuthority is not null)
  766. closestEncloser = closestAuthority.Name;
  767. else
  768. throw new InvalidOperationException(); //cannot find closest encloser
  769. }
  770. IReadOnlyList<DnsResourceRecord> nsec3ParamRecords = closestAuthority.GetRecords(DnsResourceRecordType.NSEC3PARAM);
  771. if (nsec3ParamRecords.Count == 0)
  772. throw new InvalidOperationException("Zone does not have NSEC3 deployed.");
  773. DnsNSEC3PARAMRecordData nsec3Param = nsec3ParamRecords[0].RDATA as DnsNSEC3PARAMRecordData;
  774. //find correct closest encloser
  775. string hashedNextCloserName;
  776. while (true)
  777. {
  778. string nextCloserName = DnsNSEC3RecordData.GetNextCloserName(domain, closestEncloser);
  779. hashedNextCloserName = nsec3Param.ComputeHashedOwnerNameBase32HexString(nextCloserName) + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : "");
  780. AuthZone nsec3Zone = GetAuthZone(closestAuthority.Name, hashedNextCloserName);
  781. if (nsec3Zone is null)
  782. break; //next closer name does not exists
  783. //next closer name exists as an ENT
  784. closestEncloser = nextCloserName;
  785. if (domain.Equals(closestEncloser, StringComparison.OrdinalIgnoreCase))
  786. {
  787. //domain exists as an ENT; return no data proof
  788. return FindNSec3ProofOfNonExistenceNoData(nsec3Zone);
  789. }
  790. }
  791. if (isWildcardAnswer)
  792. {
  793. //add proof of cover for the domain to prove non-existence (wildcard)
  794. AddProofOfCoverFor(hashedNextCloserName, closestAuthority.Name);
  795. }
  796. else
  797. {
  798. //add closest encloser proof
  799. string hashedClosestEncloser = nsec3Param.ComputeHashedOwnerNameBase32HexString(closestEncloser) + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : "");
  800. AuthZone nsec3Zone = GetAuthZone(closestAuthority.Name, hashedClosestEncloser);
  801. if (nsec3Zone is null)
  802. throw new InvalidOperationException();
  803. IReadOnlyList<DnsResourceRecord> closestEncloserProofRecords = nsec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
  804. if (closestEncloserProofRecords.Count == 0)
  805. throw new InvalidOperationException();
  806. nsec3Records.AddRange(closestEncloserProofRecords);
  807. DnsResourceRecord closestEncloserProofRecord = closestEncloserProofRecords[0];
  808. DnsNSEC3RecordData closestEncloserProof = closestEncloserProofRecord.RDATA as DnsNSEC3RecordData;
  809. //add proof of cover for the next closer name
  810. if (!DnsNSECRecordData.IsDomainCovered(closestEncloserProofRecord.Name, closestEncloserProof.NextHashedOwnerName + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : ""), hashedNextCloserName))
  811. AddProofOfCoverFor(hashedNextCloserName, closestAuthority.Name);
  812. //add proof of cover to prove that a wildcard expansion was not possible
  813. string wildcardDomain = closestEncloser.Length > 0 ? "*." + closestEncloser : "*";
  814. string hashedWildcardDomainName = nsec3Param.ComputeHashedOwnerNameBase32HexString(wildcardDomain) + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : "");
  815. if (!DnsNSECRecordData.IsDomainCovered(closestEncloserProofRecord.Name, closestEncloserProof.NextHashedOwnerName + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : ""), hashedWildcardDomainName))
  816. AddProofOfCoverFor(hashedWildcardDomainName, closestAuthority.Name);
  817. }
  818. return nsec3Records;
  819. }
  820. public IReadOnlyList<DnsResourceRecord> FindNSecProofOfNonExistenceNoData(AuthZone zone)
  821. {
  822. IReadOnlyList<DnsResourceRecord> nsecRecords = zone.QueryRecords(DnsResourceRecordType.NSEC, true);
  823. if (nsecRecords.Count == 0)
  824. throw new InvalidOperationException("Zone does not have NSEC deployed correctly.");
  825. return nsecRecords;
  826. }
  827. public IReadOnlyList<DnsResourceRecord> FindNSec3ProofOfNonExistenceNoData(AuthZone zone, ApexZone apexZone)
  828. {
  829. IReadOnlyList<DnsResourceRecord> nsec3ParamRecords = apexZone.GetRecords(DnsResourceRecordType.NSEC3PARAM);
  830. if (nsec3ParamRecords.Count == 0)
  831. throw new InvalidOperationException("Zone does not have NSEC3 deployed.");
  832. DnsNSEC3PARAMRecordData nsec3Param = nsec3ParamRecords[0].RDATA as DnsNSEC3PARAMRecordData;
  833. string hashedOwnerName = nsec3Param.ComputeHashedOwnerNameBase32HexString(zone.Name) + (apexZone.Name.Length > 0 ? "." + apexZone.Name : "");
  834. AuthZone nsec3Zone = GetAuthZone(apexZone.Name, hashedOwnerName);
  835. if (nsec3Zone is null)
  836. {
  837. //this is probably since the domain in request is for an nsec3 record owner name
  838. return FindNSec3ProofOfNonExistenceNxDomain(zone.Name, false);
  839. }
  840. return FindNSec3ProofOfNonExistenceNoData(nsec3Zone);
  841. }
  842. public IReadOnlyList<DnsResourceRecord> FindNSec3ProofOfNonExistenceNoData(AuthZone nsec3Zone)
  843. {
  844. IReadOnlyList<DnsResourceRecord> nsec3Records = nsec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
  845. if (nsec3Records.Count > 0)
  846. return nsec3Records;
  847. return Array.Empty<DnsResourceRecord>();
  848. }
  849. #endregion
  850. }
  851. }