/*
Technitium DNS Server
Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
using DnsServerCore.Dns.Zones;
using System;
using System.Collections.Generic;
using System.Threading;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
namespace DnsServerCore.Dns.Trees
{
class AuthZoneTree : ZoneTree
{
#region variables
static readonly char[] _starPeriodTrimChars = new char[] { '*', '.' };
#endregion
#region private
private static Node GetPreviousSubDomainZoneNode(byte[] key, Node currentNode, int baseDepth)
{
int k;
NodeValue currentValue = currentNode.Value;
if (currentValue is null)
{
//key value does not exists
if (currentNode.Children is null)
{
//no children available; move to previous sibling
k = currentNode.K - 1; //find previous node from sibling starting at k - 1
currentNode = currentNode.Parent;
}
else
{
if (key.Length == currentNode.Depth)
{
//current node belongs to the key
k = currentNode.K - 1; //find previous node from sibling starting at k - 1
currentNode = currentNode.Parent;
}
else
{
//find the previous node for the given k in current node's children
k = key[currentNode.Depth];
}
}
}
else
{
int x = DnsNSECRecordData.CanonicalComparison(currentValue.Key, key);
if (x == 0)
{
//current node value matches the key
k = currentNode.K - 1; //find previous node from sibling starting at k - 1
currentNode = currentNode.Parent;
}
else if (x > 0)
{
//current node value is larger for the key
k = currentNode.K - 1; //find previous node from sibling starting at k - 1
currentNode = currentNode.Parent;
}
else
{
//current node value is smaller for the key
if (currentNode.Children is null)
{
//the current node is previous node since no children exists and value is smaller for the key
return currentNode;
}
else
{
//find the previous node for the given k in current node's children
k = key[currentNode.Depth];
}
}
}
//start reverse tree traversal
while ((currentNode is not null) && (currentNode.Depth >= baseDepth))
{
Node[] children = currentNode.Children;
if (children is not null)
{
//find previous child node
Node child = null;
for (int i = k; i > -1; i--)
{
child = Volatile.Read(ref children[i]);
if (child is not null)
{
bool childNodeHasApexZone = false;
NodeValue childValue = child.Value;
if (childValue is not null)
{
AuthZoneNode authZoneNode = childValue.Value;
if (authZoneNode is not null)
{
if (authZoneNode.ApexZone is not null)
childNodeHasApexZone = true; //must stop checking children of the apex of the sub zone
}
}
if (!childNodeHasApexZone && child.Children is not null)
break; //child has further children so check them first
if (childValue is not null)
{
AuthZoneNode authZoneNode = childValue.Value;
if (authZoneNode is not null)
{
if (authZoneNode.ParentSideZone is not null)
{
//is sub domain zone
return child; //child has value so return it
}
if (authZoneNode.ApexZone is not null)
{
//is apex zone
//skip to next child to avoid listing this auth zone's sub domains
child = null; //set null to avoid child being set as current after the loop
}
}
}
}
}
if (child is not null)
{
//make found child as current
k = children.Length - 1;
currentNode = child;
continue; //start over
}
}
//no child node available; check for current node value
{
NodeValue value = currentNode.Value;
if (value is not null)
{
AuthZoneNode authZoneNode = value.Value;
if (authZoneNode is not null)
{
if ((authZoneNode.ApexZone is not null) && (currentNode.Depth == baseDepth))
{
//current node contains apex zone for the base depth i.e. current zone; return it
return currentNode;
}
if (authZoneNode.ParentSideZone is not null)
{
//current node contains sub domain zone; return it
return currentNode;
}
}
}
}
//move up to parent node for previous sibling
k = currentNode.K - 1;
currentNode = currentNode.Parent;
}
return null;
}
private static Node GetNextSubDomainZoneNode(byte[] key, Node currentNode, int baseDepth)
{
int k;
NodeValue currentValue = currentNode.Value;
if (currentValue is null)
{
//key value does not exists
if (currentNode.Children is null)
{
//no children available; move to next sibling
k = currentNode.K + 1; //find next node from sibling starting at k + 1
currentNode = currentNode.Parent;
}
else
{
if (key.Length == currentNode.Depth)
{
//current node belongs to the key
k = 0; //find next node from first child of current node
}
else
{
//find next node for the given k in current node's children
k = key[currentNode.Depth];
}
}
}
else
{
//check if node contains apex zone
bool foundApexZone = false;
if (currentNode.Depth > baseDepth)
{
AuthZoneNode authZoneNode = currentValue.Value;
if (authZoneNode is not null)
{
ApexZone apexZone = authZoneNode.ApexZone;
if (apexZone is not null)
foundApexZone = true;
}
}
if (foundApexZone)
{
//current contains apex for a sub zone; move up to parent node
k = currentNode.K + 1; //find next node from sibling starting at k + 1
currentNode = currentNode.Parent;
}
else
{
int x = DnsNSECRecordData.CanonicalComparison(currentValue.Key, key);
if (x == 0)
{
//current node value matches the key
k = 0; //find next node from children starting at k
}
else if (x > 0)
{
//current node value is larger for the key thus current is the next node
return currentNode;
}
else
{
//current node value is smaller for the key
k = key[currentNode.Depth]; //find next node from children starting at k = key[depth]
}
}
}
//start tree traversal
while ((currentNode is not null) && (currentNode.Depth >= baseDepth))
{
Node[] children = currentNode.Children;
if (children is not null)
{
//find next child node
Node child = null;
for (int i = k; i < children.Length; i++)
{
child = Volatile.Read(ref children[i]);
if (child is not null)
{
NodeValue childValue = child.Value;
if (childValue is not null)
{
AuthZoneNode authZoneNode = childValue.Value;
if (authZoneNode is not null)
{
if (authZoneNode.ParentSideZone is not null)
{
//is sub domain zone
return child; //child has value so return it
}
if (authZoneNode.ApexZone is not null)
{
//is apex zone
//skip to next child to avoid listing this auth zone's sub domains
child = null; //set null to avoid child being set as current after the loop
continue;
}
}
}
if (child.Children is not null)
break;
}
}
if (child is not null)
{
//make found child as current
k = 0;
currentNode = child;
continue; //start over
}
}
//no child nodes available; move up to parent node
k = currentNode.K + 1;
currentNode = currentNode.Parent;
}
return null;
}
private static bool SubDomainExists(byte[] key, Node currentNode)
{
Node[] children = currentNode.Children;
if (children is not null)
{
Node child = Volatile.Read(ref children[1]); //[*]
if (child is not null)
return true; //wildcard exists so subdomain name exists: RFC 4592 section 4.9
}
Node nextSubDomain = GetNextSubDomainZoneNode(key, currentNode, currentNode.Depth);
if (nextSubDomain is null)
return false;
NodeValue value = nextSubDomain.Value;
if (value is null)
return false;
return IsKeySubDomain(key, value.Key, false);
}
private static AuthZone GetAuthZoneFromNode(Node node, string zoneName)
{
NodeValue value = node.Value;
if (value is not null)
{
AuthZoneNode authZoneNode = value.Value;
if (authZoneNode is not null)
return authZoneNode.GetAuthZone(zoneName);
}
return null;
}
private void RemoveAllSubDomains(string domain, Node currentNode)
{
//remove all sub domains under current zone
Node current = currentNode;
byte[] currentKey = ConvertToByteKey(domain);
do
{
current = GetNextSubDomainZoneNode(currentKey, current, currentNode.Depth);
if (current is null)
break;
NodeValue v = current.Value;
if (v is not null)
{
AuthZoneNode z = v.Value;
if (z is not null)
{
if (z.ApexZone is null)
{
//no apex zone at this node; remove complete zone node
current.RemoveNodeValue(v.Key, out _); //remove node value
current.CleanThisBranch();
}
else
{
//apex node exists; remove parent size sub domain
z.TryRemove(out SubDomainZone _);
}
}
currentKey = v.Key;
}
}
while (true);
}
#endregion
#region protected
protected override void GetClosestValuesForZone(AuthZoneNode zoneValue, out SubDomainZone closestSubDomain, out SubDomainZone closestDelegation, out ApexZone closestAuthority)
{
ApexZone apexZone = zoneValue.ApexZone;
if (apexZone is not null)
{
//hosted primary/secondary/stub/forwarder zone found
closestSubDomain = null;
closestDelegation = zoneValue.ParentSideZone;
closestAuthority = apexZone;
}
else
{
//hosted sub domain
SubDomainZone subDomainZone = zoneValue.ParentSideZone;
if (subDomainZone.ContainsNameServerRecords())
{
//delegated sub domain found
closestSubDomain = null;
closestDelegation = subDomainZone;
}
else
{
closestSubDomain = subDomainZone;
closestDelegation = null;
}
closestAuthority = null;
}
}
#endregion
#region public
public bool TryAdd(ApexZone zone)
{
AuthZoneNode zoneNode = GetOrAdd(zone.Name, delegate (string key)
{
return new AuthZoneNode(null, zone);
});
if (ReferenceEquals(zoneNode.ApexZone, zone))
return true; //added successfully
return zoneNode.TryAdd(zone);
}
public bool TryGet(string zoneName, string domain, out AuthZone authZone)
{
if (TryGet(domain, out AuthZoneNode authZoneNode))
{
authZone = authZoneNode.GetAuthZone(zoneName);
return authZone is not null;
}
authZone = null;
return false;
}
public bool TryGet(string zoneName, out ApexZone apexZone)
{
if (TryGet(zoneName, out AuthZoneNode authZoneNode) && (authZoneNode.ApexZone is not null))
{
apexZone = authZoneNode.ApexZone;
return true;
}
apexZone = null;
return false;
}
public bool TryRemove(string domain, out ApexZone apexZone)
{
if (!TryGet(domain, out AuthZoneNode authZoneNode, out Node currentNode) || (authZoneNode.ApexZone is null))
{
apexZone = null;
return false;
}
apexZone = authZoneNode.ApexZone;
if (authZoneNode.ParentSideZone is null)
{
//remove complete zone node
if (!base.TryRemove(domain, out AuthZoneNode _))
{
apexZone = null;
return false;
}
}
else
{
//parent side sub domain exists; remove only apex zone from zone node
if (!authZoneNode.TryRemove(out ApexZone _))
{
apexZone = null;
return false;
}
}
//remove all sub domains under current apex zone
RemoveAllSubDomains(domain, currentNode);
currentNode.CleanThisBranch();
return true;
}
public bool TryRemove(string domain, out SubDomainZone subDomainZone, bool removeAllSubDomains = false)
{
if (!TryGet(domain, out AuthZoneNode zoneNode, out Node currentNode) || (zoneNode.ParentSideZone is null))
{
subDomainZone = null;
return false;
}
subDomainZone = zoneNode.ParentSideZone;
if (zoneNode.ApexZone is null)
{
//remove complete zone node
if (!base.TryRemove(domain, out AuthZoneNode _))
{
subDomainZone = null;
return false;
}
}
else
{
//apex zone exists; remove only parent side sub domain from zone node
if (!zoneNode.TryRemove(out SubDomainZone _))
{
subDomainZone = null;
return false;
}
}
if (removeAllSubDomains)
RemoveAllSubDomains(domain, currentNode); //remove all sub domains under current subdomain zone
currentNode.CleanThisBranch();
return true;
}
public override bool TryRemove(string key, out AuthZoneNode authZoneNode)
{
throw new InvalidOperationException();
}
public IReadOnlyList GetApexZoneWithSubDomainZones(string zoneName)
{
List zones = new List();
byte[] key = ConvertToByteKey(zoneName);
NodeValue nodeValue = _root.FindNodeValue(key, out Node currentNode);
if (nodeValue is not null)
{
AuthZoneNode authZoneNode = nodeValue.Value;
if (authZoneNode is not null)
{
ApexZone apexZone = authZoneNode.ApexZone;
if (apexZone is not null)
{
zones.Add(apexZone);
Node current = currentNode;
byte[] currentKey = key;
do
{
current = GetNextSubDomainZoneNode(currentKey, current, currentNode.Depth);
if (current is null)
break;
NodeValue value = current.Value;
if (value is not null)
{
authZoneNode = value.Value;
if (authZoneNode is not null)
zones.Add(authZoneNode.ParentSideZone);
currentKey = value.Key;
}
}
while (true);
}
}
}
return zones;
}
public IReadOnlyList GetSubDomainZoneWithSubDomainZones(string domain)
{
List zones = new List();
byte[] key = ConvertToByteKey(domain);
NodeValue nodeValue = _root.FindNodeValue(key, out Node currentNode);
if (nodeValue is not null)
{
AuthZoneNode authZoneNode = nodeValue.Value;
if (authZoneNode is not null)
{
SubDomainZone subDomainZone = authZoneNode.ParentSideZone;
if (subDomainZone is not null)
{
zones.Add(subDomainZone);
Node current = currentNode;
byte[] currentKey = key;
do
{
current = GetNextSubDomainZoneNode(currentKey, current, currentNode.Depth);
if (current is null)
break;
NodeValue value = current.Value;
if (value is not null)
{
authZoneNode = value.Value;
if (authZoneNode is not null)
zones.Add(authZoneNode.ParentSideZone);
currentKey = value.Key;
}
}
while (true);
}
}
}
return zones;
}
public AuthZone GetOrAddSubDomainZone(string zoneName, string domain, Func valueFactory)
{
bool isApex = zoneName.Equals(domain, StringComparison.OrdinalIgnoreCase);
AuthZoneNode authZoneNode = GetOrAdd(domain, delegate (string key)
{
if (isApex)
throw new DnsServerException("Zone was not found for domain: " + key);
return new AuthZoneNode(valueFactory(), null);
});
if (isApex)
{
if (authZoneNode.ApexZone is null)
throw new DnsServerException("Zone was not found: " + zoneName);
return authZoneNode.ApexZone;
}
else
{
return authZoneNode.GetOrAddParentSideZone(valueFactory);
}
}
public AuthZone GetAuthZone(string zoneName, string domain)
{
if (TryGet(domain, out AuthZoneNode authZoneNode))
return authZoneNode.GetAuthZone(zoneName);
return null;
}
public ApexZone GetApexZone(string zoneName)
{
if (TryGet(zoneName, out AuthZoneNode authZoneNode))
return authZoneNode.ApexZone;
return null;
}
public AuthZone FindZone(string domain, out SubDomainZone closest, out SubDomainZone delegation, out ApexZone authority, out bool hasSubDomains)
{
byte[] key = ConvertToByteKey(domain);
AuthZoneNode authZoneNode = FindZoneNode(key, true, out Node currentNode, out Node closestSubDomainNode, out _, out SubDomainZone closestSubDomain, out SubDomainZone closestDelegation, out ApexZone closestAuthority);
if (authZoneNode is null)
{
//zone not found
closest = closestSubDomain;
delegation = closestDelegation;
authority = closestAuthority;
if (authority is null)
{
//no authority so no sub domains
hasSubDomains = false;
}
else if ((closestSubDomainNode is not null) && !closestSubDomainNode.HasChildren)
{
//closest sub domain node does not have any children so no sub domains
hasSubDomains = false;
}
else
{
//check if current node has sub domains
hasSubDomains = SubDomainExists(key, currentNode);
}
return null;
}
else
{
//zone found
AuthZone zone;
ApexZone apexZone = authZoneNode.ApexZone;
if (apexZone is not null)
{
zone = apexZone;
closest = null;
delegation = authZoneNode.ParentSideZone;
authority = apexZone;
}
else
{
SubDomainZone subDomainZone = authZoneNode.ParentSideZone;
zone = subDomainZone;
if (zone == closestSubDomain)
closest = null;
else
closest = closestSubDomain;
if (closestDelegation is not null)
delegation = closestDelegation;
else if (subDomainZone.ContainsNameServerRecords())
delegation = subDomainZone;
else
delegation = null;
authority = closestAuthority;
}
if (zone.Disabled)
{
if ((closestSubDomainNode is not null) && !closestSubDomainNode.HasChildren)
{
//closest sub domain node does not have any children so no sub domains
hasSubDomains = false;
}
else
{
//check if current node has sub domains
hasSubDomains = SubDomainExists(key, currentNode);
}
}
else
{
//since zone is found, it does not matter if subdomain exists or not
hasSubDomains = false;
}
return zone;
}
}
public AuthZone FindPreviousSubDomainZone(string zoneName, string domain)
{
byte[] key = ConvertToByteKey(domain);
AuthZoneNode authZoneNode = FindZoneNode(key, false, out Node currentNode, out _, out Node closestAuthorityNode, out _, out _, out _);
if (authZoneNode is not null)
{
//zone exists
ApexZone apexZone = authZoneNode.ApexZone;
SubDomainZone parentSideZone = authZoneNode.ParentSideZone;
if ((apexZone is not null) && (parentSideZone is not null))
{
//found ambiguity between apex zone and sub domain zone
if (!apexZone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
{
//zone name does not match with apex zone and thus not match with closest authority node
//find the closest authority zone for given zone name
if (!TryGet(zoneName, out _, out Node closestNodeForZoneName))
throw new InvalidOperationException();
closestAuthorityNode = closestNodeForZoneName;
}
}
}
Node previousNode = GetPreviousSubDomainZoneNode(key, currentNode, closestAuthorityNode.Depth);
if (previousNode is not null)
{
AuthZone authZone = GetAuthZoneFromNode(previousNode, zoneName);
if (authZone is not null)
return authZone;
}
return null;
}
public AuthZone FindNextSubDomainZone(string zoneName, string domain)
{
byte[] key = ConvertToByteKey(domain);
AuthZoneNode authZoneNode = FindZoneNode(key, false, out Node currentNode, out _, out Node closestAuthorityNode, out _, out _, out _);
if (authZoneNode is not null)
{
//zone exists
ApexZone apexZone = authZoneNode.ApexZone;
SubDomainZone parentSideZone = authZoneNode.ParentSideZone;
if ((apexZone is not null) && (parentSideZone is not null))
{
//found ambiguity between apex zone and sub domain zone
if (!apexZone.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
{
//zone name does not match with apex zone and thus not match with closest authority node
//find the closest authority zone for given zone name
if (!TryGet(zoneName, out _, out Node closestNodeForZoneName))
throw new InvalidOperationException();
closestAuthorityNode = closestNodeForZoneName;
}
}
}
Node nextNode = GetNextSubDomainZoneNode(key, currentNode, closestAuthorityNode.Depth);
if (nextNode is not null)
{
AuthZone authZone = GetAuthZoneFromNode(nextNode, zoneName);
if (authZone is not null)
return authZone;
}
return null;
}
public bool SubDomainExistsFor(string zoneName, string domain)
{
AuthZone nextAuthZone = FindNextSubDomainZone(zoneName, domain);
if (nextAuthZone is null)
return false;
return nextAuthZone.Name.EndsWith("." + domain);
}
#endregion
#region DNSSEC
public IReadOnlyList FindNSecProofOfNonExistenceNxDomain(string domain, bool isWildcardAnswer)
{
List nsecRecords = new List(2 * 2);
void AddProofOfCoverFor(string domain)
{
byte[] key = ConvertToByteKey(domain);
AuthZoneNode authZoneNode = FindZoneNode(key, false, out Node currentNode, out _, out Node closestAuthorityNode, out _, out _, out ApexZone closestAuthority);
if (authZoneNode is not null)
throw new InvalidOperationException(); //domain exists! cannot prove non-existence
Node previousNode = GetPreviousSubDomainZoneNode(key, currentNode, closestAuthorityNode.Depth);
if (previousNode is not null)
{
AuthZone authZone = GetAuthZoneFromNode(previousNode, closestAuthority.Name);
if (authZone is not null)
{
IReadOnlyList proofOfCoverRecords = authZone.QueryRecords(DnsResourceRecordType.NSEC, true);
foreach (DnsResourceRecord proofOfCoverRecord in proofOfCoverRecords)
{
if (!nsecRecords.Contains(proofOfCoverRecord))
nsecRecords.Add(proofOfCoverRecord);
}
}
}
}
//add proof of cover for domain
AddProofOfCoverFor(domain);
if (isWildcardAnswer)
return nsecRecords;
//add proof of cover for wildcard
if (nsecRecords.Count > 0)
{
//add wildcard proof to prove that a wildcard expansion was not possible
DnsResourceRecord nsecRecord = nsecRecords[0];
DnsNSECRecordData nsec = nsecRecord.RDATA as DnsNSECRecordData;
string wildcardName = DnsNSECRecordData.GetWildcardFor(nsecRecord, domain);
if (!DnsNSECRecordData.IsDomainCovered(nsecRecord.Name, nsec.NextDomainName, wildcardName))
AddProofOfCoverFor(wildcardName);
}
return nsecRecords;
}
public IReadOnlyList FindNSec3ProofOfNonExistenceNxDomain(string domain, bool isWildcardAnswer, CancellationToken cancellationToken)
{
List nsec3Records = new List(3 * 2);
void AddProofOfCoverFor(string hashedOwnerName, string zoneName)
{
//find previous NSEC3 for the hashed owner name
IReadOnlyList proofOfCoverRecords = null;
string currentOwnerName = hashedOwnerName;
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
AuthZone previousNSec3Zone = FindPreviousSubDomainZone(zoneName, currentOwnerName);
if (previousNSec3Zone is null)
break;
IReadOnlyList previousNSec3Records = previousNSec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
if (previousNSec3Records.Count > 0)
{
proofOfCoverRecords = previousNSec3Records;
break;
}
currentOwnerName = previousNSec3Zone.Name;
}
if (proofOfCoverRecords is null)
{
//didnt find previous NSEC3; find the last NSEC3
currentOwnerName = hashedOwnerName;
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
AuthZone nextNSec3Zone = GetAuthZone(zoneName, currentOwnerName);
if (nextNSec3Zone is null)
{
nextNSec3Zone = FindNextSubDomainZone(zoneName, currentOwnerName);
if (nextNSec3Zone is null)
break;
}
IReadOnlyList nextNSec3Records = nextNSec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
if (nextNSec3Records.Count > 0)
{
proofOfCoverRecords = nextNSec3Records;
DnsResourceRecord previousNSec3Record = nextNSec3Records[0];
string nextHashedOwnerNameString = (previousNSec3Record.RDATA as DnsNSEC3RecordData).NextHashedOwnerName + (zoneName.Length > 0 ? "." + zoneName : "");
if (DnsNSECRecordData.CanonicalComparison(previousNSec3Record.Name, nextHashedOwnerNameString) >= 0)
break; //found last NSEC3
//jump to next hashed owner
currentOwnerName = nextHashedOwnerNameString;
}
else
{
currentOwnerName = nextNSec3Zone.Name;
}
}
}
if (proofOfCoverRecords is null)
throw new InvalidOperationException();
foreach (DnsResourceRecord proofOfCoverRecord in proofOfCoverRecords)
{
if (!nsec3Records.Contains(proofOfCoverRecord))
nsec3Records.Add(proofOfCoverRecord);
}
}
byte[] key = ConvertToByteKey(domain);
string closestEncloser;
AuthZoneNode authZoneNode = FindZoneNode(key, isWildcardAnswer, out _, out _, out _, out SubDomainZone closestSubDomain, out _, out ApexZone closestAuthority);
if (authZoneNode is not null)
{
if (isWildcardAnswer && (closestSubDomain is not null) && closestSubDomain.Name.StartsWith('*'))
{
closestEncloser = closestSubDomain.Name.TrimStart(_starPeriodTrimChars);
}
else
{
//subdomain that contains only NSEC3 record does not really exists: RFC5155 section 7.2.8
if ((authZoneNode.ApexZone is not null) || ((authZoneNode.ParentSideZone is not null) && !authZoneNode.ParentSideZone.HasOnlyNSec3Records()))
throw new InvalidOperationException(); //domain exists! cannot prove non-existence
//continue to prove non-existence of this nsec3 owner name
closestEncloser = closestAuthority.Name;
}
}
else
{
if (closestSubDomain is not null)
closestEncloser = closestSubDomain.Name;
else if (closestAuthority is not null)
closestEncloser = closestAuthority.Name;
else
throw new InvalidOperationException(); //cannot find closest encloser
}
IReadOnlyList nsec3ParamRecords = closestAuthority.GetRecords(DnsResourceRecordType.NSEC3PARAM);
if (nsec3ParamRecords.Count == 0)
throw new InvalidOperationException("Zone does not have NSEC3 deployed.");
DnsNSEC3PARAMRecordData nsec3Param = nsec3ParamRecords[0].RDATA as DnsNSEC3PARAMRecordData;
//find correct closest encloser
string hashedNextCloserName;
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
string nextCloserName = DnsNSEC3RecordData.GetNextCloserName(domain, closestEncloser);
hashedNextCloserName = nsec3Param.ComputeHashedOwnerNameBase32HexString(nextCloserName) + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : "");
AuthZone nsec3Zone = GetAuthZone(closestAuthority.Name, hashedNextCloserName);
if (nsec3Zone is null)
break; //next closer name does not exists
//next closer name exists as an ENT
closestEncloser = nextCloserName;
if (domain.Equals(closestEncloser, StringComparison.OrdinalIgnoreCase))
{
//domain exists as an ENT; return no data proof
return FindNSec3ProofOfNonExistenceNoData(nsec3Zone);
}
}
if (isWildcardAnswer)
{
//add proof of cover for the domain to prove non-existence (wildcard)
AddProofOfCoverFor(hashedNextCloserName, closestAuthority.Name);
}
else
{
//add closest encloser proof
string hashedClosestEncloser = nsec3Param.ComputeHashedOwnerNameBase32HexString(closestEncloser) + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : "");
AuthZone nsec3Zone = GetAuthZone(closestAuthority.Name, hashedClosestEncloser);
if (nsec3Zone is null)
throw new InvalidOperationException();
IReadOnlyList closestEncloserProofRecords = nsec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
if (closestEncloserProofRecords.Count == 0)
throw new InvalidOperationException();
nsec3Records.AddRange(closestEncloserProofRecords);
DnsResourceRecord closestEncloserProofRecord = closestEncloserProofRecords[0];
DnsNSEC3RecordData closestEncloserProof = closestEncloserProofRecord.RDATA as DnsNSEC3RecordData;
//add proof of cover for the next closer name
if (!DnsNSECRecordData.IsDomainCovered(closestEncloserProofRecord.Name, closestEncloserProof.NextHashedOwnerName + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : ""), hashedNextCloserName))
AddProofOfCoverFor(hashedNextCloserName, closestAuthority.Name);
//add proof of cover to prove that a wildcard expansion was not possible
string wildcardDomain = closestEncloser.Length > 0 ? "*." + closestEncloser : "*";
string hashedWildcardDomainName = nsec3Param.ComputeHashedOwnerNameBase32HexString(wildcardDomain) + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : "");
if (!DnsNSECRecordData.IsDomainCovered(closestEncloserProofRecord.Name, closestEncloserProof.NextHashedOwnerName + (closestAuthority.Name.Length > 0 ? "." + closestAuthority.Name : ""), hashedWildcardDomainName))
AddProofOfCoverFor(hashedWildcardDomainName, closestAuthority.Name);
}
return nsec3Records;
}
public static IReadOnlyList FindNSecProofOfNonExistenceNoData(AuthZone zone)
{
IReadOnlyList nsecRecords = zone.QueryRecords(DnsResourceRecordType.NSEC, true);
if (nsecRecords.Count == 0)
throw new InvalidOperationException("Zone does not have NSEC deployed correctly.");
return nsecRecords;
}
public IReadOnlyList FindNSec3ProofOfNonExistenceNoData(AuthZone zone, ApexZone apexZone, CancellationToken cancellationToken)
{
IReadOnlyList nsec3ParamRecords = apexZone.GetRecords(DnsResourceRecordType.NSEC3PARAM);
if (nsec3ParamRecords.Count == 0)
throw new InvalidOperationException("Zone does not have NSEC3 deployed.");
DnsNSEC3PARAMRecordData nsec3Param = nsec3ParamRecords[0].RDATA as DnsNSEC3PARAMRecordData;
string hashedOwnerName = nsec3Param.ComputeHashedOwnerNameBase32HexString(zone.Name) + (apexZone.Name.Length > 0 ? "." + apexZone.Name : "");
AuthZone nsec3Zone = GetAuthZone(apexZone.Name, hashedOwnerName);
if (nsec3Zone is null)
{
//this is probably since the domain in request is for an nsec3 record owner name
return FindNSec3ProofOfNonExistenceNxDomain(zone.Name, false, cancellationToken);
}
return FindNSec3ProofOfNonExistenceNoData(nsec3Zone);
}
private static IReadOnlyList FindNSec3ProofOfNonExistenceNoData(AuthZone nsec3Zone)
{
IReadOnlyList nsec3Records = nsec3Zone.QueryRecords(DnsResourceRecordType.NSEC3, true);
if (nsec3Records.Count > 0)
return nsec3Records;
return Array.Empty();
}
#endregion
}
}