1use crate::dns_string::DnsString;
2use crate::{
3 read_exact, read_u16_be, read_u32_be, write_bytes, write_u16_be, write_u32_be, DnsClass,
4 DnsError, DnsName, DnsType,
5};
6use core::fmt::{Debug, Formatter};
7use fixed_buffer::FixedBuf;
8use std::convert::TryFrom;
9use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
10
11fn check_empty<const SIZE: usize>(f: &FixedBuf<SIZE>) -> Result<(), DnsError> {
12 if f.is_empty() {
13 Ok(())
14 } else {
15 Err(DnsError::RecordHasAdditionalBytes)
16 }
17}
18
19#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
64pub enum DnsRecord {
65 A(DnsName, std::net::Ipv4Addr),
66 AAAA(DnsName, std::net::Ipv6Addr),
67 CNAME(DnsName, DnsName),
68 NS(DnsName, DnsName),
69 TXT(DnsName, Vec<DnsString>),
77 Unknown(DnsName, DnsType),
78}
79impl DnsRecord {
80 pub fn read_rdata<const N: usize>(buf: &mut FixedBuf<N>) -> Result<FixedBuf<65535>, DnsError> {
83 let len = read_u16_be(buf)?;
84 if buf.len() < (len as usize) {
85 return Err(DnsError::Truncated);
86 }
87 let borrowed_rdata = buf.read_bytes(len as usize);
88 let mut rdata: FixedBuf<65535> = FixedBuf::new();
89 rdata
90 .write_bytes(borrowed_rdata)
91 .map_err(|_| DnsError::Unreachable(file!(), line!()))?;
92 Ok(rdata)
93 }
94
95 pub fn write_rdata<const N: usize>(
98 bytes: &[u8],
99 out: &mut FixedBuf<N>,
100 ) -> Result<(), DnsError> {
101 let len =
102 u16::try_from(bytes.len()).map_err(|_| DnsError::Unreachable(file!(), line!()))?;
103 write_u16_be(out, len)?;
104 write_bytes(out, bytes)?;
105 Ok(())
106 }
107
108 pub fn new_a(name: &str, ipv4_addr: &str) -> Result<Self, String> {
112 let dns_name = DnsName::new(name)?;
113 let ip_addr: IpAddr = ipv4_addr
114 .parse()
115 .map_err(|e| format!("failed parsing {ipv4_addr:?} as an IP address: {e}"))?;
116 match ip_addr {
117 IpAddr::V4(addr) => Ok(Self::A(dns_name, addr)),
118 IpAddr::V6(addr) => Err(format!(
119 "cannot create an A record with ipv6 address {addr:?}"
120 )),
121 }
122 }
123
124 pub fn new_aaaa(name: &str, ipv6_addr: &str) -> Result<Self, String> {
128 let dns_name = DnsName::new(name)?;
129 let ip_addr: IpAddr = ipv6_addr
130 .parse()
131 .map_err(|e| format!("failed parsing {ipv6_addr:?} as an IP address: {e}"))?;
132 match ip_addr {
133 IpAddr::V4(addr) => Err(format!(
134 "cannot create an AAAA record with ipv4 address {addr:?}"
135 )),
136 IpAddr::V6(addr) => Ok(Self::AAAA(dns_name, addr)),
137 }
138 }
139
140 pub fn new_cname(name: &str, target: &str) -> Result<Self, String> {
143 let dns_name = DnsName::new(name)?;
144 let dns_name_target = DnsName::new(target)?;
145 Ok(Self::CNAME(dns_name, dns_name_target))
146 }
147
148 pub fn new_ns(name: &str, target: &str) -> Result<Self, String> {
151 let dns_name = DnsName::new(name)?;
152 let dns_name_target = DnsName::new(target)?;
153 Ok(Self::NS(dns_name, dns_name_target))
154 }
155
156 pub fn new_txt(name: &str, content: &str) -> Result<Self, String> {
159 let dns_name = DnsName::new(name)?;
160 let dns_string = DnsString::new(content)?;
161 Ok(Self::TXT(dns_name, vec![dns_string]))
162 }
163
164 pub fn new_txt_multi(name: &str, lines: &[&str]) -> Result<Self, String> {
167 let dns_name = DnsName::new(name)?;
168 let mut dns_strings = Vec::new();
169 for line in lines {
170 dns_strings.push(DnsString::new(line)?);
171 }
172 Ok(Self::TXT(dns_name, dns_strings))
173 }
174
175 #[must_use]
176 pub fn name(&self) -> &DnsName {
177 match self {
178 DnsRecord::A(dns_name, _)
179 | DnsRecord::AAAA(dns_name, _)
180 | DnsRecord::CNAME(dns_name, _)
181 | DnsRecord::NS(dns_name, _)
182 | DnsRecord::TXT(dns_name, _)
183 | DnsRecord::Unknown(dns_name, _) => dns_name,
184 }
185 }
186
187 #[must_use]
188 pub fn typ(&self) -> DnsType {
189 match self {
190 DnsRecord::A(..) => DnsType::A,
191 DnsRecord::AAAA(..) => DnsType::AAAA,
192 DnsRecord::CNAME(..) => DnsType::CNAME,
193 DnsRecord::NS(..) => DnsType::NS,
194 DnsRecord::TXT(..) => DnsType::TXT,
195 DnsRecord::Unknown(_, typ) => DnsType::Unknown(typ.num()),
196 }
197 }
198
199 pub fn read<const N: usize>(buf: &mut FixedBuf<N>) -> Result<Self, DnsError> {
202 let name = DnsName::read(buf)?;
203 let typ = DnsType::read(buf)?;
204 let class = DnsClass::read(buf)?;
205 if class != DnsClass::Internet && class != DnsClass::Any {
206 return Err(DnsError::InvalidClass);
207 }
208 let _ttl_seconds = read_u32_be(buf)?;
209 let mut rdata = Self::read_rdata(buf)?;
210 let record = match typ {
211 DnsType::A => {
212 let octets: [u8; 4] = read_exact(&mut rdata)?;
213 check_empty(&rdata)?;
214 DnsRecord::A(name, Ipv4Addr::from(octets))
215 }
216 DnsType::AAAA => {
217 let octets: [u8; 16] = read_exact(&mut rdata)?;
218 check_empty(&rdata)?;
219 DnsRecord::AAAA(name, Ipv6Addr::from(octets))
220 }
221 DnsType::CNAME => {
222 let target = DnsName::read(&mut rdata)?;
223 check_empty(&rdata)?;
224 DnsRecord::CNAME(name, target)
225 }
226 DnsType::NS => {
227 let target = DnsName::read(&mut rdata)?;
228 check_empty(&rdata)?;
229 DnsRecord::NS(name, target)
230 }
231 DnsType::TXT => {
232 let strings = DnsString::read_multiple(&mut rdata)?;
233 DnsRecord::TXT(name, strings)
234 }
235 DnsType::MX | DnsType::PTR | DnsType::SOA | DnsType::ANY | DnsType::Unknown(_) => {
236 DnsRecord::Unknown(name, typ)
237 }
238 };
239 Ok(record)
240 }
241
242 pub fn write<const N: usize>(&self, out: &mut FixedBuf<N>) -> Result<(), DnsError> {
245 self.name().write(out)?;
246 self.typ().write(out)?;
247 DnsClass::Internet.write(out)?;
248 write_u32_be(out, 300)?; #[allow(clippy::match_same_arms)]
250 match self {
251 DnsRecord::A(_, ipv4_addr) => Self::write_rdata(&ipv4_addr.octets(), out)?,
252 DnsRecord::AAAA(_, ipv6_addr) => Self::write_rdata(&ipv6_addr.octets(), out)?,
253 DnsRecord::CNAME(_, target_name) => {
254 Self::write_rdata(target_name.as_bytes()?.readable(), out)?;
255 }
256 DnsRecord::NS(_, target_name) => {
257 Self::write_rdata(target_name.as_bytes()?.readable(), out)?;
258 }
259 DnsRecord::TXT(_, strings) => {
260 let mut buf = Vec::new();
261 for string in strings {
262 let bytes = string.as_bytes()?;
263 buf.extend(bytes.readable());
264 }
265 Self::write_rdata(&buf, out)?;
266 }
267 DnsRecord::Unknown(_, _) => {
268 return Err(DnsError::Internal(format!("cannot write record {self:?}")))
269 }
270 }
271 Ok(())
272 }
273}
274impl Debug for DnsRecord {
275 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
276 match self {
277 DnsRecord::A(name, addr) => write!(f, "DnsRecord::A({name},{addr})"),
278 DnsRecord::AAAA(name, addr) => write!(f, "DnsRecord::AAAA({name},{addr})"),
279 DnsRecord::CNAME(name, target) => write!(f, "DnsRecord::CNAME({name},{target})"),
280 DnsRecord::NS(name, target) => write!(f, "DnsRecord::NS({name},{target})"),
281 DnsRecord::TXT(name, strings) => {
282 write!(f, "DnsRecord::TXT({name},['")?;
283 let mut first = true;
284 for string in strings {
285 if first {
286 first = false;
287 write!(f, "{string}")?;
288 } else {
289 write!(f, "', '{string}")?;
290 }
291 }
292 write!(f, "'])")
293 }
294 DnsRecord::Unknown(name, typ) => write!(f, "DnsRecord::Unknown({name},{typ})"),
295 }
296 }
297}
298
299#[cfg(test)]
300#[test]
301fn test_dns_record() {
302 use std::net::{Ipv4Addr, Ipv6Addr};
303 assert_eq!(
305 DnsRecord::A(DnsName::new("a.b").unwrap(), Ipv4Addr::new(1, 2, 3, 4)),
306 DnsRecord::new_a("a.b", "1.2.3.4").unwrap()
307 );
308 assert_eq!(
309 DnsRecord::AAAA(
310 DnsName::new("a.b").unwrap(),
311 Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)
312 ),
313 DnsRecord::new_aaaa("a.b", "2001:db8::").unwrap()
314 );
315 assert_eq!(
316 DnsRecord::CNAME(DnsName::new("a.b").unwrap(), DnsName::new("c.d").unwrap()),
317 DnsRecord::new_cname("a.b", "c.d").unwrap()
318 );
319 assert_eq!(
320 DnsRecord::NS(DnsName::new("a.b").unwrap(), DnsName::new("c.d").unwrap()),
321 DnsRecord::new_ns("a.b", "c.d").unwrap()
322 );
323 assert_eq!(
324 DnsRecord::TXT(
325 DnsName::new("a.b").unwrap(),
326 vec![DnsString::new("s1").unwrap()]
327 ),
328 DnsRecord::new_txt("a.b", "s1").unwrap()
329 );
330 assert_eq!(
332 "DnsRecord::A(a.b,1.2.3.4)",
333 format!(
334 "{:?}",
335 DnsRecord::A(DnsName::new("a.b").unwrap(), Ipv4Addr::new(1, 2, 3, 4))
336 )
337 );
338 assert_eq!(
339 "DnsRecord::AAAA(a.b,2001:db8::)",
340 format!(
341 "{:?}",
342 DnsRecord::AAAA(
343 DnsName::new("a.b").unwrap(),
344 Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)
345 )
346 )
347 );
348 assert_eq!(
349 "DnsRecord::CNAME(a.b,c.d)",
350 format!(
351 "{:?}",
352 DnsRecord::CNAME(DnsName::new("a.b").unwrap(), DnsName::new("c.d").unwrap())
353 )
354 );
355 assert_eq!(
356 "DnsRecord::NS(a.b,c.d)",
357 format!(
358 "{:?}",
359 DnsRecord::NS(DnsName::new("a.b").unwrap(), DnsName::new("c.d").unwrap())
360 )
361 );
362 assert_eq!(
363 "DnsRecord::TXT(a.b,['s1'])",
364 format!("{:?}", DnsRecord::new_txt("a.b", "s1").unwrap())
365 );
366 assert_eq!(
367 "DnsRecord::TXT(a.b,['s1', 's2'])",
368 format!(
369 "{:?}",
370 DnsRecord::new_txt_multi("a.b", &["s1", "s2"]).unwrap()
371 )
372 );
373}