@@ -645,6 +645,14 @@ impl CachePlugin {
645645 q. qclass( ) . to_u16( )
646646 ) ;
647647
648+ trace ! (
649+ "Generated cache key: {} | qname={} | qtype={:?} | qclass={:?}" ,
650+ key,
651+ qname_lower,
652+ q. qtype( ) ,
653+ q. qclass( )
654+ ) ;
655+
648656 // TODO: Add EDNS0 flags if message has EDNS0
649657 // This would ensure DNSSEC queries are cached separately from non-DNSSEC
650658 // Currently, we focus on the main fix: domain name normalization
@@ -857,6 +865,11 @@ impl Plugin for CachePlugin {
857865 let response_ref = Arc :: make_mut ( & mut response_arc) ;
858866 Self :: update_ttls ( response_ref, STALE_RESPONSE_TTL_SECS ) ; // stale response TTL is fixed to 5s (matches upstream)
859867 response_ref. set_id ( context. request ( ) . id ( ) ) ;
868+ // Sync question section with current request to fix question/answer mismatch
869+ response_ref. clear_questions ( ) ;
870+ for q in context. request ( ) . questions ( ) {
871+ response_ref. add_question ( q. clone ( ) ) ;
872+ }
860873 context. set_response_arc ( Some ( response_arc) ) ;
861874
862875 // Mark that response came from cache to prevent Phase 2 re-execution
@@ -1012,6 +1025,11 @@ impl Plugin for CachePlugin {
10121025 let response_ref = Arc :: make_mut ( & mut response_arc) ;
10131026 Self :: update_ttls ( response_ref, remaining_ttl) ;
10141027 response_ref. set_id ( context. request ( ) . id ( ) ) ;
1028+ // Sync question section with current request to fix question/answer mismatch
1029+ response_ref. clear_questions ( ) ;
1030+ for q in context. request ( ) . questions ( ) {
1031+ response_ref. add_question ( q. clone ( ) ) ;
1032+ }
10151033 context. set_response_arc ( Some ( response_arc) ) ;
10161034
10171035 // Mark that response came from cache to prevent Phase 2 re-execution
@@ -1125,6 +1143,11 @@ impl Plugin for CachePlugin {
11251143 let response_ref = Arc :: make_mut ( & mut response_arc) ;
11261144 Self :: update_ttls ( response_ref, remaining_ttl) ;
11271145 response_ref. set_id ( context. request ( ) . id ( ) ) ;
1146+ // Sync question section with current request to fix question/answer mismatch
1147+ response_ref. clear_questions ( ) ;
1148+ for q in context. request ( ) . questions ( ) {
1149+ response_ref. add_question ( q. clone ( ) ) ;
1150+ }
11281151 context. set_response_arc ( Some ( response_arc) ) ;
11291152
11301153 // Mark that response came from cache to prevent Phase 2 re-execution
@@ -1179,9 +1202,32 @@ impl Plugin for CachePlugin {
11791202 if ttl > 0 {
11801203 // Determine cache TTL: if cache_ttl is set, use it for positive answers
11811204 let cache_ttl = self . cache_ttl . unwrap_or ( ttl) ;
1205+
1206+ // Verify cache key matches request question type
1207+ let request_qtype = context
1208+ . request ( )
1209+ . questions ( )
1210+ . first ( )
1211+ . map ( |q| format ! ( "{:?}" , q. qtype( ) ) )
1212+ . unwrap_or_else ( || "N/A" . to_string ( ) ) ;
1213+
1214+ // Get response answer record types
1215+ let answer_types: Vec < String > = response
1216+ . answers ( )
1217+ . iter ( )
1218+ . map ( |r| r. rtype ( ) )
1219+ . collect :: < std:: collections:: HashSet < _ > > ( )
1220+ . iter ( )
1221+ . map ( |rt| format ! ( "{:?}" , rt) )
1222+ . collect ( ) ;
1223+
11821224 debug ! (
1183- "Storing response in cache: {} (message TTL: {}s, cache TTL: {}s)" ,
1184- key, ttl, cache_ttl
1225+ "Storing response in cache: {} | request_qtype={} | answer_types={} | message_ttl={}s | cache_ttl={}s" ,
1226+ key,
1227+ request_qtype,
1228+ answer_types. join( "," ) ,
1229+ ttl,
1230+ cache_ttl
11851231 ) ;
11861232
11871233 // Always create/replace with new entry
@@ -1909,4 +1955,52 @@ plugins:
19091955 // because we're testing with a different cache instance. But we've verified the task
19101956 // spawns and runs without errors.
19111957 }
1958+
1959+ #[ tokio:: test]
1960+ async fn test_cache_hit_syncs_question_section ( ) {
1961+ // Test the critical fix: cache hit should sync question section with current request
1962+ let cache = CachePlugin :: new ( 100 ) ;
1963+
1964+ // Create initial A query response with A question
1965+ let mut a_response = create_test_response ( ) ;
1966+ a_response. clear_questions ( ) ;
1967+ a_response. add_question ( Question :: new ( "example.com" , RecordType :: A , RecordClass :: IN ) ) ;
1968+
1969+ // Store in cache with key "example.com:1:1" (A query)
1970+ let entry = CacheEntry :: new ( a_response, 300 , 300 ) ;
1971+ cache
1972+ . cache
1973+ . write ( )
1974+ . push ( "example.com:1:1" . to_string ( ) , entry) ;
1975+
1976+ // Verify the cached response has A question
1977+ let cached = cache. cache . write ( ) . get ( "example.com:1:1" ) . cloned ( ) ;
1978+ assert ! ( cached. is_some( ) ) ;
1979+ let cached_resp = cached. unwrap ( ) . response ;
1980+ assert_eq ! ( cached_resp. questions( ) [ 0 ] . qtype( ) , RecordType :: A ) ;
1981+
1982+ // Simulate what cache does: prepare response for AAAA request
1983+ // by syncing question section (this is the fix)
1984+ let mut aaaa_request = Message :: new ( ) ;
1985+ aaaa_request. add_question ( Question :: new (
1986+ "example.com" ,
1987+ RecordType :: AAAA ,
1988+ RecordClass :: IN ,
1989+ ) ) ;
1990+
1991+ if let Some ( entry) = cache. cache . write ( ) . get ( "example.com:1:1" ) . cloned ( ) {
1992+ let mut response_arc = Arc :: clone ( & entry. response ) ;
1993+ let response_ref = Arc :: make_mut ( & mut response_arc) ;
1994+
1995+ // This is the fix: sync question section with current request
1996+ response_ref. clear_questions ( ) ;
1997+ for q in aaaa_request. questions ( ) {
1998+ response_ref. add_question ( q. clone ( ) ) ;
1999+ }
2000+
2001+ // Verify the question section was updated
2002+ assert_eq ! ( response_ref. question_count( ) , 1 ) ;
2003+ assert_eq ! ( response_ref. questions( ) [ 0 ] . qtype( ) , RecordType :: AAAA ) ;
2004+ }
2005+ }
19122006}
0 commit comments