bindy/bind9/records/
srv.rs1use super::super::types::{RndcKeyData, SRVRecordData};
7use super::{build_authenticated_client, build_record_fqdn, should_update_record};
8use anyhow::{Context, Result};
9use hickory_net::client::ClientHandle;
10use hickory_proto::op::ResponseCode;
11use hickory_proto::rr::{rdata, DNSClass, Name, RData, Record, RecordType};
12use std::str::FromStr;
13use tracing::info;
14
15use crate::constants::DEFAULT_DNS_RECORD_TTL_SECS;
16
17#[allow(clippy::too_many_arguments)]
27pub async fn add_srv_record(
28 zone_name: &str,
29 name: &str,
30 srv_data: &SRVRecordData,
31 server: &str,
32 key_data: &RndcKeyData,
33) -> Result<()> {
34 let srv_data_for_comparison = srv_data.clone();
35 let priority_u16 = u16::try_from(srv_data.priority)
36 .context(format!("Invalid SRV priority: {}", srv_data.priority))?;
37 let weight_u16 = u16::try_from(srv_data.weight)
38 .context(format!("Invalid SRV weight: {}", srv_data.weight))?;
39 let port_u16 =
40 u16::try_from(srv_data.port).context(format!("Invalid SRV port: {}", srv_data.port))?;
41
42 let should_update = should_update_record(
43 zone_name,
44 name,
45 RecordType::SRV,
46 "SRV",
47 server,
48 |existing_records| {
49 if existing_records.len() == 1 {
50 if let RData::SRV(existing_srv) = &existing_records[0].data {
51 let priority_match = existing_srv.priority
52 == u16::try_from(srv_data_for_comparison.priority).unwrap_or(0);
53 let weight_match = existing_srv.weight
54 == u16::try_from(srv_data_for_comparison.weight).unwrap_or(0);
55 let port_match = existing_srv.port
56 == u16::try_from(srv_data_for_comparison.port).unwrap_or(0);
57 let target_match =
58 existing_srv.target.to_string() == srv_data_for_comparison.target;
59 return priority_match && weight_match && port_match && target_match;
60 }
61 }
62 false
63 },
64 )
65 .await?;
66
67 if !should_update {
68 return Ok(());
69 }
70
71 let ttl_value = u32::try_from(srv_data.ttl.unwrap_or(DEFAULT_DNS_RECORD_TTL_SECS))
72 .unwrap_or(u32::try_from(DEFAULT_DNS_RECORD_TTL_SECS).unwrap_or(300));
73
74 let zone =
75 Name::from_str(zone_name).context(format!("Invalid zone name for SRV: {zone_name}"))?;
76 let fqdn = build_record_fqdn(zone_name, name)?;
77 let target_name = Name::from_str(&srv_data.target).context(format!(
78 "Invalid target for SRV record: {}",
79 srv_data.target
80 ))?;
81
82 let record_data = rdata::SRV::new(priority_u16, weight_u16, port_u16, target_name);
83 let mut record = Record::from_rdata(fqdn.clone(), ttl_value, RData::SRV(record_data));
84 record.dns_class = DNSClass::IN;
85
86 let mut client = build_authenticated_client(server, key_data).await?;
87 let response = client
88 .append(record, zone, false)
89 .await
90 .context(format!("Failed to send SRV record update for {fqdn}"))?;
91
92 match response.metadata.response_code {
93 ResponseCode::NoError => {
94 info!(
95 "Successfully added SRV record: {} -> {}:{} (priority: {}, weight: {}, TTL: {})",
96 fqdn, srv_data.target, srv_data.port, srv_data.priority, srv_data.weight, ttl_value
97 );
98 Ok(())
99 }
100 code => {
101 anyhow::bail!("DNS server rejected SRV record update for {fqdn}: {code:?}");
102 }
103 }
104}
105
106#[cfg(test)]
107#[path = "srv_tests.rs"]
108mod srv_tests;