Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "dyns"
description = "DNS discovery and resolver support for DHTTP applications"
version = "0.6.0-beta.1"
version = "0.6.0-beta.2"
edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/genmeta/ddns"
Expand All @@ -20,7 +20,7 @@ bitfield-struct = "0.13"
bytes = "1"
dashmap = { version = "6", optional = true }
dhttp-identity = "0.2.0"
dquic = "0.7.0-beta.1"
dquic = "0.7.0-beta.2"
flume = { version = "0.12", optional = true }
futures = "0.3"
libc = { version = "0.2", optional = true }
Expand All @@ -47,7 +47,7 @@ tokio = { version = "1", features = [
tracing = "0.1"
x509-parser = { version = "0.18", features = ["verify"] }

h3x = { version = "0.6.0-beta.1", default-features = false, optional = true }
h3x = { version = "0.6.0-beta.2", default-features = false, optional = true }
http = { version = "1", optional = true }
http-body = { version = "1", optional = true }
http-body-util = { version = "0.1", optional = true }
Expand Down Expand Up @@ -80,7 +80,7 @@ mdns = ["dep:dashmap", "dep:flume", "dep:libc", "dep:socket2"]

[dev-dependencies]
clap = { version = "4", features = ["derive"] }
h3x = { version = "0.6.0-beta.1", default-features = false, features = ["dquic"] }
h3x = { version = "0.6.0-beta.2", default-features = false, features = ["dquic"] }
shellexpand = "3"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

Expand Down
121 changes: 108 additions & 13 deletions src/h3/lookup.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{io, sync::Arc};

use dhttp_identity::certificate::CertificateSequence;
use dquic::qresolve::{RecordStream, Source};
Expand All @@ -12,7 +12,12 @@ use super::{
H3LookupError, H3Resolver, LOOKUP_REQUEST_ATTEMPTS, LOOKUP_REQUEST_TIMEOUT, LookupDecodeError,
h3_lookup_error, lookup_decode_error,
};
use crate::core::{parser::packet::be_packet, wire::be_multi_response};
use crate::{
core::{parser::packet::be_packet, wire::be_multi_response},
resolvers::endpoint_candidates::{
EndpointCandidateGroup, EndpointCandidates, ResolveEndpointCandidates,
},
};

const LOOKUP_API_PATH: &str = "/api/v2/lookup";

Expand All @@ -35,11 +40,11 @@ pub(super) struct LookupRecords {
}

impl LookupRecords {
pub(super) fn decode(
pub(super) fn decode_candidate_groups(
domain: &str,
sequence: Option<CertificateSequence>,
response: &[u8],
) -> Result<Self, LookupDecodeError> {
) -> Result<crate::resolvers::endpoint_candidates::EndpointCandidateGroups<()>, LookupDecodeError>
{
use crate::core::parser::record;

let (remain, multi) = match be_multi_response(response) {
Expand Down Expand Up @@ -90,7 +95,13 @@ impl LookupRecords {
);
return None;
}
Some((ep.clone(), publisher_chain_key.clone()))
Some(
crate::resolvers::endpoint_candidates::TaggedEndpointCandidate {
tag: (),
record: ep.clone(),
fallback_chain_key: publisher_chain_key.clone(),
},
)
}
_ => {
tracing::debug!(?answer, "ignored record");
Expand All @@ -100,14 +111,33 @@ impl LookupRecords {
));
}

Ok(crate::resolvers::endpoint_candidates::grouped_endpoint_candidates(endpoint_records))
}

pub(super) fn decode(
domain: &str,
sequence: Option<CertificateSequence>,
response: &[u8],
) -> Result<Self, LookupDecodeError> {
let groups = Self::decode_candidate_groups(domain, response)?;
let endpoints = match sequence {
Some(sequence) => groups
.into_iter()
.find(|(chain_key, _)| {
chain_key.kind() == dhttp_identity::certificate::CertificateChainKind::Primary
&& chain_key.sequence() == sequence
})
.map(|(_, endpoints)| endpoints)
.unwrap_or_default(),
None => groups
.into_iter()
.next()
.map(|(_, endpoints)| endpoints)
.unwrap_or_default(),
};

Ok(Self {
endpoints:
crate::resolvers::endpoint_group::selected_endpoint_records_with_fallback_chain_keys(
endpoint_records
.into_iter()
.map(|(endpoint, fallback_chain_key)| ((), endpoint, fallback_chain_key)),
sequence,
)
endpoints: endpoints
.into_iter()
.map(|((), endpoint)| endpoint)
.collect(),
Expand Down Expand Up @@ -242,6 +272,50 @@ where
}
}

impl<C> ResolveEndpointCandidates for H3Resolver<C>
where
C: quic::Connect + quic::WithLocalAuthority + Send + Sync + 'static,
C::Error: Send + Sync + 'static,
C::Connection: Send + 'static,
{
fn lookup_endpoint_candidates<'a>(
&'a self,
name: &'a str,
) -> crate::resolvers::endpoint_candidates::EndpointCandidateFuture<'a> {
Box::pin(async move {
let Some((domain, _sequence)) =
crate::resolvers::endpoint_lookup_name_and_sequence(name)
else {
return Err(io::Error::other("no DNS record found"));
};

let url = lookup_url(&self.base_url, domain, None);
let uri: http::Uri = url.as_str().parse().expect("URL should be valid URI");
let response = self
.lookup_response_with_retry(uri)
.await
.map_err(io::Error::other)?;
let source = Source::H3 {
server: Arc::from(self.base_url.origin().ascii_serialization()),
};
let groups = LookupRecords::decode_candidate_groups(domain, response.as_ref())
.map_err(io::Error::other)?
.into_iter()
.map(|(chain, endpoints)| EndpointCandidateGroup {
chain,
endpoints: endpoints
.into_iter()
.map(|((), endpoint)| endpoint)
.collect(),
sources: vec![source.clone()],
})
.collect();

Ok(EndpointCandidates { groups })
})
}
}

#[cfg(test)]
mod tests {
use std::{collections::HashMap, net::SocketAddrV4};
Expand Down Expand Up @@ -270,6 +344,27 @@ mod tests {
MultiResponse::new([ResponseRecord::unsigned(packet, Vec::new())]).encode()
}

#[test]
fn lookup_records_decode_candidate_groups_returns_all_primary_sequences() {
let response = response_for(
"demo.dhttp.net",
vec![
direct("192.0.2.10:4433", true, 0),
direct("192.0.2.20:4433", true, 1),
direct("192.0.2.21:4433", true, 1),
],
);

let groups = LookupRecords::decode_candidate_groups("demo.dhttp.net", response.as_ref())
.expect("candidate groups decode");

assert_eq!(groups.len(), 2);
assert_eq!(groups[0].0.to_string(), "primary:0");
assert_eq!(groups[0].1.len(), 1);
assert_eq!(groups[1].0.to_string(), "primary:1");
assert_eq!(groups[1].1.len(), 2);
}

#[test]
fn h3_lookup_url_targets_v2_api_from_origin_base() {
let base_url = url::Url::parse("https://dns.example.test:4433").expect("url");
Expand Down
Loading
Loading