ApexZone.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using DnsServerCore.Dns.ResourceRecords;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Net;
  19. using System.Threading;
  20. using System.Threading.Tasks;
  21. using TechnitiumLibrary.Net.Dns;
  22. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  23. namespace DnsServerCore.Dns.Zones
  24. {
  25. public enum AuthZoneTransfer : byte
  26. {
  27. Deny = 0,
  28. Allow = 1,
  29. AllowOnlyZoneNameServers = 2,
  30. AllowOnlySpecifiedNameServers = 3,
  31. AllowBothZoneAndSpecifiedNameServers = 4
  32. }
  33. public enum AuthZoneNotify : byte
  34. {
  35. None = 0,
  36. ZoneNameServers = 1,
  37. SpecifiedNameServers = 2,
  38. BothZoneAndSpecifiedNameServers = 3
  39. }
  40. public enum AuthZoneUpdate : byte
  41. {
  42. Deny = 0,
  43. Allow = 1,
  44. AllowOnlyZoneNameServers = 2,
  45. AllowOnlySpecifiedIpAddresses = 3,
  46. AllowBothZoneNameServersAndSpecifiedIpAddresses = 4
  47. }
  48. abstract class ApexZone : AuthZone, IDisposable
  49. {
  50. #region variables
  51. protected AuthZoneTransfer _zoneTransfer;
  52. protected IReadOnlyCollection<IPAddress> _zoneTransferNameServers;
  53. protected AuthZoneNotify _notify;
  54. protected IReadOnlyCollection<IPAddress> _notifyNameServers;
  55. protected AuthZoneUpdate _update;
  56. protected IReadOnlyCollection<IPAddress> _updateIpAddresses;
  57. protected List<DnsResourceRecord> _zoneHistory; //for IXFR support
  58. protected IReadOnlyDictionary<string, object> _zoneTransferTsigKeyNames;
  59. protected IReadOnlyDictionary<string, IReadOnlyDictionary<string, IReadOnlyList<DnsResourceRecordType>>> _updateSecurityPolicies;
  60. protected AuthZoneDnssecStatus _dnssecStatus;
  61. Timer _notifyTimer;
  62. bool _notifyTimerTriggered;
  63. const int NOTIFY_TIMER_INTERVAL = 10000;
  64. List<string> _notifyList;
  65. List<string> _notifyFailed;
  66. const int NOTIFY_TIMEOUT = 10000;
  67. const int NOTIFY_RETRIES = 5;
  68. protected bool _syncFailed;
  69. #endregion
  70. #region constructor
  71. protected ApexZone(AuthZoneInfo zoneInfo)
  72. : base(zoneInfo)
  73. {
  74. _zoneTransfer = zoneInfo.ZoneTransfer;
  75. _zoneTransferNameServers = zoneInfo.ZoneTransferNameServers;
  76. _notify = zoneInfo.Notify;
  77. _notifyNameServers = zoneInfo.NotifyNameServers;
  78. _update = zoneInfo.Update;
  79. _updateIpAddresses = zoneInfo.UpdateIpAddresses;
  80. if (zoneInfo.ZoneHistory is null)
  81. _zoneHistory = new List<DnsResourceRecord>();
  82. else
  83. _zoneHistory = new List<DnsResourceRecord>(zoneInfo.ZoneHistory);
  84. _zoneTransferTsigKeyNames = zoneInfo.ZoneTransferTsigKeyNames;
  85. _updateSecurityPolicies = zoneInfo.UpdateSecurityPolicies;
  86. }
  87. protected ApexZone(string name)
  88. : base(name)
  89. {
  90. _zoneHistory = new List<DnsResourceRecord>();
  91. }
  92. #endregion
  93. #region IDisposable
  94. bool _disposed;
  95. protected virtual void Dispose(bool disposing)
  96. {
  97. if (_disposed)
  98. return;
  99. if (disposing)
  100. {
  101. if (_notifyTimer is not null)
  102. _notifyTimer.Dispose();
  103. }
  104. _disposed = true;
  105. }
  106. public void Dispose()
  107. {
  108. Dispose(true);
  109. }
  110. #endregion
  111. #region protected
  112. protected void CleanupHistory(List<DnsResourceRecord> history)
  113. {
  114. DnsSOARecordData soa = _entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData;
  115. DateTime expiry = DateTime.UtcNow.AddSeconds(-soa.Expire);
  116. int index = 0;
  117. while (index < history.Count)
  118. {
  119. //check difference sequence
  120. if (history[index].GetAuthRecordInfo().DeletedOn > expiry)
  121. break; //found record to keep
  122. //skip to next difference sequence
  123. index++;
  124. int soaCount = 1;
  125. while (index < history.Count)
  126. {
  127. if (history[index].Type == DnsResourceRecordType.SOA)
  128. {
  129. soaCount++;
  130. if (soaCount == 3)
  131. break;
  132. }
  133. index++;
  134. }
  135. }
  136. if (index == history.Count)
  137. {
  138. //delete entire history
  139. history.Clear();
  140. return;
  141. }
  142. //remove expired records
  143. history.RemoveRange(0, index);
  144. }
  145. protected void InitNotify(DnsServer dnsServer)
  146. {
  147. _notifyTimer = new Timer(NotifyTimerCallback, dnsServer, Timeout.Infinite, Timeout.Infinite);
  148. _notifyList = new List<string>();
  149. _notifyFailed = new List<string>();
  150. }
  151. protected void DisableNotifyTimer()
  152. {
  153. if (_notifyTimer is not null)
  154. _notifyTimer.Change(Timeout.Infinite, Timeout.Infinite);
  155. }
  156. #endregion
  157. #region private
  158. private async void NotifyTimerCallback(object state)
  159. {
  160. DnsServer dnsServer = state as DnsServer;
  161. async Task NotifyZoneNameServers(List<string> existingNameServers)
  162. {
  163. string primaryNameServer = (_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).PrimaryNameServer;
  164. IReadOnlyList<DnsResourceRecord> nsRecords = GetRecords(DnsResourceRecordType.NS); //stub zone has no authority so cant use QueryRecords
  165. //notify all secondary name servers
  166. foreach (DnsResourceRecord nsRecord in nsRecords)
  167. {
  168. if (nsRecord.GetAuthRecordInfo().Disabled)
  169. continue;
  170. string nameServerHost = (nsRecord.RDATA as DnsNSRecordData).NameServer;
  171. if (primaryNameServer.Equals(nameServerHost, StringComparison.OrdinalIgnoreCase))
  172. continue; //skip primary name server
  173. existingNameServers.Add(nameServerHost);
  174. List<NameServerAddress> nameServers = new List<NameServerAddress>(2);
  175. await ResolveNameServerAddressesAsync(dnsServer, nsRecord, nameServers);
  176. if (nameServers.Count > 0)
  177. {
  178. _ = NotifyNameServerAsync(dnsServer, nameServerHost, nameServers);
  179. }
  180. else
  181. {
  182. lock (_notifyFailed)
  183. {
  184. if (!_notifyFailed.Contains(nameServerHost))
  185. _notifyFailed.Add(nameServerHost);
  186. }
  187. LogManager log = dnsServer.LogManager;
  188. if (log != null)
  189. log.Write("DNS Server failed to notify name server '" + nameServerHost + "' due to failure in resolving its IP address for zone: " + (_name == "" ? "<root>" : _name));
  190. }
  191. }
  192. }
  193. void NotifySpecifiedNameServers(List<string> existingNameServers)
  194. {
  195. IReadOnlyCollection<IPAddress> specifiedNameServers = _notifyNameServers;
  196. if (specifiedNameServers is not null)
  197. {
  198. foreach (IPAddress specifiedNameServer in specifiedNameServers)
  199. {
  200. string nameServerHost = specifiedNameServer.ToString();
  201. existingNameServers.Add(nameServerHost);
  202. _ = NotifyNameServerAsync(dnsServer, nameServerHost, new NameServerAddress[] { new NameServerAddress(specifiedNameServer) });
  203. }
  204. }
  205. }
  206. try
  207. {
  208. List<string> existingNameServers = new List<string>();
  209. switch (_notify)
  210. {
  211. case AuthZoneNotify.ZoneNameServers:
  212. await NotifyZoneNameServers(existingNameServers);
  213. break;
  214. case AuthZoneNotify.SpecifiedNameServers:
  215. NotifySpecifiedNameServers(existingNameServers);
  216. break;
  217. case AuthZoneNotify.BothZoneAndSpecifiedNameServers:
  218. await NotifyZoneNameServers(existingNameServers);
  219. NotifySpecifiedNameServers(existingNameServers);
  220. break;
  221. }
  222. //remove non-existent name servers from notify failed list
  223. lock (_notifyFailed)
  224. {
  225. List<string> toRemove = new List<string>();
  226. foreach (string failedNameServer in _notifyFailed)
  227. {
  228. bool found = false;
  229. foreach (string existingNameServer in existingNameServers)
  230. {
  231. if (failedNameServer.Equals(existingNameServer))
  232. {
  233. found = true;
  234. break;
  235. }
  236. }
  237. if (!found)
  238. toRemove.Add(failedNameServer);
  239. }
  240. if (toRemove.Count > 0)
  241. {
  242. foreach (string failedNameServer in toRemove)
  243. _notifyFailed.Remove(failedNameServer);
  244. }
  245. }
  246. }
  247. catch (Exception ex)
  248. {
  249. LogManager log = dnsServer.LogManager;
  250. if (log != null)
  251. log.Write(ex);
  252. }
  253. finally
  254. {
  255. _notifyTimerTriggered = false;
  256. }
  257. }
  258. private async Task NotifyNameServerAsync(DnsServer dnsServer, string nameServerHost, IReadOnlyList<NameServerAddress> nameServers)
  259. {
  260. //use notify list to prevent multiple threads from notifying the same name server
  261. lock (_notifyList)
  262. {
  263. if (_notifyList.Contains(nameServerHost))
  264. return; //already notifying the name server in another thread
  265. _notifyList.Add(nameServerHost);
  266. }
  267. try
  268. {
  269. DnsClient client = new DnsClient(nameServers);
  270. client.Proxy = dnsServer.Proxy;
  271. client.Timeout = NOTIFY_TIMEOUT;
  272. client.Retries = NOTIFY_RETRIES;
  273. DnsDatagram notifyRequest = new DnsDatagram(0, false, DnsOpcode.Notify, true, false, false, false, false, false, DnsResponseCode.NoError, new DnsQuestionRecord[] { new DnsQuestionRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN) }, _entries[DnsResourceRecordType.SOA]);
  274. DnsDatagram response = await client.ResolveAsync(notifyRequest);
  275. switch (response.RCODE)
  276. {
  277. case DnsResponseCode.NoError:
  278. case DnsResponseCode.NotImplemented:
  279. {
  280. //transaction complete
  281. lock (_notifyFailed)
  282. {
  283. _notifyFailed.Remove(nameServerHost);
  284. }
  285. LogManager log = dnsServer.LogManager;
  286. if (log is not null)
  287. log.Write("DNS Server successfully notified name server '" + nameServerHost + "' for zone: " + (_name == "" ? "<root>" : _name));
  288. }
  289. break;
  290. default:
  291. {
  292. //transaction failed
  293. lock (_notifyFailed)
  294. {
  295. if (!_notifyFailed.Contains(nameServerHost))
  296. _notifyFailed.Add(nameServerHost);
  297. }
  298. LogManager log = dnsServer.LogManager;
  299. if (log is not null)
  300. log.Write("DNS Server failed to notify name server '" + nameServerHost + "' (RCODE=" + response.RCODE.ToString() + ") for zone : " + (_name == "" ? "<root>" : _name));
  301. }
  302. break;
  303. }
  304. }
  305. catch (Exception ex)
  306. {
  307. lock (_notifyFailed)
  308. {
  309. if (!_notifyFailed.Contains(nameServerHost))
  310. _notifyFailed.Add(nameServerHost);
  311. }
  312. dnsServer.LogManager?.Write("DNS Server failed to notify name server '" + nameServerHost + "' for zone: " + (_name == "" ? "<root>" : _name) + "\r\n" + ex.ToString());
  313. }
  314. finally
  315. {
  316. lock (_notifyList)
  317. {
  318. _notifyList.Remove(nameServerHost);
  319. }
  320. }
  321. }
  322. private static async Task ResolveNameServerAddressesAsync(DnsServer dnsServer, string nsDomain, int port, DnsTransportProtocol protocol, List<NameServerAddress> outNameServers)
  323. {
  324. try
  325. {
  326. DnsDatagram response = await dnsServer.DirectQueryAsync(new DnsQuestionRecord(nsDomain, DnsResourceRecordType.A, DnsClass.IN));
  327. if (response.Answer.Count > 0)
  328. {
  329. IReadOnlyList<IPAddress> addresses = DnsClient.ParseResponseA(response);
  330. foreach (IPAddress address in addresses)
  331. outNameServers.Add(new NameServerAddress(nsDomain, new IPEndPoint(address, port), protocol));
  332. }
  333. }
  334. catch
  335. { }
  336. if (dnsServer.PreferIPv6)
  337. {
  338. try
  339. {
  340. DnsDatagram response = await dnsServer.DirectQueryAsync(new DnsQuestionRecord(nsDomain, DnsResourceRecordType.AAAA, DnsClass.IN));
  341. if (response.Answer.Count > 0)
  342. {
  343. IReadOnlyList<IPAddress> addresses = DnsClient.ParseResponseAAAA(response);
  344. foreach (IPAddress address in addresses)
  345. outNameServers.Add(new NameServerAddress(nsDomain, new IPEndPoint(address, port), protocol));
  346. }
  347. }
  348. catch
  349. { }
  350. }
  351. }
  352. private static Task ResolveNameServerAddressesAsync(DnsServer dnsServer, DnsResourceRecord nsRecord, List<NameServerAddress> outNameServers)
  353. {
  354. string nsDomain = (nsRecord.RDATA as DnsNSRecordData).NameServer;
  355. IReadOnlyList<DnsResourceRecord> glueRecords = nsRecord.GetAuthRecordInfo().GlueRecords;
  356. if (glueRecords is not null)
  357. {
  358. foreach (DnsResourceRecord glueRecord in glueRecords)
  359. {
  360. switch (glueRecord.Type)
  361. {
  362. case DnsResourceRecordType.A:
  363. outNameServers.Add(new NameServerAddress(nsDomain, (glueRecord.RDATA as DnsARecordData).Address));
  364. break;
  365. case DnsResourceRecordType.AAAA:
  366. if (dnsServer.PreferIPv6)
  367. outNameServers.Add(new NameServerAddress(nsDomain, (glueRecord.RDATA as DnsAAAARecordData).Address));
  368. break;
  369. }
  370. }
  371. return Task.CompletedTask;
  372. }
  373. else
  374. {
  375. return ResolveNameServerAddressesAsync(dnsServer, nsDomain, 53, DnsTransportProtocol.Udp, outNameServers);
  376. }
  377. }
  378. internal virtual void UpdateDnssecStatus()
  379. {
  380. if (!_entries.ContainsKey(DnsResourceRecordType.DNSKEY))
  381. _dnssecStatus = AuthZoneDnssecStatus.Unsigned;
  382. else if (_entries.ContainsKey(DnsResourceRecordType.NSEC3PARAM))
  383. _dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC3;
  384. else
  385. _dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC;
  386. }
  387. #endregion
  388. #region public
  389. public IReadOnlyList<DnsResourceRecord> GetZoneHistory()
  390. {
  391. lock (_zoneHistory)
  392. {
  393. return _zoneHistory.ToArray();
  394. }
  395. }
  396. public void TriggerNotify()
  397. {
  398. if (_disabled)
  399. return;
  400. if (_notify == AuthZoneNotify.None)
  401. {
  402. if (_notifyFailed is not null)
  403. {
  404. lock (_notifyFailed)
  405. {
  406. _notifyFailed.Clear();
  407. }
  408. }
  409. return;
  410. }
  411. if (_notifyTimerTriggered)
  412. return;
  413. if (_disposed)
  414. return;
  415. if (_notifyTimer is null)
  416. return;
  417. _notifyTimer.Change(NOTIFY_TIMER_INTERVAL, Timeout.Infinite);
  418. _notifyTimerTriggered = true;
  419. }
  420. public async Task<IReadOnlyList<NameServerAddress>> GetPrimaryNameServerAddressesAsync(DnsServer dnsServer)
  421. {
  422. DnsResourceRecord soaRecord = _entries[DnsResourceRecordType.SOA][0];
  423. IReadOnlyList<NameServerAddress> primaryNameServers = soaRecord.GetAuthRecordInfo().PrimaryNameServers;
  424. if (primaryNameServers is not null)
  425. {
  426. List<NameServerAddress> resolvedNameServers = new List<NameServerAddress>(primaryNameServers.Count * 2);
  427. foreach (NameServerAddress nameServer in primaryNameServers)
  428. {
  429. if (nameServer.IsIPEndPointStale)
  430. await ResolveNameServerAddressesAsync(dnsServer, nameServer.Host, nameServer.Port, nameServer.Protocol, resolvedNameServers);
  431. else
  432. resolvedNameServers.Add(nameServer);
  433. }
  434. return resolvedNameServers;
  435. }
  436. string primaryNameServer = (soaRecord.RDATA as DnsSOARecordData).PrimaryNameServer;
  437. IReadOnlyList<DnsResourceRecord> nsRecords = GetRecords(DnsResourceRecordType.NS); //stub zone has no authority so cant use QueryRecords
  438. List<NameServerAddress> nameServers = new List<NameServerAddress>(nsRecords.Count * 2);
  439. foreach (DnsResourceRecord nsRecord in nsRecords)
  440. {
  441. if (nsRecord.GetAuthRecordInfo().Disabled)
  442. continue;
  443. if (primaryNameServer.Equals((nsRecord.RDATA as DnsNSRecordData).NameServer, StringComparison.OrdinalIgnoreCase))
  444. {
  445. //found primary NS
  446. await ResolveNameServerAddressesAsync(dnsServer, nsRecord, nameServers);
  447. break;
  448. }
  449. }
  450. if (nameServers.Count < 1)
  451. await ResolveNameServerAddressesAsync(dnsServer, primaryNameServer, 53, DnsTransportProtocol.Udp, nameServers);
  452. return nameServers;
  453. }
  454. public async Task<IReadOnlyList<NameServerAddress>> GetSecondaryNameServerAddressesAsync(DnsServer dnsServer)
  455. {
  456. string primaryNameServer = (_entries[DnsResourceRecordType.SOA][0].RDATA as DnsSOARecordData).PrimaryNameServer;
  457. IReadOnlyList<DnsResourceRecord> nsRecords = GetRecords(DnsResourceRecordType.NS); //stub zone has no authority so cant use QueryRecords
  458. List<NameServerAddress> nameServers = new List<NameServerAddress>(nsRecords.Count * 2);
  459. foreach (DnsResourceRecord nsRecord in nsRecords)
  460. {
  461. if (nsRecord.GetAuthRecordInfo().Disabled)
  462. continue;
  463. if (primaryNameServer.Equals((nsRecord.RDATA as DnsNSRecordData).NameServer, StringComparison.OrdinalIgnoreCase))
  464. continue; //skip primary name server
  465. await ResolveNameServerAddressesAsync(dnsServer, nsRecord, nameServers);
  466. }
  467. return nameServers;
  468. }
  469. #endregion
  470. #region properties
  471. public virtual AuthZoneTransfer ZoneTransfer
  472. {
  473. get { return _zoneTransfer; }
  474. set { _zoneTransfer = value; }
  475. }
  476. public IReadOnlyCollection<IPAddress> ZoneTransferNameServers
  477. {
  478. get { return _zoneTransferNameServers; }
  479. set
  480. {
  481. if ((value is not null) && (value.Count > byte.MaxValue))
  482. throw new ArgumentOutOfRangeException(nameof(ZoneTransferNameServers), "Name server addresses cannot be more than 255.");
  483. _zoneTransferNameServers = value;
  484. }
  485. }
  486. public virtual AuthZoneNotify Notify
  487. {
  488. get { return _notify; }
  489. set
  490. {
  491. if (_notify != value)
  492. {
  493. _notify = value;
  494. lock (_notifyFailed)
  495. {
  496. _notifyFailed.Clear();
  497. }
  498. }
  499. }
  500. }
  501. public IReadOnlyCollection<IPAddress> NotifyNameServers
  502. {
  503. get { return _notifyNameServers; }
  504. set
  505. {
  506. if ((value is not null) && (value.Count > byte.MaxValue))
  507. throw new ArgumentOutOfRangeException(nameof(NotifyNameServers), "Name server addresses cannot be more than 255.");
  508. if (_notifyNameServers != value)
  509. {
  510. _notifyNameServers = value;
  511. lock (_notifyFailed)
  512. {
  513. _notifyFailed.Clear();
  514. }
  515. }
  516. }
  517. }
  518. public virtual AuthZoneUpdate Update
  519. {
  520. get { return _update; }
  521. set { _update = value; }
  522. }
  523. public IReadOnlyCollection<IPAddress> UpdateIpAddresses
  524. {
  525. get { return _updateIpAddresses; }
  526. set
  527. {
  528. if ((value is not null) && (value.Count > byte.MaxValue))
  529. throw new ArgumentOutOfRangeException(nameof(ZoneTransferNameServers), "IP addresses cannot be more than 255.");
  530. _updateIpAddresses = value;
  531. }
  532. }
  533. public IReadOnlyDictionary<string, object> ZoneTransferTsigKeyNames
  534. {
  535. get { return _zoneTransferTsigKeyNames; }
  536. set { _zoneTransferTsigKeyNames = value; }
  537. }
  538. public IReadOnlyDictionary<string, IReadOnlyDictionary<string, IReadOnlyList<DnsResourceRecordType>>> UpdateSecurityPolicies
  539. {
  540. get { return _updateSecurityPolicies; }
  541. set { _updateSecurityPolicies = value; }
  542. }
  543. public bool NotifyFailed
  544. {
  545. get
  546. {
  547. if (_notifyFailed is null)
  548. return false;
  549. lock (_notifyFailed)
  550. {
  551. return _notifyFailed.Count > 0;
  552. }
  553. }
  554. }
  555. public bool SyncFailed
  556. { get { return _syncFailed; } }
  557. public AuthZoneDnssecStatus DnssecStatus
  558. { get { return _dnssecStatus; } }
  559. #endregion
  560. }
  561. }