Address.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2021 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 DnsApplicationCommon;
  16. using MaxMind.GeoIP2;
  17. using MaxMind.GeoIP2.Model;
  18. using MaxMind.GeoIP2.Responses;
  19. using Newtonsoft.Json;
  20. using System;
  21. using System.Collections.Generic;
  22. using System.IO;
  23. using System.Net;
  24. using System.Net.Sockets;
  25. using System.Threading.Tasks;
  26. using TechnitiumLibrary.Net.Dns;
  27. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  28. namespace GeoDistance
  29. {
  30. public sealed class Address : IDnsApplicationRequestHandler
  31. {
  32. #region variables
  33. DatabaseReader _mmCityReader;
  34. #endregion
  35. #region IDisposable
  36. bool _disposed;
  37. private void Dispose(bool disposing)
  38. {
  39. if (_disposed)
  40. return;
  41. if (disposing)
  42. {
  43. if (_mmCityReader != null)
  44. _mmCityReader.Dispose();
  45. }
  46. _disposed = true;
  47. }
  48. public void Dispose()
  49. {
  50. Dispose(true);
  51. }
  52. #endregion
  53. #region private
  54. private static double GetDistance(double lat1, double long1, double lat2, double long2)
  55. {
  56. double d1 = lat1 * (Math.PI / 180.0);
  57. double num1 = long1 * (Math.PI / 180.0);
  58. double d2 = lat2 * (Math.PI / 180.0);
  59. double num2 = long2 * (Math.PI / 180.0) - num1;
  60. double d3 = Math.Pow(Math.Sin((d2 - d1) / 2.0), 2.0) + Math.Cos(d1) * Math.Cos(d2) * Math.Pow(Math.Sin(num2 / 2.0), 2.0);
  61. return 6376500.0 * (2.0 * Math.Atan2(Math.Sqrt(d3), Math.Sqrt(1.0 - d3)));
  62. }
  63. #endregion
  64. #region public
  65. public Task InitializeAsync(IDnsServer dnsServer, string config)
  66. {
  67. if (_mmCityReader == null)
  68. {
  69. string mmFile = Path.Combine(dnsServer.ApplicationFolder, "GeoIP2-City.mmdb");
  70. if (!File.Exists(mmFile))
  71. mmFile = Path.Combine(dnsServer.ApplicationFolder, "GeoLite2-City.mmdb");
  72. if (!File.Exists(mmFile))
  73. throw new FileNotFoundException("MaxMind City file is missing!");
  74. _mmCityReader = new DatabaseReader(mmFile);
  75. }
  76. return Task.CompletedTask;
  77. }
  78. public Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, string zoneName, uint appRecordTtl, string appRecordData, bool isRecursionAllowed, IDnsServer dnsServer)
  79. {
  80. switch (request.Question[0].Type)
  81. {
  82. case DnsResourceRecordType.A:
  83. case DnsResourceRecordType.AAAA:
  84. Location location = null;
  85. if (_mmCityReader.TryCity(remoteEP.Address, out CityResponse response))
  86. location = response.Location;
  87. dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData);
  88. dynamic jsonClosestServer = null;
  89. if ((location == null) || !location.HasCoordinates)
  90. {
  91. jsonClosestServer = jsonAppRecordData[0];
  92. }
  93. else
  94. {
  95. double lastDistance = double.MaxValue;
  96. foreach (dynamic jsonServer in jsonAppRecordData)
  97. {
  98. double lat = Convert.ToDouble(jsonServer.lat.Value);
  99. double @long = Convert.ToDouble(jsonServer.@long.Value);
  100. double distance = GetDistance(lat, @long, location.Latitude.Value, location.Longitude.Value);
  101. if (distance < lastDistance)
  102. {
  103. lastDistance = distance;
  104. jsonClosestServer = jsonServer;
  105. }
  106. }
  107. }
  108. if (jsonClosestServer == null)
  109. return Task.FromResult<DnsDatagram>(null);
  110. List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
  111. foreach (dynamic jsonAddress in jsonClosestServer.addresses)
  112. {
  113. IPAddress address = IPAddress.Parse(jsonAddress.Value);
  114. switch (request.Question[0].Type)
  115. {
  116. case DnsResourceRecordType.A:
  117. if (address.AddressFamily == AddressFamily.InterNetwork)
  118. answers.Add(new DnsResourceRecord(request.Question[0].Name, DnsResourceRecordType.A, DnsClass.IN, appRecordTtl, new DnsARecord(address)));
  119. break;
  120. case DnsResourceRecordType.AAAA:
  121. if (address.AddressFamily == AddressFamily.InterNetworkV6)
  122. answers.Add(new DnsResourceRecord(request.Question[0].Name, DnsResourceRecordType.AAAA, DnsClass.IN, appRecordTtl, new DnsAAAARecord(address)));
  123. break;
  124. }
  125. }
  126. if (answers.Count == 0)
  127. return Task.FromResult<DnsDatagram>(null);
  128. return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers));
  129. default:
  130. return Task.FromResult<DnsDatagram>(null);
  131. }
  132. }
  133. #endregion
  134. #region properties
  135. public string Description
  136. { get { return "Returns A or AAAA records of the server located geographically closest to the client using MaxMind GeoIP2 City database. Use the geographic coordinates in decimal degrees (DD) form for the city the server is located in."; } }
  137. public string ApplicationRecordDataTemplate
  138. {
  139. get
  140. {
  141. return @"[
  142. {
  143. ""name"": ""server1-mumbai"",
  144. ""lat"": ""19.07283"",
  145. ""long"": ""72.88261"",
  146. ""addresses"": [
  147. ""1.1.1.1""
  148. ]
  149. },
  150. {
  151. ""name"": ""server2-london"",
  152. ""lat"": ""51.50853"",
  153. ""long"": ""-0.12574"",
  154. ""addresses"": [
  155. ""2.2.2.2""
  156. ]
  157. }
  158. ]";
  159. }
  160. }
  161. #endregion
  162. }
  163. }