mod relayer;
use crate::auth::AuthClient;
use crate::users_file_auth::UsersFileClientAuthenticator;
use quincy::config::ClientConfig;
use quincy::constants::QUINN_RUNTIME;
use quincy::network::socket::bind_socket;
use quincy::{QuincyError, Result};
use quinn::{Connection, Endpoint};
use ipnet::IpNet;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};
use std::time::Duration;
use crate::client::relayer::ClientRelayer;
use quincy::network::interface::{Interface, InterfaceIO};
use tracing::{debug, info};
pub struct QuincyClient {
config: ClientConfig,
relayer: Option<ClientRelayer>,
client_address: Option<IpNet>,
server_address: Option<IpNet>,
}
impl QuincyClient {
pub fn new(config: ClientConfig) -> Self {
Self {
config,
relayer: None,
client_address: None,
server_address: None,
}
}
pub async fn start<I: InterfaceIO>(&mut self) -> Result<()> {
if self.relayer.is_some() {
return Err(QuincyError::system("Client is already started"));
}
let connection = self.connect_to_server().await?;
let authenticator = Box::new(UsersFileClientAuthenticator::new(
&self.config.authentication,
));
let auth_client = AuthClient::new(
authenticator,
Duration::from_secs(self.config.connection.connection_timeout_s),
);
let (client_address, server_address) = auth_client.authenticate(&connection).await?;
info!("Successfully authenticated");
info!("Received client address: {client_address}");
info!("Received server address: {server_address}");
self.client_address = Some(client_address);
self.server_address = Some(server_address);
let interface: Interface<I> = Interface::create(
client_address,
self.config.connection.mtu,
Some(server_address.addr()),
Some(self.config.network.routes.clone()),
Some(self.config.network.dns_servers.clone()),
)?;
let relayer = ClientRelayer::start(interface, connection)?;
self.relayer.replace(relayer);
Ok(())
}
pub fn is_running(&self) -> bool {
self.relayer.is_some()
}
pub async fn stop(&mut self) -> Result<()> {
if let Some(relayer) = self.relayer.as_mut() {
relayer.stop().await?;
}
self.client_address = None;
self.server_address = None;
Ok(())
}
pub async fn wait_for_shutdown(&mut self) -> Result<()> {
if let Some(relayer) = self.relayer.take() {
relayer.wait_for_shutdown().await?;
}
Ok(())
}
pub fn relayer(&self) -> Option<&ClientRelayer> {
self.relayer.as_ref()
}
pub fn client_address(&self) -> Option<IpNet> {
self.client_address
}
pub fn server_address(&self) -> Option<IpNet> {
self.server_address
}
async fn connect_to_server(&self) -> Result<Connection> {
let quinn_config = self.config.quinn_client_config()?;
let server_hostname = self
.config
.connection_string
.split(':')
.next()
.ok_or_else(|| {
QuincyError::config_file_not_found(format!(
"Could not parse hostname from connection string '{}'",
self.config.connection_string
))
})?;
let server_addr = self
.config
.connection_string
.to_socket_addrs()?
.next()
.ok_or_else(|| {
QuincyError::connection_failed(format!(
"Connection string '{}' is invalid",
self.config.connection_string
))
})?;
info!("Connecting: {}", self.config.connection_string);
let endpoint = self.create_quinn_endpoint(server_addr)?;
let connection = endpoint
.connect_with(quinn_config, server_addr, server_hostname)?
.await?;
info!("Connection established: {}", self.config.connection_string);
Ok(connection)
}
fn create_quinn_endpoint(&self, remote_address: SocketAddr) -> Result<Endpoint> {
let bind_addr: SocketAddr = SocketAddr::new(
match remote_address.ip() {
IpAddr::V4(_) => Ipv4Addr::UNSPECIFIED.into(),
IpAddr::V6(_) => Ipv6Addr::UNSPECIFIED.into(),
},
0,
);
debug!("QUIC socket local address: {:?}", bind_addr);
let socket = bind_socket(
bind_addr,
self.config.connection.send_buffer_size as usize,
self.config.connection.recv_buffer_size as usize,
false,
)?;
let endpoint_config = self.config.connection.as_endpoint_config()?;
let endpoint = Endpoint::new(endpoint_config, None, socket, QUINN_RUNTIME.clone())?;
Ok(endpoint)
}
}