refactor: clarify shared DNS resolution state

This commit is contained in:
ldm0
2026-09-18 14:13:58 +08:00
committed by Donough Liu
parent d0f4ec8a3e
commit 6aff3c598f
10 changed files with 49 additions and 34 deletions
@@ -109,7 +109,7 @@ fn request(
context: (),
origin: None,
deadline: Some(Instant::now() + Duration::from_secs(10)),
dns_resolution: CurlDnsResolution::curl_managed(),
dns_resolution: CurlDnsResolution::no_shared_resolution(),
priority: 1,
label: "owner probe HTTP".into(),
})
+19 -5
View File
@@ -12,10 +12,12 @@ use crate::NetworkAddressPolicy;
/// Curl-side policy for DNS ownership before a transfer enters the multi set.
///
/// `origin == None` means curl owns name resolution. `Some` means the transfer
/// `origin == None` means no shared origin lookup is required. The configured
/// curl handle may already have a fixed address, use an IP literal or proxy, or
/// deliberately retain libcurl's resolver behavior. `Some` means the transfer
/// must first wait in [`CurlDnsOwnerResidence`]. After that residence installs
/// the exact address list with `CURLOPT_RESOLVE`, this object transitions back
/// to curl-managed so a requeued transfer cannot resolve twice.
/// the exact address list with `CURLOPT_RESOLVE`, the shared lookup is consumed
/// so a requeued transfer cannot resolve twice.
#[derive(Debug)]
pub struct CurlDnsResolution {
origin: Option<Box<CurlDnsOriginResolution>>,
@@ -32,10 +34,22 @@ struct CurlDnsOriginResolution {
}
impl CurlDnsResolution {
pub fn curl_managed() -> Self {
/// Creates a policy that does not request Moli's shared origin resolver.
///
/// This keeps `moli-curl` transport-neutral: callers may use an IP literal,
/// preconfigure `CURLOPT_RESOLVE`, delegate the target to a proxy, or retain
/// libcurl's resolver behavior.
pub fn no_shared_resolution() -> Self {
Self { origin: None }
}
#[deprecated(
note = "use `no_shared_resolution`; no shared lookup does not necessarily mean curl performs DNS"
)]
pub fn curl_managed() -> Self {
Self::no_shared_resolution()
}
pub fn resolve_origin(target: DnsTarget, static_entries: Vec<String>) -> Self {
let policy_target = format!("{}:{}", target.host(), target.port());
Self {
@@ -279,7 +293,7 @@ mod tests {
}
#[test]
fn installed_origin_transitions_back_to_curl_managed() {
fn installed_origin_consumes_shared_resolution() {
let target = DnsTarget::new("example.test", 443);
let mut policy = CurlDnsResolution::resolve_origin(target.clone(), Vec::new());
let mut easy = Easy2::new(TestHandler);
+4 -3
View File
@@ -45,9 +45,10 @@ pub struct CurlMultiJob<H: Handler, C> {
pub deadline: Option<Instant>,
/// DNS ownership chosen by the caller before this transfer enters curl.
///
/// A curl-managed policy preserves libcurl's resolver behavior. A shared
/// origin policy parks the transfer outside the curl multi handle set until
/// the bounded system resolver publishes an answer.
/// A no-shared-resolution policy leaves the already configured curl handle
/// in charge of routing; it may need no DNS lookup at all. A shared-origin
/// policy parks the transfer outside the curl multi handle set until the
/// bounded system resolver publishes an answer.
pub dns_resolution: CurlDnsResolution,
/// Higher values start before lower values when jobs are queued.
pub priority: u8,
+1 -1
View File
@@ -531,7 +531,7 @@ mod tests {
context: label.to_owned(),
origin,
deadline: None,
dns_resolution: CurlDnsResolution::curl_managed(),
dns_resolution: CurlDnsResolution::no_shared_resolution(),
priority,
label: label.to_owned(),
}
+1 -1
View File
@@ -153,7 +153,7 @@ mod tests {
context: label.to_owned(),
origin,
deadline: None,
dns_resolution: CurlDnsResolution::curl_managed(),
dns_resolution: CurlDnsResolution::no_shared_resolution(),
priority,
label: label.to_owned(),
}
+1 -1
View File
@@ -40,7 +40,7 @@ fn request(
port: Some(address.port()),
}),
deadline,
dns_resolution: CurlDnsResolution::curl_managed(),
dns_resolution: CurlDnsResolution::no_shared_resolution(),
priority: 1,
label: path.to_owned(),
}
+2 -2
View File
@@ -51,7 +51,7 @@ fn submitted_identity_reaches_the_matching_runtime_completion() {
context: "matching-context".to_owned(),
origin: None,
deadline: None,
dns_resolution: CurlDnsResolution::curl_managed(),
dns_resolution: CurlDnsResolution::no_shared_resolution(),
priority: 1,
label: "identity-test".to_owned(),
})
@@ -87,7 +87,7 @@ fn http_sender_does_not_keep_owner_alive_and_returns_rejected_job() {
context: vec![7; 1024],
origin: None,
deadline: None,
dns_resolution: CurlDnsResolution::curl_managed(),
dns_resolution: CurlDnsResolution::no_shared_resolution(),
priority: 1,
label: "closed".into(),
})
+1 -1
View File
@@ -58,7 +58,7 @@ impl CurlWebSocketRequest {
proxy: None,
proxy_headers: Vec::new(),
tls: CurlTlsConfig::default(),
dns_resolution: CurlDnsResolution::curl_managed(),
dns_resolution: CurlDnsResolution::no_shared_resolution(),
handshake_timeout: Duration::from_secs(30),
}
}
+1 -1
View File
@@ -56,7 +56,7 @@ fn request(url: &str, handler: HttpCapture, timeout: Duration) -> CurlMultiJob<H
port: url.port_or_known_default(),
}),
deadline: Some(std::time::Instant::now() + timeout),
dns_resolution: CurlDnsResolution::curl_managed(),
dns_resolution: CurlDnsResolution::no_shared_resolution(),
priority: 1,
label: "mixed test".to_owned(),
}
+18 -18
View File
@@ -12,14 +12,14 @@ use crate::{
/// Fetch-side DNS admission decision.
///
/// The shared resolver is used only when Fetch can prove that curl will
/// connect directly to an HTTP(S) origin. Proxy traffic stays curl-managed
/// only when no address policy is active; otherwise a proxy-resolved hostname
/// cannot be verified locally and is rejected. IP literals and matching
/// explicit host-resolve entries already have exact routing and are checked
/// synchronously before this decision.
/// connect directly to an HTTP(S) origin. Proxy traffic needs no shared origin
/// lookup only when no address policy is active; otherwise a proxy-resolved
/// hostname cannot be verified locally and is rejected. IP literals and
/// matching explicit host-resolve entries already have exact routing and are
/// checked synchronously before this decision.
#[derive(Debug, Clone, PartialEq, Eq)]
enum FetchCurlDnsAdmission {
CurlManaged,
NoSharedResolution,
SharedResolver(DnsTarget),
}
@@ -29,7 +29,7 @@ pub(crate) fn curl_dns_resolution(
proxy_route: &HttpProxyRoute,
) -> Result<CurlDnsResolution> {
match curl_dns_admission(config, url, proxy_route)? {
FetchCurlDnsAdmission::CurlManaged => Ok(CurlDnsResolution::curl_managed()),
FetchCurlDnsAdmission::NoSharedResolution => Ok(CurlDnsResolution::no_shared_resolution()),
FetchCurlDnsAdmission::SharedResolver(target) => {
let policy = config.network_address_policy();
Ok(CurlDnsResolution::resolve_origin(
@@ -50,13 +50,13 @@ fn curl_dns_admission(
proxy_route: &HttpProxyRoute,
) -> Result<FetchCurlDnsAdmission> {
if !matches!(url.scheme(), "http" | "https") {
return Ok(FetchCurlDnsAdmission::CurlManaged);
return Ok(FetchCurlDnsAdmission::NoSharedResolution);
}
let Some(Host::Domain(host)) = url.host() else {
return Ok(FetchCurlDnsAdmission::CurlManaged);
return Ok(FetchCurlDnsAdmission::NoSharedResolution);
};
let Some(port) = url.port_or_known_default() else {
return Ok(FetchCurlDnsAdmission::CurlManaged);
return Ok(FetchCurlDnsAdmission::NoSharedResolution);
};
if proxy_route.is_proxy() {
if config.network_address_policy().is_enforced() {
@@ -64,10 +64,10 @@ fn curl_dns_admission(
"cannot enforce network address policy for proxied hostname `{host}` in `{url}`; the proxy must not resolve an unchecked target hostname"
);
}
return Ok(FetchCurlDnsAdmission::CurlManaged);
return Ok(FetchCurlDnsAdmission::NoSharedResolution);
}
if resolve_host_resolve_override_ips(config.http_host_resolve(), host, port)?.is_some() {
return Ok(FetchCurlDnsAdmission::CurlManaged);
return Ok(FetchCurlDnsAdmission::NoSharedResolution);
}
Ok(FetchCurlDnsAdmission::SharedResolver(DnsTarget::new(
host, port,
@@ -121,21 +121,21 @@ mod tests {
}
#[test]
fn ip_literals_and_matching_host_resolve_entries_stay_curl_managed() {
fn ip_literals_and_matching_host_resolve_entries_need_no_shared_resolution() {
let mut config = FetchConfig::default();
assert_eq!(
admission(&config, "http://127.0.0.1/path", &HttpProxyRoute::Direct,),
FetchCurlDnsAdmission::CurlManaged
FetchCurlDnsAdmission::NoSharedResolution
);
assert_eq!(
admission(&config, "http://[::1]/path", &HttpProxyRoute::Direct,),
FetchCurlDnsAdmission::CurlManaged
FetchCurlDnsAdmission::NoSharedResolution
);
config.set_http_host_resolve(vec!["example.test:80:127.0.0.1".to_owned()]);
assert_eq!(
admission(&config, "http://example.test/path", &HttpProxyRoute::Direct,),
FetchCurlDnsAdmission::CurlManaged
FetchCurlDnsAdmission::NoSharedResolution
);
assert_eq!(
admission(&config, "http://other.test/path", &HttpProxyRoute::Direct,),
@@ -145,7 +145,7 @@ mod tests {
}
#[test]
fn selected_proxy_route_uses_curl_resolution() {
fn selected_proxy_route_needs_no_shared_origin_resolution() {
let config = FetchConfig::default();
assert_eq!(
@@ -154,7 +154,7 @@ mod tests {
"https://api.example.test/path",
&HttpProxyRoute::Proxy("http://proxy.test:8080".to_owned()),
),
FetchCurlDnsAdmission::CurlManaged
FetchCurlDnsAdmission::NoSharedResolution
);
}