ForwarderZone: updated implementation to support notify and zone transfer features. Added support for record auto expiry feature. Added pseudo SOA record support for zone transfer support. Updated code to prevent adding DNSSEC related records. Added support for zone versioning. Code refactoring done.

This commit is contained in:
Shreyas Zare
2024-09-14 17:52:56 +05:30
parent 17844b7b59
commit c622118d6b

View File

@@ -29,46 +29,61 @@ namespace DnsServerCore.Dns.Zones
{
#region constructor
public ForwarderZone(AuthZoneInfo zoneInfo)
: base(zoneInfo)
{ }
public ForwarderZone(string name)
: base(name)
public ForwarderZone(DnsServer dnsServer, AuthZoneInfo zoneInfo)
: base(dnsServer, zoneInfo)
{
_zoneTransfer = AuthZoneTransfer.Deny;
_notify = AuthZoneNotify.None;
_update = AuthZoneUpdate.Deny;
InitNotify();
InitRecordExpiry();
}
public ForwarderZone(string name, DnsTransportProtocol forwarderProtocol, string forwarder, bool dnssecValidation, DnsForwarderRecordProxyType proxyType, string proxyAddress, ushort proxyPort, string proxyUsername, string proxyPassword, string fwdRecordComments)
: base(name)
public ForwarderZone(DnsServer dnsServer, string name)
: base(dnsServer, name)
{
_zoneTransfer = AuthZoneTransfer.Deny;
_notify = AuthZoneNotify.None;
_update = AuthZoneUpdate.Deny;
InitZone();
InitNotify();
InitRecordExpiry();
}
DnsResourceRecord fwdRecord = new DnsResourceRecord(name, DnsResourceRecordType.FWD, DnsClass.IN, 0, new DnsForwarderRecordData(forwarderProtocol, forwarder, dnssecValidation, proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword));
public ForwarderZone(DnsServer dnsServer, string name, DnsTransportProtocol forwarderProtocol, string forwarder, bool dnssecValidation, DnsForwarderRecordProxyType proxyType, string proxyAddress, ushort proxyPort, string proxyUsername, string proxyPassword, string fwdRecordComments)
: base(dnsServer, name)
{
DnsResourceRecord fwdRecord = new DnsResourceRecord(name, DnsResourceRecordType.FWD, DnsClass.IN, 0, new DnsForwarderRecordData(forwarderProtocol, forwarder, dnssecValidation, proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword, 0));
if (!string.IsNullOrEmpty(fwdRecordComments))
fwdRecord.GetAuthGenericRecordInfo().Comments = fwdRecordComments;
_entries[DnsResourceRecordType.FWD] = new DnsResourceRecord[] { fwdRecord };
fwdRecord.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
_entries[DnsResourceRecordType.FWD] = [fwdRecord];
InitZone();
InitNotify();
InitRecordExpiry();
}
#endregion
#region internal
internal void UpdateLastModified()
internal virtual void InitZone()
{
_lastModified = DateTime.UtcNow;
//init forwarder zone with dummy SOA record
DnsSOARecordData soa = new DnsSOARecordData(_dnsServer.ServerDomain, "invalid", 1, 900, 300, 604800, 900);
DnsResourceRecord soaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, 0, soa);
soaRecord.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
_entries[DnsResourceRecordType.SOA] = [soaRecord];
}
#endregion
#region public
public override string GetZoneTypeName()
{
return "Conditional Forwarder";
}
public override void SetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
{
switch (type)
@@ -77,12 +92,72 @@ namespace DnsServerCore.Dns.Zones
throw new InvalidOperationException("Cannot set CNAME record at zone apex.");
case DnsResourceRecordType.SOA:
if ((records.Count != 1) || !records[0].Name.Equals(_name, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("Invalid SOA record.");
DnsResourceRecord newSoaRecord = records[0];
DnsSOARecordData newSoa = newSoaRecord.RDATA as DnsSOARecordData;
if (newSoaRecord.OriginalTtlValue > newSoa.Expire)
throw new DnsServerException("Failed to set records: TTL cannot be greater than SOA EXPIRE.");
if (newSoa.Retry > newSoa.Refresh)
throw new DnsServerException("Failed to set records: SOA RETRY cannot be greater than SOA REFRESH.");
if (newSoa.Refresh > newSoa.Expire)
throw new DnsServerException("Failed to set records: SOA REFRESH cannot be greater than SOA EXPIRE.");
{
//reset fixed record values
DnsSOARecordData modifiedSoa = new DnsSOARecordData(newSoa.PrimaryNameServer, "invalid", newSoa.Serial, newSoa.Refresh, newSoa.Retry, newSoa.Expire, newSoa.Minimum);
newSoaRecord = new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, 0, modifiedSoa) { Tag = newSoaRecord.Tag };
records = [newSoaRecord];
}
//remove any record info except serial date scheme and comments
bool useSoaSerialDateScheme;
string comments;
{
SOARecordInfo recordInfo = newSoaRecord.GetAuthSOARecordInfo();
useSoaSerialDateScheme = recordInfo.UseSoaSerialDateScheme;
comments = recordInfo.Comments;
}
newSoaRecord.Tag = null; //remove old record info
{
SOARecordInfo recordInfo = newSoaRecord.GetAuthSOARecordInfo();
recordInfo.UseSoaSerialDateScheme = useSoaSerialDateScheme;
recordInfo.Comments = comments;
recordInfo.LastModified = DateTime.UtcNow;
}
//setting new SOA
CommitAndIncrementSerial(null, records);
TriggerNotify();
break;
case DnsResourceRecordType.DS:
throw new DnsServerException("The record type is not supported by forwarder zones.");
case DnsResourceRecordType.DNSKEY:
case DnsResourceRecordType.RRSIG:
case DnsResourceRecordType.NSEC:
case DnsResourceRecordType.NSEC3PARAM:
case DnsResourceRecordType.NSEC3:
throw new InvalidOperationException("Cannot set DNSSEC records.");
default:
base.SetRecords(type, records);
UpdateLastModified();
if (records[0].OriginalTtlValue > GetZoneSoaExpire())
throw new DnsServerException("Failed to set records: TTL cannot be greater than SOA EXPIRE.");
if (!TrySetRecords(type, records, out IReadOnlyList<DnsResourceRecord> deletedRecords))
throw new DnsServerException("Failed to set records. Please try again.");
CommitAndIncrementSerial(deletedRecords, records);
TriggerNotify();
break;
}
}
@@ -92,72 +167,174 @@ namespace DnsServerCore.Dns.Zones
switch (record.Type)
{
case DnsResourceRecordType.DS:
throw new DnsServerException("The record type is not supported by forwarder zones.");
case DnsResourceRecordType.DNSKEY:
case DnsResourceRecordType.RRSIG:
case DnsResourceRecordType.NSEC:
case DnsResourceRecordType.NSEC3PARAM:
case DnsResourceRecordType.NSEC3:
throw new InvalidOperationException("Cannot set DNSSEC records.");
default:
base.AddRecord(record);
UpdateLastModified();
if (record.OriginalTtlValue > GetZoneSoaExpire())
throw new DnsServerException("Failed to add record: TTL cannot be greater than SOA EXPIRE.");
AddRecord(record, out IReadOnlyList<DnsResourceRecord> addedRecords, out IReadOnlyList<DnsResourceRecord> deletedRecords);
if (addedRecords.Count > 0)
{
CommitAndIncrementSerial(deletedRecords, addedRecords);
TriggerNotify();
}
break;
}
}
public override bool DeleteRecords(DnsResourceRecordType type)
{
if (base.DeleteRecords(type))
switch (type)
{
UpdateLastModified();
return true;
}
case DnsResourceRecordType.SOA:
throw new InvalidOperationException("Cannot delete SOA record.");
return false;
default:
if (_entries.TryRemove(type, out IReadOnlyList<DnsResourceRecord> removedRecords))
{
CommitAndIncrementSerial(removedRecords);
TriggerNotify();
return true;
}
return false;
}
}
public override bool DeleteRecord(DnsResourceRecordType type, DnsResourceRecordData rdata)
{
if (base.DeleteRecord(type, rdata))
switch (type)
{
UpdateLastModified();
return true;
}
case DnsResourceRecordType.SOA:
throw new InvalidOperationException("Cannot delete SOA record.");
return false;
default:
if (TryDeleteRecord(type, rdata, out DnsResourceRecord deletedRecord))
{
CommitAndIncrementSerial([deletedRecord]);
TriggerNotify();
return true;
}
return false;
}
}
public override void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
{
base.UpdateRecord(oldRecord, newRecord);
UpdateLastModified();
switch (oldRecord.Type)
{
case DnsResourceRecordType.SOA:
throw new InvalidOperationException("Cannot update record: use SetRecords() for " + oldRecord.Type.ToString() + " record");
default:
if (oldRecord.Type != newRecord.Type)
throw new InvalidOperationException("Old and new record types do not match.");
if (newRecord.OriginalTtlValue > GetZoneSoaExpire())
throw new DnsServerException("Cannot update record: TTL cannot be greater than SOA EXPIRE.");
if (!TryDeleteRecord(oldRecord.Type, oldRecord.RDATA, out DnsResourceRecord deletedRecord))
throw new DnsServerException("Cannot update record: the record does not exists to be updated.");
AddRecord(newRecord, out IReadOnlyList<DnsResourceRecord> addedRecords, out IReadOnlyList<DnsResourceRecord> deletedRecords);
List<DnsResourceRecord> allDeletedRecords = new List<DnsResourceRecord>(deletedRecords.Count + 1);
allDeletedRecords.Add(deletedRecord);
allDeletedRecords.AddRange(deletedRecords);
CommitAndIncrementSerial(allDeletedRecords, addedRecords);
TriggerNotify();
break;
}
}
public override IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool dnssecOk)
{
if (type == DnsResourceRecordType.SOA)
return []; //forwarder zone is not authoritative and contains dummy SOA record
return base.QueryRecords(type, dnssecOk);
}
#endregion
#region properties
public override AuthZoneQueryAccess QueryAccess
{
get { return base.QueryAccess; }
set
{
switch (value)
{
case AuthZoneQueryAccess.AllowOnlyZoneNameServers:
case AuthZoneQueryAccess.AllowZoneNameServersAndUseSpecifiedNetworkACL:
throw new ArgumentException("The Query Access option is invalid for " + GetZoneTypeName() + " zones: " + value.ToString(), nameof(QueryAccess));
}
base.QueryAccess = value;
}
}
public override AuthZoneTransfer ZoneTransfer
{
get { return _zoneTransfer; }
set { throw new InvalidOperationException(); }
get { return base.ZoneTransfer; }
set
{
switch (value)
{
case AuthZoneTransfer.AllowOnlyZoneNameServers:
case AuthZoneTransfer.AllowZoneNameServersAndUseSpecifiedNetworkACL:
throw new ArgumentException("The Zone Transfer option is invalid for " + GetZoneTypeName() + " zones: " + value.ToString(), nameof(ZoneTransfer));
}
base.ZoneTransfer = value;
}
}
public override AuthZoneNotify Notify
{
get { return _notify; }
set { throw new InvalidOperationException(); }
get { return base.Notify; }
set
{
switch (value)
{
case AuthZoneNotify.ZoneNameServers:
case AuthZoneNotify.BothZoneAndSpecifiedNameServers:
throw new ArgumentException("The Notify option is invalid for " + GetZoneTypeName() + " zones: " + value.ToString(), nameof(Notify));
}
base.Notify = value;
}
}
public override AuthZoneUpdate Update
{
get { return _update; }
get { return base.Update; }
set
{
switch (value)
{
case AuthZoneUpdate.AllowOnlyZoneNameServers:
case AuthZoneUpdate.AllowBothZoneNameServersAndSpecifiedIpAddresses:
throw new ArgumentException("The Dynamic Updates option is invalid for Conditional Forwarder zones: " + value.ToString(), nameof(Update));
case AuthZoneUpdate.AllowZoneNameServersAndUseSpecifiedNetworkACL:
throw new ArgumentException("The Dynamic Updates option is invalid for " + GetZoneTypeName() + " zones: " + value.ToString(), nameof(Update));
}
_update = value;
base.Update = value;
}
}