diff --git a/Cargo.lock b/Cargo.lock
index 49a668dee7570f..1ac47dc6838216 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2136,6 +2136,7 @@ dependencies = [
"thiserror 2.0.3",
"tokio",
"tokio-eld",
+ "tower-service",
"url",
"webpki-root-certs",
"winapi",
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
index 0e266c89a5cbf1..fdfa8831eda42a 100644
--- a/ext/node/Cargo.toml
+++ b/ext/node/Cargo.toml
@@ -98,6 +98,7 @@ sys_traits = { workspace = true, features = ["real", "winapi", "libc"] }
thiserror.workspace = true
tokio.workspace = true
tokio-eld = "0.2"
+tower-service.workspace = true
url.workspace = true
webpki-root-certs.workspace = true
winapi.workspace = true
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index 68d16bfd6a2b80..9359f55980e8bc 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -312,6 +312,7 @@ deno_core::extension!(deno_node,
ops::crypto::x509::op_node_x509_get_serial_number,
ops::crypto::x509::op_node_x509_key_usage,
ops::crypto::x509::op_node_x509_public_key,
+ ops::dns::op_getaddrinfo,
ops::fs::op_node_fs_exists_sync
,
ops::fs::op_node_fs_exists
,
ops::fs::op_node_cp_sync
,
diff --git a/ext/node/ops/dns.rs b/ext/node/ops/dns.rs
new file mode 100644
index 00000000000000..5342f7897dec66
--- /dev/null
+++ b/ext/node/ops/dns.rs
@@ -0,0 +1,50 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+
+use std::str::FromStr;
+
+use deno_core::op2;
+use hyper_util::client::legacy::connect::dns::GaiResolver;
+use hyper_util::client::legacy::connect::dns::Name;
+use serde::Serialize;
+use tower_service::Service;
+
+#[derive(Serialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct GetAddrInfoResult {
+ family: usize,
+ address: String,
+}
+
+#[derive(Debug, thiserror::Error, deno_error::JsError)]
+#[class(generic)]
+#[error("Could not resolve the hostname '{hostname}'")]
+pub struct GetAddrInfoError {
+ hostname: String,
+}
+
+#[op2(async, stack_trace)]
+#[serde]
+pub async fn op_getaddrinfo(
+ #[string] hostname: String,
+) -> Result, GetAddrInfoError> {
+ let mut resolver = GaiResolver::new();
+ let name = Name::from_str(&hostname).map_err(|_| GetAddrInfoError {
+ hostname: hostname.clone(),
+ })?;
+ resolver
+ .call(name)
+ .await
+ .map_err(|_| GetAddrInfoError { hostname })
+ .map(|addrs| {
+ addrs
+ .into_iter()
+ .map(|addr| GetAddrInfoResult {
+ family: match addr {
+ std::net::SocketAddr::V4(_) => 4,
+ std::net::SocketAddr::V6(_) => 6,
+ },
+ address: addr.ip().to_string(),
+ })
+ .collect::>()
+ })
+}
diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs
index 8dae6aeff68259..a251ec416cb47e 100644
--- a/ext/node/ops/mod.rs
+++ b/ext/node/ops/mod.rs
@@ -3,6 +3,7 @@
pub mod blocklist;
pub mod buffer;
pub mod crypto;
+pub mod dns;
pub mod fs;
pub mod http;
pub mod http2;
diff --git a/ext/node/polyfills/internal_binding/cares_wrap.ts b/ext/node/polyfills/internal_binding/cares_wrap.ts
index 6a864e5855c48c..fc52ac11dde8ce 100644
--- a/ext/node/polyfills/internal_binding/cares_wrap.ts
+++ b/ext/node/polyfills/internal_binding/cares_wrap.ts
@@ -28,7 +28,6 @@
// deno-lint-ignore-file prefer-primordials
import type { ErrnoException } from "ext:deno_node/internal/errors.ts";
-import { isIPv4 } from "ext:deno_node/internal/net.ts";
import { codeMap } from "ext:deno_node/internal_binding/uv.ts";
import {
AsyncWrap,
@@ -36,6 +35,7 @@ import {
} from "ext:deno_node/internal_binding/async_wrap.ts";
import { ares_strerror } from "ext:deno_node/internal_binding/ares.ts";
import { notImplemented } from "ext:deno_node/_utils.ts";
+import { op_getaddrinfo } from "ext:core/ops";
interface LookupAddress {
address: string;
@@ -67,38 +67,31 @@ export function getaddrinfo(
_hints: number,
verbatim: boolean,
): number {
- const addresses: string[] = [];
+ type Addr = {
+ address: string;
+ family: number;
+ };
+ let addresses: Addr[] = [];
// TODO(cmorten): use hints
// REF: https://nodejs.org/api/dns.html#dns_supported_getaddrinfo_flags
- const recordTypes: ("A" | "AAAA")[] = [];
-
- if (family === 0 || family === 4) {
- recordTypes.push("A");
- }
- if (family === 0 || family === 6) {
- recordTypes.push("AAAA");
- }
-
(async () => {
- await Promise.allSettled(
- recordTypes.map((recordType) =>
- Deno.resolveDns(hostname, recordType).then((records) => {
- records.forEach((record) => addresses.push(record));
- })
- ),
- );
+ try {
+ addresses.push(...await op_getaddrinfo(hostname));
+ } catch {
+ // pass
+ }
const error = addresses.length ? 0 : codeMap.get("EAI_NODATA")!;
// TODO(cmorten): needs work
// REF: https://github.com/nodejs/node/blob/master/src/cares_wrap.cc#L1444
if (!verbatim) {
- addresses.sort((a: string, b: string): number => {
- if (isIPv4(a)) {
+ addresses.sort((a: Addr, b: Addr): number => {
+ if (a.family === 4 && b.family === 6) {
return -1;
- } else if (isIPv4(b)) {
+ } else if (a.family === 6 && b.family === 4) {
return 1;
}
@@ -106,7 +99,11 @@ export function getaddrinfo(
});
}
- req.oncomplete(error, addresses);
+ if (family === 4 || family === 6) {
+ addresses = addresses.filter((addr) => addr.family === family);
+ }
+
+ req.oncomplete(error, addresses.map((addr) => addr.address));
})();
return 0;