1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-28 16:20:57 -05:00

feat(unstable): add Deno.resolveDns API (#8790)

This commit is contained in:
Yusuke Tanaka 2021-01-19 23:39:04 +09:00 committed by GitHub
parent cf3202644d
commit 0ef8c915c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 982 additions and 9 deletions

213
Cargo.lock generated
View file

@ -397,6 +397,12 @@ dependencies = [
"num_cpus", "num_cpus",
] ]
[[package]]
name = "data-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993a608597367c6377b258c25d7120740f00ed23a2252b729b1932dd7866f908"
[[package]] [[package]]
name = "deno" name = "deno"
version = "1.6.3" version = "1.6.3"
@ -449,6 +455,8 @@ dependencies = [
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tower-test", "tower-test",
"trust-dns-client",
"trust-dns-server",
"uuid", "uuid",
"walkdir", "walkdir",
"winapi 0.3.9", "winapi 0.3.9",
@ -564,6 +572,8 @@ dependencies = [
"test_util", "test_util",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"trust-dns-proto",
"trust-dns-resolver",
"uuid", "uuid",
"webpki", "webpki",
"webpki-roots", "webpki-roots",
@ -698,6 +708,24 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[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]] [[package]]
name = "enum_kind" name = "enum_kind"
version = "0.2.0" version = "0.2.0"
@ -1080,6 +1108,17 @@ dependencies = [
"libc", "libc",
] ]
[[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]] [[package]]
name = "http" name = "http"
version = "0.2.3" version = "0.2.3"
@ -1238,6 +1277,18 @@ dependencies = [
"libc", "libc",
] ]
[[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]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.3.0" version = "2.3.0"
@ -1306,6 +1357,12 @@ version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929" checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.2" version = "0.4.2"
@ -1325,6 +1382,15 @@ dependencies = [
"serde", "serde",
] ]
[[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]] [[package]]
name = "lsp-types" name = "lsp-types"
version = "0.86.0" version = "0.86.0"
@ -1373,6 +1439,12 @@ dependencies = [
"syn 1.0.56", "syn 1.0.56",
] ]
[[package]]
name = "match_cfg"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]] [[package]]
name = "matches" name = "matches"
version = "0.1.8" version = "0.1.8"
@ -1490,6 +1562,15 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 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]] [[package]]
name = "nix" name = "nix"
version = "0.19.1" version = "0.19.1"
@ -1848,6 +1929,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quote" name = "quote"
version = "0.6.13" version = "0.6.13"
@ -1878,6 +1965,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" 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]] [[package]]
name = "rand" name = "rand"
version = "0.7.3" version = "0.7.3"
@ -2042,7 +2139,17 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"webpki-roots", "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]] [[package]]
@ -3030,6 +3137,95 @@ dependencies = [
"tracing", "tracing",
] ]
[[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]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.3" version = "0.2.3"
@ -3301,6 +3497,12 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "widestring"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.2.8" version = "0.2.8"
@ -3344,6 +3546,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 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]] [[package]]
name = "winreg" name = "winreg"
version = "0.7.0" version = "0.7.0"

View file

@ -91,6 +91,8 @@ chrono = "0.4.19"
os_pipe = "0.9.2" os_pipe = "0.9.2"
test_util = { path = "../test_util" } test_util = { path = "../test_util" }
tower-test = "0.4.0" tower-test = "0.4.0"
trust-dns-server = "0.20.0"
trust-dns-client = "0.20.0"
[target.'cfg(unix)'.dev-dependencies] [target.'cfg(unix)'.dev-dependencies]
exec = "0.3.1" # Used in test_raw_tty exec = "0.3.1" # Used in test_raw_tty

View file

@ -835,6 +835,92 @@ declare namespace Deno {
mtime: number | Date, mtime: number | Date,
): Promise<void>; ): 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. /** **UNSTABLE**: new API, yet to be vetted.
* *
* A generic transport listener for message-oriented protocols. */ * A generic transport listener for message-oriented protocols. */

View file

@ -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();
}

36
cli/tests/resolve_dns.ts Normal file
View file

@ -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));

View file

@ -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"]]

View file

@ -61,6 +61,8 @@ tokio-rustls = "0.22.0"
uuid = { version = "0.8.2", features = ["v4"] } uuid = { version = "0.8.2", features = ["v4"] }
webpki = "0.21.4" webpki = "0.21.4"
webpki-roots = "0.21.0" 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] [target.'cfg(windows)'.dependencies]
fwdansi = "1.1.0" fwdansi = "1.1.0"

View file

@ -33,6 +33,10 @@
return core.jsonOpAsync("op_datagram_send", args, zeroCopy); return core.jsonOpAsync("op_datagram_send", args, zeroCopy);
} }
function resolveDns(query, recordType, options) {
return core.jsonOpAsync("op_dns_resolve", { query, recordType, options });
}
class Conn { class Conn {
#rid = 0; #rid = 0;
#remoteAddr = null; #remoteAddr = null;
@ -210,5 +214,6 @@
Listener, Listener,
shutdown, shutdown,
Datagram, Datagram,
resolveDns,
}; };
})(this); })(this);

View file

@ -111,6 +111,7 @@
applySourceMap: __bootstrap.errorStack.opApplySourceMap, applySourceMap: __bootstrap.errorStack.opApplySourceMap,
formatDiagnostics: __bootstrap.errorStack.opFormatDiagnostics, formatDiagnostics: __bootstrap.errorStack.opFormatDiagnostics,
shutdown: __bootstrap.net.shutdown, shutdown: __bootstrap.net.shutdown,
resolveDns: __bootstrap.net.resolveDns,
listen: __bootstrap.netUnstable.listen, listen: __bootstrap.netUnstable.listen,
connect: __bootstrap.netUnstable.connect, connect: __bootstrap.netUnstable.connect,
listenDatagram: __bootstrap.netUnstable.listenDatagram, listenDatagram: __bootstrap.netUnstable.listenDatagram,

View file

@ -22,6 +22,7 @@ use deno_core::RcRef;
use deno_core::Resource; use deno_core::Resource;
use deno_core::ZeroCopyBuf; use deno_core::ZeroCopyBuf;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::RefCell; use std::cell::RefCell;
use std::net::SocketAddr; use std::net::SocketAddr;
@ -30,6 +31,13 @@ use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::net::UdpSocket; 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)] #[cfg(unix)]
use super::net_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_sync(rt, "op_listen", op_listen);
super::reg_json_async(rt, "op_datagram_receive", op_datagram_receive); 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_datagram_send", op_datagram_send);
super::reg_json_async(rt, "op_dns_resolve", op_dns_resolve);
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -531,3 +540,249 @@ fn op_listen(
_ => Err(type_error("Wrong argument format!")), _ => 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(),
]))
);
}
}

View file

@ -818,7 +818,7 @@ mod tests {
} }
#[test] #[test]
fn test_check_net() { fn test_check_net_with_values() {
let perms = Permissions::from_options(&PermissionsOptions { let perms = Permissions::from_options(&PermissionsOptions {
allow_net: Some(svec![ allow_net: Some(svec![
"localhost", "localhost",
@ -854,6 +854,93 @@ mod tests {
("192.168.0.1", 0, false), ("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![ let url_tests = vec![
// Any protocol + port for localhost should be ok, since we don't specify // Any protocol + port for localhost should be ok, since we don't specify
("http://localhost", true), ("http://localhost", true),
@ -893,13 +980,9 @@ mod tests {
("https://www.github.com:443/robots.txt", true), ("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(); let u = url::Url::parse(url_str).unwrap();
assert_eq!(*is_ok, perms.check_net_url(&u).is_ok()); 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());
} }
} }

View file

@ -5,7 +5,6 @@
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
use core::mem::replace;
use futures::FutureExt; use futures::FutureExt;
use futures::Stream; use futures::Stream;
use futures::StreamExt; use futures::StreamExt;
@ -28,6 +27,7 @@ use std::env;
use std::io; use std::io;
use std::io::Read; use std::io::Read;
use std::io::Write; use std::io::Write;
use std::mem::replace;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::PathBuf; use std::path::PathBuf;
use std::pin::Pin; use std::pin::Pin;