diff --git a/src/Extensions/Cloudflare.Dns/DnsFirewallExtensions.cs b/src/Extensions/Cloudflare.Dns/DnsFirewallExtensions.cs
index cc7332b..287ce6e 100644
--- a/src/Extensions/Cloudflare.Dns/DnsFirewallExtensions.cs
+++ b/src/Extensions/Cloudflare.Dns/DnsFirewallExtensions.cs
@@ -9,6 +9,8 @@ namespace AMWD.Net.Api.Cloudflare.Dns
///
public static class DnsFirewallExtensions
{
+ #region DNS Firewall
+
///
/// List DNS Firewall clusters for an account.
///
@@ -150,5 +152,45 @@ namespace AMWD.Net.Api.Cloudflare.Dns
return client.DeleteAsync($"/accounts/{accountId}/dns_firewall/{dnsFirewallId}", null, cancellationToken);
}
+
+ #endregion DNS Firewall
+
+ #region Reverse DNS
+
+ ///
+ /// Show reverse DNS configuration (PTR records) for a DNS Firewall cluster
+ ///
+ /// The instance.
+ /// The account identifier.
+ /// The DNS firewall identifier.
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ public static Task> ShowDNSFirewallClusterReverseDNS(this ICloudflareClient client, string accountId, string dnsFirewallId, CancellationToken cancellationToken = default)
+ {
+ accountId.ValidateCloudflareId();
+ dnsFirewallId.ValidateCloudflareId();
+
+ return client.GetAsync($"/accounts/{accountId}/dns_firewall/{dnsFirewallId}/reverse_dns", null, cancellationToken);
+ }
+
+ ///
+ /// Update reverse DNS configuration (PTR records) for a DNS Firewall cluster.
+ ///
+ /// The instance.
+ /// The request.
+ /// A cancellation token used to propagate notification that this operation should be canceled.
+ public static Task> UpdateDNSFirewallClusterReverseDNS(this ICloudflareClient client, UpdateDNSFirewallClusterReverseDNSRequest request, CancellationToken cancellationToken = default)
+ {
+ request.AccountId.ValidateCloudflareId();
+ request.DnsFirewallId.ValidateCloudflareId();
+
+ var req = new InternalUpdateDNSFirewallClusterReverseDNSRequest
+ {
+ Ptr = request.ReverseDNS
+ };
+
+ return client.PatchAsync($"/accounts/{request.AccountId}/dns_firewall/{request.DnsFirewallId}/reverse_dns", req, cancellationToken);
+ }
+
+ #endregion Reverse DNS
}
}
diff --git a/src/Extensions/Cloudflare.Dns/Internals/InternalUpdateDNSFirewallClusterReverseDNSRequest.cs b/src/Extensions/Cloudflare.Dns/Internals/InternalUpdateDNSFirewallClusterReverseDNSRequest.cs
new file mode 100644
index 0000000..4e61614
--- /dev/null
+++ b/src/Extensions/Cloudflare.Dns/Internals/InternalUpdateDNSFirewallClusterReverseDNSRequest.cs
@@ -0,0 +1,8 @@
+namespace AMWD.Net.Api.Cloudflare.Dns.Internals
+{
+ internal class InternalUpdateDNSFirewallClusterReverseDNSRequest
+ {
+ [JsonProperty("ptr")]
+ public IReadOnlyDictionary? Ptr { get; set; }
+ }
+}
diff --git a/src/Extensions/Cloudflare.Dns/README.md b/src/Extensions/Cloudflare.Dns/README.md
index 1a75866..2292e74 100644
--- a/src/Extensions/Cloudflare.Dns/README.md
+++ b/src/Extensions/Cloudflare.Dns/README.md
@@ -122,6 +122,13 @@ This package contains the feature set of the _DNS_ section of the Cloudflare API
- [Delete DNS Firewall Cluster](https://developers.cloudflare.com/api/resources/dns_firewall/methods/delete/)
+#### [Reverse DNS]
+
+- [Show DNS Firewall Cluster Reverse DNS](https://developers.cloudflare.com/api/resources/dns_firewall/subresources/reverse_dns/methods/get/)
+- [Update DNS Firewall Cluster Reverse DNS](https://developers.cloudflare.com/api/resources/dns_firewall/subresources/reverse_dns/methods/edit/)
+
+
+
---
Published under MIT License (see [choose a license])
@@ -146,3 +153,4 @@ Published under MIT License (see [choose a license])
[Peers]: https://developers.cloudflare.com/api/resources/dns/subresources/zone_transfers/subresources/peers/
[TSIGs]: https://developers.cloudflare.com/api/resources/dns/subresources/zone_transfers/subresources/tsigs/
[DNS Firewall]: https://developers.cloudflare.com/api/resources/dns_firewall/
+ [Reverse DNS]: https://developers.cloudflare.com/api/resources/dns_firewall/subresources/reverse_dns/
diff --git a/src/Extensions/Cloudflare.Dns/Requests/UpdateDNSFirewallClusterReverseDNSRequest.cs b/src/Extensions/Cloudflare.Dns/Requests/UpdateDNSFirewallClusterReverseDNSRequest.cs
new file mode 100644
index 0000000..e72b2ed
--- /dev/null
+++ b/src/Extensions/Cloudflare.Dns/Requests/UpdateDNSFirewallClusterReverseDNSRequest.cs
@@ -0,0 +1,34 @@
+namespace AMWD.Net.Api.Cloudflare.Dns
+{
+ ///
+ /// Represents a request to update the reverse DNS configuration for a DNS firewall cluster.
+ ///
+ public class UpdateDNSFirewallClusterReverseDNSRequest
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The account identifier.
+ /// The DNS firewall cluster identifier.
+ public UpdateDNSFirewallClusterReverseDNSRequest(string accountId, string dnsFirewallId)
+ {
+ AccountId = accountId;
+ DnsFirewallId = dnsFirewallId;
+ }
+
+ ///
+ /// The account identifier.
+ ///
+ public string AccountId { get; set; }
+
+ ///
+ /// The DNS firewall cluster identifier.
+ ///
+ public string DnsFirewallId { get; set; }
+
+ ///
+ /// Map of cluster IP addresses to PTR record contents.
+ ///
+ public IReadOnlyDictionary? ReverseDNS { get; set; }
+ }
+}
diff --git a/src/Extensions/Cloudflare.Dns/Responses/ReverseDnsResponse.cs b/src/Extensions/Cloudflare.Dns/Responses/ReverseDnsResponse.cs
new file mode 100644
index 0000000..3aed691
--- /dev/null
+++ b/src/Extensions/Cloudflare.Dns/Responses/ReverseDnsResponse.cs
@@ -0,0 +1,14 @@
+namespace AMWD.Net.Api.Cloudflare.Dns
+{
+ ///
+ /// Represents the response of reverse DNS records of a firewall cluster.
+ ///
+ public class ReverseDnsResponse
+ {
+ ///
+ /// Map of cluster IP addresses to PTR record contents.
+ ///
+ [JsonProperty("ptr")]
+ public IReadOnlyDictionary? ReverseDNS { get; set; }
+ }
+}
diff --git a/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/ShowDNSFirewallClusterReverseDNSTest.cs b/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/ShowDNSFirewallClusterReverseDNSTest.cs
new file mode 100644
index 0000000..07168c1
--- /dev/null
+++ b/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/ShowDNSFirewallClusterReverseDNSTest.cs
@@ -0,0 +1,79 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.Cloudflare;
+using AMWD.Net.Api.Cloudflare.Dns;
+using Moq;
+
+namespace Cloudflare.Dns.Tests.DnsFirewallExtensions.ReverseDns
+{
+ [TestClass]
+ public class ShowDNSFirewallClusterReverseDNSTest
+ {
+ public TestContext TestContext { get; set; }
+
+ private const string AccountId = "023e105f4ecef8ad9ca31a8372d0c353";
+ private const string ClusterId = "023e105f4ecef8ad9ca31a8372d0c355";
+
+ private Mock _clientMock;
+ private CloudflareResponse _response;
+ private List<(string RequestPath, IQueryParameterFilter QueryFilter)> _callbacks;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ _callbacks = [];
+
+ _response = new CloudflareResponse
+ {
+ Success = true,
+ Messages = [new ResponseInfo(1000, "Message 1")],
+ Errors = [new ResponseInfo(1000, "Error 1")],
+ Result = new ReverseDnsResponse
+ {
+ ReverseDNS = new Dictionary
+ {
+ ["192.0.2.1"] = "ptr.example.com"
+ }
+ }
+ };
+ }
+
+ [TestMethod]
+ public async Task ShouldShowDnsFirewallClusterReverseDns()
+ {
+ // Arrange
+ var client = GetClient();
+
+ // Act
+ var response = await client.ShowDNSFirewallClusterReverseDNS(AccountId, ClusterId, TestContext.CancellationToken);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.Success);
+ Assert.IsNotNull(response.Result);
+ Assert.IsTrue(response.Result.ReverseDNS?.ContainsKey("192.0.2.1") ?? false);
+ Assert.AreEqual("ptr.example.com", response.Result.ReverseDNS!["192.0.2.1"]);
+
+ Assert.HasCount(1, _callbacks);
+
+ var (requestPath, queryFilter) = _callbacks.First();
+ Assert.AreEqual($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", requestPath);
+ Assert.IsNull(queryFilter);
+
+ _clientMock.Verify(m => m.GetAsync($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", null, TestContext.CancellationToken), Times.Once);
+ _clientMock.VerifyNoOtherCalls();
+ }
+
+ private ICloudflareClient GetClient()
+ {
+ _clientMock = new Mock();
+ _clientMock
+ .Setup(m => m.GetAsync(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Callback((requestPath, queryFilter, _) => _callbacks.Add((requestPath, queryFilter)))
+ .ReturnsAsync(() => _response);
+
+ return _clientMock.Object;
+ }
+ }
+}
diff --git a/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/UpdateDNSFirewallClusterReverseDNSTest.cs b/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/UpdateDNSFirewallClusterReverseDNSTest.cs
new file mode 100644
index 0000000..8508141
--- /dev/null
+++ b/test/Extensions/Cloudflare.Dns.Tests/DnsFirewallExtensions/ReverseDns/UpdateDNSFirewallClusterReverseDNSTest.cs
@@ -0,0 +1,118 @@
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AMWD.Net.Api.Cloudflare;
+using AMWD.Net.Api.Cloudflare.Dns;
+using AMWD.Net.Api.Cloudflare.Dns.Internals;
+using Moq;
+
+namespace Cloudflare.Dns.Tests.DnsFirewallExtensions.ReverseDns
+{
+ [TestClass]
+ public class UpdateDNSFirewallClusterReverseDNSTest
+ {
+ public TestContext TestContext { get; set; }
+
+ private const string AccountId = "023e105f4ecef8ad9ca31a8372d0c353";
+ private const string ClusterId = "023e105f4ecef8ad9ca31a8372d0c355";
+
+ private Mock _clientMock;
+ private CloudflareResponse _response;
+ private List<(string RequestPath, InternalUpdateDNSFirewallClusterReverseDNSRequest Request)> _callbacks;
+ private UpdateDNSFirewallClusterReverseDNSRequest _request;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ _callbacks = [];
+
+ _response = new CloudflareResponse
+ {
+ Success = true,
+ Messages = [new ResponseInfo(1000, "Message 1")],
+ Errors = [new ResponseInfo(1000, "Error 1")],
+ Result = new ReverseDnsResponse
+ {
+ ReverseDNS = new Dictionary
+ {
+ ["10.0.0.1"] = "ptr1.example.com"
+ }
+ }
+ };
+
+ _request = new UpdateDNSFirewallClusterReverseDNSRequest(AccountId, ClusterId)
+ {
+ ReverseDNS = new Dictionary
+ {
+ ["10.0.0.1"] = "ptr1.example.com"
+ }
+ };
+ }
+
+ [TestMethod]
+ public async Task ShouldUpdateDnsFirewallClusterReverseDns()
+ {
+ // Arrange
+ var client = GetClient();
+
+ // Act
+ var response = await client.UpdateDNSFirewallClusterReverseDNS(_request, TestContext.CancellationToken);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.Success);
+ Assert.IsNotNull(response.Result);
+ Assert.IsTrue(response.Result.ReverseDNS?.ContainsKey("10.0.0.1") ?? false);
+ Assert.AreEqual("ptr1.example.com", response.Result.ReverseDNS!["10.0.0.1"]);
+
+ Assert.HasCount(1, _callbacks);
+
+ var (requestPath, request) = _callbacks.First();
+ Assert.AreEqual($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", requestPath);
+
+ Assert.IsNotNull(request);
+ Assert.IsNotNull(request.Ptr);
+ Assert.IsTrue(request.Ptr.ContainsKey("10.0.0.1"));
+ Assert.AreEqual("ptr1.example.com", request.Ptr["10.0.0.1"]);
+
+ _clientMock.Verify(m => m.PatchAsync($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", It.IsAny(), TestContext.CancellationToken), Times.Once);
+ _clientMock.VerifyNoOtherCalls();
+ }
+
+ [TestMethod]
+ public async Task ShouldUpdateDnsFirewallClusterReverseDnsMinimalSet()
+ {
+ // Arrange
+ _request = new UpdateDNSFirewallClusterReverseDNSRequest(AccountId, ClusterId);
+ var client = GetClient();
+
+ // Act
+ var response = await client.UpdateDNSFirewallClusterReverseDNS(_request, TestContext.CancellationToken);
+
+ // Assert
+ Assert.IsNotNull(response);
+ Assert.IsTrue(response.Success);
+ Assert.AreEqual(_response.Result, response.Result);
+
+ Assert.HasCount(1, _callbacks);
+
+ var (requestPath, request) = _callbacks.First();
+ Assert.AreEqual($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", requestPath);
+ Assert.IsNull(request.Ptr);
+
+ _clientMock.Verify(m => m.PatchAsync($"/accounts/{AccountId}/dns_firewall/{ClusterId}/reverse_dns", It.IsAny(), TestContext.CancellationToken), Times.Once);
+ _clientMock.VerifyNoOtherCalls();
+ }
+
+ private ICloudflareClient GetClient()
+ {
+ _clientMock = new Mock();
+ _clientMock
+ .Setup(m => m.PatchAsync(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Callback((requestPath, request, _) => _callbacks.Add((requestPath, request)))
+ .ReturnsAsync(() => _response);
+
+ return _clientMock.Object;
+ }
+ }
+}