Skip to content

Commit

Permalink
refactor!: remove tunnel, add signal handlers (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
M0dEx authored Feb 25, 2024
1 parent 84f42cf commit 41b7e7b
Show file tree
Hide file tree
Showing 18 changed files with 519 additions and 597 deletions.
216 changes: 121 additions & 95 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "quincy"
version = "0.7.1"
version = "0.8.0"
authors = ["Jakub Kubík <[email protected]>"]
license = "MIT"
description = "QUIC-based VPN"
Expand Down Expand Up @@ -51,6 +51,7 @@ tokio = { version = "^1.25", features = [
"macros",
"sync",
"io-util",
"signal",
] }
dashmap = "^5.4"
futures = "^0.3.17"
Expand Down
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ $ cargo build --release
The resulting binaries can be found in the `target/debug` and `target/release` directories.

## Usage
Quincy is split into 3 binaries:
Quincy is split into 3 binaries:
- `client`: The VPN client
- `server`: The VPN server
- `users`: A utility binary meant for managing the `users` file
Expand Down Expand Up @@ -85,9 +85,7 @@ Any additional routes now have to be set up manually.
### Server
The Quincy server requires a separate configuration file, an example of which can be found in `examples/server.toml`:
```toml
# Section representing tunnel configuration
[tunnels.tun0]
# Name of the tunnel (currently not used as the name of the interface)
# Name of the server instance (currently not used as the name of the interface)
name = "tun0"
# Path to the certificate used for TLS
certificate_file = "examples/cert/server_cert.pem"
Expand Down Expand Up @@ -115,7 +113,7 @@ $ quincy-server --config-path examples/server.toml
```

### Users
The users utility can be used to manage entries in the `users` file.
The users utility can be used to manage entries in the `users` file.
The `users` file contains usernames and password hashes in the following format (`examples/users`):
```
test:$argon2id$v=19$m=19456,t=2,p=1$S9rMLOcz/dnYN4cnyc/TJg$ES0p+DErLfcWoUJ2tvZlxZSSIGYNUEe0ZpKBDz7MOj0
Expand All @@ -128,9 +126,9 @@ $ quincy-users --add examples/users

The prompts will look something like this:
```
Enter the username: test
Enter password for user 'test':
Confirm password for user 'test':
Enter the username: test
Enter password for user 'test':
Confirm password for user 'test':
```

A similar command can be used to remove users from the file:
Expand All @@ -140,7 +138,7 @@ $ quincy-users --remove examples/users

The prompt will again look something like this:
```
Enter the username: test
Enter the username: test
```

## Certificate management
Expand Down Expand Up @@ -168,7 +166,7 @@ $ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out <your_

2) Generate a certificate request (you can fill out the fields with whatever information you want)
```
$ openssl req -new -key <your_certificate_key_file> -out <your_certificate_request_file>
$ openssl req -new -key <your_certificate_key_file> -out <your_certificate_request_file>
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
Expand Down
2 changes: 1 addition & 1 deletion examples/client.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ mtu = 1400

[log]
# The log level
level = "info"
level = "info"
4 changes: 1 addition & 3 deletions examples/server.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# Section representing tunnel configuration
[tunnels.tun0]
# Name of the tunnel (currently not used as the name of the interface)
# Name of the server instance (currently not used as the name of the interface)
name = "tun0"
# Path to the certificate used for TLS
certificate_file = "examples/cert/server_cert.pem"
Expand Down
6 changes: 4 additions & 2 deletions src/auth/server.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::{net::IpAddr, sync::Arc, time::Duration};

use crate::constants::{AUTH_FAILED_MESSAGE, AUTH_MESSAGE_BUFFER_SIZE, AUTH_TIMEOUT_MESSAGE};
use crate::server::address_pool::AddressPool;
use crate::{
constants::{AUTH_FAILED_MESSAGE, AUTH_MESSAGE_BUFFER_SIZE, AUTH_TIMEOUT_MESSAGE},
server::address_pool::AddressPool,
};
use anyhow::{anyhow, Context, Result};
use bytes::BytesMut;
use ipnet::IpNet;
Expand Down
125 changes: 68 additions & 57 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::auth::client::AuthClient;

use crate::config::ClientConfig;
use crate::constants::{PACKET_BUFFER_SIZE, QUINN_RUNTIME};
use crate::constants::{PACKET_BUFFER_SIZE, PACKET_CHANNEL_SIZE, QUINN_RUNTIME};
use crate::utils::signal_handler::handle_ctrl_c;
use crate::utils::socket::bind_socket;
use crate::utils::tasks::abort_all;
use anyhow::{anyhow, Result};
use quinn::{Connection, Endpoint};
use quinn::{Connection, Endpoint, VarInt};

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};

Expand All @@ -13,86 +15,79 @@ use bytes::Bytes;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use std::sync::Arc;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tracing::{debug, info};

/// Represents a Quincy client that connects to a server and relays packets between the server and a TUN interface.
pub struct QuincyClient {
client_config: ClientConfig,
config: ClientConfig,
}

impl QuincyClient {
/// Creates a new instance of a Quincy client.
///
/// ### Arguments
/// - `client_config` - the configuration for the client
pub fn new(client_config: ClientConfig) -> Self {
Self { client_config }
pub fn new(config: ClientConfig) -> Self {
Self { config }
}

/// Connects to the Quincy server and starts the workers for this instance of the Quincy client.
pub async fn run<I: Interface>(&self) -> Result<()> {
let connection = self.connect_to_server().await?;
let mut auth_client =
AuthClient::new(&connection, &self.client_config.authentication).await?;
let mut auth_client = AuthClient::new(&connection, &self.config.authentication).await?;

let assigned_address = auth_client.authenticate().await?;

info!("Successfully authenticated");
info!("Received client address: {assigned_address}");

let interface = I::create(assigned_address, self.client_config.connection.mtu)?;
let mtu = self.config.connection.mtu;
let interface = I::create(assigned_address, mtu)?;

self.relay_packets(
connection,
interface,
self.client_config.connection.mtu as usize,
)
.await
self.relay_packets(connection, interface, mtu as usize)
.await
}

/// Connects to the Quincy server.
///
/// ### Returns
/// - `Connection` - a Quinn connection representing the connection to the Quincy server
async fn connect_to_server(&self) -> Result<Connection> {
let quinn_config = self.client_config.as_quinn_client_config()?;
let quinn_config = self.config.as_quinn_client_config()?;

let server_hostname = self
.client_config
.config
.connection_string
.split(':')
.next()
.ok_or_else(|| {
anyhow!(
"Could not parse hostname from connection string '{}'",
self.client_config.connection_string
self.config.connection_string
)
})?;

let server_addr = self
.client_config
.config
.connection_string
.to_socket_addrs()?
.next()
.ok_or_else(|| {
anyhow!(
"Connection string '{}' is invalid",
self.client_config.connection_string
self.config.connection_string
)
})?;

info!("Connecting: {}", self.client_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.client_config.connection_string
);
info!("Connection established: {}", self.config.connection_string);

Ok(connection)
}
Expand All @@ -113,51 +108,62 @@ impl QuincyClient {

let socket = bind_socket(
bind_addr,
self.client_config.connection.send_buffer_size as usize,
self.client_config.connection.recv_buffer_size as usize,
self.config.connection.send_buffer_size as usize,
self.config.connection.recv_buffer_size as usize,
)?;

let endpoint_config = self.client_config.connection.as_endpoint_config()?;
let endpoint_config = self.config.connection.as_endpoint_config()?;
let endpoint = Endpoint::new(endpoint_config, None, socket, QUINN_RUNTIME.clone())?;

Ok(endpoint)
}

/// Relays packets between the TUN interface and the Quincy server.
/// Relays packets between the TUN interface and the Quincy clients.
///
/// ### Arguments
/// - `connection` - a Quinn connection representing the connection to the Quincy server
/// - `interface` - the TUN interface
/// - `interface_mtu` - the MTU of the TUN interface
async fn relay_packets(
&self,
connection: Connection,
interface: impl Interface,
interface_mtu: usize,
) -> Result<()> {
let connection = Arc::new(connection);
let (tun_queue_send, tun_queue_recv) = unbounded_channel();
let (tun_read, tun_write) = tokio::io::split(interface);

let mut client_tasks = FuturesUnordered::new();

client_tasks.push(tokio::spawn(Self::process_inbound_traffic(
connection.clone(),
tun_queue_send,
)));
client_tasks.push(tokio::spawn(Self::process_tun_queue(
tun_queue_recv,
tun_write,
)));
client_tasks.push(tokio::spawn(Self::process_outgoing_traffic(
connection.clone(),
tun_read,
interface_mtu,
)));

client_tasks
.next()
.await
.expect("Client tasks are not empty")?
let (tun_queue_send, tun_queue_recv) = channel(PACKET_CHANNEL_SIZE);
let (tun_read, tun_write) = interface.split();

let mut tasks = FuturesUnordered::new();

tasks.extend([
tokio::spawn(Self::process_inbound_traffic(
connection.clone(),
tun_queue_send,
)),
tokio::spawn(Self::process_tun_queue(tun_queue_recv, tun_write)),
tokio::spawn(Self::process_outgoing_traffic(
connection.clone(),
tun_read,
interface_mtu,
)),
]);

let result = tokio::select! {
Some(task_result) = tasks.next() => task_result?,
signal_res = handle_ctrl_c() => {
info!("Received shutdown signal, shutting down");
signal_res
},
};

// Stop all running tasks
let _ = abort_all(tasks).await;

// Close the QUIC connection
connection.close(VarInt::from_u32(0x01), "Client shutdown".as_bytes());

result
}

/// Handles incoming packets from the TUN interface and relays them to the Quincy server.
Expand Down Expand Up @@ -186,8 +192,13 @@ impl QuincyClient {
}
}

/// Handles incoming packets from the Quincy clients and relays them to the TUN interface.
///
/// ### Arguments
/// - `tun_queue` - the TUN queue
/// - `tun_write` - the write half of the TUN interface
async fn process_tun_queue(
mut tun_queue: UnboundedReceiver<Bytes>,
mut tun_queue: Receiver<Bytes>,
mut tun_write: impl InterfaceWrite,
) -> Result<()> {
debug!("Started TUN queue task (interface -> QUIC tunnel)");
Expand All @@ -202,14 +213,14 @@ impl QuincyClient {
}
}

/// Handles incoming packets from the Quincy server and relays them to the TUN interface.
/// Handles incoming packets from the Quincy server and relays them to the TUN interface queue.
///
/// ### Arguments
/// - `connection` - a Quinn connection representing the connection to the Quincy server
/// - `write_interface` - the write half of the TUN interface
/// - `tun_queue` - the TUN queue
async fn process_inbound_traffic(
connection: Arc<Connection>,
tun_queue: UnboundedSender<Bytes>,
tun_queue: Sender<Bytes>,
) -> Result<()> {
debug!("Started inbound traffic task (QUIC tunnel -> interface)");

Expand All @@ -222,7 +233,7 @@ impl QuincyClient {
connection.remote_address()
);

tun_queue.send(data)?;
tun_queue.send(data).await?;
}
}
}
Loading

0 comments on commit 41b7e7b

Please sign in to comment.