BlockListZoneManager.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  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 System;
  16. using System.Collections.Generic;
  17. using System.IO;
  18. using System.Net;
  19. using System.Net.Http;
  20. using System.Security.Cryptography;
  21. using System.Text;
  22. using System.Threading.Tasks;
  23. using TechnitiumLibrary.Net;
  24. using TechnitiumLibrary.Net.Dns;
  25. using TechnitiumLibrary.Net.Dns.EDnsOptions;
  26. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  27. using TechnitiumLibrary.Net.Http.Client;
  28. namespace DnsServerCore.Dns.ZoneManagers
  29. {
  30. public sealed class BlockListZoneManager
  31. {
  32. #region variables
  33. readonly static char[] _popWordSeperator = new char[] { ' ', '\t' };
  34. readonly DnsServer _dnsServer;
  35. readonly string _localCacheFolder;
  36. readonly List<Uri> _allowListUrls = new List<Uri>();
  37. readonly List<Uri> _blockListUrls = new List<Uri>();
  38. Dictionary<string, object> _allowListZone = new Dictionary<string, object>();
  39. Dictionary<string, List<Uri>> _blockListZone = new Dictionary<string, List<Uri>>();
  40. DnsSOARecordData _soaRecord;
  41. DnsNSRecordData _nsRecord;
  42. readonly IReadOnlyCollection<DnsARecordData> _aRecords = new DnsARecordData[] { new DnsARecordData(IPAddress.Any) };
  43. readonly IReadOnlyCollection<DnsAAAARecordData> _aaaaRecords = new DnsAAAARecordData[] { new DnsAAAARecordData(IPAddress.IPv6Any) };
  44. #endregion
  45. #region constructor
  46. public BlockListZoneManager(DnsServer dnsServer)
  47. {
  48. _dnsServer = dnsServer;
  49. _localCacheFolder = Path.Combine(_dnsServer.ConfigFolder, "blocklists");
  50. if (!Directory.Exists(_localCacheFolder))
  51. Directory.CreateDirectory(_localCacheFolder);
  52. UpdateServerDomain();
  53. }
  54. #endregion
  55. #region private
  56. internal void UpdateServerDomain()
  57. {
  58. _soaRecord = new DnsSOARecordData(_dnsServer.ServerDomain, _dnsServer.ResponsiblePerson.Address, 1, 14400, 3600, 604800, _dnsServer.BlockingAnswerTtl);
  59. _nsRecord = new DnsNSRecordData(_dnsServer.ServerDomain);
  60. }
  61. private string GetBlockListFilePath(Uri blockListUrl)
  62. {
  63. return Path.Combine(_localCacheFolder, Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(blockListUrl.AbsoluteUri))).ToLowerInvariant());
  64. }
  65. private static string PopWord(ref string line)
  66. {
  67. if (line.Length == 0)
  68. return line;
  69. line = line.TrimStart(_popWordSeperator);
  70. int i = line.IndexOfAny(_popWordSeperator);
  71. string word;
  72. if (i < 0)
  73. {
  74. word = line;
  75. line = "";
  76. }
  77. else
  78. {
  79. word = line.Substring(0, i);
  80. line = line.Substring(i + 1);
  81. }
  82. return word;
  83. }
  84. private Queue<string> ReadListFile(Uri listUrl, bool isAllowList, out Queue<string> exceptionDomains)
  85. {
  86. Queue<string> domains = new Queue<string>();
  87. exceptionDomains = new Queue<string>();
  88. try
  89. {
  90. _dnsServer.LogManager?.Write("DNS Server is reading " + (isAllowList ? "allow" : "block") + " list from: " + listUrl.AbsoluteUri);
  91. string listFilePath = GetBlockListFilePath(listUrl);
  92. if (listUrl.IsFile)
  93. {
  94. if (!File.Exists(listFilePath) || (File.GetLastWriteTimeUtc(listUrl.LocalPath) > File.GetLastWriteTimeUtc(listFilePath)))
  95. {
  96. File.Copy(listUrl.LocalPath, listFilePath, true);
  97. _dnsServer.LogManager?.Write("DNS Server successfully downloaded " + (isAllowList ? "allow" : "block") + " list (" + WebUtilities.GetFormattedSize(new FileInfo(listFilePath).Length) + "): " + listUrl.AbsoluteUri);
  98. }
  99. }
  100. using (FileStream fS = new FileStream(listFilePath, FileMode.Open, FileAccess.Read))
  101. {
  102. //parse hosts file and populate block zone
  103. StreamReader sR = new StreamReader(fS, true);
  104. char[] trimSeperator = new char[] { ' ', '\t', '*', '.' };
  105. string line;
  106. string firstWord;
  107. string secondWord;
  108. string hostname;
  109. string domain;
  110. string options;
  111. int i;
  112. while (true)
  113. {
  114. line = sR.ReadLine();
  115. if (line is null)
  116. break; //eof
  117. line = line.TrimStart(trimSeperator);
  118. if (line.Length == 0)
  119. continue; //skip empty line
  120. if (line.StartsWith('#') || line.StartsWith('!'))
  121. continue; //skip comment line
  122. if (line.StartsWith("||"))
  123. {
  124. //adblock format
  125. i = line.IndexOf('^');
  126. if (i > -1)
  127. {
  128. domain = line.Substring(2, i - 2);
  129. options = line.Substring(i + 1);
  130. if (((options.Length == 0) || (options.StartsWith('$') && (options.Contains("doc") || options.Contains("all")))) && DnsClient.IsDomainNameValid(domain))
  131. domains.Enqueue(domain.ToLowerInvariant());
  132. }
  133. else
  134. {
  135. domain = line.Substring(2);
  136. if (DnsClient.IsDomainNameValid(domain))
  137. domains.Enqueue(domain.ToLowerInvariant());
  138. }
  139. }
  140. else if (line.StartsWith("@@||"))
  141. {
  142. //adblock format - exception syntax
  143. i = line.IndexOf('^');
  144. if (i > -1)
  145. {
  146. domain = line.Substring(4, i - 4);
  147. options = line.Substring(i + 1);
  148. if (((options.Length == 0) || (options.StartsWith('$') && (options.Contains("doc") || options.Contains("all")))) && DnsClient.IsDomainNameValid(domain))
  149. exceptionDomains.Enqueue(domain.ToLowerInvariant());
  150. }
  151. else
  152. {
  153. domain = line.Substring(4);
  154. if (DnsClient.IsDomainNameValid(domain))
  155. exceptionDomains.Enqueue(domain.ToLowerInvariant());
  156. }
  157. }
  158. else
  159. {
  160. //hosts file format
  161. firstWord = PopWord(ref line);
  162. if (line.Length == 0)
  163. {
  164. hostname = firstWord;
  165. }
  166. else
  167. {
  168. secondWord = PopWord(ref line);
  169. if ((secondWord.Length == 0) || secondWord.StartsWith('#'))
  170. hostname = firstWord;
  171. else
  172. hostname = secondWord;
  173. }
  174. hostname = hostname.Trim('.').ToLowerInvariant();
  175. switch (hostname)
  176. {
  177. case "":
  178. case "localhost":
  179. case "localhost.localdomain":
  180. case "local":
  181. case "broadcasthost":
  182. case "ip6-localhost":
  183. case "ip6-loopback":
  184. case "ip6-localnet":
  185. case "ip6-mcastprefix":
  186. case "ip6-allnodes":
  187. case "ip6-allrouters":
  188. case "ip6-allhosts":
  189. continue; //skip these hostnames
  190. }
  191. if (!DnsClient.IsDomainNameValid(hostname))
  192. continue;
  193. if (IPAddress.TryParse(hostname, out _))
  194. continue; //skip line when hostname is IP address
  195. domains.Enqueue(hostname);
  196. }
  197. }
  198. }
  199. _dnsServer.LogManager?.Write("DNS Server read " + (isAllowList ? "allow" : "block") + " list file (" + domains.Count + " domains) from: " + listUrl.AbsoluteUri);
  200. }
  201. catch (Exception ex)
  202. {
  203. _dnsServer.LogManager?.Write("DNS Server failed to read " + (isAllowList ? "allow" : "block") + " list from: " + listUrl.AbsoluteUri + "\r\n" + ex.ToString());
  204. }
  205. return domains;
  206. }
  207. private List<Uri> IsZoneBlocked(string domain, out string blockedDomain)
  208. {
  209. domain = domain.ToLowerInvariant();
  210. do
  211. {
  212. if (_blockListZone.TryGetValue(domain, out List<Uri> blockLists))
  213. {
  214. //found zone blocked
  215. blockedDomain = domain;
  216. return blockLists;
  217. }
  218. domain = AuthZoneManager.GetParentZone(domain);
  219. }
  220. while (domain is not null);
  221. blockedDomain = null;
  222. return null;
  223. }
  224. private bool IsZoneAllowed(string domain)
  225. {
  226. domain = domain.ToLowerInvariant();
  227. do
  228. {
  229. if (_allowListZone.TryGetValue(domain, out _))
  230. return true;
  231. domain = AuthZoneManager.GetParentZone(domain);
  232. }
  233. while (domain is not null);
  234. return false;
  235. }
  236. #endregion
  237. #region public
  238. public void LoadBlockLists()
  239. {
  240. Dictionary<Uri, Queue<string>> allowListQueues = new Dictionary<Uri, Queue<string>>(_allowListUrls.Count);
  241. Dictionary<Uri, Queue<string>> blockListQueues = new Dictionary<Uri, Queue<string>>(_blockListUrls.Count);
  242. int totalAllowedDomains = 0;
  243. int totalBlockedDomains = 0;
  244. //read all allow lists in a queue
  245. foreach (Uri allowListUrl in _allowListUrls)
  246. {
  247. if (!allowListQueues.ContainsKey(allowListUrl))
  248. {
  249. Queue<string> allowListQueue = ReadListFile(allowListUrl, true, out Queue<string> blockListQueue);
  250. totalAllowedDomains += allowListQueue.Count;
  251. allowListQueues.Add(allowListUrl, allowListQueue);
  252. totalBlockedDomains += blockListQueue.Count;
  253. blockListQueues.Add(allowListUrl, blockListQueue);
  254. }
  255. }
  256. //read all block lists in a queue
  257. foreach (Uri blockListUrl in _blockListUrls)
  258. {
  259. if (!blockListQueues.ContainsKey(blockListUrl))
  260. {
  261. Queue<string> blockListQueue = ReadListFile(blockListUrl, false, out Queue<string> allowListQueue);
  262. totalBlockedDomains += blockListQueue.Count;
  263. blockListQueues.Add(blockListUrl, blockListQueue);
  264. totalAllowedDomains += allowListQueue.Count;
  265. allowListQueues.Add(blockListUrl, allowListQueue);
  266. }
  267. }
  268. //load block list zone
  269. Dictionary<string, object> allowListZone = new Dictionary<string, object>(totalAllowedDomains);
  270. foreach (KeyValuePair<Uri, Queue<string>> allowListQueue in allowListQueues)
  271. {
  272. Queue<string> queue = allowListQueue.Value;
  273. while (queue.Count > 0)
  274. {
  275. string domain = queue.Dequeue();
  276. allowListZone.TryAdd(domain, null);
  277. }
  278. }
  279. Dictionary<string, List<Uri>> blockListZone = new Dictionary<string, List<Uri>>(totalBlockedDomains);
  280. foreach (KeyValuePair<Uri, Queue<string>> blockListQueue in blockListQueues)
  281. {
  282. Queue<string> queue = blockListQueue.Value;
  283. while (queue.Count > 0)
  284. {
  285. string domain = queue.Dequeue();
  286. if (!blockListZone.TryGetValue(domain, out List<Uri> blockLists))
  287. {
  288. blockLists = new List<Uri>(2);
  289. blockListZone.Add(domain, blockLists);
  290. }
  291. blockLists.Add(blockListQueue.Key);
  292. }
  293. }
  294. //set new allowed and blocked zones
  295. _allowListZone = allowListZone;
  296. _blockListZone = blockListZone;
  297. _dnsServer.LogManager?.Write("DNS Server block list zone was loaded successfully.");
  298. }
  299. public void Flush()
  300. {
  301. _allowListZone = new Dictionary<string, object>();
  302. _blockListZone = new Dictionary<string, List<Uri>>();
  303. }
  304. public async Task<bool> UpdateBlockListsAsync(bool forceReload)
  305. {
  306. bool downloaded = false;
  307. bool notModified = false;
  308. async Task DownloadListUrlAsync(Uri listUrl, bool isAllowList)
  309. {
  310. try
  311. {
  312. _dnsServer.LogManager?.Write("DNS Server is downloading " + (isAllowList ? "allow" : "block") + " list: " + listUrl.AbsoluteUri);
  313. string listFilePath = GetBlockListFilePath(listUrl);
  314. if (listUrl.IsFile)
  315. {
  316. if (File.Exists(listFilePath))
  317. {
  318. if (File.GetLastWriteTimeUtc(listUrl.LocalPath) <= File.GetLastWriteTimeUtc(listFilePath))
  319. {
  320. notModified = true;
  321. _dnsServer.LogManager?.Write("DNS Server successfully checked for a new update of the " + (isAllowList ? "allow" : "block") + " list: " + listUrl.AbsoluteUri);
  322. return;
  323. }
  324. }
  325. File.Copy(listUrl.LocalPath, listFilePath, true);
  326. downloaded = true;
  327. _dnsServer.LogManager?.Write("DNS Server successfully downloaded " + (isAllowList ? "allow" : "block") + " list (" + WebUtilities.GetFormattedSize(new FileInfo(listFilePath).Length) + "): " + listUrl.AbsoluteUri);
  328. }
  329. else
  330. {
  331. SocketsHttpHandler handler = new SocketsHttpHandler();
  332. handler.Proxy = _dnsServer.Proxy;
  333. handler.UseProxy = _dnsServer.Proxy is not null;
  334. handler.AutomaticDecompression = DecompressionMethods.All;
  335. using (HttpClient http = new HttpClient(new HttpClientNetworkHandler(handler, _dnsServer.PreferIPv6 ? HttpClientNetworkType.PreferIPv6 : HttpClientNetworkType.Default, _dnsServer)))
  336. {
  337. if (File.Exists(listFilePath))
  338. http.DefaultRequestHeaders.IfModifiedSince = File.GetLastWriteTimeUtc(listFilePath);
  339. HttpResponseMessage httpResponse = await http.GetAsync(listUrl);
  340. switch (httpResponse.StatusCode)
  341. {
  342. case HttpStatusCode.OK:
  343. {
  344. string listDownloadFilePath = listFilePath + ".downloading";
  345. using (FileStream fS = new FileStream(listDownloadFilePath, FileMode.Create, FileAccess.Write))
  346. {
  347. using (Stream httpStream = await httpResponse.Content.ReadAsStreamAsync())
  348. {
  349. await httpStream.CopyToAsync(fS);
  350. }
  351. }
  352. File.Move(listDownloadFilePath, listFilePath, true);
  353. if (httpResponse.Content.Headers.LastModified != null)
  354. File.SetLastWriteTimeUtc(listFilePath, httpResponse.Content.Headers.LastModified.Value.UtcDateTime);
  355. downloaded = true;
  356. _dnsServer.LogManager?.Write("DNS Server successfully downloaded " + (isAllowList ? "allow" : "block") + " list (" + WebUtilities.GetFormattedSize(new FileInfo(listFilePath).Length) + "): " + listUrl.AbsoluteUri);
  357. }
  358. break;
  359. case HttpStatusCode.NotModified:
  360. {
  361. notModified = true;
  362. _dnsServer.LogManager?.Write("DNS Server successfully checked for a new update of the " + (isAllowList ? "allow" : "block") + " list: " + listUrl.AbsoluteUri);
  363. }
  364. break;
  365. default:
  366. throw new HttpRequestException((int)httpResponse.StatusCode + " " + httpResponse.ReasonPhrase);
  367. }
  368. }
  369. }
  370. }
  371. catch (Exception ex)
  372. {
  373. _dnsServer.LogManager?.Write("DNS Server failed to download " + (isAllowList ? "allow" : "block") + " list and will use previously downloaded file (if available): " + listUrl.AbsoluteUri + "\r\n" + ex.ToString());
  374. }
  375. }
  376. List<Task> tasks = new List<Task>();
  377. foreach (Uri allowListUrl in _allowListUrls)
  378. tasks.Add(DownloadListUrlAsync(allowListUrl, true));
  379. foreach (Uri blockListUrl in _blockListUrls)
  380. tasks.Add(DownloadListUrlAsync(blockListUrl, false));
  381. await Task.WhenAll(tasks);
  382. if (downloaded || forceReload)
  383. {
  384. LoadBlockLists();
  385. //force GC collection to remove old zone data from memory quickly
  386. GC.Collect();
  387. }
  388. return downloaded || notModified;
  389. }
  390. public bool IsAllowed(DnsDatagram request)
  391. {
  392. if (_allowListZone.Count < 1)
  393. return false;
  394. return IsZoneAllowed(request.Question[0].Name);
  395. }
  396. public DnsDatagram Query(DnsDatagram request)
  397. {
  398. if (_blockListZone.Count < 1)
  399. return null;
  400. DnsQuestionRecord question = request.Question[0];
  401. List<Uri> blockLists = IsZoneBlocked(question.Name, out string blockedDomain);
  402. if (blockLists is null)
  403. return null; //zone not blocked
  404. //zone is blocked
  405. if (_dnsServer.AllowTxtBlockingReport && (question.Type == DnsResourceRecordType.TXT))
  406. {
  407. //return meta data
  408. DnsResourceRecord[] answer = new DnsResourceRecord[blockLists.Count];
  409. for (int i = 0; i < answer.Length; i++)
  410. answer[i] = new DnsResourceRecord(question.Name, DnsResourceRecordType.TXT, question.Class, _dnsServer.BlockingAnswerTtl, new DnsTXTRecordData("source=block-list-zone; blockListUrl=" + blockLists[i].AbsoluteUri + "; domain=" + blockedDomain));
  411. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answer);
  412. }
  413. else
  414. {
  415. EDnsOption[] options = null;
  416. if (_dnsServer.AllowTxtBlockingReport && (request.EDNS is not null))
  417. {
  418. options = new EDnsOption[blockLists.Count];
  419. for (int i = 0; i < options.Length; i++)
  420. options[i] = new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.Blocked, "source=block-list-zone; blockListUrl=" + blockLists[i].AbsoluteUri + "; domain=" + blockedDomain));
  421. }
  422. IReadOnlyCollection<DnsARecordData> aRecords;
  423. IReadOnlyCollection<DnsAAAARecordData> aaaaRecords;
  424. switch (_dnsServer.BlockingType)
  425. {
  426. case DnsServerBlockingType.AnyAddress:
  427. aRecords = _aRecords;
  428. aaaaRecords = _aaaaRecords;
  429. break;
  430. case DnsServerBlockingType.CustomAddress:
  431. aRecords = _dnsServer.CustomBlockingARecords;
  432. aaaaRecords = _dnsServer.CustomBlockingAAAARecords;
  433. break;
  434. case DnsServerBlockingType.NxDomain:
  435. string parentDomain = AuthZoneManager.GetParentZone(blockedDomain);
  436. if (parentDomain is null)
  437. parentDomain = string.Empty;
  438. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NxDomain, request.Question, null, [new DnsResourceRecord(parentDomain, DnsResourceRecordType.SOA, question.Class, _dnsServer.BlockingAnswerTtl, _soaRecord)], null, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options);
  439. default:
  440. throw new InvalidOperationException();
  441. }
  442. IReadOnlyList<DnsResourceRecord> answer = null;
  443. IReadOnlyList<DnsResourceRecord> authority = null;
  444. switch (question.Type)
  445. {
  446. case DnsResourceRecordType.A:
  447. {
  448. if (aRecords.Count > 0)
  449. {
  450. DnsResourceRecord[] rrList = new DnsResourceRecord[aRecords.Count];
  451. int i = 0;
  452. foreach (DnsARecordData record in aRecords)
  453. rrList[i++] = new DnsResourceRecord(question.Name, DnsResourceRecordType.A, question.Class, _dnsServer.BlockingAnswerTtl, record);
  454. answer = rrList;
  455. }
  456. else
  457. {
  458. authority = [new DnsResourceRecord(blockedDomain, DnsResourceRecordType.SOA, question.Class, _dnsServer.BlockingAnswerTtl, _soaRecord)];
  459. }
  460. }
  461. break;
  462. case DnsResourceRecordType.AAAA:
  463. {
  464. if (aaaaRecords.Count > 0)
  465. {
  466. DnsResourceRecord[] rrList = new DnsResourceRecord[aaaaRecords.Count];
  467. int i = 0;
  468. foreach (DnsAAAARecordData record in aaaaRecords)
  469. rrList[i++] = new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, question.Class, _dnsServer.BlockingAnswerTtl, record);
  470. answer = rrList;
  471. }
  472. else
  473. {
  474. authority = [new DnsResourceRecord(blockedDomain, DnsResourceRecordType.SOA, question.Class, _dnsServer.BlockingAnswerTtl, _soaRecord)];
  475. }
  476. }
  477. break;
  478. case DnsResourceRecordType.NS:
  479. if (question.Name.Equals(blockedDomain, StringComparison.OrdinalIgnoreCase))
  480. answer = [new DnsResourceRecord(blockedDomain, DnsResourceRecordType.NS, question.Class, _dnsServer.BlockingAnswerTtl, _nsRecord)];
  481. else
  482. authority = [new DnsResourceRecord(blockedDomain, DnsResourceRecordType.SOA, question.Class, _dnsServer.BlockingAnswerTtl, _soaRecord)];
  483. break;
  484. case DnsResourceRecordType.SOA:
  485. answer = [new DnsResourceRecord(blockedDomain, DnsResourceRecordType.SOA, question.Class, _dnsServer.BlockingAnswerTtl, _soaRecord)];
  486. break;
  487. default:
  488. authority = [new DnsResourceRecord(blockedDomain, DnsResourceRecordType.SOA, question.Class, _dnsServer.BlockingAnswerTtl, _soaRecord)];
  489. break;
  490. }
  491. return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, request.Question, answer, authority, null, request.EDNS is null ? ushort.MinValue : _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options);
  492. }
  493. }
  494. #endregion
  495. #region properties
  496. public List<Uri> AllowListUrls
  497. { get { return _allowListUrls; } }
  498. public List<Uri> BlockListUrls
  499. { get { return _blockListUrls; } }
  500. public int TotalZonesAllowed
  501. { get { return _allowListZone.Count; } }
  502. public int TotalZonesBlocked
  503. { get { return _blockListZone.Count; } }
  504. #endregion
  505. }
  506. }