@@ -105,40 +105,13 @@ pub async fn handle_anonymized_dns(
105105 if is_encrypted_response ( & response, response_len) {
106106 break ( response_len, false ) ;
107107 }
108- if is_certificate_response ( & response, encrypted_packet) {
108+ if is_certificate_response ( & response, response_len , encrypted_packet) {
109109 break ( response_len, true ) ;
110110 }
111111 } ;
112112 response. truncate ( response_len) ;
113113 if is_certificate_response {
114- let mut hasher = globals. hasher ;
115- hasher. write ( & relayed_packet[ ..ANONYMIZED_DNSCRYPT_OVERHEAD ] ) ;
116- hasher. write ( & dns:: qname ( encrypted_packet) ?) ;
117- let packet_hash = hasher. finish128 ( ) . as_u128 ( ) ;
118- let cached_response = {
119- match globals. cert_cache . lock ( ) . get ( & packet_hash) {
120- None => None ,
121- Some ( response) if !( * response) . has_expired ( ) => {
122- trace ! ( "Relayed certificate cached" ) ;
123- let mut cached_response = ( * response) . clone ( ) ;
124- cached_response. set_tid ( dns:: tid ( encrypted_packet) ) ;
125- Some ( cached_response. into_response ( ) )
126- }
127- Some ( _) => {
128- trace ! ( "Relayed certificate expired" ) ;
129- None
130- }
131- }
132- } ;
133- match cached_response {
134- None => {
135- globals. cert_cache . lock ( ) . insert (
136- packet_hash,
137- CachedResponse :: new ( & globals. cert_cache , response. clone ( ) ) ,
138- ) ;
139- }
140- Some ( cached_response) => response = cached_response,
141- }
114+ response = cache_certificate_response ( & globals, relayed_packet, response) ?;
142115 }
143116
144117 #[ cfg( feature = "metrics" ) ]
@@ -147,13 +120,55 @@ pub async fn handle_anonymized_dns(
147120 respond_to_query ( client_ctx, response) . await
148121}
149122
123+ fn cache_certificate_response (
124+ globals : & Globals ,
125+ relayed_packet : & [ u8 ] ,
126+ mut response : Vec < u8 > ,
127+ ) -> Result < Vec < u8 > , Error > {
128+ let encrypted_packet = & relayed_packet[ ANONYMIZED_DNSCRYPT_OVERHEAD ..] ;
129+ let mut hasher = globals. hasher ;
130+ hasher. write ( & relayed_packet[ ..ANONYMIZED_DNSCRYPT_OVERHEAD ] ) ;
131+ hasher. write ( & dns:: qname ( encrypted_packet) ?) ;
132+ let packet_hash = hasher. finish128 ( ) . as_u128 ( ) ;
133+ let cached_response = {
134+ match globals. cert_cache . lock ( ) . get ( & packet_hash) {
135+ None => None ,
136+ Some ( response) if !( * response) . has_expired ( ) => {
137+ trace ! ( "Relayed certificate cached" ) ;
138+ let mut cached_response = ( * response) . clone ( ) ;
139+ cached_response. set_tid ( dns:: tid ( encrypted_packet) ) ;
140+ let cached_response = cached_response. into_response ( ) ;
141+ // Cached certificates must obey the same amplification limit
142+ // as the response just received from the upstream.
143+ ( cached_response. len ( ) <= encrypted_packet. len ( ) ) . then_some ( cached_response)
144+ }
145+ Some ( _) => {
146+ trace ! ( "Relayed certificate expired" ) ;
147+ None
148+ }
149+ }
150+ } ;
151+ match cached_response {
152+ None if !dns:: is_truncated ( & response) => {
153+ globals. cert_cache . lock ( ) . insert (
154+ packet_hash,
155+ CachedResponse :: new ( & globals. cert_cache , response. clone ( ) ) ,
156+ ) ;
157+ }
158+ None => { }
159+ Some ( cached_response) => response = cached_response,
160+ }
161+ Ok ( response)
162+ }
163+
150164#[ inline]
151165fn is_encrypted_response ( response : & [ u8 ] , response_len : usize ) -> bool {
152166 ( DNSCRYPT_UDP_RESPONSE_MIN_SIZE ..=DNSCRYPT_UDP_RESPONSE_MAX_SIZE ) . contains ( & response_len)
153167 && response[ ..DNSCRYPT_RESPONSE_MAGIC_SIZE ] == DNSCRYPT_RESPONSE_MAGIC
154168}
155169
156- fn is_certificate_response ( response : & [ u8 ] , query : & [ u8 ] ) -> bool {
170+ fn is_certificate_response ( response : & [ u8 ] , response_len : usize , query : & [ u8 ] ) -> bool {
171+ let response = & response[ ..response_len] ;
157172 let prefix = b"2.dnscrypt-cert." ;
158173 if !( ( DNS_HEADER_SIZE + prefix. len ( ) + 4 ..=DNS_MAX_PACKET_SIZE ) . contains ( & query. len ( ) )
159174 && ( DNS_HEADER_SIZE + prefix. len ( ) + 4 ..=DNS_MAX_PACKET_SIZE ) . contains ( & response. len ( ) )
@@ -207,11 +222,117 @@ mod tests {
207222 fn only_txt_in_answers_are_certificate_responses ( ) {
208223 let txt_query = cert_packet ( dns:: DNS_TYPE_TXT , false ) ;
209224 let txt_response = cert_packet ( dns:: DNS_TYPE_TXT , true ) ;
210- assert ! ( is_certificate_response( & txt_response, & txt_query) ) ;
225+ assert ! ( is_certificate_response(
226+ & txt_response,
227+ txt_response. len( ) ,
228+ & txt_query
229+ ) ) ;
211230
212231 let a_query = cert_packet ( dns:: DNS_TYPE_A , false ) ;
213232 let a_response = cert_packet ( dns:: DNS_TYPE_A , true ) ;
214- assert ! ( !is_certificate_response( & a_response, & a_query) ) ;
215- assert ! ( !is_certificate_response( & a_response, & txt_query) ) ;
233+ assert ! ( !is_certificate_response(
234+ & a_response,
235+ a_response. len( ) ,
236+ & a_query
237+ ) ) ;
238+ assert ! ( !is_certificate_response(
239+ & a_response,
240+ a_response. len( ) ,
241+ & txt_query
242+ ) ) ;
243+ }
244+
245+ #[ test]
246+ fn certificate_validation_ignores_bytes_beyond_the_received_datagram ( ) {
247+ let query = cert_packet ( dns:: DNS_TYPE_TXT , false ) ;
248+ let response = cert_packet ( dns:: DNS_TYPE_TXT , true ) ;
249+ let mut buffer = vec ! [ 0 ; DNSCRYPT_UDP_RESPONSE_MAX_SIZE ] ;
250+ buffer[ ..response. len ( ) ] . copy_from_slice ( & response) ;
251+ assert ! ( is_certificate_response( & buffer, response. len( ) , & query) ) ;
252+ // A subsequent datagram leaves the earlier question in the receive buffer.
253+ for len in [ DNS_HEADER_SIZE , response. len ( ) - 1 ] {
254+ assert ! ( !is_certificate_response( & buffer, len, & query) ) ;
255+ }
256+ }
257+
258+ fn relayed_cert_query ( size : usize ) -> Vec < u8 > {
259+ let mut packet = vec ! [ 0 ; ANONYMIZED_DNSCRYPT_OVERHEAD ] ;
260+ let mut query = cert_packet ( dns:: DNS_TYPE_TXT , false ) ;
261+ query. resize ( size, 0 ) ;
262+ packet. extend ( query) ;
263+ packet
264+ }
265+
266+ fn cert_response ( txt_size : usize ) -> Vec < u8 > {
267+ let mut response = cert_packet ( dns:: DNS_TYPE_TXT , true ) ;
268+ response[ 7 ] = 1 ;
269+ response. extend_from_slice ( & [ 0xc0 , 0x0c , 0 , 16 , 0 , 1 , 0 , 0 , 2 , 88 ] ) ;
270+ response. extend_from_slice ( & ( ( txt_size + 1 ) as u16 ) . to_be_bytes ( ) ) ;
271+ response. push ( txt_size as u8 ) ;
272+ response. resize ( response. len ( ) + txt_size, 0x42 ) ;
273+ response
274+ }
275+
276+ #[ test]
277+ fn cached_certificate_respects_the_current_query_size ( ) {
278+ let runtime = tokio:: runtime:: Builder :: new_current_thread ( )
279+ . enable_all ( )
280+ . build ( )
281+ . unwrap ( ) ;
282+ runtime. block_on ( async {
283+ let globals = crate :: tests:: globals ( "127.0.0.1:9" . parse ( ) . unwrap ( ) ) ;
284+ let large = cert_response ( 255 ) ;
285+ cache_certificate_response ( & globals, & relayed_cert_query ( 512 ) , large) . unwrap ( ) ;
286+ let small = cert_response ( 124 ) ;
287+ let response =
288+ cache_certificate_response ( & globals, & relayed_cert_query ( 256 ) , small. clone ( ) )
289+ . unwrap ( ) ;
290+ assert ! (
291+ response. len( ) <= 256 ,
292+ "cache bypassed the relay amplification limit"
293+ ) ;
294+ assert_eq ! ( response, small) ;
295+ } ) ;
296+ }
297+
298+ #[ test]
299+ fn truncated_certificate_does_not_replace_a_complete_response ( ) {
300+ let runtime = tokio:: runtime:: Builder :: new_current_thread ( )
301+ . enable_all ( )
302+ . build ( )
303+ . unwrap ( ) ;
304+ runtime. block_on ( async {
305+ let globals = crate :: tests:: globals ( "127.0.0.1:9" . parse ( ) . unwrap ( ) ) ;
306+ let mut truncated = cert_response ( 124 ) ;
307+ dns:: truncate ( & mut truncated) ;
308+ cache_certificate_response ( & globals, & relayed_cert_query ( 256 ) , truncated) . unwrap ( ) ;
309+ let complete = cert_response ( 255 ) ;
310+ let response =
311+ cache_certificate_response ( & globals, & relayed_cert_query ( 512 ) , complete. clone ( ) )
312+ . unwrap ( ) ;
313+ assert ! ( !dns:: is_truncated( & response) ) ;
314+ assert_eq ! ( response, complete) ;
315+ } ) ;
316+ }
317+
318+ #[ test]
319+ fn complete_cached_certificate_is_reused_when_it_fits ( ) {
320+ let runtime = tokio:: runtime:: Builder :: new_current_thread ( )
321+ . enable_all ( )
322+ . build ( )
323+ . unwrap ( ) ;
324+ runtime. block_on ( async {
325+ let globals = crate :: tests:: globals ( "127.0.0.1:9" . parse ( ) . unwrap ( ) ) ;
326+ let mut query = relayed_cert_query ( 512 ) ;
327+ let mut cached = cert_response ( 124 ) ;
328+ cache_certificate_response ( & globals, & query, cached. clone ( ) ) . unwrap ( ) ;
329+ dns:: set_tid ( & mut query[ ANONYMIZED_DNSCRYPT_OVERHEAD ..] , 0xabcd ) ;
330+ dns:: set_tid ( & mut cached, 0xabcd ) ;
331+ for mut fresh in [ cert_response ( 255 ) , cert_response ( 124 ) ] {
332+ dns:: set_tid ( & mut fresh, 0xabcd ) ;
333+ let response = cache_certificate_response ( & globals, & query, fresh) . unwrap ( ) ;
334+ assert_eq ! ( response, cached) ;
335+ }
336+ } ) ;
216337 }
217338}
0 commit comments