Skip to content

Commit 742568d

Browse files
committed
Generate DNSSECError messages when DNSSEC resolution fails
lightning/blips#71 updated the DNSSEC resolution bLIP to include an explicit error message when DNS(SEC) resolution was attempted but failed, allowing for faster fallback to LN-Address (for clients that do) and faster payment failure. Here we add service-side support for generating the error messages, informing requesters that their resolutions have failed. Largely written by an LLM
1 parent 30626ff commit 742568d

1 file changed

Lines changed: 100 additions & 7 deletions

File tree

  • lightning-dns-resolver/src

lightning-dns-resolver/src/lib.rs

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::net::SocketAddr;
99
use std::sync::atomic::{AtomicUsize, Ordering};
1010
use std::sync::{Arc, Mutex};
1111

12-
use dnssec_prover::query::build_txt_proof_async;
12+
use dnssec_prover::query::{build_txt_proof_async, ProofBuildingError};
1313

1414
use lightning::blinded_path::message::DNSResolverContext;
1515
use lightning::ln::peer_handler::IgnoringMessageHandler;
@@ -127,11 +127,25 @@ impl<PH: DNSResolverMessageHandler> DNSResolverMessageHandler for OMDomainResolv
127127
}
128128
let us = Arc::clone(&self.state);
129129
runtime.spawn(async move {
130-
if let Ok((proof, _ttl)) = build_txt_proof_async(us.resolver, &q.0).await {
131-
let contents = DNSResolverMessage::DNSSECProof(DNSSECProof { name: q.0, proof });
132-
let instructions = responder.respond().into_instructions();
133-
us.pending_replies.lock().unwrap().push((contents, instructions));
134-
}
130+
let contents = match build_txt_proof_async(us.resolver, &q.0).await {
131+
Ok((proof, _ttl)) => {
132+
DNSResolverMessage::DNSSECProof(DNSSECProof { name: q.0, proof })
133+
},
134+
Err(e) => {
135+
// We might get an Unauthenticated error if the DNS resolver does not support
136+
// DNSSEC, so we only set `definitely_unresolvable` if we get an NXDOMAIN.
137+
let definitely_unresolvable = matches!(
138+
e.get_ref().and_then(|e| e.downcast_ref::<ProofBuildingError>()),
139+
Some(ProofBuildingError::NoSuchName)
140+
);
141+
DNSResolverMessage::DNSSECError(DNSSECError {
142+
name: q.0,
143+
definitely_unresolvable,
144+
})
145+
},
146+
};
147+
let instructions = responder.respond().into_instructions();
148+
us.pending_replies.lock().unwrap().push((contents, instructions));
135149
us.pending_query_count.fetch_sub(1, Ordering::Relaxed);
136150
});
137151
None
@@ -217,6 +231,7 @@ mod test {
217231

218232
struct URIResolver {
219233
resolved_uri: Mutex<Option<(HumanReadableName, PaymentId, String)>>,
234+
resolved_error: Mutex<Option<(HumanReadableName, PaymentId, bool)>>,
220235
resolver: OMNameResolver,
221236
pending_messages: Mutex<Vec<(DNSResolverMessage, MessageSendInstructions)>>,
222237
}
@@ -236,7 +251,13 @@ mod test {
236251
assert!(result.is_none());
237252
}
238253
fn handle_dnssec_error(&self, msg: DNSSECError, context: DNSResolverContext) {
239-
// TODO
254+
let definitely_unresolvable = msg.definitely_unresolvable;
255+
let mut failed = self.resolver.handle_dnssec_error(msg, context);
256+
assert_eq!(failed.len(), 1);
257+
let (name, payment_id) = failed.pop().unwrap();
258+
let mut result = Some((name, payment_id, definitely_unresolvable));
259+
core::mem::swap(&mut *self.resolved_error.lock().unwrap(), &mut result);
260+
assert!(result.is_none());
240261
}
241262
fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> {
242263
core::mem::take(&mut *self.pending_messages.lock().unwrap())
@@ -286,6 +307,7 @@ mod test {
286307
let payer_id = payer_keys.get_node_id(Recipient::Node).unwrap();
287308
let payer = Arc::new(URIResolver {
288309
resolved_uri: Mutex::new(None),
310+
resolved_error: Mutex::new(None),
289311
resolver: OMNameResolver::new(now as u32, 1),
290312
pending_messages: Mutex::new(Vec::new()),
291313
});
@@ -331,6 +353,76 @@ mod test {
331353
assert!(resolution.2[.."bitcoin:".len()].eq_ignore_ascii_case("bitcoin:"));
332354
}
333355

356+
#[tokio::test]
357+
async fn resolution_failure_test() {
358+
// Test that querying for a name which does not exist results in a `DNSSECError` with
359+
// `definitely_unresolvable` set being returned (rather than a `DNSSECProof`).
360+
361+
let (resolver_messenger, resolver_id) = create_resolver();
362+
363+
let resolver_dest = Destination::Node(resolver_id);
364+
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
365+
366+
let payment_id = PaymentId([43; 32]);
367+
// `mattcorallo.com` is DNSSEC-signed, so a name which does not exist under it will result in
368+
// an authenticated NXDOMAIN, i.e. a definitely-unresolvable name.
369+
let name =
370+
HumanReadableName::from_encoded("nonexistent-user-ldk-test@mattcorallo.com").unwrap();
371+
372+
let payer_keys = Arc::new(KeysManager::new(&[3; 32], 42, 43, true));
373+
let payer_logger = TestLogger { node: "payer" };
374+
let payer_id = payer_keys.get_node_id(Recipient::Node).unwrap();
375+
let payer = Arc::new(URIResolver {
376+
resolved_uri: Mutex::new(None),
377+
resolved_error: Mutex::new(None),
378+
resolver: OMNameResolver::new(now as u32, 1),
379+
pending_messages: Mutex::new(Vec::new()),
380+
});
381+
let payer_messenger = Arc::new(OnionMessenger::new(
382+
Arc::clone(&payer_keys),
383+
Arc::clone(&payer_keys),
384+
payer_logger,
385+
DummyNodeLookup {},
386+
DirectlyConnectedRouter {},
387+
IgnoringMessageHandler {},
388+
IgnoringMessageHandler {},
389+
Arc::clone(&payer),
390+
IgnoringMessageHandler {},
391+
));
392+
393+
let init_msg = get_om_init();
394+
payer_messenger.peer_connected(resolver_id, &init_msg, true).unwrap();
395+
resolver_messenger.get_om().peer_connected(payer_id, &init_msg, false).unwrap();
396+
397+
let messages = payer
398+
.resolver
399+
.resolve_name(payment_id, name.clone(), vec![resolver_dest], &*payer_keys)
400+
.unwrap();
401+
payer.pending_messages.lock().unwrap().extend(messages);
402+
403+
let query = payer_messenger.next_onion_message_for_peer(resolver_id).unwrap();
404+
resolver_messenger.get_om().handle_onion_message(payer_id, &query);
405+
406+
assert!(resolver_messenger.get_om().next_onion_message_for_peer(payer_id).is_none());
407+
let start = Instant::now();
408+
let response = loop {
409+
tokio::time::sleep(Duration::from_millis(10)).await;
410+
if let Some(msg) = resolver_messenger.get_om().next_onion_message_for_peer(payer_id) {
411+
break msg;
412+
}
413+
assert!(start.elapsed() < Duration::from_secs(10), "Resolution took too long");
414+
};
415+
416+
payer_messenger.handle_onion_message(resolver_id, &response);
417+
let (failed_name, failed_payment_id, definitely_unresolvable) =
418+
payer.resolved_error.lock().unwrap().take().unwrap();
419+
assert_eq!(failed_name, name);
420+
assert_eq!(failed_payment_id, payment_id);
421+
assert!(definitely_unresolvable);
422+
// The failed resolution should have been removed from the resolver.
423+
assert!(payer.resolved_uri.lock().unwrap().is_none());
424+
}
425+
334426
#[tokio::test]
335427
async fn failed_query_does_not_leak_pending_counter() {
336428
use std::sync::atomic::Ordering;
@@ -368,6 +460,7 @@ mod test {
368460
let payer_id = payer_keys.get_node_id(Recipient::Node).unwrap();
369461
let payer = Arc::new(URIResolver {
370462
resolved_uri: Mutex::new(None),
463+
resolved_error: Mutex::new(None),
371464
resolver: OMNameResolver::new(now as u32, 1),
372465
pending_messages: Mutex::new(Vec::new()),
373466
});

0 commit comments

Comments
 (0)