quinn-rs / quinn

Async-friendly QUIC implementation in Rust
Apache License 2.0
3.85k stars 394 forks source link

Connection timeout quinn 0.7.2 Android (cargo-ndk) #1683

Closed MateusArthur closed 7 months ago

MateusArthur commented 1 year ago

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"

Ralith commented 1 year ago

Have you tried using a recent quinn version?

alanpoon commented 7 months ago

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.

Ralith commented 7 months ago

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.

MateusArthur commented 7 months ago

I updated the version and everything worked fine.

Ralith commented 7 months ago

Thanks for the update!