From b534bb4b930bbb67b55b1ff1df7ca423ffd5986d Mon Sep 17 00:00:00 2001 From: Al Liu Date: Thu, 6 Jun 2024 16:15:26 +0800 Subject: [PATCH] Finish hostname resolution (#1119) --- Cargo.lock | 164 ++++++++++++++++++++++++++- crates/core/Cargo.toml | 1 + crates/core/src/config.rs | 2 +- crates/core/src/node.rs | 104 +++++++++++++++-- crates/core/src/node/testing_impl.rs | 4 +- crates/core/src/server.rs | 2 +- 6 files changed, 261 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46210c70d..c49fe0365 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1252,6 +1252,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -1507,6 +1519,7 @@ dependencies = [ "freenet-stdlib", "futures", "headers", + "hickory-resolver", "itertools 0.13.0", "notify", "once_cell", @@ -1915,6 +1928,56 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hickory-proto" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.4.0", + "ipnet", + "once_cell", + "rand", + "rustls", + "rustls-pemfile 1.0.4", + "thiserror", + "tinyvec", + "tokio", + "tokio-rustls", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "rustls", + "smallvec", + "thiserror", + "tokio", + "tokio-rustls", + "tracing", +] + [[package]] name = "hkdf" version = "0.12.4" @@ -1942,6 +2005,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.2.12" @@ -2138,6 +2212,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.5.0" @@ -2214,6 +2298,18 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2", + "widestring", + "windows-sys 0.48.0", + "winreg 0.50.0", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -2364,6 +2460,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2392,6 +2494,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[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 = "lzma-sys" version = "0.1.20" @@ -2421,6 +2532,12 @@ dependencies = [ "libc", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.1.0" @@ -3209,6 +3326,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.36" @@ -3444,7 +3567,17 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.52.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]] @@ -3547,6 +3680,7 @@ version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ + "log", "ring", "rustls-webpki", "sct", @@ -4528,6 +4662,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -4975,7 +5119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", "serde", ] @@ -5415,6 +5559,12 @@ dependencies = [ "safe_arch", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" @@ -5655,6 +5805,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.52.0" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 5b6398de8..a4b1a6f3b 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -38,6 +38,7 @@ flatbuffers = "24.3" futures = "0.3" semver = { version = "1", features = ["serde"] } headers = "0.4" +hickory-resolver = { version = "0.24", features = ["dns-over-rustls"] } itertools = "0.13" notify = "6" once_cell = "1" diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index f9e9151be..48e295f4d 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -536,7 +536,7 @@ const fn default_local_address() -> IpAddr { } #[inline] -const fn default_gateway_port() -> u16 { +pub(crate) const fn default_gateway_port() -> u16 { 50509 } diff --git a/crates/core/src/node.rs b/crates/core/src/node.rs index eedc46b15..ff0eb82fd 100644 --- a/crates/core/src/node.rs +++ b/crates/core/src/node.rs @@ -9,10 +9,11 @@ //! - inter-process: similar to in-memory, but can be rana cross multiple processes, closer to the real p2p impl use std::{ + borrow::Cow, fmt::Display, fs::File, io::Read, - net::{IpAddr, SocketAddr}, + net::{IpAddr, SocketAddr, ToSocketAddrs}, sync::Arc, time::Duration, }; @@ -30,7 +31,7 @@ use tracing::Instrument; use self::p2p_impl::NodeP2P; use crate::{ client_events::{BoxedClient, ClientEventsProxy, ClientId, OpenRequest}, - config::{GatewayConfig, GlobalExecutor}, + config::{Address, GatewayConfig, GlobalExecutor}, contract::{ Callback, ClientResponsesReceiver, ClientResponsesSender, ContractError, ExecutorToEventLoopChannel, NetworkContractHandler, @@ -110,7 +111,7 @@ pub struct NodeConfig { } impl NodeConfig { - pub fn new(config: Config) -> anyhow::Result { + pub async fn new(config: Config) -> anyhow::Result { let mut gateways = Vec::with_capacity(config.gateways.len()); for gw in &config.gateways { let GatewayConfig { @@ -118,18 +119,13 @@ impl NodeConfig { public_key_path, } = gw; - let mut key_file = File::open(&public_key_path)?; + let mut key_file = File::open(public_key_path)?; let mut buf = String::new(); key_file.read_to_string(&mut buf)?; let pub_key = rsa::RsaPublicKey::from_public_key_pem(&buf)?; - let address = match address { - crate::config::Address::Hostname(hostname) => { - todo!("impl resolution of hostname to ip: {hostname}") - } - crate::config::Address::HostAddress(addr) => *addr, - }; + let address = Self::parse_socket_addr(address).await?; let peer_id = PeerId::new(address, TransportPublicKey::from(pub_key)); gateways.push(InitPeerNode::new(peer_id, Location::from_address(&address))); } @@ -152,6 +148,66 @@ impl NodeConfig { }) } + async fn parse_socket_addr(address: &Address) -> anyhow::Result { + let (hostname, port) = match address { + crate::config::Address::Hostname(hostname) => { + match hostname.rsplit_once(':') { + None => { + // no port found, use default + let hostname_with_port = + format!("{}:{}", hostname, crate::config::default_gateway_port()); + + if let Ok(mut addrs) = hostname_with_port.to_socket_addrs() { + if let Some(addr) = addrs.next() { + return Ok(addr); + } + } + + (Cow::Borrowed(hostname.as_str()), None) + } + Some((host, port)) => match port.parse::() { + Ok(port) => { + if let Ok(mut addrs) = hostname.to_socket_addrs() { + if let Some(addr) = addrs.next() { + return Ok(addr); + } + } + + (Cow::Borrowed(host), Some(port)) + } + Err(_) => return Err(anyhow::anyhow!("Invalid port number: {port}")), + }, + } + } + Address::HostAddress(addr) => return Ok(*addr), + }; + + let (conf, opts) = hickory_resolver::system_conf::read_system_conf()?; + let resolver = hickory_resolver::TokioAsyncResolver::new( + conf, + opts, + hickory_resolver::name_server::GenericConnector::new( + hickory_resolver::name_server::TokioRuntimeProvider::new(), + ), + ); + + // only issue one query with . + let hostname = if hostname.ends_with('.') { + hostname + } else { + Cow::Owned(format!("{}.", hostname)) + }; + + let ips = resolver.lookup_ip(hostname.as_ref()).await?; + match ips.into_iter().next() { + Some(ip) => Ok(SocketAddr::new( + ip, + port.unwrap_or_else(crate::config::default_gateway_port), + )), + None => Err(anyhow::anyhow!("Fail to resolve IP address of {hostname}")), + } + } + pub fn config(&self) -> &Config { &self.config } @@ -940,3 +996,31 @@ impl Display for PeerId { write!(f, "{:?}", self.addr) } } + +#[cfg(test)] +mod tests { + use std::net::Ipv4Addr; + + use super::*; + + #[tokio::test] + async fn test_hostname_resolution() { + let addr = Address::Hostname("localhost".to_string()); + let socket_addr = NodeConfig::parse_socket_addr(&addr).await.unwrap(); + assert_eq!( + socket_addr, + SocketAddr::new( + IpAddr::V4(Ipv4Addr::LOCALHOST), + crate::config::default_gateway_port() + ) + ); + + let addr = Address::Hostname("google.com".to_string()); + let socket_addr = NodeConfig::parse_socket_addr(&addr).await.unwrap(); + assert_eq!(socket_addr.port(), crate::config::default_gateway_port()); + + let addr = Address::Hostname("google.com:8080".to_string()); + let socket_addr = NodeConfig::parse_socket_addr(&addr).await.unwrap(); + assert_eq!(socket_addr.port(), 8080); + } +} diff --git a/crates/core/src/node/testing_impl.rs b/crates/core/src/node/testing_impl.rs index fceb66b2d..3940b356b 100644 --- a/crates/core/src/node/testing_impl.rs +++ b/crates/core/src/node/testing_impl.rs @@ -378,7 +378,7 @@ impl SimNetwork { let mut config_args = ConfigArgs::default(); config_args.id = Some(format!("{label}")); - let mut config = NodeConfig::new(config_args.build().unwrap()).unwrap(); + let mut config = NodeConfig::new(config_args.build().unwrap()).await.unwrap(); config.key_pair = keypair; config.network_listener_ip = Ipv6Addr::LOCALHOST.into(); config.network_listener_port = port; @@ -448,7 +448,7 @@ impl SimNetwork { let mut config_args = ConfigArgs::default(); config_args.id = Some(format!("{label}")); - let mut config = NodeConfig::new(config_args.build().unwrap()).unwrap(); + let mut config = NodeConfig::new(config_args.build().unwrap()).await.unwrap(); for GatewayConfig { id, location, .. } in &gateways { config.add_gateway(InitPeerNode::new(id.clone(), *location)); } diff --git a/crates/core/src/server.rs b/crates/core/src/server.rs index ed4b8c24d..582103f7a 100644 --- a/crates/core/src/server.rs +++ b/crates/core/src/server.rs @@ -194,7 +194,7 @@ pub mod network_node { let (ws_proxy, ws_router) = WebSocketProxy::as_router(gw_router); serve(ws_socket, ws_router.layer(TraceLayer::new_for_http())); - let node_config = NodeConfig::new(config)?; + let node_config = NodeConfig::new(config).await?; let is_gateway = node_config.is_gateway; let node = node_config .build([Box::new(gw), Box::new(ws_proxy)])