@@ -49,6 +49,7 @@ pub async fn a_crud() -> Result<(), anyhow::Error> {
4949
5050 // add an a record
5151 let name = "devron" . to_string ( ) ;
52+ let fqdn = name. clone ( ) + "." + TEST_ZONE + "." ;
5253 let addr = Ipv4Addr :: new ( 10 , 1 , 2 , 3 ) ;
5354 let a = DnsRecord :: A ( addr) ;
5455 let input_records = HashMap :: from ( [ ( name. clone ( ) , vec ! [ a] ) ] ) ;
@@ -59,10 +60,24 @@ pub async fn a_crud() -> Result<(), anyhow::Error> {
5960 assert_eq ! ( input_records, records) ;
6061
6162 // resolve the name
62- let response = resolver. lookup_ip ( name + "." + TEST_ZONE + "." ) . await ?;
63+ let response = resolver. lookup_ip ( fqdn . clone ( ) ) . await ?;
6364 let address = response. iter ( ) . next ( ) . expect ( "no addresses returned!" ) ;
6465 assert_eq ! ( address, addr) ;
6566
67+ // as with other cases, `hickory-resolver` does not let us see if the answer
68+ // is authoritative, so we'll have to query again with a lower level
69+ // interface to validate that.
70+ let raw_response = raw_dns_client_query (
71+ test_ctx. dns_server . local_address ( ) ,
72+ Name :: from_ascii ( & fqdn) . expect ( "name is valid" ) ,
73+ RecordType :: A ,
74+ )
75+ . await
76+ . expect ( "can issue DNS query" ) ;
77+
78+ assert ! ( raw_response. authoritative( ) ) ;
79+ assert_eq ! ( raw_response. answers( ) . len( ) , 1 ) ;
80+
6681 test_ctx. cleanup ( ) . await ;
6782 Ok ( ( ) )
6883}
@@ -79,6 +94,7 @@ pub async fn aaaa_crud() -> Result<(), anyhow::Error> {
7994
8095 // add an aaaa record
8196 let name = "devron" . to_string ( ) ;
97+ let fqdn = name. clone ( ) + "." + TEST_ZONE + "." ;
8298 let addr = Ipv6Addr :: new ( 0xfd , 0 , 0 , 0 , 0 , 0 , 0 , 0x1 ) ;
8399 let aaaa = DnsRecord :: Aaaa ( addr) ;
84100 let input_records = HashMap :: from ( [ ( name. clone ( ) , vec ! [ aaaa] ) ] ) ;
@@ -89,10 +105,24 @@ pub async fn aaaa_crud() -> Result<(), anyhow::Error> {
89105 assert_eq ! ( input_records, records) ;
90106
91107 // resolve the name
92- let response = resolver. lookup_ip ( name + "." + TEST_ZONE + "." ) . await ?;
108+ let response = resolver. lookup_ip ( fqdn . clone ( ) ) . await ?;
93109 let address = response. iter ( ) . next ( ) . expect ( "no addresses returned!" ) ;
94110 assert_eq ! ( address, addr) ;
95111
112+ // as with other cases, `hickory-resolver` does not let us see if the answer
113+ // is authoritative, so we'll have to query again with a lower level
114+ // interface to validate that.
115+ let raw_response = raw_dns_client_query (
116+ test_ctx. dns_server . local_address ( ) ,
117+ Name :: from_ascii ( & fqdn) . expect ( "name is valid" ) ,
118+ RecordType :: AAAA ,
119+ )
120+ . await
121+ . expect ( "can issue DNS query" ) ;
122+
123+ assert ! ( raw_response. authoritative( ) ) ;
124+ assert_eq ! ( raw_response. answers( ) . len( ) , 1 ) ;
125+
96126 test_ctx. cleanup ( ) . await ;
97127 Ok ( ( ) )
98128}
@@ -128,7 +158,7 @@ pub async fn answers_match_question() -> Result<(), anyhow::Error> {
128158 //
129159 // `raw_dns_client_query` avoids using a hickory Resolver, so we can assert
130160 // on the exact answer from our server.
131- let response = raw_dns_client_query (
161+ let raw_response = raw_dns_client_query (
132162 test_ctx. dns_server . local_address ( ) ,
133163 name,
134164 RecordType :: A ,
@@ -142,9 +172,12 @@ pub async fn answers_match_question() -> Result<(), anyhow::Error> {
142172 // have
143173 // * no additionals: the server could return AAAA records as additionals to
144174 // an A query, but does not currently.
145- assert_eq ! ( response. header( ) . response_code( ) , ResponseCode :: NoError ) ;
146- assert_eq ! ( response. answers( ) , & [ ] ) ;
147- assert_eq ! ( response. additionals( ) , & [ ] ) ;
175+ // * authoritative: the nameserver we've queried is the source of truth for
176+ // this name (and zone!)
177+ assert_eq ! ( raw_response. header( ) . response_code( ) , ResponseCode :: NoError ) ;
178+ assert_eq ! ( raw_response. answers( ) , & [ ] ) ;
179+ assert_eq ! ( raw_response. additionals( ) , & [ ] ) ;
180+ assert ! ( raw_response. authoritative( ) ) ;
148181
149182 test_ctx. cleanup ( ) . await ;
150183 Ok ( ( ) )
@@ -223,6 +256,7 @@ pub async fn srv_crud() -> Result<(), anyhow::Error> {
223256 assert_eq ! ( response. additionals( ) . len( ) , 2 ) ;
224257 assert_eq ! ( response. additionals( ) [ 0 ] . record_type( ) , RecordType :: AAAA ) ;
225258 assert_eq ! ( response. additionals( ) [ 1 ] . record_type( ) , RecordType :: AAAA ) ;
259+ assert ! ( response. authoritative( ) ) ;
226260
227261 test_ctx. cleanup ( ) . await ;
228262 Ok ( ( ) )
@@ -263,11 +297,8 @@ pub async fn multi_record_crud() -> Result<(), anyhow::Error> {
263297 Ok ( ( ) )
264298}
265299
266- async fn lookup_ip_expect_nxdomain ( resolver : & TokioAsyncResolver , name : & str ) {
267- lookup_ip_expect_error_code ( resolver, name, ResponseCode :: NXDomain ) . await ;
268- }
269-
270300async fn lookup_ip_expect_error_code (
301+ server_addr : std:: net:: SocketAddr ,
271302 resolver : & TokioAsyncResolver ,
272303 name : & str ,
273304 expected_code : ResponseCode ,
@@ -278,6 +309,58 @@ async fn lookup_ip_expect_error_code(
278309 }
279310 Err ( e) => expect_no_records_error_code ( & e, expected_code) ,
280311 } ;
312+
313+ // We are authoritative for all records served from internal DNS or external
314+ // DNS. This means that if we return an NXDOMAIN answer, the queried name
315+ // is one that we would be authoritative for, if it existed. For other
316+ // errors, we do not set the authoritative bit even if the name is one we
317+ // might have been authoritative for. This is primarily for simplicity; the
318+ // authoritative non-NXDOMAIN errors have no RFC-defined meaning.
319+ let expected_authoritativeness = expected_code == ResponseCode :: NXDomain ;
320+
321+ // `lookup_ip` doesn't let us find out if the actual DNS message was
322+ // authoritative. So instead, query again via `hickory-client` to get the
323+ // exact Message from our DNS server. `lookup_ip` queries both A and AAAA
324+ // and merges the answers, so we'll query both here too.
325+
326+ let raw_response =
327+ raw_query_expect_err ( server_addr, name, RecordType :: A ) . await ;
328+
329+ assert_eq ! ( raw_response. authoritative( ) , expected_authoritativeness) ;
330+ assert_eq ! ( raw_response. response_code( ) , expected_code) ;
331+
332+ let raw_response =
333+ raw_query_expect_err ( server_addr, name, RecordType :: AAAA ) . await ;
334+ assert_eq ! ( raw_response. authoritative( ) , expected_authoritativeness) ;
335+ assert_eq ! ( raw_response. response_code( ) , expected_code) ;
336+ }
337+
338+ async fn raw_query_expect_err (
339+ server_addr : std:: net:: SocketAddr ,
340+ name : & str ,
341+ query_ty : RecordType ,
342+ ) -> DnsResponse {
343+ let name = Name :: from_ascii ( name) . expect ( "can parse domain name" ) ;
344+
345+ let raw_response = raw_dns_client_query ( server_addr, name, query_ty)
346+ . await
347+ . expect ( "can issue DNS query" ) ;
348+
349+ // The caller may have a specific error in mind, but we know that the
350+ // response definitely should be that there was *some* kind of error.
351+ assert_ne ! ( raw_response. response_code( ) , ResponseCode :: NoError ) ;
352+
353+ // We do not currently return answers or additionals for any errors.
354+ assert ! ( raw_response. answers( ) . is_empty( ) ) ;
355+ assert ! ( raw_response. additionals( ) . is_empty( ) ) ;
356+
357+ // Optionally, the DNS server is permitted to return SOA records with
358+ // negative answers as a guide for how long to cache the result. We don't do
359+ // that right now, so test that there are no name servers in the answer.
360+ // This should change if the DNS server is changed.
361+ assert ! ( raw_response. name_servers( ) . is_empty( ) ) ;
362+
363+ raw_response
281364}
282365
283366fn expect_no_records_error_code (
@@ -338,7 +421,13 @@ pub async fn name_contains_zone() -> Result<(), anyhow::Error> {
338421 assert_eq ! ( address, addr) ;
339422
340423 // A lookup shouldn't work without the zone's name appended twice.
341- lookup_ip_expect_nxdomain ( resolver, "epsilon3.oxide.test" ) . await ;
424+ lookup_ip_expect_error_code (
425+ test_ctx. dns_server . local_address ( ) ,
426+ resolver,
427+ "epsilon3.oxide.test" ,
428+ ResponseCode :: NXDomain ,
429+ )
430+ . await ;
342431
343432 test_ctx. cleanup ( ) . await ;
344433 Ok ( ( ) )
@@ -362,7 +451,13 @@ pub async fn empty_record() -> Result<(), anyhow::Error> {
362451 assert ! ( records. is_empty( ) ) ;
363452
364453 // resolve the name
365- lookup_ip_expect_nxdomain ( & resolver, & ( name + "." + TEST_ZONE + "." ) ) . await ;
454+ lookup_ip_expect_error_code (
455+ test_ctx. dns_server . local_address ( ) ,
456+ & resolver,
457+ & ( name + "." + TEST_ZONE + "." ) ,
458+ ResponseCode :: NXDomain ,
459+ )
460+ . await ;
366461
367462 test_ctx. cleanup ( ) . await ;
368463 Ok ( ( ) )
@@ -450,15 +545,24 @@ pub async fn soa() -> Result<(), anyhow::Error> {
450545
451546 // SOA queries under the zone we now know we are authoritative for should
452547 // fail with NXDomain.
453- //
454- // TODO: we should see the authoritative bit set here. It's not clear that
455- // hickory-proto has a way to see if that bit is present in an error.
548+ let no_soa_name = format ! ( "foo.{TEST_ZONE}." ) ;
456549 let lookup_err = resolver
457- . soa_lookup ( format ! ( "foo.{TEST_ZONE}." ) )
550+ . soa_lookup ( & no_soa_name )
458551 . await
459552 . expect_err ( "test zone should not exist" ) ;
460553 expect_no_records_error_code ( & lookup_err, ResponseCode :: NXDomain ) ;
461554
555+ // As with other NXDomain answers, we should see the authoritative bit.
556+ let raw_response = raw_query_expect_err (
557+ test_ctx. dns_server . local_address ( ) ,
558+ & no_soa_name,
559+ RecordType :: A ,
560+ )
561+ . await ;
562+
563+ assert ! ( raw_response. authoritative( ) ) ;
564+ assert_eq ! ( raw_response. response_code( ) , ResponseCode :: NXDomain ) ;
565+
462566 // If the zone has no ns1 for some reason, we ought to see an SOA record
463567 // referencing the next lowest-numbered name server.
464568 let mut records = HashMap :: new ( ) ;
@@ -500,8 +604,13 @@ pub async fn nxdomain() -> Result<(), anyhow::Error> {
500604
501605 // asking for a nonexistent record within the domain of the internal DNS
502606 // server should result in an NXDOMAIN
503- lookup_ip_expect_nxdomain ( & resolver, & format ! ( "unicorn.{}." , TEST_ZONE ) )
504- . await ;
607+ lookup_ip_expect_error_code (
608+ test_ctx. dns_server . local_address ( ) ,
609+ & resolver,
610+ & format ! ( "unicorn.{}." , TEST_ZONE ) ,
611+ ResponseCode :: NXDomain ,
612+ )
613+ . await ;
505614
506615 test_ctx. cleanup ( ) . await ;
507616 Ok ( ( ) )
@@ -514,8 +623,10 @@ pub async fn servfail() -> Result<(), anyhow::Error> {
514623
515624 // In this case, we haven't defined any zones yet, so any request should be
516625 // outside the server's authoritative zones. That should result in a
517- // SERVFAIL.
626+ // SERVFAIL. Further, `lookup_ip_expect_error_code` will check that the
627+ // error is not authoritative.
518628 lookup_ip_expect_error_code (
629+ test_ctx. dns_server . local_address ( ) ,
519630 & resolver,
520631 "unicorn.oxide.internal" ,
521632 ResponseCode :: ServFail ,
0 commit comments