quinn
quinn copied to clipboard
Connection timeout quinn 0.7.2 Android (cargo-ndk)
Hello, I'm trying to make a connection from a client to a rust server using quinn 0.7.2, but when I try to connect on Android I get 'timeout'. I've already checked that my server works correctly because when I try to run the same code on Windows the connection works perfectly. Which leads me to believe that Android has some limitation in relation to Quin. I am compiling using the android armeabi-v7a architecture and for android 5+ versions. Here is the command I use to compile the client: cargo ndk -t armeabi-v7a -p 21 -- build --release. On the server side I am using cargo +nightly-i686 build --release --no-default-features to compile the server. The connection reaches the server and times out when it arrives in the accept_connection function. What am I doing wrong? What should I check?
Server side:
use std::{io::Error};
use std::io::{self, ErrorKind};
use futures_util::StreamExt;
use quinn::{
PrivateKey, Connecting, Connection, Endpoint, EndpointBuilder, Incoming, IncomingUniStreams, NewConnection,
};
use quinn::{
Certificate, CertificateChain, ServerConfig,
ServerConfigBuilder, TransportConfig,
};
use std::net::SocketAddr;
use tokio::{
runtime::Runtime,
sync::{
mpsc::{self, UnboundedReceiver as Recv, UnboundedSender as Sender},
oneshot,
},
};
use std::sync::Arc;
use std::time::Duration;
use std::panic;
pub fn make_self_signed() -> anyhow::Result<EndpointBuilder> {
let cert = rcgen::generate_simple_self_signed(vec!["samp.cef".into()])?;
let cert_der = cert.serialize_der()?;
let priv_key = cert.serialize_private_key_der();
let cert = Certificate::from_der(&cert_der)?;
let priv_key = PrivateKey::from_der(&priv_key)?;
let cfg = configure_server(cert, priv_key)?;
let mut endpoint_builder = Endpoint::builder();
endpoint_builder.listen(cfg);
Ok(endpoint_builder)
}
fn configure_server(cert: Certificate, priv_key: PrivateKey) -> anyhow::Result<ServerConfig> {
let mut transport_config = TransportConfig::default();
transport_config.keep_alive_interval(Some(Duration::from_secs(1)));
let mut server_config = ServerConfig::default();
server_config.transport = Arc::new(transport_config);
let mut cfg_builder = ServerConfigBuilder::new(server_config);
cfg_builder.certificate(CertificateChain::from_certs(vec![cert]), priv_key)?;
Ok(cfg_builder.build())
}
#[tokio::main]
async fn main() -> Result<(), Error> {
panic::set_hook(Box::new(|info| {
println!("Panic: {}", info);
}));
println!("Init...");
let builder = make_self_signed().unwrap();
println!("Self-signed certificate created successfully.");
// Criar o endpoint do servidor
//let (endpoint, incoming) = builder.bind(&"0.0.0.0:7779".parse().unwrap());
let addr = "0.0.0.0:7779".parse().map_err(|e| {
io::Error::new(ErrorKind::InvalidInput, format!("Invalid address: {}", e))
})?;
println!("Successfully parsed address: {:?}", addr);
let (_, incoming) = builder.bind(&addr).unwrap();
println!("Linked successfully.");
println!("Server listening on the port 7779...");
// Processar as conexões recebidas
tokio::spawn(accept_connections(incoming));
println!("Ready to accept connections.");
let mut interval = tokio::time::interval(Duration::from_secs(1));
loop {
interval.tick().await;
}
}
async fn accept_connections(
mut incoming: Incoming,
) {
while let Some(conn) = incoming.next().await {
println!("Connection comming");
tokio::spawn(process_connection(conn));
}
}
async fn process_connection(
connecting: Connecting
) -> anyhow::Result<()> {
let NewConnection {
connection,
uni_streams,
..
} = match connecting.await {
Ok(conn) => conn,
Err(e) => {
//println!("Ocorreu um erro: {:?}", e);
match e {
quinn::ConnectionError::VersionMismatch => println!("Incompatibilidade de versão"),
quinn::ConnectionError::TransportError(e) => {
println!("Transport error: {:?}", e);
// Aqui você pode inspecionar os detalhes do erro de transporte
},
quinn::ConnectionError::ApplicationClosed(close) => {
println!("Application closed: {:?}", close.reason);
// Aqui você pode inspecionar os detalhes do fechamento da aplicação
},
quinn::ConnectionError::TimedOut => println!("Connection timed out"),
quinn::ConnectionError::LocallyClosed => println!("The connection was closed locally"),
_ => println!("Another error: {:?}", e),
}
return Ok(());
}
};
println!("It worked");
Ok(())
}
Client Side:
use std::net::SocketAddr;
use std::sync::Arc;
use quinn::{ClientConfig, Connecting};
use std::time::Duration;
use std::ffi::CString;
use std::os::raw::c_char;
extern "C" {
fn log_android(input: *const c_char);
}
pub fn logtoandroid(text: &str) {
let c_string = CString::new(text).expect("Failed to create CString");
unsafe {
log_android(c_string.as_ptr());
}
}
#[no_mangle]
pub extern "C" fn iniciar_teste() {
logtoandroid("RUST: Inicializando cliente");
std::thread::spawn(|| {
main();
});
}
#[tokio::main]
async fn main() {
logtoandroid("Teste: init conection");
let server_addr: SocketAddr = "192.168.0.105:7779".parse().unwrap();
let addr = "0.0.0.0:0".parse().unwrap();
let builder = make_insecure_client();
logtoandroid("Teste: tokio::spawn(process_connection");
let (endpoint, _) = builder.bind(&addr).unwrap();
let connecting = endpoint.connect(&server_addr, "samp.cef").unwrap();
tokio::spawn(process_connection(connecting));
let mut interval = tokio::time::interval(Duration::from_secs(1));
loop {
interval.tick().await;
}
}
async fn process_connection(
connecting: Connecting
) -> anyhow::Result<()> {
let NewConnection {
connection,
uni_streams,
..
} = match connecting.await {
Ok(conn) => conn,
Err(e) => {
println!("Teste: Ocorreu um erro: {:?}", e);
return Ok(());
}
};
logtoandroid("Teste: Funcionou");
Ok(())
}
struct SkipServerVerification;
impl SkipServerVerification {
fn new() -> Arc<Self> {
Arc::new(Self)
}
}
impl rustls::ServerCertVerifier for SkipServerVerification {
fn verify_server_cert(
&self, _roots: &rustls::RootCertStore, _presented_certs: &[rustls::Certificate],
_dns_name: webpki::DNSNameRef, _ocsp_response: &[u8],
) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
Ok(rustls::ServerCertVerified::assertion())
}
}
pub fn make_insecure_client() -> EndpointBuilder {
let client_cfg = configure_insecure_client();
let mut endpoint_builder = Endpoint::builder();
endpoint_builder.default_client_config(client_cfg);
endpoint_builder
}
fn configure_insecure_client() -> ClientConfig {
let mut cfg = ClientConfigBuilder::default().build();
let tls_cfg: &mut rustls::ClientConfig = Arc::get_mut(&mut cfg.crypto).unwrap();
tls_cfg
.dangerous()
.set_certificate_verifier(SkipServerVerification::new());
cfg
}
I call the iniciar_teste() function in c++ using jni it calls normally but it stops on the server at: quinn::ConnectionError::TimedOut => println!("Connection timed out"),
So are there any limitations in relation to Android? I don't know what it could be. Remembering: When compiling the client for PC it works normally!
Cargo.toml client
[lib] name = "src" crate-type = ["staticlib"]
dependencies on both:
[dependencies] quinn = "0.7.2" futures-util = "0.3.17" tokio = { version = "1.11", features = ["full"] } slotmap = "1.0.6" anyhow = "1.0.44" rcgen = "0.8.13"
Have you tried using a recent quinn version?
I tried with recent Quinn version, it seems like nested Tokio's spawn does not work in android. There is a tokio::spawn here in "tokio::spawn(process_connection(connecting));" and Endpoint::new_with_abstract_socket will call another tokio::spawn.
I doubt that is the case; Android should work about the same as (old) Linux. Regardless, if you think you have a tokio bug, raise it with tokio.
Closing as the original reporter never responded.
I updated the version and everything worked fine.
Thanks for the update!