bindy/bind9/records/
srv.rs1use super::super::types::{RndcKeyData, SRVRecordData};
7use super::should_update_record;
8use anyhow::{Context, 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)]
29pub async fn add_srv_record(
30 zone_name: &str,
31 name: &str,
32 srv_data: &SRVRecordData,
33 server: &str,
34 key_data: &RndcKeyData,
35) -> Result<()> {
36 use hickory_client::rr::RecordType;
37
38 let srv_data_for_comparison = srv_data.clone();
40 let should_update = should_update_record(
41 zone_name,
42 name,
43 RecordType::SRV,
44 "SRV",
45 server,
46 |existing_records| {
47 if existing_records.len() == 1 {
49 if let Some(RData::SRV(existing_srv)) = existing_records[0].data() {
50 let priority_match = existing_srv.priority()
51 == u16::try_from(srv_data_for_comparison.priority).unwrap_or(0);
52 let weight_match = existing_srv.weight()
53 == u16::try_from(srv_data_for_comparison.weight).unwrap_or(0);
54 let port_match = existing_srv.port()
55 == u16::try_from(srv_data_for_comparison.port).unwrap_or(0);
56 let target_match =
57 existing_srv.target().to_string() == srv_data_for_comparison.target;
58 return priority_match && weight_match && port_match && target_match;
59 }
60 }
61 false
62 },
63 )
64 .await?;
65
66 if !should_update {
67 return Ok(());
68 }
69
70 let zone_name_str = zone_name.to_string();
71 let name_str = name.to_string();
72 let target_str = srv_data.target.clone();
73 let port = srv_data.port;
74 let priority = srv_data.priority;
75 let weight = srv_data.weight;
76 let ttl = srv_data.ttl;
77 let server_str = server.to_string();
78 let key_data = key_data.clone();
79
80 tokio::task::spawn_blocking(move || {
81 let server_addr = server_str.parse::<std::net::SocketAddr>().context(format!(
82 "Invalid server address for SRV record update: {server_str}"
83 ))?;
84
85 let conn = UdpClientConnection::new(server_addr)
86 .context("Failed to create UDP connection for SRV record")?;
87
88 let signer = create_tsig_signer(&key_data)
89 .context("Failed to create TSIG signer for SRV record")?;
90
91 let client = SyncClient::with_tsigner(conn, signer);
92
93 let fqdn_str = if name_str.is_empty() || name_str == "@" {
94 zone_name_str.clone()
95 } else {
96 format!("{name_str}.{zone_name_str}")
97 };
98
99 let fqdn = Name::from_str(&fqdn_str)
100 .context(format!("Invalid FQDN for SRV record: {fqdn_str}"))?;
101
102 let zone = Name::from_str(&zone_name_str)
103 .context(format!("Invalid zone name for SRV: {zone_name_str}"))?;
104
105 let target_name = Name::from_str(&target_str)
106 .context(format!("Invalid target for SRV record: {target_str}"))?;
107
108 let ttl_value = u32::try_from(ttl.unwrap_or(DEFAULT_DNS_RECORD_TTL_SECS))
109 .unwrap_or(u32::try_from(DEFAULT_DNS_RECORD_TTL_SECS).unwrap_or(300));
110
111 let priority_u16 =
113 u16::try_from(priority).context(format!("Invalid SRV priority: {priority}"))?;
114 let weight_u16 =
115 u16::try_from(weight).context(format!("Invalid SRV weight: {weight}"))?;
116 let port_u16 = u16::try_from(port).context(format!("Invalid SRV port: {port}"))?;
117
118 let record_data = rdata::SRV::new(priority_u16, weight_u16, port_u16, target_name);
119
120 let mut record = Record::from_rdata(fqdn.clone(), ttl_value, RData::SRV(record_data));
121 record.set_dns_class(DNSClass::IN);
122
123 let response = client
125 .append(record, zone.clone(), false)
126 .context(format!("Failed to send SRV record update for {fqdn_str}"))?;
127
128 match response.response_code() {
129 ResponseCode::NoError => {
130 info!(
131 "Successfully added SRV record: {} -> {}:{} (priority: {}, weight: {}, TTL: {})",
132 fqdn_str, target_str, port, priority, weight, ttl_value
133 );
134 }
135 code => {
136 anyhow::bail!("DNS server rejected SRV record update for {fqdn_str}: {code:?}");
137 }
138 }
139
140 Ok(())
141 })
142 .await
143 .context("SRV record update task panicked")??;
144
145 Ok(())
146}
147
148#[cfg(test)]
149#[path = "srv_tests.rs"]
150mod srv_tests;