@@ -40,12 +40,16 @@ use core::fmt;
4040use core:: ops:: Deref ;
4141
4242use crate :: blinded_path:: message:: DNSResolverContext ;
43+ #[ cfg( feature = "dnssec" ) ]
44+ use crate :: blinded_path:: message:: MessageContext ;
4345use crate :: io;
4446#[ cfg( feature = "dnssec" ) ]
4547use crate :: ln:: channelmanager:: PaymentId ;
4648use crate :: ln:: msgs:: DecodeError ;
4749#[ cfg( feature = "dnssec" ) ]
4850use crate :: offers:: offer:: Offer ;
51+ #[ cfg( feature = "dnssec" ) ]
52+ use crate :: onion_message:: messenger:: Destination ;
4953use crate :: onion_message:: messenger:: { MessageSendInstructions , Responder , ResponseInstruction } ;
5054use crate :: onion_message:: packet:: OnionMessageContents ;
5155use crate :: prelude:: * ;
@@ -375,6 +379,7 @@ struct PendingResolution {
375379 context : DNSResolverContext ,
376380 name : HumanReadableName ,
377381 payment_id : PaymentId ,
382+ pending_queries : usize ,
378383}
379384
380385/// A stateful resolver which maps BIP 353 Human Readable Names to URIs and BOLT12 [`Offer`]s.
@@ -462,11 +467,18 @@ impl OMNameResolver {
462467
463468 /// Begins the process of resolving a BIP 353 Human Readable Name.
464469 ///
465- /// Returns a [`DNSSECQuery`] onion message and a [`DNSResolverContext`] which should be sent
466- /// to a resolver (with the context used to generate the blinded response path) on success.
470+ /// Returns a list of [`DNSSECQuery`] onion messages and the [`MessageSendInstructions`] over
471+ /// which each should be sent - one entry per provided `destination`. The query may be sent to
472+ /// multiple resolvers (i.e. multiple `destinations`) to improve the chance of a timely
473+ /// response; the resolution is only considered failed once a [`DNSSECError`] has been received
474+ /// for every one of them (see [`Self::handle_dnssec_error`]).
475+ ///
476+ /// Each [`MessageSendInstructions`] includes a reply path built from the same
477+ /// [`DNSResolverContext`], so that responses can be matched back to this resolution.
467478 pub fn resolve_name < ES : EntropySource + ?Sized > (
468- & self , payment_id : PaymentId , name : HumanReadableName , entropy_source : & ES ,
469- ) -> Result < ( DNSSECQuery , DNSResolverContext ) , ( ) > {
479+ & self , payment_id : PaymentId , name : HumanReadableName , destinations : Vec < Destination > ,
480+ entropy_source : & ES ,
481+ ) -> Result < Vec < ( DNSResolverMessage , MessageSendInstructions ) > , ( ) > {
470482 let dns_name =
471483 Name :: try_from ( format ! ( "{}.user._bitcoin-payment.{}." , name. user( ) , name. domain( ) ) ) ;
472484 debug_assert ! (
@@ -477,11 +489,23 @@ impl OMNameResolver {
477489 context. nonce . copy_from_slice ( & entropy_source. get_secure_random_bytes ( ) [ ..16 ] ) ;
478490 if let Ok ( dns_name) = dns_name {
479491 let start_height = self . latest_block_height . load ( Ordering :: Acquire ) as u32 ;
492+ let pending_queries = destinations. len ( ) ;
493+ let query = DNSResolverMessage :: DNSSECQuery ( DNSSECQuery ( dns_name. clone ( ) ) ) ;
494+ let messages = destinations
495+ . into_iter ( )
496+ . map ( |destination| {
497+ let instructions = MessageSendInstructions :: WithReplyPath {
498+ destination,
499+ context : MessageContext :: DNSResolver ( context. clone ( ) ) ,
500+ } ;
501+ ( query. clone ( ) , instructions)
502+ } )
503+ . collect ( ) ;
480504 let mut pending_resolves = self . pending_resolves . lock ( ) . unwrap ( ) ;
481- let context_ret = context . clone ( ) ;
482- let resolution = PendingResolution { start_height, context, name, payment_id } ;
483- pending_resolves. entry ( dns_name. clone ( ) ) . or_insert_with ( Vec :: new) . push ( resolution) ;
484- Ok ( ( DNSSECQuery ( dns_name ) , context_ret ) )
505+ let resolution =
506+ PendingResolution { start_height, context, name, payment_id, pending_queries } ;
507+ pending_resolves. entry ( dns_name) . or_insert_with ( Vec :: new) . push ( resolution) ;
508+ Ok ( messages )
485509 } else {
486510 Err ( ( ) )
487511 }
@@ -607,12 +631,86 @@ impl OMNameResolver {
607631 }
608632 None
609633 }
634+
635+ /// Handles a [`DNSSECError`] message, indicating that one of the resolvers we sent a
636+ /// [`DNSSECQuery`] to was unable to provide a [`DNSSECProof`] for the requested name.
637+ ///
638+ /// Each error counts as a failure of one of the queries we made for the matching resolution
639+ /// (see the `num_queries` argument to [`Self::resolve_name`]). A resolution is only considered
640+ /// failed once we have received a [`DNSSECError`] for *all* the queries we made for it, as a
641+ /// [`DNSSECProof`] may still arrive from one of the other resolvers we queried. When a
642+ /// resolution does fail, it is removed and its [`HumanReadableName`] and [`PaymentId`] (as
643+ /// passed to [`Self::resolve_name`]) are included in the returned list. The caller should
644+ /// consider those resolutions failed.
645+ ///
646+ /// Note that [`DNSSECError::definitely_unresolvable`] is intentionally ignored - a single
647+ /// resolver claiming a name is unresolvable does not preclude another resolver we queried from
648+ /// successfully resolving it, so we always wait for all queries to fail.
649+ ///
650+ /// As with [`Self::handle_dnssec_proof_for_uri`], the [`DNSResolverContext`] is checked against
651+ /// the contexts of any pending resolutions for the name to ensure the error was received over a
652+ /// blinded path we created when making the relevant [`DNSSECQuery`].
653+ pub fn handle_dnssec_error (
654+ & self , msg : DNSSECError , context : DNSResolverContext ,
655+ ) -> Vec < ( HumanReadableName , PaymentId ) > {
656+ let DNSSECError { name, .. } = msg;
657+ let mut failed_resolutions = Vec :: new ( ) ;
658+ let mut pending_resolves = self . pending_resolves . lock ( ) . unwrap ( ) ;
659+ if let hash_map:: Entry :: Occupied ( mut entry) = pending_resolves. entry ( name) {
660+ entry. get_mut ( ) . retain_mut ( |resolution| {
661+ if resolution. context != context {
662+ // This error was not received over the blinded path we created for this
663+ // resolution (the contexts differ), so it does not pertain to this resolution.
664+ return true ;
665+ }
666+ // One of the queries we made for this resolution failed. Only fail (and remove) the
667+ // resolution once all the queries we made for it have failed.
668+ resolution. pending_queries = resolution. pending_queries . saturating_sub ( 1 ) ;
669+ if resolution. pending_queries == 0 {
670+ failed_resolutions. push ( ( resolution. name , resolution. payment_id ) ) ;
671+ false
672+ } else {
673+ true
674+ }
675+ } ) ;
676+ if entry. get ( ) . is_empty ( ) {
677+ entry. remove ( ) ;
678+ }
679+ }
680+ failed_resolutions
681+ }
610682}
611683
612684#[ cfg( test) ]
613685mod tests {
614686 use super :: * ;
615687
688+ #[ cfg( feature = "dnssec" ) ]
689+ use crate :: util:: test_utils:: pubkey;
690+
691+ #[ cfg( feature = "dnssec" ) ]
692+ fn dest ( b : u8 ) -> Destination {
693+ Destination :: Node ( pubkey ( b) )
694+ }
695+
696+ /// Extracts the DNS [`Name`] and [`DNSResolverContext`] from the messages returned by
697+ /// [`OMNameResolver::resolve_name`].
698+ #[ cfg( feature = "dnssec" ) ]
699+ fn dns_name_and_context (
700+ messages : & [ ( DNSResolverMessage , MessageSendInstructions ) ] ,
701+ ) -> ( Name , DNSResolverContext ) {
702+ match & messages[ 0 ] {
703+ (
704+ DNSResolverMessage :: DNSSECQuery ( DNSSECQuery ( name) ) ,
705+ MessageSendInstructions :: WithReplyPath {
706+ context : MessageContext :: DNSResolver ( context) ,
707+ ..
708+ } ,
709+ ) => ( name. clone ( ) , context. clone ( ) ) ,
710+ _ => panic ! ( "Unexpected resolve_name output" ) ,
711+ }
712+ }
713+
616714 #[ test]
617715 fn test_hrn_display_format ( ) {
618716 let user = "user" ;
@@ -651,6 +749,21 @@ mod tests {
651749 assert ! ( HumanReadableName :: new( "user" , & huge_domain) . is_err( ) ) ;
652750 }
653751
752+ #[ test]
753+ fn test_dnssec_error_roundtrip ( ) {
754+ let name = Name :: try_from ( "test.user._bitcoin-payment.example.com." . to_owned ( ) ) . unwrap ( ) ;
755+ for definitely_unresolvable in [ false , true ] {
756+ let msg = DNSResolverMessage :: DNSSECError ( DNSSECError {
757+ name : name. clone ( ) ,
758+ definitely_unresolvable,
759+ } ) ;
760+ let mut buf = Vec :: new ( ) ;
761+ msg. write ( & mut buf) . unwrap ( ) ;
762+ let read = DNSResolverMessage :: read ( & mut & buf[ ..] , DNSSEC_ERROR_TYPE ) . unwrap ( ) ;
763+ assert_eq ! ( msg, read) ;
764+ }
765+ }
766+
654767 #[ test]
655768 #[ cfg( feature = "dnssec" ) ]
656769 fn test_expiry ( ) {
@@ -659,22 +772,22 @@ mod tests {
659772 let name = HumanReadableName :: new ( "user" , "example.com" ) . unwrap ( ) ;
660773
661774 // Queue up a resolution
662- resolver. resolve_name ( PaymentId ( [ 0 ; 32 ] ) , name. clone ( ) , & keys) . unwrap ( ) ;
775+ resolver. resolve_name ( PaymentId ( [ 0 ; 32 ] ) , name. clone ( ) , vec ! [ dest ( 42 ) ] , & keys) . unwrap ( ) ;
663776 assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
664777 // and check that it expires after two blocks
665778 resolver. new_best_block ( 44 , 42 ) ;
666779 assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 0 ) ;
667780
668781 // Queue up another resolution
669- resolver. resolve_name ( PaymentId ( [ 1 ; 32 ] ) , name. clone ( ) , & keys) . unwrap ( ) ;
782+ resolver. resolve_name ( PaymentId ( [ 1 ; 32 ] ) , name. clone ( ) , vec ! [ dest ( 42 ) ] , & keys) . unwrap ( ) ;
670783 assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
671784 // it won't expire after one block
672785 resolver. new_best_block ( 45 , 42 ) ;
673786 assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
674787 assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . iter( ) . next( ) . unwrap( ) . 1 . len( ) , 1 ) ;
675788 // and queue up a second and third resolution of the same name
676- resolver. resolve_name ( PaymentId ( [ 2 ; 32 ] ) , name. clone ( ) , & keys) . unwrap ( ) ;
677- resolver. resolve_name ( PaymentId ( [ 3 ; 32 ] ) , name. clone ( ) , & keys) . unwrap ( ) ;
789+ resolver. resolve_name ( PaymentId ( [ 2 ; 32 ] ) , name. clone ( ) , vec ! [ dest ( 42 ) ] , & keys) . unwrap ( ) ;
790+ resolver. resolve_name ( PaymentId ( [ 3 ; 32 ] ) , name. clone ( ) , vec ! [ dest ( 42 ) ] , & keys) . unwrap ( ) ;
678791 assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
679792 assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . iter( ) . next( ) . unwrap( ) . 1 . len( ) , 3 ) ;
680793 // after another block the first will expire, but the second and third won't
@@ -689,4 +802,68 @@ mod tests {
689802 resolver. new_best_block ( 47 , 42 ) ;
690803 assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 0 ) ;
691804 }
805+
806+ #[ test]
807+ #[ cfg( feature = "dnssec" ) ]
808+ fn test_dnssec_error ( ) {
809+ let keys = crate :: sign:: KeysManager :: new ( & [ 33 ; 32 ] , 0 , 0 , true ) ;
810+ let resolver = OMNameResolver :: new ( 42 , 42 ) ;
811+ let name = HumanReadableName :: new ( "user" , "example.com" ) . unwrap ( ) ;
812+
813+ // Resolve a name, sending the query to two resolvers.
814+ let messages = resolver
815+ . resolve_name ( PaymentId ( [ 0 ; 32 ] ) , name. clone ( ) , vec ! [ dest( 1 ) , dest( 2 ) ] , & keys)
816+ . unwrap ( ) ;
817+ assert_eq ! ( messages. len( ) , 2 ) ;
818+ let ( dns_name, context) = dns_name_and_context ( & messages) ;
819+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
820+
821+ // An error whose context doesn't match any pending resolution is ignored entirely (and does
822+ // not count against the resolution's queries), even if it claims the name is unresolvable.
823+ let mut wrong_context = context. clone ( ) ;
824+ wrong_context. nonce [ 0 ] ^= 0x01 ;
825+ let wrong = DNSSECError { name : dns_name. clone ( ) , definitely_unresolvable : true } ;
826+ assert ! ( resolver. handle_dnssec_error( wrong, wrong_context) . is_empty( ) ) ;
827+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
828+
829+ // The first matching error fails one of the two queries but not the resolution itself - even
830+ // though `definitely_unresolvable` is set - as a proof may yet arrive from the other query.
831+ let err = DNSSECError { name : dns_name. clone ( ) , definitely_unresolvable : true } ;
832+ assert ! ( resolver. handle_dnssec_error( err, context. clone( ) ) . is_empty( ) ) ;
833+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 1 ) ;
834+
835+ // The second matching error fails the last query, so the resolution now fails - even though
836+ // this error only indicates a transient failure.
837+ let err = DNSSECError { name : dns_name, definitely_unresolvable : false } ;
838+ let failed = resolver. handle_dnssec_error ( err, context) ;
839+ assert_eq ! ( failed, vec![ ( name, PaymentId ( [ 0 ; 32 ] ) ) ] ) ;
840+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . len( ) , 0 ) ;
841+ }
842+
843+ #[ test]
844+ #[ cfg( feature = "dnssec" ) ]
845+ fn test_dnssec_error_only_fails_matching_resolution ( ) {
846+ // An error only counts against the resolution whose blinded path (context) it was received
847+ // over; other resolutions for the same name (queued with a different `PaymentId`, and thus a
848+ // different context) are left untouched.
849+ let keys = crate :: sign:: KeysManager :: new ( & [ 33 ; 32 ] , 0 , 0 , true ) ;
850+ let resolver = OMNameResolver :: new ( 42 , 42 ) ;
851+ let name = HumanReadableName :: new ( "user" , "example.com" ) . unwrap ( ) ;
852+
853+ let messages =
854+ resolver. resolve_name ( PaymentId ( [ 0 ; 32 ] ) , name. clone ( ) , vec ! [ dest( 1 ) ] , & keys) . unwrap ( ) ;
855+ let ( dns_name, context_a) = dns_name_and_context ( & messages) ;
856+ resolver. resolve_name ( PaymentId ( [ 1 ; 32 ] ) , name. clone ( ) , vec ! [ dest( 2 ) ] , & keys) . unwrap ( ) ;
857+ assert_eq ! ( resolver. pending_resolves. lock( ) . unwrap( ) . iter( ) . next( ) . unwrap( ) . 1 . len( ) , 2 ) ;
858+
859+ // A single error over `context_a` fails only payment 0's (single-query) resolution.
860+ let err = DNSSECError { name : dns_name, definitely_unresolvable : false } ;
861+ let failed = resolver. handle_dnssec_error ( err, context_a) ;
862+ assert_eq ! ( failed, vec![ ( name, PaymentId ( [ 0 ; 32 ] ) ) ] ) ;
863+
864+ // Payment 1's resolution is still pending.
865+ let pending = resolver. pending_resolves . lock ( ) . unwrap ( ) ;
866+ assert_eq ! ( pending. iter( ) . next( ) . unwrap( ) . 1 . len( ) , 1 ) ;
867+ assert_eq ! ( pending. iter( ) . next( ) . unwrap( ) . 1 [ 0 ] . payment_id, PaymentId ( [ 1 ; 32 ] ) ) ;
868+ }
692869}
0 commit comments