summaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-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
5 files changed, 353 insertions, 7 deletions
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());
}
}