Address.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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.Net;
  19. using System.Security.Cryptography;
  20. using System.Text.Json;
  21. using System.Threading.Tasks;
  22. using TechnitiumLibrary.Net.Dns;
  23. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  24. namespace WeightedRoundRobin
  25. {
  26. public sealed class Address : IDnsApplication, IDnsAppRecordRequestHandler
  27. {
  28. #region IDisposable
  29. public void Dispose()
  30. {
  31. //do nothing
  32. }
  33. #endregion
  34. #region public
  35. public Task InitializeAsync(IDnsServer dnsServer, string config)
  36. {
  37. return Task.CompletedTask;
  38. }
  39. public Task<DnsDatagram> ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData)
  40. {
  41. DnsQuestionRecord question = request.Question[0];
  42. if (!question.Name.Equals(appRecordName, StringComparison.OrdinalIgnoreCase) && !appRecordName.StartsWith('*'))
  43. return Task.FromResult<DnsDatagram>(null);
  44. string jsonPropertyName;
  45. switch (question.Type)
  46. {
  47. case DnsResourceRecordType.A:
  48. jsonPropertyName = "ipv4Addresses";
  49. break;
  50. case DnsResourceRecordType.AAAA:
  51. jsonPropertyName = "ipv6Addresses";
  52. break;
  53. default:
  54. return Task.FromResult<DnsDatagram>(null);
  55. }
  56. List<WeightedAddress> addresses;
  57. int totalWeight = 0;
  58. using (JsonDocument jsonDocument = JsonDocument.Parse(appRecordData))
  59. {
  60. JsonElement jsonAppRecordData = jsonDocument.RootElement;
  61. if (!jsonAppRecordData.TryGetProperty(jsonPropertyName, out JsonElement jsonAddresses) || (jsonAddresses.ValueKind == JsonValueKind.Null))
  62. return Task.FromResult<DnsDatagram>(null);
  63. addresses = new List<WeightedAddress>(jsonAddresses.GetArrayLength());
  64. foreach (JsonElement jsonAddressEntry in jsonAddresses.EnumerateArray())
  65. {
  66. if (jsonAddressEntry.TryGetProperty("enabled", out JsonElement jsonEnabled) && (jsonEnabled.ValueKind != JsonValueKind.Null) && !jsonEnabled.GetBoolean())
  67. continue;
  68. if (!jsonAddressEntry.TryGetProperty("address", out JsonElement jsonAddress) || (jsonAddress.ValueKind == JsonValueKind.Null) || !IPAddress.TryParse(jsonAddress.GetString(), out IPAddress address))
  69. continue;
  70. if (!jsonAddressEntry.TryGetProperty("weight", out JsonElement jsonWeight) || (jsonWeight.ValueKind == JsonValueKind.Null))
  71. continue;
  72. int weight = jsonWeight.GetInt32();
  73. if (weight < 1)
  74. continue;
  75. addresses.Add(new WeightedAddress() { Address = address, Weight = weight });
  76. totalWeight += weight;
  77. }
  78. }
  79. if (addresses.Count == 0)
  80. return Task.FromResult<DnsDatagram>(null);
  81. int randomSelection = RandomNumberGenerator.GetInt32(1, 101);
  82. int rangeFrom;
  83. int rangeTo = 0;
  84. DnsResourceRecord answer = null;
  85. for (int i = 0; i < addresses.Count; i++)
  86. {
  87. rangeFrom = rangeTo + 1;
  88. if (i == addresses.Count - 1)
  89. rangeTo = 100;
  90. else
  91. rangeTo += addresses[i].Weight * 100 / totalWeight;
  92. if ((rangeFrom <= randomSelection) && (randomSelection <= rangeTo))
  93. {
  94. switch (question.Type)
  95. {
  96. case DnsResourceRecordType.A:
  97. answer = new DnsResourceRecord(question.Name, question.Type, DnsClass.IN, appRecordTtl, new DnsARecordData(addresses[i].Address));
  98. break;
  99. case DnsResourceRecordType.AAAA:
  100. answer = new DnsResourceRecord(question.Name, question.Type, DnsClass.IN, appRecordTtl, new DnsAAAARecordData(addresses[i].Address));
  101. break;
  102. default:
  103. throw new InvalidOperationException();
  104. }
  105. break;
  106. }
  107. }
  108. if (answer is null)
  109. throw new InvalidOperationException();
  110. return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, new DnsResourceRecord[] { answer }));
  111. }
  112. #endregion
  113. #region properties
  114. public string Description
  115. { get { return "Returns an A or AAAA record using weighted round-robin load balancing."; } }
  116. public string ApplicationRecordDataTemplate
  117. {
  118. get
  119. {
  120. return @"{
  121. ""ipv4Addresses"": [
  122. {
  123. ""address"": ""1.1.1.1"",
  124. ""weight"": 5,
  125. ""enabled"": true
  126. },
  127. {
  128. ""address"": ""2.2.2.2"",
  129. ""weight"": 3,
  130. ""enabled"": true
  131. }
  132. ],
  133. ""ipv6Addresses"": [
  134. {
  135. ""address"": ""::1"",
  136. ""weight"": 2,
  137. ""enabled"": true
  138. },
  139. {
  140. ""address"": ""::2"",
  141. ""weight"": 3,
  142. ""enabled"": true
  143. }
  144. ]
  145. }";
  146. }
  147. }
  148. #endregion
  149. struct WeightedAddress
  150. {
  151. public IPAddress Address;
  152. public int Weight;
  153. }
  154. }
  155. }