@@ -1319,7 +1319,7 @@ mod tests {
13191319 let ipv4 = "1.2.3.4" ;
13201320
13211321 // Act: Generate static IP answer
1322- let ( rcode, answers) = make_static_ip_answer ( domain, ipv4) ;
1322+ let ( rcode, answers) = make_static_ip_answer ( domain, RecordType :: A , ipv4) ;
13231323
13241324 // Assert: Verify response code and record type
13251325 assert_eq ! (
@@ -1342,7 +1342,7 @@ mod tests {
13421342 let ipv6 = "2001:db8::1" ;
13431343
13441344 // Act: Generate static IP answer
1345- let ( rcode, answers) = make_static_ip_answer ( domain, ipv6) ;
1345+ let ( rcode, answers) = make_static_ip_answer ( domain, RecordType :: AAAA , ipv6) ;
13461346
13471347 // Assert: Verify response code and record type
13481348 assert_eq ! (
@@ -1359,21 +1359,143 @@ mod tests {
13591359 }
13601360
13611361 #[ test]
1362- fn make_static_ip_answer_rejects_invalid_input ( ) {
1363- // Arrange: Define test domain and invalid IP
1364- let domain = "example.com" ;
1365- let invalid_ip = "not-an-ip" ;
1362+ fn make_static_ip_answer_filters_comma_separated_addresses_by_query_type ( ) {
1363+ let ips = " 192.0.2.1, 2001:db8::1,192.0.2.2 " ;
1364+
1365+ let ( a_rcode, a_answers) = make_static_ip_answer ( "example.com" , RecordType :: A , ips) ;
1366+ assert_eq ! ( a_rcode, ResponseCode :: NoError ) ;
1367+ assert_eq ! ( a_answers. len( ) , 2 ) ;
1368+ assert ! (
1369+ a_answers
1370+ . iter( )
1371+ . all( |answer| answer. record_type( ) == RecordType :: A )
1372+ ) ;
1373+
1374+ let ( aaaa_rcode, aaaa_answers) =
1375+ make_static_ip_answer ( "example.com" , RecordType :: AAAA , ips) ;
1376+ assert_eq ! ( aaaa_rcode, ResponseCode :: NoError ) ;
1377+ assert_eq ! ( aaaa_answers. len( ) , 1 ) ;
1378+ assert_eq ! ( aaaa_answers[ 0 ] . record_type( ) , RecordType :: AAAA ) ;
1379+ }
1380+
1381+ #[ test]
1382+ fn make_static_ip_answer_returns_nodata_for_unconfigured_query_family ( ) {
1383+ let ( rcode, answers) = make_static_ip_answer ( "example.com" , RecordType :: AAAA , "192.0.2.1" ) ;
1384+
1385+ assert_eq ! ( rcode, ResponseCode :: NoError ) ;
1386+ assert ! ( answers. is_empty( ) ) ;
1387+ }
1388+
1389+ #[ test]
1390+ fn make_static_ip_answer_returns_nodata_for_https_query ( ) {
1391+ let ( rcode, answers) =
1392+ make_static_ip_answer ( "example.com" , RecordType :: HTTPS , "192.0.2.1,2001:db8::1" ) ;
1393+
1394+ assert_eq ! ( rcode, ResponseCode :: NoError ) ;
1395+ assert ! ( answers. is_empty( ) ) ;
1396+ }
13661397
1367- // Act: Generate static IP answer with invalid input
1368- let ( rcode, answers) = make_static_ip_answer ( domain, invalid_ip) ;
1398+ #[ test]
1399+ fn make_static_ip_answer_returns_both_families_for_any_query ( ) {
1400+ let ( rcode, answers) =
1401+ make_static_ip_answer ( "example.com" , RecordType :: ANY , "192.0.2.1,2001:db8::1" ) ;
1402+
1403+ assert_eq ! ( rcode, ResponseCode :: NoError ) ;
1404+ assert_eq ! ( answers. len( ) , 2 ) ;
1405+ assert_eq ! ( answers[ 0 ] . record_type( ) , RecordType :: A ) ;
1406+ assert_eq ! ( answers[ 1 ] . record_type( ) , RecordType :: AAAA ) ;
1407+ }
1408+
1409+ #[ test]
1410+ fn make_static_ip_answer_rejects_invalid_input_atomically ( ) {
1411+ let ( rcode, answers) = make_static_ip_answer (
1412+ "example.com" ,
1413+ RecordType :: A ,
1414+ "192.0.2.1,not-an-ip,2001:db8::1" ,
1415+ ) ;
13691416
1370- // Assert: Verify ServFail response and empty answers
13711417 assert_eq ! (
13721418 rcode,
13731419 ResponseCode :: ServFail ,
1374- "Should return ServFail for invalid IP"
1420+ "Should return ServFail when any IP is invalid"
1421+ ) ;
1422+ assert ! ( answers. is_empty( ) , "Should not return a partial answer" ) ;
1423+ }
1424+
1425+ #[ test]
1426+ fn make_static_ip_answer_rejects_empty_entries ( ) {
1427+ for ips in [ "" , "192.0.2.1," , ",192.0.2.1" , "192.0.2.1,,2001:db8::1" ] {
1428+ let ( rcode, answers) = make_static_ip_answer ( "example.com" , RecordType :: A , ips) ;
1429+ assert_eq ! ( rcode, ResponseCode :: ServFail , "input: {ips:?}" ) ;
1430+ assert ! ( answers. is_empty( ) , "input: {ips:?}" ) ;
1431+ }
1432+ }
1433+
1434+ #[ tokio:: test]
1435+ async fn static_ip_fast_path_serializes_multiple_answers_by_query_type ( ) {
1436+ let raw = serde_json:: json!( {
1437+ "settings" : { "default_upstream" : "1.1.1.1:53" } ,
1438+ "pipelines" : [ {
1439+ "id" : "static" ,
1440+ "rules" : [ {
1441+ "name" : "static-ip" ,
1442+ "matchers" : [ { "type" : "domain_suffix" , "value" : "example.com" } ] ,
1443+ "actions" : [ {
1444+ "type" : "static_ip_response" ,
1445+ "ip" : "192.0.2.1,2001:db8::1,192.0.2.2"
1446+ } ]
1447+ } ]
1448+ } ]
1449+ } ) ;
1450+ let cfg: crate :: config:: PipelineConfig = serde_json:: from_value ( raw) . expect ( "parse config" ) ;
1451+ let runtime = RuntimePipelineConfig :: from_config ( cfg) . expect ( "runtime config" ) ;
1452+ let engine = Engine :: new ( runtime, "test" . to_string ( ) ) . expect ( "initialize engine" ) ;
1453+ let peer = "127.0.0.1:53000" . parse ( ) . unwrap ( ) ;
1454+
1455+ let assert_response = |qtype, expected_ips : & [ IpAddr ] | {
1456+ let mut request = Message :: new ( 0xCAFE , MessageType :: Query , OpCode :: Query ) ;
1457+ request. metadata . recursion_desired = true ;
1458+ request. add_query ( Query :: query (
1459+ Name :: from_str ( "www.example.com" ) . unwrap ( ) ,
1460+ qtype,
1461+ ) ) ;
1462+
1463+ let response = match engine
1464+ . handle_packet_fast ( & request. to_vec ( ) . unwrap ( ) , peer)
1465+ . expect ( "fast path" )
1466+ {
1467+ Some ( FastPathResponse :: Direct ( bytes) ) => Message :: from_bytes ( & bytes) . unwrap ( ) ,
1468+ other => panic ! ( "expected direct fast-path response, got {other:?}" ) ,
1469+ } ;
1470+
1471+ assert_eq ! ( response. metadata. id, 0xCAFE ) ;
1472+ assert_eq ! ( response. metadata. message_type, MessageType :: Response ) ;
1473+ assert_eq ! ( response. metadata. response_code, ResponseCode :: NoError ) ;
1474+ assert ! ( response. metadata. recursion_desired) ;
1475+ assert ! ( response. metadata. recursion_available) ;
1476+ assert_eq ! ( response. queries. len( ) , 1 ) ;
1477+ assert_eq ! ( response. queries[ 0 ] . query_type( ) , qtype) ;
1478+
1479+ let actual_ips: Vec < IpAddr > = response
1480+ . answers
1481+ . iter ( )
1482+ . map ( |answer| {
1483+ assert_eq ! ( answer. ttl, 300 ) ;
1484+ match & answer. data {
1485+ RData :: A ( address) => IpAddr :: V4 ( address. 0 ) ,
1486+ RData :: AAAA ( address) => IpAddr :: V6 ( address. 0 ) ,
1487+ other => panic ! ( "unexpected static IP answer: {other:?}" ) ,
1488+ }
1489+ } )
1490+ . collect ( ) ;
1491+ assert_eq ! ( actual_ips, expected_ips) ;
1492+ } ;
1493+
1494+ assert_response (
1495+ RecordType :: A ,
1496+ & [ "192.0.2.1" . parse ( ) . unwrap ( ) , "192.0.2.2" . parse ( ) . unwrap ( ) ] ,
13751497 ) ;
1376- assert ! ( answers . is_empty ( ) , "Should have no answers for invalid IP" ) ;
1498+ assert_response ( RecordType :: AAAA , & [ "2001:db8::1" . parse ( ) . unwrap ( ) ] ) ;
13771499 }
13781500
13791501 #[ test]
0 commit comments