bindy/bind9/records/
ptr.rs1use super::super::types::{PTRRecordData, RndcKeyData};
7use super::{
8 build_authenticated_client, build_delete_rrset_record, build_record_fqdn, effective_record_ttl,
9 rrset_ttl_matches, should_update_record,
10};
11use anyhow::{Context, Result};
12use hickory_net::client::ClientHandle;
13use hickory_proto::op::ResponseCode;
14use hickory_proto::rr::{rdata, DNSClass, Name, RData, Record, RecordType};
15use std::str::FromStr;
16use tracing::info;
17
18fn compare_ptr_rrset(existing_records: &[Record], target: &str, desired_ttl: u32) -> bool {
31 if existing_records.len() != 1 {
32 return false;
33 }
34 if !rrset_ttl_matches(existing_records, desired_ttl) {
35 return false;
36 }
37 let RData::PTR(existing_ptr) = &existing_records[0].data else {
38 return false;
39 };
40 existing_ptr.0.to_string() == target
41}
42
43#[allow(clippy::too_many_arguments)]
56pub async fn add_ptr_record(
57 zone_name: &str,
58 name: &str,
59 ptr_data: &PTRRecordData,
60 server: &str,
61 key_data: &RndcKeyData,
62) -> Result<()> {
63 let ttl_value = effective_record_ttl(ptr_data.ttl);
64
65 let should_update = should_update_record(
66 zone_name,
67 name,
68 RecordType::PTR,
69 "PTR",
70 server,
71 |existing_records| compare_ptr_rrset(existing_records, &ptr_data.target, ttl_value),
72 )
73 .await?;
74
75 if !should_update {
76 return Ok(());
77 }
78
79 let zone =
80 Name::from_str(zone_name).context(format!("Invalid zone name for PTR: {zone_name}"))?;
81 let fqdn = build_record_fqdn(zone_name, name)?;
82 let target_name = Name::from_str(&ptr_data.target).context(format!(
83 "Invalid target for PTR record: {}",
84 ptr_data.target
85 ))?;
86
87 let mut record =
88 Record::from_rdata(fqdn.clone(), ttl_value, RData::PTR(rdata::PTR(target_name)));
89 record.dns_class = DNSClass::IN;
90
91 let mut client = build_authenticated_client(server, key_data).await?;
92
93 let delete_record = build_delete_rrset_record(&fqdn, RecordType::PTR);
95 let _ = client.delete_rrset(delete_record, zone.clone()).await;
96
97 let response = client
99 .append(record, zone, false)
100 .await
101 .context(format!("Failed to send PTR record update for {fqdn}"))?;
102
103 match response.metadata.response_code {
104 ResponseCode::NoError => {
105 info!(
106 "Successfully added PTR record: {} -> {} (TTL: {})",
107 fqdn, ptr_data.target, ttl_value
108 );
109 Ok(())
110 }
111 code => {
112 anyhow::bail!("DNS server rejected PTR record update for {fqdn}: {code:?}");
113 }
114 }
115}
116
117#[cfg(test)]
118#[path = "ptr_tests.rs"]
119mod ptr_tests;