SecondaryZone.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using DnsServerCore.Dns.ResourceRecords;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.IO;
  19. using System.Security.Cryptography;
  20. using System.Threading;
  21. using System.Threading.Tasks;
  22. using TechnitiumLibrary;
  23. using TechnitiumLibrary.Net.Dns;
  24. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  25. namespace DnsServerCore.Dns.Zones
  26. {
  27. //Message Digest for DNS Zones
  28. //https://datatracker.ietf.org/doc/rfc8976/
  29. class SecondaryZone : ApexZone
  30. {
  31. #region variables
  32. readonly object _refreshTimerLock = new object();
  33. Timer _refreshTimer;
  34. bool _refreshTimerTriggered;
  35. const int REFRESH_TIMER_INTERVAL = 5000;
  36. const int REFRESH_SOA_TIMEOUT = 10000;
  37. const int REFRESH_XFR_TIMEOUT = 120000;
  38. const int REFRESH_RETRIES = 5;
  39. const int REFRESH_TSIG_FUDGE = 300;
  40. bool _overrideCatalogPrimaryNameServers;
  41. IReadOnlyList<NameServerAddress> _primaryNameServerAddresses;
  42. DnsTransportProtocol _primaryZoneTransferProtocol;
  43. string _primaryZoneTransferTsigKeyName;
  44. DateTime _expiry;
  45. bool _isExpired;
  46. bool _validateZone;
  47. bool _validationFailed;
  48. bool _resync;
  49. #endregion
  50. #region constructor
  51. public SecondaryZone(DnsServer dnsServer, AuthZoneInfo zoneInfo)
  52. : base(dnsServer, zoneInfo)
  53. {
  54. _overrideCatalogPrimaryNameServers = zoneInfo.OverrideCatalogPrimaryNameServers;
  55. _primaryNameServerAddresses = zoneInfo.PrimaryNameServerAddresses;
  56. _primaryZoneTransferProtocol = zoneInfo.PrimaryZoneTransferProtocol;
  57. _primaryZoneTransferTsigKeyName = zoneInfo.PrimaryZoneTransferTsigKeyName;
  58. _expiry = zoneInfo.Expiry;
  59. _isExpired = DateTime.UtcNow > _expiry;
  60. _validateZone = zoneInfo.ValidateZone;
  61. _validationFailed = zoneInfo.ValidationFailed;
  62. _refreshTimer = new Timer(RefreshTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
  63. InitNotify();
  64. }
  65. protected SecondaryZone(DnsServer dnsServer, string name, IReadOnlyList<NameServerAddress> primaryNameServerAddresses, DnsTransportProtocol primaryZoneTransferProtocol, string primaryZoneTransferTsigKeyName, bool validateZone)
  66. : base(dnsServer, name)
  67. {
  68. PrimaryZoneTransferProtocol = primaryZoneTransferProtocol;
  69. PrimaryNameServerAddresses = primaryNameServerAddresses?.Convert(delegate (NameServerAddress nameServer)
  70. {
  71. if (nameServer.Protocol != primaryZoneTransferProtocol)
  72. nameServer = nameServer.ChangeProtocol(primaryZoneTransferProtocol);
  73. return nameServer;
  74. });
  75. PrimaryZoneTransferTsigKeyName = primaryZoneTransferTsigKeyName;
  76. _validateZone = validateZone;
  77. _isExpired = true; //new secondary zone is considered expired till it refreshes
  78. _refreshTimer = new Timer(RefreshTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
  79. InitNotify();
  80. }
  81. #endregion
  82. #region static
  83. public static async Task<SecondaryZone> CreateAsync(DnsServer dnsServer, string name, IReadOnlyList<NameServerAddress> primaryNameServerAddresses = null, DnsTransportProtocol primaryZoneTransferProtocol = DnsTransportProtocol.Tcp, string primaryZoneTransferTsigKeyName = null, bool validateZone = false, bool ignoreSoaFailure = false)
  84. {
  85. SecondaryZone secondaryZone = new SecondaryZone(dnsServer, name, primaryNameServerAddresses, primaryZoneTransferProtocol, primaryZoneTransferTsigKeyName, validateZone);
  86. try
  87. {
  88. DnsDatagram soaResponse;
  89. try
  90. {
  91. DnsQuestionRecord soaQuestion = new DnsQuestionRecord(secondaryZone._name, DnsResourceRecordType.SOA, DnsClass.IN);
  92. if (secondaryZone.PrimaryNameServerAddresses is null)
  93. {
  94. soaResponse = await secondaryZone._dnsServer.DirectQueryAsync(soaQuestion);
  95. }
  96. else
  97. {
  98. DnsClient dnsClient = new DnsClient(secondaryZone.PrimaryNameServerAddresses);
  99. foreach (NameServerAddress nameServerAddress in dnsClient.Servers)
  100. {
  101. if (nameServerAddress.IsIPEndPointStale)
  102. await nameServerAddress.ResolveIPAddressAsync(secondaryZone._dnsServer, secondaryZone._dnsServer.PreferIPv6);
  103. }
  104. dnsClient.Proxy = secondaryZone._dnsServer.Proxy;
  105. dnsClient.PreferIPv6 = secondaryZone._dnsServer.PreferIPv6;
  106. DnsDatagram soaRequest = new DnsDatagram(0, false, DnsOpcode.StandardQuery, false, false, false, false, false, false, DnsResponseCode.NoError, [soaQuestion], null, null, null, secondaryZone._dnsServer.UdpPayloadSize);
  107. if (string.IsNullOrEmpty(primaryZoneTransferTsigKeyName))
  108. soaResponse = await dnsClient.RawResolveAsync(soaRequest);
  109. else if ((secondaryZone._dnsServer.TsigKeys is not null) && secondaryZone._dnsServer.TsigKeys.TryGetValue(primaryZoneTransferTsigKeyName, out TsigKey key))
  110. soaResponse = await dnsClient.TsigResolveAsync(soaRequest, key, REFRESH_TSIG_FUDGE);
  111. else
  112. throw new DnsServerException("No such TSIG key was found configured: " + primaryZoneTransferTsigKeyName);
  113. }
  114. }
  115. catch (Exception ex)
  116. {
  117. throw new DnsServerException("DNS Server failed to find SOA record for: " + secondaryZone.ToString(), ex);
  118. }
  119. if ((soaResponse.Answer.Count == 0) || (soaResponse.Answer[0].Type != DnsResourceRecordType.SOA))
  120. throw new DnsServerException("DNS Server did not receive SOA record in response from any of the primary name servers for: " + secondaryZone.ToString());
  121. DnsResourceRecord receivedSoaRecord = soaResponse.Answer[0];
  122. DnsSOARecordData receivedSoa = receivedSoaRecord.RDATA as DnsSOARecordData;
  123. DnsSOARecordData soa = new DnsSOARecordData(receivedSoa.PrimaryNameServer, receivedSoa.ResponsiblePerson, 0u, receivedSoa.Refresh, receivedSoa.Retry, receivedSoa.Expire, receivedSoa.Minimum);
  124. DnsResourceRecord soaRecord = new DnsResourceRecord(secondaryZone._name, DnsResourceRecordType.SOA, DnsClass.IN, receivedSoaRecord.OriginalTtlValue, soa);
  125. secondaryZone._entries[DnsResourceRecordType.SOA] = [soaRecord];
  126. }
  127. catch
  128. {
  129. if (!ignoreSoaFailure)
  130. throw;
  131. //continue with dummy SOA
  132. DnsSOARecordData soa = new DnsSOARecordData(secondaryZone._dnsServer.ServerDomain, "invalid", 0, 300, 60, 604800, 900);
  133. DnsResourceRecord soaRecord = new DnsResourceRecord(secondaryZone._name, DnsResourceRecordType.SOA, DnsClass.IN, 0, soa);
  134. soaRecord.GetAuthGenericRecordInfo().LastModified = DateTime.UtcNow;
  135. secondaryZone._entries[DnsResourceRecordType.SOA] = [soaRecord];
  136. }
  137. return secondaryZone;
  138. }
  139. #endregion
  140. #region IDisposable
  141. bool _disposed;
  142. protected override void Dispose(bool disposing)
  143. {
  144. try
  145. {
  146. if (_disposed)
  147. return;
  148. if (disposing)
  149. {
  150. lock (_refreshTimerLock)
  151. {
  152. if (_refreshTimer != null)
  153. {
  154. _refreshTimer.Dispose();
  155. _refreshTimer = null;
  156. }
  157. }
  158. }
  159. _disposed = true;
  160. }
  161. finally
  162. {
  163. base.Dispose(disposing);
  164. }
  165. }
  166. #endregion
  167. #region private
  168. private async void RefreshTimerCallback(object state)
  169. {
  170. try
  171. {
  172. if (Disabled && !_resync)
  173. return;
  174. _isExpired = DateTime.UtcNow > _expiry;
  175. //get primary name server addresses
  176. IReadOnlyList<NameServerAddress> primaryNameServerAddresses;
  177. DnsTransportProtocol primaryZoneTransferProtocol;
  178. string primaryZoneTransferTsigKeyName;
  179. SecondaryCatalogZone secondaryCatalogZone = SecondaryCatalogZone;
  180. if ((secondaryCatalogZone is not null) && !_overrideCatalogPrimaryNameServers)
  181. {
  182. primaryNameServerAddresses = await GetResolvedNameServerAddressesAsync(secondaryCatalogZone.PrimaryNameServerAddresses);
  183. primaryZoneTransferProtocol = secondaryCatalogZone.PrimaryZoneTransferProtocol;
  184. primaryZoneTransferTsigKeyName = secondaryCatalogZone.PrimaryZoneTransferTsigKeyName;
  185. }
  186. else
  187. {
  188. primaryNameServerAddresses = await GetResolvedPrimaryNameServerAddressesAsync();
  189. primaryZoneTransferProtocol = _primaryZoneTransferProtocol;
  190. primaryZoneTransferTsigKeyName = _primaryZoneTransferTsigKeyName;
  191. }
  192. DnsResourceRecord currentSoaRecord = _entries[DnsResourceRecordType.SOA][0];
  193. DnsSOARecordData currentSoa = currentSoaRecord.RDATA as DnsSOARecordData;
  194. if (primaryNameServerAddresses.Count == 0)
  195. {
  196. _dnsServer.LogManager?.Write("DNS Server could not find primary name server IP addresses for " + GetZoneTypeName() + " zone: " + ToString());
  197. //set timer for retry
  198. ResetRefreshTimer(currentSoa.Retry * 1000);
  199. _syncFailed = true;
  200. return;
  201. }
  202. TsigKey key = null;
  203. if (!string.IsNullOrEmpty(primaryZoneTransferTsigKeyName) && ((_dnsServer.TsigKeys is null) || !_dnsServer.TsigKeys.TryGetValue(primaryZoneTransferTsigKeyName, out key)))
  204. {
  205. _dnsServer.LogManager?.Write("DNS Server does not have TSIG key '" + primaryZoneTransferTsigKeyName + "' configured for refreshing " + GetZoneTypeName() + " zone: " + ToString());
  206. //set timer for retry
  207. ResetRefreshTimer(currentSoa.Retry * 1000);
  208. _syncFailed = true;
  209. return;
  210. }
  211. //refresh zone
  212. if (await RefreshZoneAsync(primaryNameServerAddresses, primaryZoneTransferProtocol, key, _validateZone))
  213. {
  214. DnsSOARecordData latestSoa = _entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData;
  215. _syncFailed = false;
  216. _expiry = DateTime.UtcNow.AddSeconds(latestSoa.Expire);
  217. _isExpired = false;
  218. _resync = false;
  219. _dnsServer.AuthZoneManager.SaveZoneFile(_name);
  220. if (_validationFailed)
  221. ResetRefreshTimer(latestSoa.Retry * 1000); //zone validation failed, set timer for retry
  222. else
  223. ResetRefreshTimer(latestSoa.Refresh * 1000); //zone refreshed; set timer for refresh
  224. return;
  225. }
  226. //no response from any of the name servers; set timer for retry
  227. DnsSOARecordData soa = _entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData;
  228. ResetRefreshTimer(soa.Retry * 1000);
  229. _syncFailed = true;
  230. }
  231. catch (Exception ex)
  232. {
  233. _dnsServer.LogManager?.Write(ex);
  234. //set timer for retry
  235. DnsSOARecordData soa = _entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData;
  236. ResetRefreshTimer(soa.Retry * 1000);
  237. _syncFailed = true;
  238. }
  239. finally
  240. {
  241. _refreshTimerTriggered = false;
  242. }
  243. }
  244. private void ResetRefreshTimer(long dueTime)
  245. {
  246. lock (_refreshTimerLock)
  247. {
  248. _refreshTimer?.Change(dueTime, Timeout.Infinite);
  249. }
  250. }
  251. private async Task<bool> RefreshZoneAsync(IReadOnlyList<NameServerAddress> primaryNameServers, DnsTransportProtocol zoneTransferProtocol, TsigKey key, bool validateZone)
  252. {
  253. try
  254. {
  255. _dnsServer.LogManager?.Write("DNS Server has started zone refresh for " + GetZoneTypeName() + " zone: " + ToString());
  256. //get nameservers list with correct zone tranfer protocol
  257. List<NameServerAddress> updatedNameServers = new List<NameServerAddress>(primaryNameServers.Count);
  258. {
  259. switch (zoneTransferProtocol)
  260. {
  261. case DnsTransportProtocol.Tls:
  262. case DnsTransportProtocol.Quic:
  263. //change name server protocol to TLS/QUIC
  264. foreach (NameServerAddress primaryNameServer in primaryNameServers)
  265. {
  266. if (primaryNameServer.Protocol == zoneTransferProtocol)
  267. updatedNameServers.Add(primaryNameServer);
  268. else
  269. updatedNameServers.Add(primaryNameServer.ChangeProtocol(zoneTransferProtocol));
  270. }
  271. break;
  272. default:
  273. //change name server protocol to TCP
  274. foreach (NameServerAddress primaryNameServer in primaryNameServers)
  275. {
  276. if (primaryNameServer.Protocol == DnsTransportProtocol.Tcp)
  277. updatedNameServers.Add(primaryNameServer);
  278. else
  279. updatedNameServers.Add(primaryNameServer.ChangeProtocol(DnsTransportProtocol.Tcp));
  280. }
  281. break;
  282. }
  283. }
  284. //init XFR DNS Client
  285. DnsClient xfrClient = new DnsClient(updatedNameServers);
  286. xfrClient.Proxy = _dnsServer.Proxy;
  287. xfrClient.PreferIPv6 = _dnsServer.PreferIPv6;
  288. xfrClient.Retries = REFRESH_RETRIES;
  289. xfrClient.Concurrency = 1;
  290. DnsResourceRecord currentSoaRecord = _entries[DnsResourceRecordType.SOA][0];
  291. DnsSOARecordData currentSoa = currentSoaRecord.RDATA as DnsSOARecordData;
  292. if (!_resync && (this is not SecondaryForwarderZone)) //skip SOA probe for Secondary Forwarder/Catalog since Forwarder/Catalog is not authoritative for SOA
  293. {
  294. //check for update
  295. xfrClient.Timeout = REFRESH_SOA_TIMEOUT;
  296. DnsDatagram soaRequest = new DnsDatagram(0, false, DnsOpcode.StandardQuery, false, false, false, false, false, false, DnsResponseCode.NoError, [new DnsQuestionRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN)], null, null, null, _dnsServer.UdpPayloadSize);
  297. DnsDatagram soaResponse;
  298. if (key is null)
  299. soaResponse = await xfrClient.RawResolveAsync(soaRequest);
  300. else
  301. soaResponse = await xfrClient.TsigResolveAsync(soaRequest, key, REFRESH_TSIG_FUDGE);
  302. if (soaResponse.RCODE != DnsResponseCode.NoError)
  303. {
  304. _dnsServer.LogManager?.Write("DNS Server received RCODE=" + soaResponse.RCODE.ToString() + " for '" + ToString() + "' " + GetZoneTypeName() + " zone refresh from: " + soaResponse.Metadata.NameServer.ToString());
  305. return false;
  306. }
  307. if ((soaResponse.Answer.Count < 1) || (soaResponse.Answer[0].Type != DnsResourceRecordType.SOA) || !_name.Equals(soaResponse.Answer[0].Name, StringComparison.OrdinalIgnoreCase))
  308. {
  309. _dnsServer.LogManager?.Write("DNS Server received an empty response for SOA query for '" + ToString() + "' " + GetZoneTypeName() + " zone refresh from: " + soaResponse.Metadata.NameServer.ToString());
  310. return false;
  311. }
  312. DnsResourceRecord receivedSoaRecord = soaResponse.Answer[0];
  313. DnsSOARecordData receivedSoa = receivedSoaRecord.RDATA as DnsSOARecordData;
  314. //compare using sequence space arithmetic
  315. if (!currentSoa.IsZoneUpdateAvailable(receivedSoa))
  316. {
  317. _dnsServer.LogManager?.Write("DNS Server successfully checked for '" + ToString() + "' " + GetZoneTypeName() + " zone update from: " + soaResponse.Metadata.NameServer.ToString());
  318. return true;
  319. }
  320. }
  321. //update available; do zone transfer
  322. xfrClient.Timeout = REFRESH_XFR_TIMEOUT;
  323. bool doIXFR = !_isExpired && !_resync;
  324. while (true)
  325. {
  326. DnsQuestionRecord xfrQuestion;
  327. IReadOnlyList<DnsResourceRecord> xfrAuthority;
  328. if (doIXFR)
  329. {
  330. xfrQuestion = new DnsQuestionRecord(_name, DnsResourceRecordType.IXFR, DnsClass.IN);
  331. xfrAuthority = [currentSoaRecord];
  332. }
  333. else
  334. {
  335. xfrQuestion = new DnsQuestionRecord(_name, DnsResourceRecordType.AXFR, DnsClass.IN);
  336. xfrAuthority = null;
  337. }
  338. DnsDatagram xfrRequest = new DnsDatagram(0, false, DnsOpcode.StandardQuery, false, false, false, false, false, false, DnsResponseCode.NoError, [xfrQuestion], null, xfrAuthority);
  339. DnsDatagram xfrResponse;
  340. if (key is null)
  341. xfrResponse = await xfrClient.RawResolveAsync(xfrRequest);
  342. else
  343. xfrResponse = await xfrClient.TsigResolveAsync(xfrRequest, key, REFRESH_TSIG_FUDGE);
  344. if (doIXFR && ((xfrResponse.RCODE == DnsResponseCode.NotImplemented) || (xfrResponse.RCODE == DnsResponseCode.Refused)))
  345. {
  346. doIXFR = false;
  347. continue;
  348. }
  349. if (xfrResponse.RCODE != DnsResponseCode.NoError)
  350. {
  351. _dnsServer.LogManager?.Write("DNS Server received a zone transfer response (RCODE=" + xfrResponse.RCODE.ToString() + ") for '" + ToString() + "' " + GetZoneTypeName() + " zone from: " + xfrResponse.Metadata.NameServer.ToString());
  352. return false;
  353. }
  354. if (xfrResponse.Answer.Count < 1)
  355. {
  356. _dnsServer.LogManager?.Write("DNS Server received an empty response for zone transfer query for '" + ToString() + "' " + GetZoneTypeName() + " zone from: " + xfrResponse.Metadata.NameServer.ToString());
  357. return false;
  358. }
  359. if (!_name.Equals(xfrResponse.Answer[0].Name, StringComparison.OrdinalIgnoreCase) || (xfrResponse.Answer[0].RDATA is not DnsSOARecordData xfrSoa))
  360. {
  361. _dnsServer.LogManager?.Write("DNS Server received invalid response for zone transfer query for '" + ToString() + "' " + GetZoneTypeName() + " zone from: " + xfrResponse.Metadata.NameServer.ToString());
  362. return false;
  363. }
  364. if (_resync || currentSoa.IsZoneUpdateAvailable(xfrSoa))
  365. {
  366. xfrResponse = xfrResponse.Join(); //join multi message response
  367. if (doIXFR)
  368. {
  369. IReadOnlyList<DnsResourceRecord> historyRecords = _dnsServer.AuthZoneManager.SyncIncrementalZoneTransferRecords(_name, xfrResponse.Answer);
  370. if (historyRecords.Count > 0)
  371. await FinalizeIncrementalZoneTransferAsync(historyRecords);
  372. else
  373. await FinalizeZoneTransferAsync(); //AXFR response was received
  374. }
  375. else
  376. {
  377. _dnsServer.AuthZoneManager.SyncZoneTransferRecords(_name, xfrResponse.Answer);
  378. await FinalizeZoneTransferAsync();
  379. }
  380. _lastModified = DateTime.UtcNow;
  381. if (validateZone)
  382. await ValidateZoneAsync();
  383. else
  384. _validationFailed = false;
  385. if (_validationFailed)
  386. {
  387. _dnsServer.LogManager?.Write("DNS Server refreshed '" + ToString() + "' " + GetZoneTypeName() + " zone with validation failure from: " + xfrResponse.Metadata.NameServer.ToString());
  388. }
  389. else
  390. {
  391. //trigger notify
  392. TriggerNotify();
  393. _dnsServer.LogManager?.Write("DNS Server successfully refreshed '" + ToString() + "' " + GetZoneTypeName() + " zone from: " + xfrResponse.Metadata.NameServer.ToString());
  394. }
  395. }
  396. else
  397. {
  398. _dnsServer.LogManager?.Write("DNS Server successfully checked for '" + ToString() + "' " + GetZoneTypeName() + " zone update from: " + xfrResponse.Metadata.NameServer.ToString());
  399. }
  400. return true;
  401. }
  402. }
  403. catch (Exception ex)
  404. {
  405. _dnsServer.LogManager?.Write("DNS Server failed to refresh '" + ToString() + "' " + GetZoneTypeName() + " zone from: " + primaryNameServers.Join() + "\r\n" + ex.ToString());
  406. return false;
  407. }
  408. }
  409. private async Task ValidateZoneAsync(CancellationToken cancellationToken = default)
  410. {
  411. try
  412. {
  413. DnsClientInternal dnsClient = new DnsClientInternal(_dnsServer);
  414. dnsClient.DnssecValidation = true;
  415. dnsClient.Timeout = 10000;
  416. IReadOnlyList<DnsZONEMDRecordData> zoneMdList = DnsClient.ParseResponseZONEMD(await dnsClient.ResolveAsync(_name, DnsResourceRecordType.ZONEMD, cancellationToken));
  417. if (zoneMdList.Count == 0)
  418. {
  419. //ZONEMD RRSet does not exists; digest verification cannot occur
  420. _validationFailed = false;
  421. _dnsServer.LogManager?.Write("ZONEMD validation cannot occur for the " + GetZoneTypeName() + " zone '" + ToString() + "': ZONEMD RRset does not exists in the zone.");
  422. return;
  423. }
  424. for (int i = 0; i < zoneMdList.Count; i++)
  425. {
  426. for (int j = 0; j < zoneMdList.Count; j++)
  427. {
  428. if (i == j)
  429. continue; //skip comparing self
  430. DnsZONEMDRecordData zoneMd = zoneMdList[i];
  431. DnsZONEMDRecordData checkZoneMd = zoneMdList[j];
  432. if ((checkZoneMd.Scheme == zoneMd.Scheme) && (checkZoneMd.HashAlgorithm == zoneMd.HashAlgorithm))
  433. {
  434. _validationFailed = true;
  435. _dnsServer.LogManager?.Write("ZONEMD validation failed for the " + GetZoneTypeName() + " zone '" + ToString() + "': ZONEMD RRset contains more than one RR with the same Scheme and Hash Algorithm.");
  436. return;
  437. }
  438. }
  439. }
  440. DnsSOARecordData soa = DnsClient.ParseResponseSOA(await dnsClient.ResolveAsync(_name, DnsResourceRecordType.SOA, cancellationToken));
  441. if (soa is null)
  442. {
  443. _validationFailed = true;
  444. _dnsServer.LogManager?.Write("ZONEMD validation failed for the " + GetZoneTypeName() + " zone '" + ToString() + "': failed to find SOA record.");
  445. return;
  446. }
  447. using MemoryStream hashStream = new MemoryStream(4096);
  448. byte[] computedDigestSHA384 = null;
  449. byte[] computedDigestSHA512 = null;
  450. bool zoneSerialized = false;
  451. foreach (DnsZONEMDRecordData zoneMd in zoneMdList)
  452. {
  453. if (soa.Serial != zoneMd.Serial)
  454. continue;
  455. if (zoneMd.Scheme != ZoneMdScheme.Simple)
  456. continue;
  457. byte[] computedDigest;
  458. switch (zoneMd.HashAlgorithm)
  459. {
  460. case ZoneMdHashAlgorithm.SHA384:
  461. if (zoneMd.Digest.Length != 48)
  462. continue;
  463. if (computedDigestSHA384 is null)
  464. {
  465. if (!zoneSerialized)
  466. {
  467. SerializeZoneTo(hashStream);
  468. zoneSerialized = true;
  469. }
  470. hashStream.Position = 0;
  471. computedDigestSHA384 = SHA384.HashData(hashStream);
  472. }
  473. computedDigest = computedDigestSHA384;
  474. break;
  475. case ZoneMdHashAlgorithm.SHA512:
  476. if (zoneMd.Digest.Length != 64)
  477. continue;
  478. if (computedDigestSHA512 is null)
  479. {
  480. if (!zoneSerialized)
  481. {
  482. SerializeZoneTo(hashStream);
  483. zoneSerialized = true;
  484. }
  485. hashStream.Position = 0;
  486. computedDigestSHA512 = SHA512.HashData(hashStream);
  487. }
  488. computedDigest = computedDigestSHA512;
  489. break;
  490. default:
  491. continue;
  492. }
  493. if (computedDigest.ListEquals(zoneMd.Digest))
  494. {
  495. //validation successfull
  496. _validationFailed = false;
  497. _dnsServer.LogManager?.Write("ZONEMD validation was completed successfully for the " + GetZoneTypeName() + " zone: " + ToString());
  498. return;
  499. }
  500. }
  501. //validation failed
  502. _validationFailed = true;
  503. _dnsServer.LogManager?.Write("ZONEMD validation failed for the " + GetZoneTypeName() + " zone '" + ToString() + "': none of the ZONEMD records could successfully validate the zone.");
  504. }
  505. catch (Exception ex)
  506. {
  507. //validation failed
  508. _validationFailed = true;
  509. _dnsServer.LogManager?.Write("ZONEMD validation failed for the " + GetZoneTypeName() + " zone '" + ToString() + "':\r\n" + ex.ToString());
  510. }
  511. }
  512. private void SerializeZoneTo(MemoryStream hashStream)
  513. {
  514. //list zone records for ZONEMD Simple scheme
  515. List<DnsResourceRecord> records;
  516. {
  517. List<DnsResourceRecord> allZoneRecords = new List<DnsResourceRecord>();
  518. _dnsServer.AuthZoneManager.ListAllZoneRecords(_name, allZoneRecords);
  519. records = new List<DnsResourceRecord>(allZoneRecords.Count);
  520. foreach (DnsResourceRecord record in allZoneRecords)
  521. {
  522. switch (record.Type)
  523. {
  524. case DnsResourceRecordType.NS:
  525. records.Add(record);
  526. IReadOnlyList<DnsResourceRecord> glueRecords = record.GetAuthNSRecordInfo().GlueRecords;
  527. if (glueRecords is not null)
  528. records.AddRange(glueRecords);
  529. break;
  530. case DnsResourceRecordType.RRSIG:
  531. if (record.Name.Equals(_name, StringComparison.OrdinalIgnoreCase) && (record.RDATA is DnsRRSIGRecordData rdata) && (rdata.TypeCovered == DnsResourceRecordType.ZONEMD))
  532. break; //skip RRSIG covering the apex ZONEMD
  533. records.Add(record);
  534. break;
  535. case DnsResourceRecordType.ZONEMD:
  536. if (record.Name.Equals(_name, StringComparison.OrdinalIgnoreCase))
  537. break; //skip apex ZONEMD
  538. records.Add(record);
  539. break;
  540. default:
  541. records.Add(record);
  542. break;
  543. }
  544. }
  545. }
  546. //group records into zones by DNS name
  547. List<KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>>> zones = new List<KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>>>(DnsResourceRecord.GroupRecords(records, true));
  548. //sort zones by canonical DNS name
  549. zones.Sort(delegate (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> x, KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> y)
  550. {
  551. return DnsNSECRecordData.CanonicalComparison(x.Key, y.Key);
  552. });
  553. //start serialization, zone by zone
  554. using MemoryStream rrBuffer = new MemoryStream(512);
  555. foreach (KeyValuePair<string, Dictionary<DnsResourceRecordType, List<DnsResourceRecord>>> zone in zones)
  556. {
  557. //list all RRSets for current zone owner name
  558. List<KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>>> rrSets = new List<KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>>>(zone.Value);
  559. //RRsets having the same owner name MUST be numerically ordered, in ascending order, by their numeric RR TYPE
  560. rrSets.Sort(delegate (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> x, KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> y)
  561. {
  562. return x.Key.CompareTo(y.Key);
  563. });
  564. //serialize records
  565. List<CanonicallySerializedResourceRecord> rrList = new List<CanonicallySerializedResourceRecord>(rrSets.Count * 4);
  566. foreach (KeyValuePair<DnsResourceRecordType, List<DnsResourceRecord>> rrSet in rrSets)
  567. {
  568. //serialize current RRSet records
  569. List<CanonicallySerializedResourceRecord> serializedResourceRecords = new List<CanonicallySerializedResourceRecord>(rrSet.Value.Count);
  570. foreach (DnsResourceRecord record in rrSet.Value)
  571. serializedResourceRecords.Add(CanonicallySerializedResourceRecord.Create(record.Name, record.Type, record.Class, record.OriginalTtlValue, record.RDATA, rrBuffer));
  572. //Canonical RR Ordering by sorting RDATA portion of the canonical form of each RR
  573. serializedResourceRecords.Sort();
  574. foreach (CanonicallySerializedResourceRecord serializedResourceRecord in serializedResourceRecords)
  575. serializedResourceRecord.WriteTo(hashStream);
  576. }
  577. }
  578. }
  579. protected virtual Task FinalizeZoneTransferAsync()
  580. {
  581. ClearZoneHistory();
  582. return Task.CompletedTask;
  583. }
  584. protected virtual Task FinalizeIncrementalZoneTransferAsync(IReadOnlyList<DnsResourceRecord> historyRecords)
  585. {
  586. CommitZoneHistory(historyRecords);
  587. return Task.CompletedTask;
  588. }
  589. #endregion
  590. #region public
  591. public override string GetZoneTypeName()
  592. {
  593. return "Secondary";
  594. }
  595. public void TriggerRefresh(int refreshInterval = REFRESH_TIMER_INTERVAL)
  596. {
  597. if (Disabled)
  598. return;
  599. if (_refreshTimerTriggered)
  600. return;
  601. _refreshTimerTriggered = true;
  602. ResetRefreshTimer(refreshInterval);
  603. }
  604. public void TriggerResync()
  605. {
  606. if (_refreshTimerTriggered)
  607. return;
  608. _resync = true;
  609. _refreshTimerTriggered = true;
  610. ResetRefreshTimer(0);
  611. }
  612. public override void SetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
  613. {
  614. throw new InvalidOperationException("Cannot set records in " + GetZoneTypeName() + " zone.");
  615. }
  616. public override void AddRecord(DnsResourceRecord record)
  617. {
  618. throw new InvalidOperationException("Cannot add record in " + GetZoneTypeName() + " zone.");
  619. }
  620. public override bool DeleteRecord(DnsResourceRecordType type, DnsResourceRecordData record)
  621. {
  622. throw new InvalidOperationException("Cannot delete record in " + GetZoneTypeName() + " zone.");
  623. }
  624. public override bool DeleteRecords(DnsResourceRecordType type)
  625. {
  626. throw new InvalidOperationException("Cannot delete records in " + GetZoneTypeName() + " zone.");
  627. }
  628. public override void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
  629. {
  630. throw new InvalidOperationException("Cannot update record in " + GetZoneTypeName() + " zone.");
  631. }
  632. #endregion
  633. #region properties
  634. public override bool Disabled
  635. {
  636. get { return base.Disabled; }
  637. set
  638. {
  639. if (base.Disabled == value)
  640. return;
  641. base.Disabled = value; //set value early to be able to use it for notify
  642. if (value)
  643. {
  644. DisableNotifyTimer();
  645. ResetRefreshTimer(Timeout.Infinite);
  646. }
  647. else
  648. {
  649. TriggerNotify();
  650. TriggerRefresh();
  651. }
  652. }
  653. }
  654. public override bool OverrideCatalogNotify
  655. {
  656. get { throw new InvalidOperationException(); }
  657. set { throw new InvalidOperationException(); }
  658. }
  659. public virtual bool OverrideCatalogPrimaryNameServers
  660. {
  661. get { return _overrideCatalogPrimaryNameServers; }
  662. set { _overrideCatalogPrimaryNameServers = value; }
  663. }
  664. public override AuthZoneNotify Notify
  665. {
  666. get { return base.Notify; }
  667. set
  668. {
  669. switch (value)
  670. {
  671. case AuthZoneNotify.SeparateNameServersForCatalogAndMemberZones:
  672. throw new ArgumentException("The Notify option is invalid for " + GetZoneTypeName() + " zones: " + value.ToString(), nameof(Notify));
  673. }
  674. base.Notify = value;
  675. }
  676. }
  677. public override AuthZoneUpdate Update
  678. {
  679. get { return base.Update; }
  680. set
  681. {
  682. switch (value)
  683. {
  684. case AuthZoneUpdate.AllowOnlyZoneNameServers:
  685. case AuthZoneUpdate.AllowZoneNameServersAndUseSpecifiedNetworkACL:
  686. throw new ArgumentException("The Dynamic Updates option is invalid for Secondary zones: " + value.ToString(), nameof(Update));
  687. }
  688. base.Update = value;
  689. }
  690. }
  691. public virtual IReadOnlyList<NameServerAddress> PrimaryNameServerAddresses
  692. {
  693. get { return _primaryNameServerAddresses; }
  694. set
  695. {
  696. if ((value is null) || (value.Count == 0))
  697. _primaryNameServerAddresses = null;
  698. else if (value.Count > byte.MaxValue)
  699. throw new ArgumentOutOfRangeException(nameof(PrimaryNameServerAddresses), "Name server addresses cannot have more than 255 entries.");
  700. else
  701. _primaryNameServerAddresses = value;
  702. }
  703. }
  704. public DnsTransportProtocol PrimaryZoneTransferProtocol
  705. {
  706. get { return _primaryZoneTransferProtocol; }
  707. set
  708. {
  709. switch (value)
  710. {
  711. case DnsTransportProtocol.Tcp:
  712. case DnsTransportProtocol.Tls:
  713. case DnsTransportProtocol.Quic:
  714. _primaryZoneTransferProtocol = value;
  715. break;
  716. default:
  717. throw new NotSupportedException("Zone transfer protocol is not supported: XFR-over-" + value.ToString().ToUpper());
  718. }
  719. }
  720. }
  721. public string PrimaryZoneTransferTsigKeyName
  722. {
  723. get { return _primaryZoneTransferTsigKeyName; }
  724. set
  725. {
  726. if (value is null)
  727. _primaryZoneTransferTsigKeyName = string.Empty;
  728. else
  729. _primaryZoneTransferTsigKeyName = value;
  730. }
  731. }
  732. public DateTime Expiry
  733. { get { return _expiry; } }
  734. public bool IsExpired
  735. { get { return _isExpired; } }
  736. public virtual bool ValidateZone
  737. {
  738. get { return _validateZone; }
  739. set { _validateZone = value; }
  740. }
  741. public bool ValidationFailed
  742. { get { return _validationFailed; } }
  743. public override bool IsActive
  744. {
  745. get { return !Disabled && !_isExpired && !_validationFailed; }
  746. }
  747. #endregion
  748. class DnsClientInternal : DnsClient, IDnsCache
  749. {
  750. #region variables
  751. readonly DnsServer _dnsServer;
  752. #endregion
  753. #region constructor
  754. public DnsClientInternal(DnsServer dnsServer)
  755. {
  756. _dnsServer = dnsServer;
  757. Cache = this; //set dummy cache to avoid DnsCache from overwriting DnsResourceRecord.Tag properties which currently has GenericRecordInfo objects
  758. }
  759. #endregion
  760. #region protected
  761. protected override Task<DnsDatagram> InternalResolveAsync(DnsDatagram request, Func<DnsDatagram, CancellationToken, Task<DnsDatagram>> getValidatedResponseAsync = null, bool doNotReorderNameServers = false, CancellationToken cancellationToken = default)
  762. {
  763. return _dnsServer.DirectQueryAsync(request, Timeout);
  764. }
  765. #endregion
  766. #region public
  767. public DnsDatagram QueryClosestDelegation(DnsDatagram request)
  768. {
  769. return null; //no cache available
  770. }
  771. public DnsDatagram Query(DnsDatagram request, bool serveStale = false, bool findClosestNameServers = false, bool resetExpiry = false)
  772. {
  773. return null; //no cache available
  774. }
  775. public void CacheResponse(DnsDatagram response, bool isDnssecBadCache = false, string zoneCut = null)
  776. {
  777. //do nothing to prevent caching
  778. }
  779. #endregion
  780. }
  781. }
  782. }