@@ -38,6 +38,7 @@ pub enum QueryType {
3838 SRV , // 33
3939 OPT , // 41
4040 TLSA , // 52
41+ HTTPS , // 65
4142}
4243
4344impl 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
177222impl 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