123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- /*
- Technitium DNS Server
- Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
- */
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using System.Threading.Tasks;
- using TechnitiumLibrary.Net.Dns;
- using TechnitiumLibrary.Net.Dns.ResourceRecords;
- namespace DnsServerCore.Dns.Zones
- {
- class PrimaryZone : AuthZone
- {
- #region variables
- readonly DnsServer _dnsServer;
- readonly bool _internal;
- readonly Timer _notifyTimer;
- const int NOTIFY_TIMER_INTERVAL = 10000;
- readonly List<NameServerAddress> _notifyList = new List<NameServerAddress>();
- const int NOTIFY_TIMEOUT = 10000;
- const int NOTIFY_RETRIES = 5;
- #endregion
- #region constructor
- public PrimaryZone(DnsServer dnsServer, AuthZoneInfo zoneInfo)
- : base(zoneInfo.Name)
- {
- _dnsServer = dnsServer;
- _disabled = zoneInfo.Disabled;
- _notifyTimer = new Timer(NotifyTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
- }
- public PrimaryZone(DnsServer dnsServer, string name, string primaryNameServer, bool @internal)
- : base(name)
- {
- _dnsServer = dnsServer;
- _internal = @internal;
- DnsSOARecord soa = new DnsSOARecord(primaryNameServer, "hostadmin." + name, 1, 14400, 3600, 604800, 900);
- _entries[DnsResourceRecordType.SOA] = new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, soa.Refresh, soa) };
- _entries[DnsResourceRecordType.NS] = new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.NS, DnsClass.IN, soa.Refresh, new DnsNSRecord(soa.PrimaryNameServer)) };
- _notifyTimer = new Timer(NotifyTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
- }
- internal PrimaryZone(DnsServer dnsServer, string name, DnsSOARecord soa, DnsNSRecord ns)
- : base(name)
- {
- _dnsServer = dnsServer;
- _internal = true;
- _entries[DnsResourceRecordType.SOA] = new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.SOA, DnsClass.IN, soa.Refresh, soa) };
- _entries[DnsResourceRecordType.NS] = new DnsResourceRecord[] { new DnsResourceRecord(_name, DnsResourceRecordType.NS, DnsClass.IN, soa.Refresh, ns) };
- _notifyTimer = new Timer(NotifyTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
- }
- #endregion
- #region IDisposable
- bool _disposed;
- protected override void Dispose(bool disposing)
- {
- if (_disposed)
- return;
- if (disposing)
- {
- if (_notifyTimer != null)
- _notifyTimer.Dispose();
- }
- _disposed = true;
- }
- #endregion
- #region private
- private async void NotifyTimerCallback(object state)
- {
- try
- {
- IReadOnlyList<NameServerAddress> secondaryNameServers = await GetSecondaryNameServerAddressesAsync(_dnsServer);
- foreach (NameServerAddress secondaryNameServer in secondaryNameServers)
- NotifyNameServer(secondaryNameServer);
- }
- catch (Exception ex)
- {
- LogManager log = _dnsServer.LogManager;
- if (log != null)
- log.Write(ex);
- }
- }
- private void NotifyNameServer(NameServerAddress nameServer)
- {
- //use notify list to prevent multiple threads from notifying the same name server
- lock (_notifyList)
- {
- if (_notifyList.Contains(nameServer))
- return; //already notifying the name server in another thread
- _notifyList.Add(nameServer);
- }
- _ = NotifyNameServerAsync(nameServer);
- }
- private async Task NotifyNameServerAsync(NameServerAddress nameServer)
- {
- try
- {
- DnsClient client = new DnsClient(nameServer);
- client.Timeout = NOTIFY_TIMEOUT;
- client.Retries = NOTIFY_RETRIES;
- 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]);
- DnsDatagram response = await client.ResolveAsync(notifyRequest);
- switch (response.RCODE)
- {
- case DnsResponseCode.NoError:
- case DnsResponseCode.NotImplemented:
- {
- //transaction complete
- LogManager log = _dnsServer.LogManager;
- if (log != null)
- log.Write("DNS Server successfully notified name server for '" + _name + "' zone changes: " + nameServer.ToString());
- }
- break;
- default:
- {
- //transaction failed
- LogManager log = _dnsServer.LogManager;
- if (log != null)
- log.Write("DNS Server received RCODE=" + response.RCODE.ToString() + " from name server for '" + _name + "' zone notification: " + nameServer.ToString());
- }
- break;
- }
- }
- catch (Exception ex)
- {
- LogManager log = _dnsServer.LogManager;
- if (log != null)
- {
- log.Write("DNS Server failed to notify name server for '" + _name + "' zone changes: " + nameServer.ToString());
- log.Write(ex);
- }
- }
- finally
- {
- lock (_notifyList)
- {
- _notifyList.Remove(nameServer);
- }
- }
- }
- #endregion
- #region public
- public void IncrementSoaSerial()
- {
- DnsResourceRecord record = _entries[DnsResourceRecordType.SOA][0];
- DnsSOARecord soa = record.RDATA as DnsSOARecord;
- uint serial = soa.Serial;
- if (serial < uint.MaxValue)
- serial++;
- else
- serial = 0;
- DnsResourceRecord newRecord = new DnsResourceRecord(record.Name, record.Type, record.Class, record.TtlValue, new DnsSOARecord(soa.PrimaryNameServer, soa.ResponsiblePerson, serial, soa.Refresh, soa.Retry, soa.Expire, soa.Minimum)) { Tag = record.Tag };
- _entries[DnsResourceRecordType.SOA] = new DnsResourceRecord[] { newRecord };
- }
- public void NotifyNameServers()
- {
- _notifyTimer.Change(NOTIFY_TIMER_INTERVAL, Timeout.Infinite);
- }
- public override void SetRecords(DnsResourceRecordType type, IReadOnlyList<DnsResourceRecord> records)
- {
- switch (type)
- {
- case DnsResourceRecordType.CNAME:
- throw new InvalidOperationException("Cannot set CNAME record to zone root.");
- case DnsResourceRecordType.SOA:
- if ((records.Count != 1) || !records[0].Name.Equals(_name, StringComparison.OrdinalIgnoreCase))
- throw new InvalidOperationException("Invalid SOA record.");
- //remove any resource record info
- records[0].Tag = null;
- break;
- }
- base.SetRecords(type, records);
- IncrementSoaSerial();
- NotifyNameServers();
- }
- public override void AddRecord(DnsResourceRecord record)
- {
- base.AddRecord(record);
- IncrementSoaSerial();
- NotifyNameServers();
- }
- public override bool DeleteRecords(DnsResourceRecordType type)
- {
- if (type == DnsResourceRecordType.SOA)
- throw new InvalidOperationException("Cannot delete SOA record.");
- if (base.DeleteRecords(type))
- {
- IncrementSoaSerial();
- NotifyNameServers();
- return true;
- }
- return false;
- }
- public override bool DeleteRecord(DnsResourceRecordType type, DnsResourceRecordData record)
- {
- if (type == DnsResourceRecordType.SOA)
- throw new InvalidOperationException("Cannot delete SOA record.");
- if (base.DeleteRecord(type, record))
- {
- IncrementSoaSerial();
- NotifyNameServers();
- return true;
- }
- return false;
- }
- #endregion
- #region properties
- public bool Internal
- { get { return _internal; } }
- public override bool Disabled
- {
- get { return _disabled; }
- set
- {
- if (_disabled != value)
- {
- _disabled = value;
- if (_disabled)
- _notifyTimer.Change(Timeout.Infinite, Timeout.Infinite);
- else
- NotifyNameServers();
- }
- }
- }
- #endregion
- }
- }
|