SimpleAddress.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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.Net.Sockets;
  21. using System.Text.Json;
  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 SplitHorizon
  28. {
  29. public class SimpleAddress : IDnsApplication, IDnsAppRecordRequestHandler
  30. {
  31. #region variables
  32. static IReadOnlyDictionary<string, List<NetworkAddress>> _networks;
  33. #endregion
  34. #region IDisposable
  35. public void Dispose()
  36. {
  37. //do nothing
  38. }
  39. #endregion
  40. #region public
  41. public async Task InitializeAsync(IDnsServer dnsServer, string config)
  42. {
  43. if (string.IsNullOrEmpty(config) || config.StartsWith('#'))
  44. {
  45. //replace old config with default config
  46. config = """
  47. {
  48. "networks": {
  49. "custom-networks": [
  50. "172.16.1.0/24",
  51. "172.16.10.0/24",
  52. "172.16.2.1"
  53. ]
  54. },
  55. "enableAddressTranslation": false,
  56. "networkGroupMap": {
  57. "10.0.0.0/8": "local1",
  58. "172.16.0.0/12": "local2",
  59. "192.168.0.0/16": "local3"
  60. },
  61. "groups": [
  62. {
  63. "name": "local1",
  64. "enabled": true,
  65. "translateReverseLookups": true,
  66. "externalToInternalTranslation": {
  67. "1.2.3.4": "10.0.0.4",
  68. "5.6.7.8": "10.0.0.5"
  69. }
  70. },
  71. {
  72. "name": "local2",
  73. "enabled": true,
  74. "translateReverseLookups": true,
  75. "externalToInternalTranslation": {
  76. "1.2.3.4": "172.16.0.4",
  77. "5.6.7.8": "172.16.0.5"
  78. }
  79. },
  80. {
  81. "name": "local3",
  82. "enabled": true,
  83. "translateReverseLookups": true,
  84. "externalToInternalTranslation": {
  85. "1.2.3.4": "192.168.0.4",
  86. "5.6.7.8": "192.168.0.5"
  87. }
  88. }
  89. ]
  90. }
  91. """;
  92. await File.WriteAllTextAsync(Path.Combine(dnsServer.ApplicationFolder, "dnsApp.config"), config);
  93. }
  94. using JsonDocument jsonDocument = JsonDocument.Parse(config);
  95. JsonElement jsonConfig = jsonDocument.RootElement;
  96. if (jsonConfig.TryGetProperty("networks", out JsonElement jsonNetworks))
  97. {
  98. Dictionary<string, List<NetworkAddress>> networks = new Dictionary<string, List<NetworkAddress>>();
  99. foreach (JsonProperty jsonProperty in jsonNetworks.EnumerateObject())
  100. {
  101. string networkName = jsonProperty.Name;
  102. JsonElement jsonNetworkAddresses = jsonProperty.Value;
  103. if (jsonNetworkAddresses.ValueKind == JsonValueKind.Array)
  104. {
  105. List<NetworkAddress> networkAddresses = new List<NetworkAddress>(jsonNetworkAddresses.GetArrayLength());
  106. foreach (JsonElement jsonNetworkAddress in jsonNetworkAddresses.EnumerateArray())
  107. networkAddresses.Add(NetworkAddress.Parse(jsonNetworkAddress.GetString()));
  108. networks.TryAdd(networkName, networkAddresses);
  109. }
  110. }
  111. _networks = networks;
  112. }
  113. else
  114. {
  115. _networks = new Dictionary<string, List<NetworkAddress>>(1);
  116. }
  117. }
  118. public Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
  119. {
  120. DnsQuestionRecord question = request.Question[0];
  121. if (!question.Name.Equals(appRecordName, StringComparison.OrdinalIgnoreCase))
  122. return Task.FromResult<DnsDatagram>(null);
  123. switch (question.Type)
  124. {
  125. case DnsResourceRecordType.A:
  126. case DnsResourceRecordType.AAAA:
  127. using (JsonDocument jsonDocument = JsonDocument.Parse(appRecordData))
  128. {
  129. JsonElement jsonAppRecordData = jsonDocument.RootElement;
  130. JsonElement jsonAddresses = default;
  131. NetworkAddress selectedNetwork = null;
  132. foreach (JsonProperty jsonProperty in jsonAppRecordData.EnumerateObject())
  133. {
  134. string name = jsonProperty.Name;
  135. if ((name == "public") || (name == "private"))
  136. continue;
  137. if (_networks.TryGetValue(name, out List<NetworkAddress> networkAddresses))
  138. {
  139. foreach (NetworkAddress networkAddress in networkAddresses)
  140. {
  141. if (networkAddress.Contains(remoteEP.Address))
  142. {
  143. jsonAddresses = jsonProperty.Value;
  144. break;
  145. }
  146. }
  147. if (jsonAddresses.ValueKind != JsonValueKind.Undefined)
  148. break;
  149. }
  150. else if (NetworkAddress.TryParse(name, out NetworkAddress networkAddress))
  151. {
  152. if (networkAddress.Contains(remoteEP.Address) && ((selectedNetwork is null) || (networkAddress.PrefixLength > selectedNetwork.PrefixLength)))
  153. {
  154. selectedNetwork = networkAddress;
  155. jsonAddresses = jsonProperty.Value;
  156. }
  157. }
  158. }
  159. if (jsonAddresses.ValueKind == JsonValueKind.Undefined)
  160. {
  161. if (NetUtilities.IsPrivateIP(remoteEP.Address))
  162. {
  163. if (!jsonAppRecordData.TryGetProperty("private", out jsonAddresses))
  164. return Task.FromResult<DnsDatagram>(null);
  165. }
  166. else
  167. {
  168. if (!jsonAppRecordData.TryGetProperty("public", out jsonAddresses))
  169. return Task.FromResult<DnsDatagram>(null);
  170. }
  171. }
  172. List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
  173. switch (question.Type)
  174. {
  175. case DnsResourceRecordType.A:
  176. foreach (JsonElement jsonAddress in jsonAddresses.EnumerateArray())
  177. {
  178. if (IPAddress.TryParse(jsonAddress.GetString(), out IPAddress address) && (address.AddressFamily == AddressFamily.InterNetwork))
  179. answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecordData(address)));
  180. }
  181. break;
  182. case DnsResourceRecordType.AAAA:
  183. foreach (JsonElement jsonAddress in jsonAddresses.EnumerateArray())
  184. {
  185. if (IPAddress.TryParse(jsonAddress.GetString(), out IPAddress address) && (address.AddressFamily == AddressFamily.InterNetworkV6))
  186. answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(address)));
  187. }
  188. break;
  189. }
  190. if (answers.Count == 0)
  191. return Task.FromResult<DnsDatagram>(null);
  192. if (answers.Count > 1)
  193. answers.Shuffle();
  194. return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers));
  195. }
  196. default:
  197. return Task.FromResult<DnsDatagram>(null);
  198. }
  199. }
  200. #endregion
  201. #region properties
  202. internal static IReadOnlyDictionary<string, List<NetworkAddress>> Networks
  203. { get { return _networks; } }
  204. public string Description
  205. { get { return "Returns A or AAAA records with different set of IP addresses for clients querying over public, private, or other specified networks."; } }
  206. public string ApplicationRecordDataTemplate
  207. {
  208. get
  209. {
  210. return @"{
  211. ""public"": [
  212. ""1.1.1.1"",
  213. ""2.2.2.2""
  214. ],
  215. ""private"": [
  216. ""192.168.1.1"",
  217. ""::1""
  218. ],
  219. ""custom-networks"": [
  220. ""172.16.1.1""
  221. ],
  222. ""10.0.0.0/8"": [
  223. ""10.1.1.1""
  224. ]
  225. }";
  226. }
  227. }
  228. #endregion
  229. }
  230. }