CNAME.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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 MaxMind.GeoIP2.Responses;
  17. using System;
  18. using System.Collections.Generic;
  19. using System.Net;
  20. using System.Text.Json;
  21. using System.Threading.Tasks;
  22. using TechnitiumLibrary.Net.Dns;
  23. using TechnitiumLibrary.Net.Dns.EDnsOptions;
  24. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  25. namespace GeoContinent
  26. {
  27. public sealed class CNAME : IDnsApplication, IDnsAppRecordRequestHandler
  28. {
  29. #region variables
  30. IDnsServer _dnsServer;
  31. MaxMind _maxMind;
  32. #endregion
  33. #region IDisposable
  34. bool _disposed;
  35. private void Dispose(bool disposing)
  36. {
  37. if (_disposed)
  38. return;
  39. if (disposing)
  40. {
  41. if (_maxMind is not null)
  42. _maxMind.Dispose();
  43. }
  44. _disposed = true;
  45. }
  46. public void Dispose()
  47. {
  48. Dispose(true);
  49. }
  50. #endregion
  51. #region public
  52. public Task InitializeAsync(IDnsServer dnsServer, string config)
  53. {
  54. _dnsServer = dnsServer;
  55. _maxMind = MaxMind.Create(dnsServer);
  56. return Task.CompletedTask;
  57. }
  58. public Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
  59. {
  60. DnsQuestionRecord question = request.Question[0];
  61. if (!question.Name.Equals(appRecordName, StringComparison.OrdinalIgnoreCase))
  62. return Task.FromResult<DnsDatagram>(null);
  63. using JsonDocument jsonDocument = JsonDocument.Parse(appRecordData);
  64. JsonElement jsonAppRecordData = jsonDocument.RootElement;
  65. JsonElement jsonContinent = default;
  66. bool ecsUsed = false;
  67. EDnsClientSubnetOptionData requestECS = request.GetEDnsClientSubnetOption();
  68. if (requestECS is not null)
  69. {
  70. if (_maxMind.DatabaseReader.TryCountry(requestECS.Address, out CountryResponse csResponse))
  71. {
  72. ecsUsed = true;
  73. if (!jsonAppRecordData.TryGetProperty(csResponse.Continent.Code, out jsonContinent))
  74. jsonAppRecordData.TryGetProperty("default", out jsonContinent);
  75. }
  76. }
  77. if (jsonContinent.ValueKind == JsonValueKind.Undefined)
  78. {
  79. if (_maxMind.DatabaseReader.TryCountry(remoteEP.Address, out CountryResponse response))
  80. {
  81. if (!jsonAppRecordData.TryGetProperty(response.Continent.Code, out jsonContinent))
  82. jsonAppRecordData.TryGetProperty("default", out jsonContinent);
  83. }
  84. else
  85. {
  86. jsonAppRecordData.TryGetProperty("default", out jsonContinent);
  87. }
  88. if (jsonContinent.ValueKind == JsonValueKind.Undefined)
  89. return Task.FromResult<DnsDatagram>(null);
  90. }
  91. string cname = jsonContinent.GetString();
  92. if (string.IsNullOrEmpty(cname))
  93. return Task.FromResult<DnsDatagram>(null);
  94. IReadOnlyList<DnsResourceRecord> answers;
  95. if (question.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase)) //check for zone apex
  96. answers = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.ANAME, DnsClass.IN, appRecordTtl, new DnsANAMERecordData(cname)) }; //use ANAME
  97. else
  98. answers = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.CNAME, DnsClass.IN, appRecordTtl, new DnsCNAMERecordData(cname)) };
  99. EDnsOption[] options;
  100. if (requestECS is null)
  101. {
  102. options = null;
  103. }
  104. else
  105. {
  106. if (ecsUsed)
  107. options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, requestECS.SourcePrefixLength, requestECS.Address);
  108. else
  109. options = EDnsClientSubnetOptionData.GetEDnsClientSubnetOption(requestECS.SourcePrefixLength, 0, requestECS.Address);
  110. }
  111. return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers, null, null, _dnsServer.UdpPayloadSize, EDnsHeaderFlags.None, options));
  112. }
  113. #endregion
  114. #region properties
  115. public string Description
  116. { get { return "Returns CNAME record based on the continent the client queries from using MaxMind GeoIP2 Country database. Note that the app will return ANAME record for an APP record at zone apex. Use the two character continent code like \"NA\" (North America) or \"OC\" (Oceania)."; } }
  117. public string ApplicationRecordDataTemplate
  118. {
  119. get
  120. {
  121. return @"{
  122. ""EU"": ""eu.example.com"",
  123. ""default"": ""example.com""
  124. }";
  125. }
  126. }
  127. #endregion
  128. }
  129. }