bindy/bind9/records/
cname.rs1use super::super::types::RndcKeyData;
7use super::should_update_record;
8use anyhow::Result;
9use hickory_client::client::{Client, SyncClient};
10use hickory_client::op::ResponseCode;
11use hickory_client::rr::{rdata, DNSClass, Name, RData, Record};
12use hickory_client::udp::UdpClientConnection;
13use std::str::FromStr;
14use tracing::info;
15
16use crate::bind9::rndc::create_tsig_signer;
17use crate::constants::DEFAULT_DNS_RECORD_TTL_SECS;
18
19#[allow(clippy::too_many_arguments)]
25pub async fn add_cname_record(
26 zone_name: &str,
27 name: &str,
28 target: &str,
29 ttl: Option<i32>,
30 server: &str,
31 key_data: &RndcKeyData,
32) -> Result<()> {
33 use hickory_client::rr::RecordType;
34
35 let target_for_comparison = target.to_string();
37 let should_update = should_update_record(
38 zone_name,
39 name,
40 RecordType::CNAME,
41 "CNAME",
42 server,
43 |existing_records| {
44 if existing_records.len() == 1 {
46 if let Some(RData::CNAME(existing_cname)) = existing_records[0].data() {
47 return existing_cname.0.to_string() == target_for_comparison;
48 }
49 }
50 false
51 },
52 )
53 .await?;
54
55 if !should_update {
56 return Ok(());
57 }
58
59 let zone_name_str = zone_name.to_string();
60 let name_str = name.to_string();
61 let target_str = target.to_string();
62 let server_str = server.to_string();
63 let ttl_value = u32::try_from(ttl.unwrap_or(DEFAULT_DNS_RECORD_TTL_SECS))
64 .unwrap_or(u32::try_from(DEFAULT_DNS_RECORD_TTL_SECS).unwrap_or(300));
65 let key_data = key_data.clone();
66
67 tokio::task::spawn_blocking(move || {
68 let server_addr = server_str.parse::<std::net::SocketAddr>()?;
69 let conn = UdpClientConnection::new(server_addr)?;
70 let signer = create_tsig_signer(&key_data)?;
71 let client = SyncClient::with_tsigner(conn, signer);
72
73 let zone = Name::from_str(&zone_name_str)?;
74 let fqdn = if name_str == "@" || name_str.is_empty() {
75 zone.clone()
76 } else {
77 Name::from_str(&format!("{name_str}.{zone_name_str}"))?
78 };
79
80 let target_name = Name::from_str(&target_str)?;
81 let cname_rdata = rdata::CNAME(target_name);
82 let mut record = Record::from_rdata(fqdn.clone(), ttl_value, RData::CNAME(cname_rdata));
83 record.set_dns_class(DNSClass::IN);
84
85 info!(
86 "Adding CNAME record: {} -> {} (TTL: {})",
87 record.name(),
88 target_str,
89 ttl_value
90 );
91 let response = client.append(record, zone, false)?;
93
94 match response.response_code() {
95 ResponseCode::NoError => {
96 info!(
97 "Successfully added CNAME record: {} -> {}",
98 name_str, target_str
99 );
100 Ok(())
101 }
102 code => Err(anyhow::anyhow!(
103 "DNS update failed with response code: {code:?}"
104 )),
105 }
106 })
107 .await?
108}
109
110#[cfg(test)]
111#[path = "cname_tests.rs"]
112mod cname_tests;