Skip to content

Commit

Permalink
feat: add configurable client isolation
Browse files Browse the repository at this point in the history
  • Loading branch information
M0dEx committed Mar 18, 2024
1 parent 7d4ac60 commit 44a46c3
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 73 deletions.
27 changes: 7 additions & 20 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ use quinn::{Connection, Endpoint, VarInt};

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

use crate::interface::{Interface, InterfaceRead, InterfaceWrite};
use bytes::Bytes;
use crate::interface::{Interface, InterfaceRead, InterfaceWrite, Packet};
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use std::sync::Arc;
Expand Down Expand Up @@ -183,15 +182,9 @@ impl QuincyClient {
debug!("Started outgoing traffic task (interface -> QUIC tunnel)");

loop {
let data = read_interface.read_packet(interface_mtu).await?;
let packet = read_interface.read_packet(interface_mtu).await?;

debug!(
"Sending {} bytes to {:?}",
data.len(),
connection.remote_address()
);

connection.send_datagram(data)?;
connection.send_datagram(packet.into())?;
}
}

Expand All @@ -201,7 +194,7 @@ impl QuincyClient {
/// - `tun_queue` - the TUN queue
/// - `tun_write` - the write half of the TUN interface
async fn process_tun_queue(
mut tun_queue: Receiver<Bytes>,
mut tun_queue: Receiver<Packet>,
mut tun_write: impl InterfaceWrite,
) -> Result<()> {
debug!("Started TUN queue task (interface -> QUIC tunnel)");
Expand All @@ -223,20 +216,14 @@ impl QuincyClient {
/// - `tun_queue` - the TUN queue
async fn process_inbound_traffic(
connection: Arc<Connection>,
tun_queue: Sender<Bytes>,
tun_queue: Sender<Packet>,
) -> Result<()> {
debug!("Started inbound traffic task (QUIC tunnel -> interface)");

loop {
let data = connection.read_datagram().await?;

debug!(
"Received {} bytes from {:?}",
data.len(),
connection.remote_address()
);
let packet = connection.read_datagram().await?.into();

tun_queue.send(data).await?;
tun_queue.send(packet).await?;
}
}
}
27 changes: 17 additions & 10 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@ pub struct ServerConfig {
pub certificate_file: PathBuf,
/// The certificate private key to use for the tunnel
pub certificate_key_file: PathBuf,
/// The address to bind the tunnel to
/// The address to bind the tunnel to (default = 0.0.0.0)
#[serde(default = "default_bind_address")]
pub bind_address: IpAddr,
/// The port to bind the tunnel to
/// The port to bind the tunnel to (default = 55555)
#[serde(default = "default_bind_port")]
pub bind_port: u16,
/// The address of this tunnel
pub address_tunnel: Ipv4Addr,
/// The address mask for this tunnel
pub address_mask: Ipv4Addr,
/// Whether to isolate clients from each other (default = true)
#[serde(default = "default_true_fn")]
pub isolate_clients: bool,
/// Authentication configuration
pub authentication: ServerAuthenticationConfig,
/// Miscellaneous connection configuration
Expand All @@ -48,7 +51,7 @@ pub struct ServerConfig {
/// Represents the configuration for a Quincy server's authentication.
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct ServerAuthenticationConfig {
/// The type of authenticator to use
/// The type of authenticator to use (default = users_file)
#[serde(default = "default_auth_type")]
pub auth_type: AuthType,
/// The path to the file containing the list of users and their password hashes
Expand All @@ -71,7 +74,7 @@ pub struct ClientConfig {
/// Represents the configuration for a Quincy client's authentication.
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct ClientAuthenticationConfig {
/// The type of authenticator to use
/// The type of authenticator to use (default = users_file)
#[serde(default = "default_auth_type")]
pub auth_type: AuthType,
/// The username to use for authentication
Expand All @@ -85,27 +88,27 @@ pub struct ClientAuthenticationConfig {
/// Represents miscellaneous connection configuration.
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct ConnectionConfig {
/// The MTU to use for connections and the TUN interface
/// The MTU to use for connections and the TUN interface (default = 1400)
#[serde(default = "default_mtu")]
pub mtu: u16,
/// The time after which a connection is considered timed out
/// The time after which a connection is considered timed out (default = 30s)
#[serde(default = "default_timeout")]
pub connection_timeout: Duration,
/// Keep alive interval for connections
/// Keep alive interval for connections (default = 25s)
#[serde(default = "default_keep_alive_interval")]
pub keep_alive_interval: Duration,
/// The size of the send buffer of the socket and Quinn endpoint
/// The size of the send buffer of the socket and Quinn endpoint (default = 2097152)
#[serde(default = "default_buffer_size")]
pub send_buffer_size: u64,
/// The size of the receive buffer of the socket and Quinn endpoint
/// The size of the receive buffer of the socket and Quinn endpoint (default = 2097152)
#[serde(default = "default_buffer_size")]
pub recv_buffer_size: u64,
}

/// Represents logging configuration.
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct LogConfig {
/// The log level to use
/// The log level to use (default = info)
#[serde(default = "default_log_level")]
pub level: String,
}
Expand Down Expand Up @@ -184,6 +187,10 @@ fn default_auth_type() -> AuthType {
AuthType::UsersFile
}

fn default_true_fn() -> bool {
true
}

impl ClientConfig {
/// Creates Quinn client configuration from this Quincy client configuration.
///
Expand Down
50 changes: 45 additions & 5 deletions src/interface.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
#![allow(async_fn_in_trait)]
use anyhow::Result;
use std::net::IpAddr;

use anyhow::{anyhow, Context, Result};
use bytes::{Bytes, BytesMut};
use etherparse::{NetHeaders, PacketHeaders};
use ipnet::IpNet;
use tokio::io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf};
use tun2::{AsyncDevice, Configuration};

pub trait InterfaceRead: AsyncReadExt + Sized + Unpin + Send + 'static {
#[inline]
async fn read_packet(&mut self, buf_size: usize) -> Result<Bytes> {
async fn read_packet(&mut self, buf_size: usize) -> Result<Packet> {
let mut buf = BytesMut::with_capacity(buf_size);
self.read_buf(&mut buf).await?;

Expand All @@ -17,14 +20,14 @@ pub trait InterfaceRead: AsyncReadExt + Sized + Unpin + Send + 'static {

pub trait InterfaceWrite: AsyncWriteExt + Sized + Unpin + Send + 'static {
#[inline]
async fn write_packet(&mut self, packet_data: &Bytes) -> Result<()> {
self.write_all(packet_data).await?;
async fn write_packet(&mut self, packet: &Packet) -> Result<()> {
self.write_all(&packet.0).await?;

Ok(())
}

#[inline]
async fn write_packets(&mut self, packets: &[Bytes]) -> Result<()> {
async fn write_packets(&mut self, packets: &[Packet]) -> Result<()> {
// TODO: Implement this using write_vectored when it actually works
for packet in packets {
self.write_packet(packet).await?;
Expand Down Expand Up @@ -65,3 +68,40 @@ impl Interface for AsyncDevice {
Ok(interface)
}
}

#[derive(Debug, Clone)]
pub struct Packet(Bytes);

impl Packet {
pub fn new(data: Bytes) -> Self {
Self(data)
}

pub fn destination(&self) -> Result<IpAddr> {
let headers = PacketHeaders::from_ip_slice(&self.0).context("failed to parse IP packet")?;
let net_header = headers.net.ok_or(anyhow!("no network header"))?;

match net_header {
NetHeaders::Ipv4(header, _) => Ok(header.destination.into()),
NetHeaders::Ipv6(header, _) => Ok(header.destination.into()),
}
}
}

impl From<BytesMut> for Packet {
fn from(data: BytesMut) -> Self {
Self::new(data.freeze())
}
}

impl From<Bytes> for Packet {
fn from(data: Bytes) -> Self {
Self::new(data)
}
}

impl From<Packet> for Bytes {
fn from(packet: Packet) -> Self {
packet.0
}
}
12 changes: 6 additions & 6 deletions src/server/connection.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{auth::server::AuthServer, utils::tasks::abort_all};
use crate::{auth::server::AuthServer, interface::Packet, utils::tasks::abort_all};
use anyhow::{anyhow, Error, Result};
use bytes::Bytes;
use futures::stream::FuturesUnordered;
Expand All @@ -15,7 +15,7 @@ pub struct QuincyConnection {
connection: Connection,
username: Option<String>,
client_address: Option<IpNet>,
ingress_queue: Sender<Bytes>,
ingress_queue: Sender<Packet>,
}

impl QuincyConnection {
Expand All @@ -24,7 +24,7 @@ impl QuincyConnection {
/// ### Arguments
/// - `connection` - the underlying QUIC connection
/// - `tun_queue` - the queue to send data to the TUN interface
pub fn new(connection: Connection, tun_queue: Sender<Bytes>) -> Self {
pub fn new(connection: Connection, tun_queue: Sender<Packet>) -> Self {
Self {
connection,
username: None,
Expand Down Expand Up @@ -106,12 +106,12 @@ impl QuincyConnection {
/// Processes incoming data and sends it to the TUN interface queue.
async fn process_incoming_data(
connection: Connection,
ingress_queue: Sender<Bytes>,
ingress_queue: Sender<Packet>,
) -> Result<()> {
loop {
let data = connection.read_datagram().await?;
let packet = connection.read_datagram().await?.into();

ingress_queue.send(data).await?;
ingress_queue.send(packet).await?;
}
}

Expand Down
Loading

0 comments on commit 44a46c3

Please sign in to comment.