Skip to content

Commit 4f2aef9

Browse files
committed
Added support for HTTPS (65) DNS record type.
1 parent 6950600 commit 4f2aef9

File tree

2 files changed

+118
-4
lines changed

2 files changed

+118
-4
lines changed

src/commons/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ pub fn is_yggdrasil_record(record: &DnsRecord) -> bool {
128128
DnsRecord::SRV { .. } => {}
129129
DnsRecord::OPT { .. } => {}
130130
DnsRecord::TLSA { .. } => {}
131+
DnsRecord::HTTPS { .. } => {}
131132
}
132133
true
133134
}

src/dns/protocol.rs

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub enum QueryType {
3838
SRV, // 33
3939
OPT, // 41
4040
TLSA, // 52
41+
HTTPS, // 65
4142
}
4243

4344
impl QueryType {
@@ -55,6 +56,7 @@ impl QueryType {
5556
QueryType::SRV => 33,
5657
QueryType::OPT => 41,
5758
QueryType::TLSA => 52,
59+
QueryType::HTTPS => 65,
5860
}
5961
}
6062

@@ -71,6 +73,7 @@ impl QueryType {
7173
33 => QueryType::SRV,
7274
41 => QueryType::OPT,
7375
52 => QueryType::TLSA,
76+
65 => QueryType::HTTPS,
7477
_ => QueryType::UNKNOWN(num),
7578
}
7679
}
@@ -172,6 +175,48 @@ pub enum DnsRecord {
172175
data: Vec<u8>,
173176
ttl: TransientTtl
174177
}, // 52
178+
HTTPS {
179+
domain: String,
180+
priority: u16,
181+
target: String,
182+
params: Vec<u8>,
183+
ttl: TransientTtl
184+
}, // 65
185+
}
186+
187+
/// Read an uncompressed domain name (does not follow compression pointers)
188+
/// Used for HTTPS/SVCB records per RFC 9460
189+
fn read_uncompressed_name<T: PacketBuffer>(buffer: &mut T) -> Result<String> {
190+
let mut outstr = String::new();
191+
let mut delim = "";
192+
193+
loop {
194+
let len = buffer.read()? as usize;
195+
196+
// Check for compression pointer (RFC 9460: HTTPS TargetName must be uncompressed)
197+
// If we encounter one, this is an error - but we'll just stop
198+
if (len & 0xC0) > 0 {
199+
// This shouldn't happen for HTTPS records per RFC 9460
200+
// Skip the second byte of the pointer
201+
buffer.read()?;
202+
break;
203+
}
204+
205+
// Names are terminated by an empty label of length 0
206+
if len == 0 {
207+
break;
208+
}
209+
210+
outstr.push_str(delim);
211+
212+
for _ in 0..len {
213+
outstr.push(buffer.read()? as char);
214+
}
215+
216+
delim = ".";
217+
}
218+
219+
Ok(outstr)
175220
}
176221

177222
impl DnsRecord {
@@ -294,6 +339,34 @@ impl DnsRecord {
294339
buffer.step(data_len as usize)?;
295340
Ok(DnsRecord::TLSA { domain, certificate_usage, selector, matching_type, data, ttl: TransientTtl(ttl) })
296341
}
342+
QueryType::HTTPS => {
343+
// Track the start position of the data section
344+
let data_start_pos = buffer.pos();
345+
346+
let priority = buffer.read_u16()?;
347+
348+
// Read TargetName without compression (RFC 9460 requirement)
349+
let target = read_uncompressed_name(buffer)?;
350+
351+
// Calculate remaining bytes for SvcParams based on data_len
352+
let bytes_consumed = buffer.pos() - data_start_pos;
353+
let params_len = if data_len as usize > bytes_consumed {
354+
data_len as usize - bytes_consumed
355+
} else {
356+
0
357+
};
358+
359+
let params = if params_len > 0 {
360+
let cur_pos = buffer.pos();
361+
let p = buffer.get_range(cur_pos, params_len)?.to_vec();
362+
buffer.step(params_len)?;
363+
p
364+
} else {
365+
Vec::new()
366+
};
367+
368+
Ok(DnsRecord::HTTPS { domain, priority, target, params, ttl: TransientTtl(ttl) })
369+
}
297370
QueryType::UNKNOWN(_) => {
298371
buffer.step(data_len as usize)?;
299372

@@ -452,6 +525,38 @@ impl DnsRecord {
452525
buffer.write_u8(*b)?;
453526
}
454527
}
528+
DnsRecord::HTTPS { ref domain, priority, ref target, ref params, ttl: TransientTtl(ttl) } => {
529+
buffer.write_qname(domain)?;
530+
buffer.write_u16(QueryType::HTTPS.to_num())?;
531+
buffer.write_u16(1)?;
532+
buffer.write_u32(ttl)?;
533+
534+
let pos = buffer.pos();
535+
buffer.write_u16(0)?;
536+
537+
buffer.write_u16(priority)?;
538+
539+
// Write TargetName WITHOUT compression (RFC 9460 requirement)
540+
let split_str = target.split('.').collect::<Vec<&str>>();
541+
for label in split_str.iter() {
542+
if label.is_empty() {
543+
continue;
544+
}
545+
let len = label.len();
546+
buffer.write_u8(len as u8)?;
547+
for b in label.as_bytes() {
548+
buffer.write_u8(*b)?;
549+
}
550+
}
551+
buffer.write_u8(0)?; // Terminate with null label
552+
553+
for b in params {
554+
buffer.write_u8(*b)?;
555+
}
556+
557+
let size = buffer.pos() - (pos + 2);
558+
buffer.set_u16(pos, size as u16)?;
559+
}
455560
DnsRecord::OPT { packet_len, flags, ref data } => {
456561
buffer.write_u8(0)?;
457562
buffer.write_u16(QueryType::OPT.to_num())?;
@@ -485,6 +590,7 @@ impl DnsRecord {
485590
DnsRecord::TXT { .. } => QueryType::TXT,
486591
DnsRecord::OPT { .. } => QueryType::OPT,
487592
DnsRecord::TLSA { .. } => QueryType::TLSA,
593+
DnsRecord::HTTPS { .. } => QueryType::HTTPS,
488594
}
489595
}
490596

@@ -500,7 +606,8 @@ impl DnsRecord {
500606
| DnsRecord::UNKNOWN { ref domain, .. }
501607
| DnsRecord::SOA { ref domain, .. }
502608
| DnsRecord::TXT { ref domain, .. }
503-
| DnsRecord::TLSA { ref domain, .. } => Some(domain.clone()),
609+
| DnsRecord::TLSA { ref domain, .. }
610+
| DnsRecord::HTTPS { ref domain, .. } => Some(domain.clone()),
504611
DnsRecord::OPT { .. } => None
505612
}
506613
}
@@ -517,7 +624,8 @@ impl DnsRecord {
517624
| DnsRecord::UNKNOWN { ref mut domain, .. }
518625
| DnsRecord::SOA { ref mut domain, .. }
519626
| DnsRecord::TXT { ref mut domain, .. }
520-
| DnsRecord::TLSA { ref mut domain, .. } => *domain = new_domain,
627+
| DnsRecord::TLSA { ref mut domain, .. }
628+
| DnsRecord::HTTPS { ref mut domain, .. } => *domain = new_domain,
521629
DnsRecord::OPT { .. } => {} // OPT records don't have a domain field
522630
}
523631
}
@@ -543,6 +651,10 @@ impl DnsRecord {
543651
let data = crate::commons::to_hex(data);
544652
Some(format!("{} {} {} {} {}", domain, certificate_usage, selector, matching_type, &data))
545653
},
654+
DnsRecord::HTTPS { ref target, priority, ref params, .. } => {
655+
let params_hex = crate::commons::to_hex(params);
656+
Some(format!("{} {} {}", priority, target, params_hex))
657+
},
546658
DnsRecord::OPT { .. } => None,
547659
}
548660
}
@@ -558,8 +670,9 @@ impl DnsRecord {
558670
| DnsRecord::MX { ttl: TransientTtl(ttl), .. }
559671
| DnsRecord::UNKNOWN { ttl: TransientTtl(ttl), .. }
560672
| DnsRecord::SOA { ttl: TransientTtl(ttl), .. }
561-
| DnsRecord::TXT { ttl: TransientTtl(ttl), .. } => ttl,
562-
| DnsRecord::TLSA { ttl: TransientTtl(ttl), .. } => ttl,
673+
| DnsRecord::TXT { ttl: TransientTtl(ttl), .. }
674+
| DnsRecord::TLSA { ttl: TransientTtl(ttl), .. }
675+
| DnsRecord::HTTPS { ttl: TransientTtl(ttl), .. } => ttl,
563676
DnsRecord::OPT { .. } => 0
564677
}
565678
}

0 commit comments

Comments
 (0)