summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock213
-rw-r--r--cli/Cargo.toml2
-rw-r--r--cli/dts/lib.deno.unstable.d.ts86
-rw-r--r--cli/tests/integration_tests.rs276
-rw-r--r--cli/tests/resolve_dns.ts36
-rw-r--r--cli/tests/resolve_dns.ts.out16
-rw-r--r--runtime/Cargo.toml2
-rw-r--r--runtime/js/30_net.js5
-rw-r--r--runtime/js/90_deno_ns.js1
-rw-r--r--runtime/ops/net.rs255
-rw-r--r--runtime/permissions.rs97
-rw-r--r--test_util/src/lib.rs2
12 files changed, 982 insertions, 9 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f03ae67be..0cb765cde 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -398,6 +398,12 @@ dependencies = [
]
[[package]]
+name = "data-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "993a608597367c6377b258c25d7120740f00ed23a2252b729b1932dd7866f908"
+
+[[package]]
name = "deno"
version = "1.6.3"
dependencies = [
@@ -449,6 +455,8 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-test",
+ "trust-dns-client",
+ "trust-dns-server",
"uuid",
"walkdir",
"winapi 0.3.9",
@@ -564,6 +572,8 @@ dependencies = [
"test_util",
"tokio",
"tokio-rustls",
+ "trust-dns-proto",
+ "trust-dns-resolver",
"uuid",
"webpki",
"webpki-roots",
@@ -699,6 +709,24 @@ dependencies = [
]
[[package]]
+name = "endian-type"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
+
+[[package]]
+name = "enum-as-inner"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
+dependencies = [
+ "heck",
+ "proc-macro2 1.0.24",
+ "quote 1.0.7",
+ "syn 1.0.56",
+]
+
+[[package]]
name = "enum_kind"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1081,6 +1109,17 @@ dependencies = [
]
[[package]]
+name = "hostname"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
+dependencies = [
+ "libc",
+ "match_cfg",
+ "winapi 0.3.9",
+]
+
+[[package]]
name = "http"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1239,6 +1278,18 @@ dependencies = [
]
[[package]]
+name = "ipconfig"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
+dependencies = [
+ "socket2",
+ "widestring",
+ "winapi 0.3.9",
+ "winreg 0.6.2",
+]
+
+[[package]]
name = "ipnet"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1307,6 +1358,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
name = "lock_api"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1326,6 +1383,15 @@ dependencies = [
]
[[package]]
+name = "lru-cache"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
name = "lsp-types"
version = "0.86.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1374,6 +1440,12 @@ dependencies = [
]
[[package]]
+name = "match_cfg"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
+
+[[package]]
name = "matches"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1491,6 +1563,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
+name = "nibble_vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
name = "nix"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1849,6 +1930,12 @@ dependencies = [
]
[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1879,6 +1966,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426"
[[package]]
+name = "radix_trie"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
+dependencies = [
+ "endian-type",
+ "nibble_vec",
+]
+
+[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2042,7 +2139,17 @@ dependencies = [
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
- "winreg",
+ "winreg 0.7.0",
+]
+
+[[package]]
+name = "resolv-conf"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
+dependencies = [
+ "hostname",
+ "quick-error",
]
[[package]]
@@ -3031,6 +3138,95 @@ dependencies = [
]
[[package]]
+name = "trust-dns-client"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "498e4de74132fb535c0608d0f221a3e64ddf6585717296afb2baf5925b38f31f"
+dependencies = [
+ "cfg-if 1.0.0",
+ "chrono",
+ "data-encoding",
+ "futures-channel",
+ "futures-util",
+ "lazy_static",
+ "log",
+ "radix_trie",
+ "rand 0.8.1",
+ "thiserror",
+ "tokio",
+ "trust-dns-proto",
+]
+
+[[package]]
+name = "trust-dns-proto"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98a0381b2864c2978db7f8e17c7b23cca5a3a5f99241076e13002261a8ecbabd"
+dependencies = [
+ "async-trait",
+ "cfg-if 1.0.0",
+ "data-encoding",
+ "enum-as-inner",
+ "futures-channel",
+ "futures-io",
+ "futures-util",
+ "idna",
+ "ipnet",
+ "lazy_static",
+ "log",
+ "rand 0.8.1",
+ "serde",
+ "smallvec",
+ "thiserror",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "trust-dns-resolver"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3072d18c10bd621cb00507d59cfab5517862285c353160366e37fbf4c74856e4"
+dependencies = [
+ "cfg-if 1.0.0",
+ "futures-util",
+ "ipconfig",
+ "lazy_static",
+ "log",
+ "lru-cache",
+ "parking_lot",
+ "resolv-conf",
+ "serde",
+ "smallvec",
+ "thiserror",
+ "tokio",
+ "trust-dns-proto",
+]
+
+[[package]]
+name = "trust-dns-server"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9da8b74feb06ae242b03f40f8da3a414e37827118200fcb3d03d8f825cbbff2c"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "cfg-if 1.0.0",
+ "chrono",
+ "enum-as-inner",
+ "env_logger",
+ "futures-executor",
+ "futures-util",
+ "log",
+ "serde",
+ "thiserror",
+ "tokio",
+ "toml",
+ "trust-dns-client",
+ "trust-dns-proto",
+]
+
+[[package]]
name = "try-lock"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3302,6 +3498,12 @@ dependencies = [
]
[[package]]
+name = "widestring"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
+
+[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3346,6 +3548,15 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winreg"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "winreg"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 38809706f..04861e302 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -91,6 +91,8 @@ chrono = "0.4.19"
os_pipe = "0.9.2"
test_util = { path = "../test_util" }
tower-test = "0.4.0"
+trust-dns-server = "0.20.0"
+trust-dns-client = "0.20.0"
[target.'cfg(unix)'.dev-dependencies]
exec = "0.3.1" # Used in test_raw_tty
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index 1de7ed8cc..c73703368 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -835,6 +835,92 @@ declare namespace Deno {
mtime: number | Date,
): Promise<void>;
+ /** The type of the resource record.
+ * Only the listed types are supported currently. */
+ export type RecordType =
+ | "A"
+ | "AAAA"
+ | "ANAME"
+ | "CNAME"
+ | "MX"
+ | "PTR"
+ | "SRV"
+ | "TXT";
+
+ export interface ResolveDnsOptions {
+ /** The name server to be used for lookups.
+ * If not specified, defaults to the system configuration e.g. `/etc/resolv.conf` on Unix. */
+ nameServer?: {
+ /** The IP address of the name server */
+ ipAddr: string;
+ /** The port number the query will be sent to.
+ * If not specified, defaults to 53. */
+ port?: number;
+ };
+ }
+
+ /** If `resolveDns` is called with "MX" record type specified, it will return an array of this interface. */
+ export interface MXRecord {
+ preference: number;
+ exchange: string;
+ }
+
+ /** If `resolveDns` is called with "SRV" record type specified, it will return an array of this interface. */
+ export interface SRVRecord {
+ priority: number;
+ weight: number;
+ port: number;
+ target: string;
+ }
+
+ export function resolveDns(
+ query: string,
+ recordType: "A" | "AAAA" | "ANAME" | "CNAME" | "PTR",
+ options?: ResolveDnsOptions,
+ ): Promise<string[]>;
+
+ export function resolveDns(
+ query: string,
+ recordType: "MX",
+ options?: ResolveDnsOptions,
+ ): Promise<MXRecord[]>;
+
+ export function resolveDns(
+ query: string,
+ recordType: "SRV",
+ options?: ResolveDnsOptions,
+ ): Promise<SRVRecord[]>;
+
+ export function resolveDns(
+ query: string,
+ recordType: "TXT",
+ options?: ResolveDnsOptions,
+ ): Promise<string[][]>;
+
+ /** ** UNSTABLE**: new API, yet to be vetted.
+ *
+ * Performs DNS resolution against the given query, returning resolved records.
+ * Fails in the cases such as:
+ * - the query is in invalid format
+ * - the options have an invalid parameter, e.g. `nameServer.port` is beyond the range of 16-bit unsigned integer
+ * - timed out
+ *
+ * ```ts
+ * const a = await Deno.resolveDns("example.com", "A");
+ *
+ * const aaaa = await Deno.resolveDns("example.com", "AAAA", {
+ * nameServer: { ipAddr: "8.8.8.8", port: 1234 },
+ * });
+ * ```
+ *
+ * Requires `allow-net` permission.
+ */
+ export function resolveDns(
+ query: string,
+ recordType: RecordType,
+ options?: ResolveDnsOptions,
+ ): Promise<string[] | MXRecord[] | SRVRecord[] | string[][]>;
+
/** **UNSTABLE**: new API, yet to be vetted.
*
* A generic transport listener for message-oriented protocols. */
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index 9cb5cd5f1..0af4709fb 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -5367,3 +5367,279 @@ fn web_platform_tests() {
}
}
}
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_resolve_dns() {
+ use std::collections::BTreeMap;
+ use std::net::Ipv4Addr;
+ use std::net::Ipv6Addr;
+ use std::net::SocketAddr;
+ use std::str::FromStr;
+ use std::sync::Arc;
+ use std::sync::RwLock;
+ use std::time::Duration;
+ use tokio::net::TcpListener;
+ use tokio::net::UdpSocket;
+ use tokio::sync::oneshot;
+ use trust_dns_client::rr::LowerName;
+ use trust_dns_client::rr::RecordType;
+ use trust_dns_client::rr::RrKey;
+ use trust_dns_server::authority::Catalog;
+ use trust_dns_server::authority::ZoneType;
+ use trust_dns_server::proto::rr::rdata::mx::MX;
+ use trust_dns_server::proto::rr::rdata::soa::SOA;
+ use trust_dns_server::proto::rr::rdata::srv::SRV;
+ use trust_dns_server::proto::rr::rdata::txt::TXT;
+ use trust_dns_server::proto::rr::record_data::RData;
+ use trust_dns_server::proto::rr::resource::Record;
+ use trust_dns_server::proto::rr::Name;
+ use trust_dns_server::proto::rr::RecordSet;
+ use trust_dns_server::store::in_memory::InMemoryAuthority;
+ use trust_dns_server::ServerFuture;
+
+ const DNS_PORT: u16 = 4553;
+
+ // Setup DNS server for testing
+ async fn run_dns_server(tx: oneshot::Sender<()>) {
+ let catalog = {
+ let records = {
+ let mut map = BTreeMap::new();
+ let lookup_name = "www.example.com".parse::<Name>().unwrap();
+ let lookup_name_lower = LowerName::new(&lookup_name);
+
+ // Inserts SOA record
+ let soa = SOA::new(
+ Name::from_str("net").unwrap(),
+ Name::from_str("example").unwrap(),
+ 0,
+ i32::MAX,
+ i32::MAX,
+ i32::MAX,
+ 0,
+ );
+ let rdata = RData::SOA(soa);
+ let record = Record::from_rdata(Name::new(), u32::MAX, rdata);
+ let record_set = RecordSet::from(record);
+ map
+ .insert(RrKey::new(Name::root().into(), RecordType::SOA), record_set);
+
+ // Inserts A record
+ let rdata = RData::A(Ipv4Addr::new(1, 2, 3, 4));
+ let record = Record::from_rdata(lookup_name.clone(), u32::MAX, rdata);
+ let record_set = RecordSet::from(record);
+ map.insert(
+ RrKey::new(lookup_name_lower.clone(), RecordType::A),
+ record_set,
+ );
+
+ // Inserts AAAA record
+ let rdata = RData::AAAA(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8));
+ let record = Record::from_rdata(lookup_name.clone(), u32::MAX, rdata);
+ let record_set = RecordSet::from(record);
+ map.insert(
+ RrKey::new(lookup_name_lower.clone(), RecordType::AAAA),
+ record_set,
+ );
+
+ // Inserts ANAME record
+ let rdata = RData::ANAME(Name::from_str("aname.com").unwrap());
+ let record = Record::from_rdata(lookup_name.clone(), u32::MAX, rdata);
+ let record_set = RecordSet::from(record);
+ map.insert(
+ RrKey::new(lookup_name_lower.clone(), RecordType::ANAME),
+ record_set,
+ );
+
+ // Inserts CNAME record
+ let rdata = RData::CNAME(Name::from_str("cname.com").unwrap());
+ let record =
+ Record::from_rdata(Name::from_str("foo").unwrap(), u32::MAX, rdata);
+ let record_set = RecordSet::from(record);
+ map.insert(
+ RrKey::new(lookup_name_lower.clone(), RecordType::CNAME),
+ record_set,
+ );
+
+ // Inserts MX record
+ let rdata = RData::MX(MX::new(0, Name::from_str("mx.com").unwrap()));
+ let record = Record::from_rdata(lookup_name.clone(), u32::MAX, rdata);
+ let record_set = RecordSet::from(record);
+ map.insert(
+ RrKey::new(lookup_name_lower.clone(), RecordType::MX),
+ record_set,
+ );
+
+ // Inserts PTR record
+ let rdata = RData::PTR(Name::from_str("ptr.com").unwrap());
+ let record = Record::from_rdata(
+ Name::from_str("5.6.7.8").unwrap(),
+ u32::MAX,
+ rdata,
+ );
+ let record_set = RecordSet::from(record);
+ map.insert(
+ RrKey::new("5.6.7.8".parse().unwrap(), RecordType::PTR),
+ record_set,
+ );
+
+ // Inserts SRV record
+ let rdata = RData::SRV(SRV::new(
+ 0,
+ 100,
+ 1234,
+ Name::from_str("srv.com").unwrap(),
+ ));
+ let record = Record::from_rdata(
+ Name::from_str("_Service._TCP.example.com").unwrap(),
+ u32::MAX,
+ rdata,
+ );
+ let record_set = RecordSet::from(record);
+ map.insert(
+ RrKey::new(lookup_name_lower.clone(), RecordType::SRV),
+ record_set,
+ );
+
+ // Inserts TXT record
+ let rdata =
+ RData::TXT(TXT::new(vec!["foo".to_string(), "bar".to_string()]));
+ let record = Record::from_rdata(lookup_name, u32::MAX, rdata);
+ let record_set = RecordSet::from(record);
+ map.insert(RrKey::new(lookup_name_lower, RecordType::TXT), record_set);
+
+ map
+ };
+
+ let authority = Box::new(Arc::new(RwLock::new(
+ InMemoryAuthority::new(
+ Name::from_str("com").unwrap(),
+ records,
+ ZoneType::Primary,
+ false,
+ )
+ .unwrap(),
+ )));
+ let mut c = Catalog::new();
+ c.upsert(Name::root().into(), authority);
+ c
+ };
+
+ let mut server_fut = ServerFuture::new(catalog);
+ let socket_addr = SocketAddr::from(([127, 0, 0, 1], DNS_PORT));
+ let tcp_listener = TcpListener::bind(socket_addr).await.unwrap();
+ let udp_socket = UdpSocket::bind(socket_addr).await.unwrap();
+ server_fut.register_socket(udp_socket);
+ server_fut.register_listener(tcp_listener, Duration::from_secs(2));
+
+ // Notifies that the DNS server is ready
+ tx.send(()).unwrap();
+
+ server_fut.block_until_done().await.unwrap();
+ }
+
+ let (ready_tx, ready_rx) = oneshot::channel();
+ let dns_server_fut = run_dns_server(ready_tx);
+ let handle = tokio::spawn(dns_server_fut);
+
+ // Waits for the DNS server to be ready
+ ready_rx.await.unwrap();
+
+ // Pass: `--allow-net`
+ {
+ let output = util::deno_cmd()
+ .current_dir(util::tests_path())
+ .env("NO_COLOR", "1")
+ .arg("run")
+ .arg("--allow-net")
+ .arg("--unstable")
+ .arg("resolve_dns.ts")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .unwrap()
+ .wait_with_output()
+ .unwrap();
+ let err = String::from_utf8_lossy(&output.stderr);
+ let out = String::from_utf8_lossy(&output.stdout);
+ assert!(output.status.success());
+ assert!(err.starts_with("Check file"));
+
+ let expected =
+ std::fs::read_to_string(util::tests_path().join("resolve_dns.ts.out"))
+ .unwrap();
+ assert_eq!(expected, out);
+ }
+
+ // Pass: `--allow-net=127.0.0.1:4553`
+ {
+ let output = util::deno_cmd()
+ .current_dir(util::tests_path())
+ .env("NO_COLOR", "1")
+ .arg("run")
+ .arg("--allow-net=127.0.0.1:4553")
+ .arg("--unstable")
+ .arg("resolve_dns.ts")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .unwrap()
+ .wait_with_output()
+ .unwrap();
+ let err = String::from_utf8_lossy(&output.stderr);
+ let out = String::from_utf8_lossy(&output.stdout);
+ assert!(output.status.success());
+ assert!(err.starts_with("Check file"));
+
+ let expected =
+ std::fs::read_to_string(util::tests_path().join("resolve_dns.ts.out"))
+ .unwrap();
+ assert_eq!(expected, out);
+ }
+
+ // Permission error: `--allow-net=deno.land`
+ {
+ let output = util::deno_cmd()
+ .current_dir(util::tests_path())
+ .env("NO_COLOR", "1")
+ .arg("run")
+ .arg("--allow-net=deno.land")
+ .arg("--unstable")
+ .arg("resolve_dns.ts")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .unwrap()
+ .wait_with_output()
+ .unwrap();
+ let err = String::from_utf8_lossy(&output.stderr);
+ let out = String::from_utf8_lossy(&output.stdout);
+ assert!(!output.status.success());
+ assert!(err.starts_with("Check file"));
+ assert!(err.contains(r#"error: Uncaught (in promise) PermissionDenied: network access to "127.0.0.1:4553""#));
+ assert!(out.is_empty());
+ }
+
+ // Permission error: no permission specified
+ {
+ let output = util::deno_cmd()
+ .current_dir(util::tests_path())
+ .env("NO_COLOR", "1")
+ .arg("run")
+ .arg("--unstable")
+ .arg("resolve_dns.ts")
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()
+ .unwrap()
+ .wait_with_output()
+ .unwrap();
+ let err = String::from_utf8_lossy(&output.stderr);
+ let out = String::from_utf8_lossy(&output.stdout);
+ assert!(!output.status.success());
+ assert!(err.starts_with("Check file"));
+ assert!(err.contains(r#"error: Uncaught (in promise) PermissionDenied: network access to "127.0.0.1:4553""#));
+ assert!(out.is_empty());
+ }
+
+ handle.abort();
+}
diff --git a/cli/tests/resolve_dns.ts b/cli/tests/resolve_dns.ts
new file mode 100644
index 000000000..35abfc803
--- /dev/null
+++ b/cli/tests/resolve_dns.ts
@@ -0,0 +1,36 @@
+const nameServer = { nameServer: { ipAddr: "127.0.0.1", port: 4553 } };
+
+const [a, aaaa, aname, cname, mx, ptr, srv, txt] = await Promise.all([
+ Deno.resolveDns("www.example.com", "A", nameServer),
+ Deno.resolveDns("www.example.com", "AAAA", nameServer),
+ Deno.resolveDns("www.example.com", "ANAME", nameServer),
+ Deno.resolveDns("foo", "CNAME", nameServer),
+ Deno.resolveDns("www.example.com", "MX", nameServer),
+ Deno.resolveDns("5.6.7.8", "PTR", nameServer),
+ Deno.resolveDns("_Service._TCP.example.com", "SRV", nameServer),
+ Deno.resolveDns("www.example.com", "TXT", nameServer),
+]);
+
+console.log("A");
+console.log(JSON.stringify(a));
+
+console.log("AAAA");
+console.log(JSON.stringify(aaaa));
+
+console.log("ANAME");
+console.log(JSON.stringify(aname));
+
+console.log("CNAME");
+console.log(JSON.stringify(cname));
+
+console.log("MX");
+console.log(JSON.stringify(mx));
+
+console.log("PTR");
+console.log(JSON.stringify(ptr));
+
+console.log("SRV");
+console.log(JSON.stringify(srv));
+
+console.log("TXT");
+console.log(JSON.stringify(txt));
diff --git a/cli/tests/resolve_dns.ts.out b/cli/tests/resolve_dns.ts.out
new file mode 100644
index 000000000..10bd78e8a
--- /dev/null
+++ b/cli/tests/resolve_dns.ts.out
@@ -0,0 +1,16 @@
+A
+["1.2.3.4"]
+AAAA
+["1:2:3:4:5:6:7:8"]
+ANAME
+["aname.com."]
+CNAME
+["cname.com."]
+MX
+[{"preference":0,"exchange":"mx.com."}]
+PTR
+["ptr.com."]
+SRV
+[{"priority":0,"weight":100,"port":1234,"target":"srv.com."}]
+TXT
+[["foo","bar"]]
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index b1718ee95..5383105b9 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -61,6 +61,8 @@ tokio-rustls = "0.22.0"
uuid = { version = "0.8.2", features = ["v4"] }
webpki = "0.21.4"
webpki-roots = "0.21.0"
+trust-dns-proto = "0.20.0"
+trust-dns-resolver = { version = "0.20.0", features = ["tokio-runtime", "serde-config"] }
[target.'cfg(windows)'.dependencies]
fwdansi = "1.1.0"
diff --git a/runtime/js/30_net.js b/runtime/js/30_net.js
index db00f5a2b..37cb50af3 100644
--- a/runtime/js/30_net.js
+++ b/runtime/js/30_net.js
@@ -33,6 +33,10 @@
return core.jsonOpAsync("op_datagram_send", args, zeroCopy);
}
+ function resolveDns(query, recordType, options) {
+ return core.jsonOpAsync("op_dns_resolve", { query, recordType, options });
+ }
+
class Conn {
#rid = 0;
#remoteAddr = null;
@@ -210,5 +214,6 @@
Listener,
shutdown,
Datagram,
+ resolveDns,
};
})(this);
diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js
index c4205a1a9..bafa1a1b5 100644
--- a/runtime/js/90_deno_ns.js
+++ b/runtime/js/90_deno_ns.js
@@ -111,6 +111,7 @@
applySourceMap: __bootstrap.errorStack.opApplySourceMap,
formatDiagnostics: __bootstrap.errorStack.opFormatDiagnostics,
shutdown: __bootstrap.net.shutdown,
+ resolveDns: __bootstrap.net.resolveDns,
listen: __bootstrap.netUnstable.listen,
connect: __bootstrap.netUnstable.connect,
listenDatagram: __bootstrap.netUnstable.listenDatagram,
diff --git a/runtime/ops/net.rs b/runtime/ops/net.rs
index caf1ef0d3..364f1c576 100644
--- a/runtime/ops/net.rs
+++ b/runtime/ops/net.rs
@@ -22,6 +22,7 @@ use deno_core::RcRef;
use deno_core::Resource;
use deno_core::ZeroCopyBuf;
use serde::Deserialize;
+use serde::Serialize;
use std::borrow::Cow;
use std::cell::RefCell;
use std::net::SocketAddr;
@@ -30,6 +31,13 @@ use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener;
use tokio::net::TcpStream;
use tokio::net::UdpSocket;
+use trust_dns_proto::rr::record_data::RData;
+use trust_dns_proto::rr::record_type::RecordType;
+use trust_dns_resolver::config::NameServerConfigGroup;
+use trust_dns_resolver::config::ResolverConfig;
+use trust_dns_resolver::config::ResolverOpts;
+use trust_dns_resolver::system_conf;
+use trust_dns_resolver::AsyncResolver;
#[cfg(unix)]
use super::net_unix;
@@ -45,6 +53,7 @@ pub fn init(rt: &mut deno_core::JsRuntime) {
super::reg_json_sync(rt, "op_listen", op_listen);
super::reg_json_async(rt, "op_datagram_receive", op_datagram_receive);
super::reg_json_async(rt, "op_datagram_send", op_datagram_send);
+ super::reg_json_async(rt, "op_dns_resolve", op_dns_resolve);
}
#[derive(Deserialize)]
@@ -531,3 +540,249 @@ fn op_listen(
_ => Err(type_error("Wrong argument format!")),
}
}
+
+#[derive(Serialize, PartialEq, Debug)]
+#[serde(untagged)]
+enum DnsReturnRecord {
+ A(String),
+ AAAA(String),
+ ANAME(String),
+ CNAME(String),
+ MX {
+ preference: u16,
+ exchange: String,
+ },
+ PTR(String),
+ SRV {
+ priority: u16,
+ weight: u16,
+ port: u16,
+ target: String,
+ },
+ TXT(Vec<String>),
+}
+
+async fn op_dns_resolve(
+ state: Rc<RefCell<OpState>>,
+ args: Value,
+ _zero_copy: BufVec,
+) -> Result<Value, AnyError> {
+ fn default_port() -> u16 {
+ 53
+ }
+
+ #[derive(Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct ResolveAddrArgs {
+ query: String,
+ record_type: RecordType,
+ options: Option<ResolveDnsOption>,
+ }
+
+ #[derive(Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct ResolveDnsOption {
+ name_server: Option<NameServer>,
+ }
+
+ #[derive(Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct NameServer {
+ ip_addr: String,
+ #[serde(default = "default_port")]
+ port: u16,
+ }
+
+ let ResolveAddrArgs {
+ query,
+ record_type,
+ options,
+ } = serde_json::from_value(args)?;
+
+ let (config, opts) = if let Some(name_server) =
+ options.as_ref().and_then(|o| o.name_server.as_ref())
+ {
+ let group = NameServerConfigGroup::from_ips_clear(
+ &[name_server.ip_addr.parse()?],
+ name_server.port,
+ true,
+ );
+ (
+ ResolverConfig::from_parts(None, vec![], group),
+ ResolverOpts::default(),
+ )
+ } else {
+ system_conf::read_system_conf()?
+ };
+
+ {
+ let s = state.borrow();
+ let perm = s.borrow::<Permissions>();
+
+ // Checks permission against the name servers which will be actually queried.
+ for ns in config.name_servers() {
+ let socker_addr = &ns.socket_addr;
+ let ip = socker_addr.ip().to_string();
+ let port = socker_addr.port();
+ perm.check_net(&(ip, Some(port)))?;
+ }
+ }
+
+ let resolver = AsyncResolver::tokio(config, opts)?;
+
+ let results: Vec<DnsReturnRecord> = resolver
+ .lookup(query, record_type, Default::default())
+ .await?
+ .iter()
+ .filter_map(rdata_to_return_record(record_type))
+ .collect();
+
+ Ok(json!(results))
+}
+
+fn rdata_to_return_record(
+ ty: RecordType,
+) -> impl Fn(&RData) -> Option<DnsReturnRecord> {
+ use RecordType::*;
+ move |r: &RData| -> Option<DnsReturnRecord> {
+ match ty {
+ A => r.as_a().map(ToString::to_string).map(DnsReturnRecord::A),
+ AAAA => r
+ .as_aaaa()
+ .map(ToString::to_string)
+ .map(DnsReturnRecord::AAAA),
+ ANAME => r
+ .as_aname()
+ .map(ToString::to_string)
+ .map(DnsReturnRecord::ANAME),
+ CNAME => r
+ .as_cname()
+ .map(ToString::to_string)
+ .map(DnsReturnRecord::CNAME),
+ MX => r.as_mx().map(|mx| DnsReturnRecord::MX {
+ preference: mx.preference(),
+ exchange: mx.exchange().to_string(),
+ }),
+ PTR => r
+ .as_ptr()
+ .map(ToString::to_string)
+ .map(DnsReturnRecord::PTR),
+ SRV => r.as_srv().map(|srv| DnsReturnRecord::SRV {
+ priority: srv.priority(),
+ weight: srv.weight(),
+ port: srv.port(),
+ target: srv.target().to_string(),
+ }),
+ TXT => r.as_txt().map(|txt| {
+ let texts: Vec<String> = txt
+ .iter()
+ .map(|bytes| {
+ // Tries to parse these bytes as Latin-1
+ bytes.iter().map(|&b| b as char).collect::<String>()
+ })
+ .collect();
+ DnsReturnRecord::TXT(texts)
+ }),
+ // TODO(magurotuna): Other record types are not supported
+ _ => todo!(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::net::Ipv4Addr;
+ use std::net::Ipv6Addr;
+ use trust_dns_proto::rr::rdata::mx::MX;
+ use trust_dns_proto::rr::rdata::srv::SRV;
+ use trust_dns_proto::rr::rdata::txt::TXT;
+ use trust_dns_proto::rr::record_data::RData;
+ use trust_dns_proto::rr::Name;
+
+ #[test]
+ fn rdata_to_return_record_a() {
+ let func = rdata_to_return_record(RecordType::A);
+ let rdata = RData::A(Ipv4Addr::new(127, 0, 0, 1));
+ assert_eq!(
+ func(&rdata),
+ Some(DnsReturnRecord::A("127.0.0.1".to_string()))
+ );
+ }
+
+ #[test]
+ fn rdata_to_return_record_aaaa() {
+ let func = rdata_to_return_record(RecordType::AAAA);
+ let rdata = RData::AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
+ assert_eq!(func(&rdata), Some(DnsReturnRecord::AAAA("::1".to_string())));
+ }
+
+ #[test]
+ fn rdata_to_return_record_aname() {
+ let func = rdata_to_return_record(RecordType::ANAME);
+ let rdata = RData::ANAME(Name::new());
+ assert_eq!(func(&rdata), Some(DnsReturnRecord::ANAME("".to_string())));
+ }
+
+ #[test]
+ fn rdata_to_return_record_cname() {
+ let func = rdata_to_return_record(RecordType::CNAME);
+ let rdata = RData::CNAME(Name::new());
+ assert_eq!(func(&rdata), Some(DnsReturnRecord::CNAME("".to_string())));
+ }
+
+ #[test]
+ fn rdata_to_return_record_mx() {
+ let func = rdata_to_return_record(RecordType::MX);
+ let rdata = RData::MX(MX::new(10, Name::new()));
+ assert_eq!(
+ func(&rdata),
+ Some(DnsReturnRecord::MX {
+ preference: 10,
+ exchange: "".to_string()
+ })
+ );
+ }
+
+ #[test]
+ fn rdata_to_return_record_ptr() {
+ let func = rdata_to_return_record(RecordType::PTR);
+ let rdata = RData::PTR(Name::new());
+ assert_eq!(func(&rdata), Some(DnsReturnRecord::PTR("".to_string())));
+ }
+
+ #[test]
+ fn rdata_to_return_record_srv() {
+ let func = rdata_to_return_record(RecordType::SRV);
+ let rdata = RData::SRV(SRV::new(1, 2, 3, Name::new()));
+ assert_eq!(
+ func(&rdata),
+ Some(DnsReturnRecord::SRV {
+ priority: 1,
+ weight: 2,
+ port: 3,
+ target: "".to_string()
+ })
+ );
+ }
+
+ #[test]
+ fn rdata_to_return_record_txt() {
+ let func = rdata_to_return_record(RecordType::TXT);
+ let rdata = RData::TXT(TXT::from_bytes(vec![
+ "foo".as_bytes(),
+ "bar".as_bytes(),
+ &[0xa3], // "£" in Latin-1
+ &[0xe3, 0x81, 0x82], // "あ" in UTF-8
+ ]));
+ assert_eq!(
+ func(&rdata),
+ Some(DnsReturnRecord::TXT(vec![
+ "foo".to_string(),
+ "bar".to_string(),
+ "£".to_string(),
+ "ã\u{81}\u{82}".to_string(),
+ ]))
+ );
+ }
+}
diff --git a/runtime/permissions.rs b/runtime/permissions.rs
index b88b86262..fdb3a425b 100644
--- a/runtime/permissions.rs
+++ b/runtime/permissions.rs
@@ -818,7 +818,7 @@ mod tests {
}
#[test]
- fn test_check_net() {
+ fn test_check_net_with_values() {
let perms = Permissions::from_options(&PermissionsOptions {
allow_net: Some(svec![
"localhost",
@@ -854,6 +854,93 @@ mod tests {
("192.168.0.1", 0, false),
];
+ for (host, port, is_ok) in domain_tests {
+ assert_eq!(is_ok, perms.check_net(&(host, Some(port))).is_ok());
+ }
+ }
+
+ #[test]
+ fn test_check_net_only_flag() {
+ let perms = Permissions::from_options(&PermissionsOptions {
+ allow_net: Some(svec![]), // this means `--allow-net` is present without values following `=` sign
+ ..Default::default()
+ });
+
+ let domain_tests = vec![
+ ("localhost", 1234),
+ ("deno.land", 0),
+ ("deno.land", 3000),
+ ("deno.lands", 0),
+ ("deno.lands", 3000),
+ ("github.com", 3000),
+ ("github.com", 0),
+ ("github.com", 2000),
+ ("github.net", 3000),
+ ("127.0.0.1", 0),
+ ("127.0.0.1", 3000),
+ ("127.0.0.2", 0),
+ ("127.0.0.2", 3000),
+ ("172.16.0.2", 8000),
+ ("172.16.0.2", 0),
+ ("172.16.0.2", 6000),
+ ("172.16.0.1", 8000),
+ ("somedomain", 0),
+ ("192.168.0.1", 0),
+ ];
+
+ for (host, port) in domain_tests {
+ assert!(perms.check_net(&(host, Some(port))).is_ok());
+ }
+ }
+
+ #[test]
+ fn test_check_net_no_flag() {
+ let perms = Permissions::from_options(&PermissionsOptions {
+ allow_net: None,
+ ..Default::default()
+ });
+
+ let domain_tests = vec![
+ ("localhost", 1234),
+ ("deno.land", 0),
+ ("deno.land", 3000),
+ ("deno.lands", 0),
+ ("deno.lands", 3000),
+ ("github.com", 3000),
+ ("github.com", 0),
+ ("github.com", 2000),
+ ("github.net", 3000),
+ ("127.0.0.1", 0),
+ ("127.0.0.1", 3000),
+ ("127.0.0.2", 0),
+ ("127.0.0.2", 3000),
+ ("172.16.0.2", 8000),
+ ("172.16.0.2", 0),
+ ("172.16.0.2", 6000),
+ ("172.16.0.1", 8000),
+ ("somedomain", 0),
+ ("192.168.0.1", 0),
+ ];
+
+ for (host, port) in domain_tests {
+ assert!(!perms.check_net(&(host, Some(port))).is_ok());
+ }
+ }
+
+ #[test]
+ fn test_check_net_url() {
+ let perms = Permissions::from_options(&PermissionsOptions {
+ allow_net: Some(svec![
+ "localhost",
+ "deno.land",
+ "github.com:3000",
+ "127.0.0.1",
+ "172.16.0.2:8000",
+ "www.github.com:443"
+ ]),
+ ..Default::default()
+ });
+
let url_tests = vec![
// Any protocol + port for localhost should be ok, since we don't specify
("http://localhost", true),
@@ -893,13 +980,9 @@ mod tests {
("https://www.github.com:443/robots.txt", true),
];
- for (url_str, is_ok) in url_tests.iter() {
+ for (url_str, is_ok) in url_tests {
let u = url::Url::parse(url_str).unwrap();
- assert_eq!(*is_ok, perms.check_net_url(&u).is_ok());
- }
-
- for (hostname, port, is_ok) in domain_tests.iter() {
- assert_eq!(*is_ok, perms.check_net(&(hostname, Some(*port))).is_ok());
+ assert_eq!(is_ok, perms.check_net_url(&u).is_ok());
}
}
diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs
index 53dc82c39..14ec723f2 100644
--- a/test_util/src/lib.rs
+++ b/test_util/src/lib.rs
@@ -5,7 +5,6 @@
#[macro_use]
extern crate lazy_static;
-use core::mem::replace;
use futures::FutureExt;
use futures::Stream;
use futures::StreamExt;
@@ -28,6 +27,7 @@ use std::env;
use std::io;
use std::io::Read;
use std::io::Write;
+use std::mem::replace;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::pin::Pin;