App.cs 34 KB

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