mirror of
https://github.com/lexmount/moli.git
synced 2026-09-26 00:01:32 +00:00
refactor: clarify shared DNS resolution state
This commit is contained in:
@@ -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(),
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user