App.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  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.ApplicationCommon;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.IO;
  19. using System.Net;
  20. using System.Text.Json;
  21. using System.Threading;
  22. using System.Threading.Tasks;
  23. using TechnitiumLibrary;
  24. using TechnitiumLibrary.Net;
  25. using TechnitiumLibrary.Net.Dns;
  26. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  27. using TechnitiumLibrary.Net.Proxy;
  28. namespace AdvancedForwarding
  29. {
  30. public class App : IDnsApplication, IDnsAuthoritativeRequestHandler
  31. {
  32. #region variables
  33. IDnsServer _dnsServer;
  34. bool _enableForwarding;
  35. Dictionary<string, ConfigProxyServer> _configProxyServers;
  36. Dictionary<string, ConfigForwarder> _configForwarders;
  37. IReadOnlyDictionary<NetworkAddress, string> _networkGroupMap;
  38. IReadOnlyDictionary<string, Group> _groups;
  39. #endregion
  40. #region IDisposable
  41. public void Dispose()
  42. {
  43. if (_groups is not null)
  44. {
  45. foreach (KeyValuePair<string, Group> group in _groups)
  46. group.Value.Dispose();
  47. }
  48. }
  49. #endregion
  50. #region private
  51. private static IReadOnlyList<DnsForwarderRecordData> GetUpdatedForwarderRecords(IReadOnlyList<DnsForwarderRecordData> forwarderRecords, bool dnssecValidation, ConfigProxyServer configProxyServer)
  52. {
  53. List<DnsForwarderRecordData> newForwarderRecords = new List<DnsForwarderRecordData>(forwarderRecords.Count);
  54. foreach (DnsForwarderRecordData forwarderRecord in forwarderRecords)
  55. newForwarderRecords.Add(GetForwarderRecord(forwarderRecord.Protocol, forwarderRecord.Forwarder, dnssecValidation, configProxyServer));
  56. return newForwarderRecords;
  57. }
  58. private static DnsForwarderRecordData GetForwarderRecord(NameServerAddress forwarder, bool dnssecValidation, ConfigProxyServer configProxyServer)
  59. {
  60. return GetForwarderRecord(forwarder.Protocol, forwarder.ToString(), dnssecValidation, configProxyServer);
  61. }
  62. private static DnsForwarderRecordData GetForwarderRecord(DnsTransportProtocol protocol, string forwarder, bool dnssecValidation, ConfigProxyServer configProxyServer)
  63. {
  64. DnsForwarderRecordData forwarderRecord;
  65. if (configProxyServer is null)
  66. forwarderRecord = new DnsForwarderRecordData(protocol, forwarder, dnssecValidation, NetProxyType.None, null, 0, null, null);
  67. else
  68. forwarderRecord = new DnsForwarderRecordData(protocol, forwarder, dnssecValidation, configProxyServer.Type, configProxyServer.ProxyAddress, configProxyServer.ProxyPort, configProxyServer.ProxyUsername, configProxyServer.ProxyPassword);
  69. return forwarderRecord;
  70. }
  71. private Tuple<string, Group> ReadGroup(JsonElement jsonGroup)
  72. {
  73. Group group;
  74. string name = jsonGroup.GetProperty("name").GetString();
  75. if ((_groups is not null) && _groups.TryGetValue(name, out group))
  76. group.ReloadConfig(_configProxyServers, _configForwarders, jsonGroup);
  77. else
  78. group = new Group(_dnsServer, _configProxyServers, _configForwarders, jsonGroup);
  79. return new Tuple<string, Group>(group.Name, group);
  80. }
  81. #endregion
  82. #region public
  83. public Task InitializeAsync(IDnsServer dnsServer, string config)
  84. {
  85. _dnsServer = dnsServer;
  86. using JsonDocument jsonDocument = JsonDocument.Parse(config);
  87. JsonElement jsonConfig = jsonDocument.RootElement;
  88. _enableForwarding = jsonConfig.GetPropertyValue("enableForwarding", true);
  89. if (jsonConfig.TryReadArrayAsMap("proxyServers", delegate (JsonElement jsonProxy)
  90. {
  91. ConfigProxyServer proxyServer = new ConfigProxyServer(jsonProxy);
  92. return new Tuple<string, ConfigProxyServer>(proxyServer.Name, proxyServer);
  93. }, out Dictionary<string, ConfigProxyServer> configProxyServers))
  94. _configProxyServers = configProxyServers;
  95. else
  96. _configProxyServers = null;
  97. if (jsonConfig.TryReadArrayAsMap("forwarders", delegate (JsonElement jsonForwarder)
  98. {
  99. ConfigForwarder forwarder = new ConfigForwarder(jsonForwarder, _configProxyServers);
  100. return new Tuple<string, ConfigForwarder>(forwarder.Name, forwarder);
  101. }, out Dictionary<string, ConfigForwarder> configForwarders))
  102. _configForwarders = configForwarders;
  103. else
  104. _configForwarders = null;
  105. _networkGroupMap = jsonConfig.ReadObjectAsMap("networkGroupMap", delegate (string network, JsonElement jsonGroup)
  106. {
  107. if (!NetworkAddress.TryParse(network, out NetworkAddress networkAddress))
  108. throw new FormatException("Network group map contains an invalid network address: " + network);
  109. return new Tuple<NetworkAddress, string>(networkAddress, jsonGroup.GetString());
  110. });
  111. if (jsonConfig.TryReadArrayAsMap("groups", ReadGroup, out Dictionary<string, Group> groups))
  112. {
  113. if (_groups is not null)
  114. {
  115. foreach (KeyValuePair<string, Group> group in _groups)
  116. {
  117. if (!groups.ContainsKey(group.Key))
  118. group.Value.Dispose();
  119. }
  120. }
  121. _groups = groups;
  122. }
  123. else
  124. {
  125. throw new FormatException("Groups array was not defined.");
  126. }
  127. return Task.CompletedTask;
  128. }
  129. public Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed)
  130. {
  131. if (!_enableForwarding || !request.RecursionDesired)
  132. return Task.FromResult<DnsDatagram>(null);
  133. IPAddress remoteIP = remoteEP.Address;
  134. NetworkAddress network = null;
  135. string groupName = null;
  136. foreach (KeyValuePair<NetworkAddress, string> entry in _networkGroupMap)
  137. {
  138. if (entry.Key.Contains(remoteIP) && ((network is null) || (entry.Key.PrefixLength > network.PrefixLength)))
  139. {
  140. network = entry.Key;
  141. groupName = entry.Value;
  142. }
  143. }
  144. if ((groupName is null) || !_groups.TryGetValue(groupName, out Group group) || !group.EnableForwarding)
  145. return Task.FromResult<DnsDatagram>(null);
  146. DnsQuestionRecord question = request.Question[0];
  147. string qname = question.Name;
  148. if (!group.TryGetForwarderRecords(qname, out IReadOnlyList<DnsForwarderRecordData> forwarderRecords))
  149. return Task.FromResult<DnsDatagram>(null);
  150. request.SetShadowEDnsClientSubnetOption(network, true);
  151. DnsResourceRecord[] authority = new DnsResourceRecord[forwarderRecords.Count];
  152. for (int i = 0; i < forwarderRecords.Count; i++)
  153. authority[i] = new DnsResourceRecord(qname, DnsResourceRecordType.FWD, DnsClass.IN, 0, forwarderRecords[i]);
  154. return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.NoError, request.Question, null, authority));
  155. }
  156. #endregion
  157. #region properties
  158. public string Description
  159. { get { return "Performs bulk conditional forwarding for configured domain names and AdGuard Upstream config files."; } }
  160. #endregion
  161. class Group : IDisposable
  162. {
  163. #region variables
  164. readonly IDnsServer _dnsServer;
  165. Dictionary<string, ConfigProxyServer> _configProxyServers;
  166. Dictionary<string, ConfigForwarder> _configForwarders;
  167. readonly string _name;
  168. bool _enableForwarding;
  169. IReadOnlyList<Forwarding> _forwardings;
  170. IReadOnlyDictionary<string, AdGuardUpstream> _adguardUpstreams;
  171. #endregion
  172. #region constructor
  173. public Group(IDnsServer dnsServer, Dictionary<string, ConfigProxyServer> configProxyServers, Dictionary<string, ConfigForwarder> configForwarders, JsonElement jsonGroup)
  174. {
  175. _dnsServer = dnsServer;
  176. _name = jsonGroup.GetProperty("name").GetString();
  177. ReloadConfig(configProxyServers, configForwarders, jsonGroup);
  178. }
  179. #endregion
  180. #region IDisposable
  181. public void Dispose()
  182. {
  183. if (_adguardUpstreams is not null)
  184. {
  185. foreach (KeyValuePair<string, AdGuardUpstream> adguardUpstream in _adguardUpstreams)
  186. adguardUpstream.Value.Dispose();
  187. _adguardUpstreams = null;
  188. }
  189. }
  190. #endregion
  191. #region private
  192. private Tuple<string, AdGuardUpstream> ReadAdGuardUpstream(JsonElement jsonAdguardUpstream)
  193. {
  194. AdGuardUpstream adGuardUpstream;
  195. string name = jsonAdguardUpstream.GetProperty("configFile").GetString();
  196. if ((_adguardUpstreams is not null) && _adguardUpstreams.TryGetValue(name, out adGuardUpstream))
  197. adGuardUpstream.ReloadConfig(_configProxyServers, jsonAdguardUpstream);
  198. else
  199. adGuardUpstream = new AdGuardUpstream(_dnsServer, _configProxyServers, jsonAdguardUpstream);
  200. return new Tuple<string, AdGuardUpstream>(adGuardUpstream.Name, adGuardUpstream);
  201. }
  202. #endregion
  203. #region public
  204. public void ReloadConfig(Dictionary<string, ConfigProxyServer> configProxyServers, Dictionary<string, ConfigForwarder> configForwarders, JsonElement jsonGroup)
  205. {
  206. _configProxyServers = configProxyServers;
  207. _configForwarders = configForwarders;
  208. _enableForwarding = jsonGroup.GetPropertyValue("enableForwarding", true);
  209. if (jsonGroup.TryReadArray("forwardings", delegate (JsonElement jsonForwarding) { return new Forwarding(jsonForwarding, _configForwarders); }, out Forwarding[] forwardings))
  210. _forwardings = forwardings;
  211. else
  212. _forwardings = null;
  213. if (jsonGroup.TryReadArrayAsMap("adguardUpstreams", ReadAdGuardUpstream, out Dictionary<string, AdGuardUpstream> adguardUpstreams))
  214. {
  215. if (_adguardUpstreams is not null)
  216. {
  217. foreach (KeyValuePair<string, AdGuardUpstream> adguardUpstream in _adguardUpstreams)
  218. {
  219. if (!adguardUpstreams.ContainsKey(adguardUpstream.Key))
  220. adguardUpstream.Value.Dispose();
  221. }
  222. }
  223. _adguardUpstreams = adguardUpstreams;
  224. }
  225. else
  226. {
  227. if (_adguardUpstreams is not null)
  228. {
  229. foreach (KeyValuePair<string, AdGuardUpstream> adguardUpstream in _adguardUpstreams)
  230. adguardUpstream.Value.Dispose();
  231. }
  232. _adguardUpstreams = null;
  233. }
  234. }
  235. public bool TryGetForwarderRecords(string domain, out IReadOnlyList<DnsForwarderRecordData> forwarderRecords)
  236. {
  237. domain = domain.ToLower();
  238. if ((_forwardings is not null) && (_forwardings.Count > 0) && Forwarding.TryGetForwarderRecords(domain, _forwardings, out forwarderRecords))
  239. return true;
  240. if (_adguardUpstreams is not null)
  241. {
  242. foreach (KeyValuePair<string, AdGuardUpstream> adguardUpstream in _adguardUpstreams)
  243. {
  244. if (adguardUpstream.Value.TryGetForwarderRecords(domain, out forwarderRecords))
  245. return true;
  246. }
  247. }
  248. forwarderRecords = null;
  249. return false;
  250. }
  251. #endregion
  252. #region properties
  253. public string Name
  254. { get { return _name; } }
  255. public bool EnableForwarding
  256. { get { return _enableForwarding; } }
  257. #endregion
  258. }
  259. class Forwarding
  260. {
  261. #region variables
  262. IReadOnlyList<DnsForwarderRecordData> _forwarderRecords;
  263. readonly IReadOnlyDictionary<string, object> _domainMap;
  264. #endregion
  265. #region constructor
  266. public Forwarding(JsonElement jsonForwarding, Dictionary<string, ConfigForwarder> configForwarders)
  267. {
  268. JsonElement jsonForwarders = jsonForwarding.GetProperty("forwarders");
  269. List<DnsForwarderRecordData> forwarderRecords = new List<DnsForwarderRecordData>();
  270. foreach (JsonElement jsonForwarder in jsonForwarders.EnumerateArray())
  271. {
  272. string forwarderName = jsonForwarder.GetString();
  273. if ((configForwarders is null) || !configForwarders.TryGetValue(forwarderName, out ConfigForwarder configForwarder))
  274. throw new FormatException("Forwarder was not defined: " + forwarderName);
  275. forwarderRecords.AddRange(configForwarder.ForwarderRecords);
  276. }
  277. _forwarderRecords = forwarderRecords;
  278. _domainMap = jsonForwarding.ReadArrayAsMap("domains", delegate (JsonElement jsonDomain)
  279. {
  280. return new Tuple<string, object>(jsonDomain.GetString().ToLower(), null);
  281. });
  282. }
  283. public Forwarding(IReadOnlyList<string> domains, NameServerAddress forwarder, bool dnssecValidation, ConfigProxyServer proxy)
  284. : this(new DnsForwarderRecordData[] { GetForwarderRecord(forwarder, dnssecValidation, proxy) }, domains)
  285. { }
  286. public Forwarding(IReadOnlyList<DnsForwarderRecordData> forwarderRecords, IReadOnlyList<string> domains)
  287. {
  288. _forwarderRecords = forwarderRecords;
  289. Dictionary<string, object> domainMap = new Dictionary<string, object>(domains.Count);
  290. foreach (string domain in domains)
  291. {
  292. if (DnsClient.IsDomainNameValid(domain))
  293. domainMap.TryAdd(domain.ToLower(), null);
  294. }
  295. _domainMap = domainMap;
  296. }
  297. #endregion
  298. #region static
  299. public static bool TryGetForwarderRecords(string domain, IReadOnlyList<Forwarding> forwardings, out IReadOnlyList<DnsForwarderRecordData> forwarderRecords)
  300. {
  301. if (forwardings.Count == 1)
  302. {
  303. if (forwardings[0].TryGetForwarderRecords(domain, out forwarderRecords, out _))
  304. return true;
  305. }
  306. else
  307. {
  308. Dictionary<string, List<DnsForwarderRecordData>> fwdMap = new Dictionary<string, List<DnsForwarderRecordData>>(forwardings.Count);
  309. foreach (Forwarding forwarding in forwardings)
  310. {
  311. if (forwarding.TryGetForwarderRecords(domain, out IReadOnlyList<DnsForwarderRecordData> fwdRecords, out string matchedDomain))
  312. {
  313. if (fwdMap.TryGetValue(matchedDomain, out List<DnsForwarderRecordData> fwdRecordsList))
  314. {
  315. fwdRecordsList.AddRange(fwdRecords);
  316. }
  317. else
  318. {
  319. fwdRecordsList = new List<DnsForwarderRecordData>(fwdRecords);
  320. fwdMap.Add(matchedDomain, fwdRecordsList);
  321. }
  322. }
  323. }
  324. if (fwdMap.Count > 0)
  325. {
  326. forwarderRecords = null;
  327. string lastMatchedDomain = null;
  328. foreach (KeyValuePair<string, List<DnsForwarderRecordData>> fwdEntry in fwdMap)
  329. {
  330. if ((lastMatchedDomain is null) || (fwdEntry.Key.Length > lastMatchedDomain.Length) || ((fwdEntry.Key.Length == lastMatchedDomain.Length) && lastMatchedDomain.StartsWith("*.")))
  331. {
  332. lastMatchedDomain = fwdEntry.Key;
  333. forwarderRecords = fwdEntry.Value;
  334. }
  335. }
  336. return true;
  337. }
  338. }
  339. forwarderRecords = null;
  340. return false;
  341. }
  342. public static bool IsForwarderDomain(string domain, IReadOnlyList<Forwarding> forwardings)
  343. {
  344. foreach (Forwarding forwarding in forwardings)
  345. {
  346. if (IsForwarderDomain(domain, forwarding._forwarderRecords))
  347. return true;
  348. }
  349. return false;
  350. }
  351. public static bool IsForwarderDomain(string domain, IReadOnlyList<DnsForwarderRecordData> forwarderRecords)
  352. {
  353. foreach (DnsForwarderRecordData forwarderRecord in forwarderRecords)
  354. {
  355. if (domain.Equals(forwarderRecord.NameServer.Host, StringComparison.OrdinalIgnoreCase))
  356. return true;
  357. }
  358. return false;
  359. }
  360. #endregion
  361. #region private
  362. private static string GetParentZone(string domain)
  363. {
  364. int i = domain.IndexOf('.');
  365. if (i > -1)
  366. return domain.Substring(i + 1);
  367. //dont return root zone
  368. return null;
  369. }
  370. private bool IsDomainMatching(string domain, out string matchedDomain)
  371. {
  372. string parent;
  373. do
  374. {
  375. if (_domainMap.TryGetValue(domain, out _))
  376. {
  377. matchedDomain = domain;
  378. return true;
  379. }
  380. parent = GetParentZone(domain);
  381. if (parent is null)
  382. {
  383. if (_domainMap.TryGetValue("*", out _))
  384. {
  385. matchedDomain = "*";
  386. return true;
  387. }
  388. break;
  389. }
  390. domain = "*." + parent;
  391. if (_domainMap.TryGetValue(domain, out _))
  392. {
  393. matchedDomain = domain;
  394. return true;
  395. }
  396. domain = parent;
  397. }
  398. while (true);
  399. matchedDomain = null;
  400. return false;
  401. }
  402. private bool TryGetForwarderRecords(string domain, out IReadOnlyList<DnsForwarderRecordData> forwarderRecords, out string matchedDomain)
  403. {
  404. if (IsDomainMatching(domain, out matchedDomain))
  405. {
  406. forwarderRecords = _forwarderRecords;
  407. return true;
  408. }
  409. forwarderRecords = null;
  410. return false;
  411. }
  412. #endregion
  413. #region public
  414. public void UpdateForwarderRecords(bool dnssecValidation, ConfigProxyServer proxy)
  415. {
  416. _forwarderRecords = GetUpdatedForwarderRecords(_forwarderRecords, dnssecValidation, proxy);
  417. }
  418. #endregion
  419. }
  420. class AdGuardUpstream : IDisposable
  421. {
  422. #region variables
  423. readonly IDnsServer _dnsServer;
  424. readonly string _name;
  425. ConfigProxyServer _configProxyServer;
  426. bool _dnssecValidation;
  427. IReadOnlyList<DnsForwarderRecordData> _defaultForwarderRecords;
  428. IReadOnlyList<Forwarding> _forwardings;
  429. readonly string _configFile;
  430. DateTime _configFileLastModified;
  431. Timer _autoReloadTimer;
  432. const int AUTO_RELOAD_TIMER_INTERVAL = 60000;
  433. #endregion
  434. #region constructor
  435. public AdGuardUpstream(IDnsServer dnsServer, Dictionary<string, ConfigProxyServer> configProxyServers, JsonElement jsonAdguardUpstream)
  436. {
  437. _dnsServer = dnsServer;
  438. _name = jsonAdguardUpstream.GetProperty("configFile").GetString();
  439. _configFile = _name;
  440. if (!Path.IsPathRooted(_configFile))
  441. _configFile = Path.Combine(_dnsServer.ApplicationFolder, _configFile);
  442. _autoReloadTimer = new Timer(delegate (object state)
  443. {
  444. try
  445. {
  446. DateTime configFileLastModified = File.GetLastWriteTimeUtc(_configFile);
  447. if (configFileLastModified > _configFileLastModified)
  448. {
  449. ReloadUpstreamsFile();
  450. //force GC collection to remove old cache data from memory quickly
  451. GC.Collect();
  452. }
  453. }
  454. catch (Exception ex)
  455. {
  456. _dnsServer.WriteLog(ex);
  457. }
  458. finally
  459. {
  460. _autoReloadTimer?.Change(AUTO_RELOAD_TIMER_INTERVAL, Timeout.Infinite);
  461. }
  462. });
  463. ReloadConfig(configProxyServers, jsonAdguardUpstream);
  464. }
  465. #endregion
  466. #region IDisposable
  467. public void Dispose()
  468. {
  469. if (_autoReloadTimer is not null)
  470. {
  471. _autoReloadTimer.Dispose();
  472. _autoReloadTimer = null;
  473. }
  474. }
  475. #endregion
  476. #region private
  477. private void ReloadUpstreamsFile()
  478. {
  479. try
  480. {
  481. _dnsServer.WriteLog("The app is reading AdGuard Upstreams config file: " + _configFile);
  482. List<DnsForwarderRecordData> defaultForwarderRecords = new List<DnsForwarderRecordData>();
  483. List<Forwarding> forwardings = new List<Forwarding>();
  484. using (FileStream fS = new FileStream(_configFile, FileMode.Open, FileAccess.Read))
  485. {
  486. StreamReader sR = new StreamReader(fS, true);
  487. string line;
  488. while (true)
  489. {
  490. line = sR.ReadLine();
  491. if (line is null)
  492. break; //eof
  493. line = line.TrimStart();
  494. if (line.Length == 0)
  495. continue; //skip empty line
  496. if (line.StartsWith('#'))
  497. continue; //skip comment line
  498. if (line.StartsWith('['))
  499. {
  500. int i = line.LastIndexOf(']');
  501. if (i < 0)
  502. throw new FormatException("Invalid AdGuard Upstreams config file format: missing ']' bracket.");
  503. string[] domains = line.Substring(1, i - 1).Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  504. string forwarder = line.Substring(i + 1);
  505. if (forwarder == "#")
  506. {
  507. if (defaultForwarderRecords.Count == 0)
  508. throw new FormatException("Invalid AdGuard Upstreams config file format: missing default upstream servers.");
  509. forwardings.Add(new Forwarding(defaultForwarderRecords, domains));
  510. }
  511. else
  512. {
  513. forwardings.Add(new Forwarding(domains, NameServerAddress.Parse(forwarder), _dnssecValidation, _configProxyServer));
  514. }
  515. }
  516. else
  517. {
  518. defaultForwarderRecords.Add(GetForwarderRecord(NameServerAddress.Parse(line), _dnssecValidation, _configProxyServer));
  519. }
  520. }
  521. _configFileLastModified = File.GetLastWriteTimeUtc(fS.SafeFileHandle);
  522. }
  523. _defaultForwarderRecords = defaultForwarderRecords;
  524. _forwardings = forwardings;
  525. _dnsServer.WriteLog("The app has successfully loaded AdGuard Upstreams config file: " + _configFile);
  526. }
  527. catch (Exception ex)
  528. {
  529. _dnsServer.WriteLog("The app failed to read AdGuard Upstreams config file: " + _configFile + "\r\n" + ex.ToString());
  530. }
  531. }
  532. #endregion
  533. #region public
  534. public void ReloadConfig(Dictionary<string, ConfigProxyServer> configProxyServers, JsonElement jsonAdguardUpstream)
  535. {
  536. string proxyName = jsonAdguardUpstream.GetPropertyValue("proxy", null);
  537. _dnssecValidation = jsonAdguardUpstream.GetPropertyValue("dnssecValidation", true);
  538. ConfigProxyServer configProxyServer = null;
  539. if (!string.IsNullOrEmpty(proxyName) && ((configProxyServers is null) || !configProxyServers.TryGetValue(proxyName, out configProxyServer)))
  540. throw new FormatException("Proxy server was not defined: " + proxyName);
  541. _configProxyServer = configProxyServer;
  542. DateTime configFileLastModified = File.GetLastWriteTimeUtc(_configFile);
  543. if (configFileLastModified > _configFileLastModified)
  544. {
  545. //reload complete config file
  546. _autoReloadTimer.Change(0, Timeout.Infinite);
  547. }
  548. else
  549. {
  550. //update only forwarder records
  551. _defaultForwarderRecords = GetUpdatedForwarderRecords(_defaultForwarderRecords, _dnssecValidation, _configProxyServer);
  552. foreach (Forwarding forwarding in _forwardings)
  553. forwarding.UpdateForwarderRecords(_dnssecValidation, _configProxyServer);
  554. }
  555. }
  556. public bool TryGetForwarderRecords(string domain, out IReadOnlyList<DnsForwarderRecordData> forwarderRecords)
  557. {
  558. if ((_forwardings is not null) && (_forwardings.Count > 0))
  559. {
  560. if (Forwarding.IsForwarderDomain(domain, _forwardings))
  561. {
  562. forwarderRecords = null;
  563. return false;
  564. }
  565. if (Forwarding.TryGetForwarderRecords(domain, _forwardings, out forwarderRecords))
  566. return true;
  567. }
  568. if ((_defaultForwarderRecords is not null) && (_defaultForwarderRecords.Count > 0))
  569. {
  570. if (Forwarding.IsForwarderDomain(domain, _defaultForwarderRecords))
  571. {
  572. forwarderRecords = null;
  573. return false;
  574. }
  575. forwarderRecords = _defaultForwarderRecords;
  576. return true;
  577. }
  578. forwarderRecords = null;
  579. return false;
  580. }
  581. #endregion
  582. #region property
  583. public string Name
  584. { get { return _name; } }
  585. #endregion
  586. }
  587. class ConfigProxyServer
  588. {
  589. #region variables
  590. readonly string _name;
  591. readonly NetProxyType _type;
  592. readonly string _proxyAddress;
  593. readonly ushort _proxyPort;
  594. readonly string _proxyUsername;
  595. readonly string _proxyPassword;
  596. #endregion
  597. #region constructor
  598. public ConfigProxyServer(JsonElement jsonProxy)
  599. {
  600. _name = jsonProxy.GetProperty("name").GetString();
  601. _type = jsonProxy.GetPropertyEnumValue("type", NetProxyType.Http);
  602. _proxyAddress = jsonProxy.GetProperty("proxyAddress").GetString();
  603. _proxyPort = jsonProxy.GetProperty("proxyPort").GetUInt16();
  604. _proxyUsername = jsonProxy.GetPropertyValue("proxyUsername", null);
  605. _proxyPassword = jsonProxy.GetPropertyValue("proxyPassword", null);
  606. }
  607. #endregion
  608. #region properties
  609. public string Name
  610. { get { return _name; } }
  611. public NetProxyType Type
  612. { get { return _type; } }
  613. public string ProxyAddress
  614. { get { return _proxyAddress; } }
  615. public ushort ProxyPort
  616. { get { return _proxyPort; } }
  617. public string ProxyUsername
  618. { get { return _proxyUsername; } }
  619. public string ProxyPassword
  620. { get { return _proxyPassword; } }
  621. #endregion
  622. }
  623. class ConfigForwarder
  624. {
  625. #region variables
  626. readonly string _name;
  627. readonly IReadOnlyList<DnsForwarderRecordData> _forwarderRecords;
  628. #endregion
  629. #region constructor
  630. public ConfigForwarder(JsonElement jsonForwarder, Dictionary<string, ConfigProxyServer> configProxyServers)
  631. {
  632. _name = jsonForwarder.GetProperty("name").GetString();
  633. string proxyName = jsonForwarder.GetPropertyValue("proxy", null);
  634. bool dnssecValidation = jsonForwarder.GetPropertyValue("dnssecValidation", true);
  635. DnsTransportProtocol forwarderProtocol = jsonForwarder.GetPropertyEnumValue("forwarderProtocol", DnsTransportProtocol.Udp);
  636. ConfigProxyServer configProxyServer = null;
  637. if (!string.IsNullOrEmpty(proxyName) && ((configProxyServers is null) || !configProxyServers.TryGetValue(proxyName, out configProxyServer)))
  638. throw new FormatException("Proxy server was not defined: " + proxyName);
  639. _forwarderRecords = jsonForwarder.ReadArray("forwarderAddresses", delegate (string address)
  640. {
  641. return GetForwarderRecord(forwarderProtocol, address, dnssecValidation, configProxyServer);
  642. });
  643. }
  644. #endregion
  645. #region properties
  646. public string Name
  647. { get { return _name; } }
  648. public IReadOnlyList<DnsForwarderRecordData> ForwarderRecords
  649. { get { return _forwarderRecords; } }
  650. #endregion
  651. }
  652. }
  653. }