diff --git a/Cargo.lock b/Cargo.lock index 98ad290..eadda34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,6 +349,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + [[package]] name = "deranged" version = "0.3.10" @@ -369,6 +375,24 @@ dependencies = [ "subtle", ] +[[package]] +name = "enum-as-inner" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.41", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "fast-socks5" version = "0.9.2" @@ -535,6 +559,31 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.11", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "heck" version = "0.4.1" @@ -553,6 +602,59 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hickory-proto" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf" +dependencies = [ + "async-trait", + "bytes", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "h2", + "http 0.2.11", + "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8" +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 = "hmac" version = "0.12.1" @@ -562,6 +664,28 @@ dependencies = [ "digest", ] +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.0.0" @@ -580,7 +704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http", + "http 1.0.0", ] [[package]] @@ -591,7 +715,7 @@ checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" dependencies = [ "bytes", "futures-util", - "http", + "http 1.0.0", "http-body", "pin-project-lite", ] @@ -617,7 +741,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.0.0", "http-body", "httparse", "httpdate", @@ -636,7 +760,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.0.0", "http-body", "hyper", "pin-project-lite", @@ -653,6 +777,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" @@ -663,6 +797,34 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[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", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itoa" version = "1.0.10" @@ -703,6 +865,12 @@ version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "lock_api" version = "0.4.11" @@ -719,6 +887,21 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[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 = "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" @@ -914,6 +1097,12 @@ dependencies = [ "unicode-ident", ] +[[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.33" @@ -1006,6 +1195,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[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]] name = "ring" version = "0.17.7" @@ -1045,12 +1244,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 2.0.0", "rustls-pki-types", "schannel", "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + [[package]] name = "rustls-pemfile" version = "2.0.0" @@ -1481,6 +1689,7 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", + "tracing", ] [[package]] @@ -1620,7 +1829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", ] @@ -1734,6 +1943,12 @@ version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" @@ -1888,6 +2103,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[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 = "wstunnel" version = "8.3.0" @@ -1903,6 +2128,7 @@ dependencies = [ "fast-socks5", "fastwebsockets", "futures-util", + "hickory-resolver", "http-body-util", "hyper", "hyper-util", @@ -1913,7 +2139,7 @@ dependencies = [ "parking_lot", "pin-project", "rustls-native-certs", - "rustls-pemfile", + "rustls-pemfile 2.0.0", "scopeguard", "serde", "socket2", diff --git a/Cargo.toml b/Cargo.toml index e5f87f1..e27c3d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ clap = { version = "4.4.11", features = ["derive"] } fast-socks5 = { version = "0.9.2", features = [] } fastwebsockets = { version = "0.6.0", features = ["upgrade", "simd", "unstable-split"] } futures-util = { version = "0.3.29" } +hickory-resolver = { version = "0.24.0", features = ["tokio", "dns-over-https-rustls", "dns-over-rustls"] } hyper = { version = "1.0.1", features = ["client", "http1"] } hyper-util = { version = "0.1.0", features = ["tokio"] } diff --git a/src/dns.rs b/src/dns.rs new file mode 100644 index 0000000..a1e649a --- /dev/null +++ b/src/dns.rs @@ -0,0 +1,32 @@ +use anyhow::Context; +use hickory_resolver::TokioAsyncResolver; +use std::net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6}; + +#[derive(Clone)] +pub enum DnsResolver { + System, + TrustDns(TokioAsyncResolver), +} + +impl DnsResolver { + pub async fn lookup_host(&self, domain: &str, port: u16) -> anyhow::Result> { + let addrs: Vec = match self { + DnsResolver::System => tokio::net::lookup_host(format!("{}:{}", domain, port)) + .await + .with_context(|| format!("cannot resolve domain: {}", domain))? + .collect(), + DnsResolver::TrustDns(dns_resolver) => dns_resolver + .lookup_ip(domain) + .await + .with_context(|| format!("cannot resolve domain: {}", domain))? + .into_iter() + .map(|ip| match ip { + IpAddr::V4(ip) => SocketAddr::V4(SocketAddrV4::new(ip, port)), + IpAddr::V6(ip) => SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)), + }) + .collect(), + }; + + Ok(addrs) + } +} diff --git a/src/main.rs b/src/main.rs index 3300bae..38c48b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod dns; mod embedded_certificate; mod socks5; mod stdio; @@ -9,13 +10,14 @@ mod udp; use base64::Engine; use clap::Parser; use futures_util::{stream, TryStreamExt}; +use hickory_resolver::config::{NameServerConfig, ResolverConfig, ResolverOpts}; use hyper::header::HOST; use hyper::http::{HeaderName, HeaderValue}; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use std::fmt::{Debug, Formatter}; use std::io::ErrorKind; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; @@ -27,6 +29,7 @@ use tokio_rustls::rustls::{Certificate, PrivateKey, ServerName}; use tracing::{error, info, Level}; +use crate::dns::DnsResolver; use crate::tunnel::to_host_port; use tracing_subscriber::EnvFilter; use url::{Host, Url}; @@ -157,6 +160,16 @@ struct Server { #[arg(long, value_name = "DEST:PORT", verbatim_doc_comment)] restrict_to: Option>, + /// Dns resolver to use to lookup ips of domain name + /// This option is not going to work if you use transparent proxy + /// Can be specified multiple time + /// Example: + /// dns://1.1.1.1 for using udp + /// dns+https://1.1.1.1 for using dns over HTTPS + /// dns+tls://8.8.8.8 for using dns over TLS + #[arg(long, verbatim_doc_comment)] + dns_resolver: Option>, + /// Server will only accept connection from if this specific path prefix is used during websocket upgrade. /// Useful if you specify in the client a custom path prefix and you want the server to only allow this one. /// The path prefix act as a secret to authenticate clients @@ -445,6 +458,7 @@ pub struct WsServerConfig { pub timeout_connect: Duration, pub websocket_mask_frame: bool, pub tls: Option, + pub dns_resolver: DnsResolver, } impl Debug for WsServerConfig { @@ -592,7 +606,14 @@ async fn main() { let remote = tunnel.remote.clone(); let cfg = client_config.clone(); let connect_to_dest = |_| async { - tcp::connect(&remote.0, remote.1, cfg.socket_so_mark, cfg.timeout_connect).await + tcp::connect( + &remote.0, + remote.1, + cfg.socket_so_mark, + cfg.timeout_connect, + &DnsResolver::System, + ) + .await }; if let Err(err) = @@ -608,8 +629,9 @@ async fn main() { tokio::spawn(async move { let cfg = client_config.clone(); let remote = tunnel.remote.clone(); - let connect_to_dest = - |_| async { udp::connect(&remote.0, remote.1, cfg.timeout_connect).await }; + let connect_to_dest = |_| async { + udp::connect(&remote.0, remote.1, cfg.timeout_connect, &DnsResolver::System).await + }; if let Err(err) = tunnel::client::run_reverse_tunnel(client_config, tunnel, connect_to_dest).await @@ -625,7 +647,9 @@ async fn main() { let connect_to_dest = |remote: (Host, u16)| { let so_mark = cfg.socket_so_mark; let timeout = cfg.timeout_connect; - async move { tcp::connect(&remote.0, remote.1, so_mark, timeout).await } + async move { + tcp::connect(&remote.0, remote.1, so_mark, timeout, &DnsResolver::System).await + } }; if let Err(err) = @@ -770,6 +794,29 @@ async fn main() { None }; + let dns_resolver = match args.dns_resolver { + None => DnsResolver::System, + Some(resolvers) => { + let mut cfg = ResolverConfig::new(); + for resolver in resolvers { + let (protocol, port) = match resolver.scheme() { + "dns" => (hickory_resolver::config::Protocol::Udp, resolver.port().unwrap_or(53)), + "dns+https" => (hickory_resolver::config::Protocol::Https, resolver.port().unwrap_or(853)), + "dns+tls" => (hickory_resolver::config::Protocol::Tls, resolver.port().unwrap_or(12)), + _ => panic!("invalid protocol for dns resolver"), + }; + let sock = match resolver.host().unwrap() { + Host::Domain(_) => panic!("Dns resolver must be an ip address"), + Host::Ipv4(ip) => SocketAddr::V4(SocketAddrV4::new(ip, port)), + Host::Ipv6(ip) => SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)), + }; + cfg.add_name_server(NameServerConfig::new(sock, protocol)) + } + + let opts = ResolverOpts::default(); + DnsResolver::TrustDns(hickory_resolver::AsyncResolver::tokio(cfg, opts)) + } + }; let server_config = WsServerConfig { socket_so_mark: args.socket_so_mark, bind: args.remote_addr.socket_addrs(|| Some(8080)).unwrap()[0], @@ -779,6 +826,7 @@ async fn main() { timeout_connect: Duration::from_secs(10), websocket_mask_frame: args.websocket_mask_frame, tls: tls_config, + dns_resolver, }; info!( diff --git a/src/tcp.rs b/src/tcp.rs index 710b99d..c6f8a19 100644 --- a/src/tcp.rs +++ b/src/tcp.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Context}; use std::{io, vec}; +use crate::dns::DnsResolver; use base64::Engine; use bytes::BytesMut; use log::warn; @@ -41,14 +42,15 @@ pub async fn connect( port: u16, so_mark: Option, connect_timeout: Duration, + dns_resolver: &DnsResolver, ) -> Result { info!("Opening TCP connection to {}:{}", host, port); let socket_addrs: Vec = match host { - Host::Domain(domain) => tokio::net::lookup_host(format!("{}:{}", domain, port)) + Host::Domain(domain) => dns_resolver + .lookup_host(domain.as_str(), port) .await - .with_context(|| format!("cannot resolve domain: {}", domain))? - .collect(), + .with_context(|| format!("cannot resolve domain: {}", domain))?, Host::Ipv4(ip) => vec![SocketAddr::V4(SocketAddrV4::new(*ip, port))], Host::Ipv6(ip) => vec![SocketAddr::V6(SocketAddrV6::new(*ip, port, 0, 0))], }; @@ -104,7 +106,7 @@ pub async fn connect_with_http_proxy( let proxy_host = proxy.host().context("Cannot parse proxy host")?.to_owned(); let proxy_port = proxy.port_or_known_default().unwrap_or(80); - let mut socket = connect(&proxy_host, proxy_port, so_mark, connect_timeout).await?; + let mut socket = connect(&proxy_host, proxy_port, so_mark, connect_timeout, &DnsResolver::System).await?; info!("Connected to http proxy {}:{}", proxy_host, proxy_port); let authorization = if let Some((user, password)) = proxy.password().map(|p| (proxy.username(), p)) { diff --git a/src/tunnel/mod.rs b/src/tunnel/mod.rs index c536ec5..fd68a89 100644 --- a/src/tunnel/mod.rs +++ b/src/tunnel/mod.rs @@ -2,6 +2,7 @@ pub mod client; mod io; pub mod server; +use crate::dns::DnsResolver; use crate::{tcp, tls, LocalProtocol, LocalToRemote, WsClientConfig}; use async_trait::async_trait; use bb8::ManageConnection; @@ -126,7 +127,7 @@ impl ManageConnection for WsClientConfig { let tcp_stream = if let Some(http_proxy) = &self.http_proxy { tcp::connect_with_http_proxy(http_proxy, host, *port, so_mark, timeout).await? } else { - tcp::connect(host, *port, so_mark, timeout).await? + tcp::connect(host, *port, so_mark, timeout, &DnsResolver::System).await? }; match &self.tls { diff --git a/src/tunnel/server.rs b/src/tunnel/server.rs index 808a541..8217a18 100644 --- a/src/tunnel/server.rs +++ b/src/tunnel/server.rs @@ -67,7 +67,13 @@ async fn from_query( match jwt.claims.p { LocalProtocol::Udp { timeout, .. } => { let host = Host::parse(&jwt.claims.r)?; - let cnx = udp::connect(&host, jwt.claims.rp, timeout.unwrap_or(Duration::from_secs(10))).await?; + let cnx = udp::connect( + &host, + jwt.claims.rp, + timeout.unwrap_or(Duration::from_secs(10)), + &server_config.dns_resolver, + ) + .await?; Ok(( LocalProtocol::Udp { timeout: None }, host, @@ -79,9 +85,15 @@ async fn from_query( LocalProtocol::Tcp => { let host = Host::parse(&jwt.claims.r)?; let port = jwt.claims.rp; - let (rx, tx) = tcp::connect(&host, port, server_config.socket_so_mark, Duration::from_secs(10)) - .await? - .into_split(); + let (rx, tx) = tcp::connect( + &host, + port, + server_config.socket_so_mark, + Duration::from_secs(10), + &server_config.dns_resolver, + ) + .await? + .into_split(); Ok((jwt.claims.p, host, port, Box::pin(rx), Box::pin(tx))) } diff --git a/src/udp.rs b/src/udp.rs index 167fb68..f7ad294 100644 --- a/src/udp.rs +++ b/src/udp.rs @@ -18,6 +18,7 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio::net::UdpSocket; use tokio::sync::futures::Notified; +use crate::dns::DnsResolver; use tokio::sync::Notify; use tokio::time::{timeout, Interval}; use tracing::{debug, error, info}; @@ -295,16 +296,21 @@ impl AsyncWrite for MyUdpSocket { } } -pub async fn connect(host: &Host, port: u16, connect_timeout: Duration) -> anyhow::Result { +pub async fn connect( + host: &Host, + port: u16, + connect_timeout: Duration, + dns_resolver: &DnsResolver, +) -> anyhow::Result { info!("Opening UDP connection to {}:{}", host, port); let socket_addrs: Vec = match host { - Host::Domain(domain) => timeout(connect_timeout, tokio::net::lookup_host(format!("{}:{}", domain, port))) - .await - .with_context(|| format!("cannot resolve domain: {}", domain))?? - .collect(), Host::Ipv4(ip) => vec![SocketAddr::V4(SocketAddrV4::new(*ip, port))], Host::Ipv6(ip) => vec![SocketAddr::V6(SocketAddrV6::new(*ip, port, 0, 0))], + Host::Domain(domain) => dns_resolver + .lookup_host(domain.as_str(), port) + .await + .with_context(|| format!("cannot resolve domain: {}", domain))?, }; let mut cnx = None;