Skip to content

Commit 6ab8a3f

Browse files
committed
fix: wrap ALL DNS resolver calls in dedicated threads
Every call to trust-dns Resolver (lookup_ip, lookup, resolve_dns) panics inside a tokio runtime. Wrap dns_resolver in Arc and spawn all DNS operations in dedicated threads.
1 parent b73df64 commit 6ab8a3f

File tree

1 file changed

+47
-14
lines changed

1 file changed

+47
-14
lines changed

crates/wasm-runtime-interface/src/network.rs

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ pub struct NetworkState {
160160
policy: ValidatedNetworkPolicy,
161161
audit_logger: Option<Arc<dyn NetworkAuditLogger>>,
162162
http_client: Client,
163-
dns_resolver: Resolver,
163+
dns_resolver: Arc<Resolver>,
164164
dns_cache: HashMap<DnsCacheKey, DnsCacheEntry>,
165165
requests_made: u32,
166166
dns_lookups: u32,
@@ -215,7 +215,7 @@ impl NetworkState {
215215
let resolver = Resolver::new(ResolverConfig::default(), resolver_opts)
216216
.map_err(|err| NetworkStateError::DnsResolver(err.to_string()))?;
217217

218-
Ok::<_, NetworkStateError>((client, resolver))
218+
Ok::<_, NetworkStateError>((client, Arc::new(resolver)))
219219
})
220220
.join()
221221
.map_err(|_| {
@@ -278,7 +278,14 @@ impl NetworkState {
278278
method: request.method,
279279
});
280280

281-
let _resolved_ip = self.resolve_and_validate_ip(&request.url)?;
281+
// Perform DNS resolution, IP validation, and the HTTP request in a
282+
// dedicated thread. All three operations can internally call
283+
// `block_on` (trust-dns lookup, reqwest blocking send) which panics
284+
// when executed inside a tokio runtime.
285+
let dns_resolver = self.dns_resolver.clone();
286+
let policy_ranges = self.policy.allowed_ip_ranges.clone();
287+
let block_private = self.policy.dns_policy.block_private_ranges;
288+
let url_clone = request.url.clone();
282289

283290
let method = to_reqwest_method(request.method);
284291
let mut builder = self.http_client.request(method, &request.url);
@@ -289,11 +296,26 @@ impl NetworkState {
289296
builder = builder.body(request.body.clone());
290297
}
291298

292-
// Execute HTTP request in a dedicated thread to avoid
293-
// "Cannot start a runtime from within a runtime" panic when
294-
// reqwest::blocking internally calls block_on for DNS resolution.
295299
let max_response_bytes = self.policy.limits.max_response_bytes;
296300
let (status, headers, body) = std::thread::spawn(move || {
301+
// DNS resolution + IP policy check
302+
if let Some(host_str) = url::Url::parse(&url_clone)
303+
.ok()
304+
.and_then(|u| u.host_str().map(|s| s.to_string()))
305+
{
306+
if host_str.parse::<std::net::IpAddr>().is_err() {
307+
if let Ok(lookup) = dns_resolver.lookup_ip(host_str.as_str()) {
308+
for ip in lookup.iter().collect::<Vec<std::net::IpAddr>>() {
309+
if block_private && is_private_ip(ip) {
310+
return Err(NetworkError::PolicyViolation(format!(
311+
"connection to private IP blocked: {ip}"
312+
)));
313+
}
314+
}
315+
}
316+
}
317+
}
318+
297319
let response = builder.send().map_err(map_reqwest_error)?;
298320
let status = response.status().as_u16();
299321
let headers = collect_headers(response.headers())?;
@@ -360,7 +382,12 @@ impl NetworkState {
360382
hostname: request.hostname.clone(),
361383
});
362384

363-
let records = resolve_dns(&self.dns_resolver, &request, &self.policy)?;
385+
let resolver = Arc::clone(&self.dns_resolver);
386+
let req_clone = request.clone();
387+
let policy_clone = self.policy.clone();
388+
let records = std::thread::spawn(move || resolve_dns(&resolver, &req_clone, &policy_clone))
389+
.join()
390+
.map_err(|_| NetworkError::DnsFailure("DNS resolve thread panicked".to_string()))??;
364391
if records.is_empty() {
365392
return Err(NetworkError::DnsFailure("no records returned".to_string()));
366393
}
@@ -458,15 +485,21 @@ impl NetworkState {
458485
return Ok(Some(ip));
459486
}
460487

461-
let lookup = self.dns_resolver.lookup_ip(host_str).map_err(|err| {
462-
NetworkError::DnsFailure(format!("pre-connect resolve failed: {err}"))
463-
})?;
464-
465-
for ip in lookup.iter() {
466-
self.validate_ip_against_policy(ip)?;
488+
let resolver = Arc::clone(&self.dns_resolver);
489+
let host = host_str.to_string();
490+
let lookup = std::thread::spawn(move || resolver.lookup_ip(host.as_str()))
491+
.join()
492+
.map_err(|_| NetworkError::DnsFailure("DNS lookup thread panicked".to_string()))?
493+
.map_err(|err| {
494+
NetworkError::DnsFailure(format!("pre-connect resolve failed: {err}"))
495+
})?;
496+
497+
let ips: Vec<IpAddr> = lookup.iter().collect();
498+
for ip in &ips {
499+
self.validate_ip_against_policy(*ip)?;
467500
}
468501

469-
Ok(lookup.iter().next())
502+
Ok(ips.into_iter().next())
470503
}
471504

472505
fn validate_ip_against_policy(&self, ip: IpAddr) -> Result<(), NetworkError> {

0 commit comments

Comments
 (0)