diff --git a/quicktap-cli/src/command.rs b/quicktap-cli/src/command.rs index e15f923..f928c8e 100644 --- a/quicktap-cli/src/command.rs +++ b/quicktap-cli/src/command.rs @@ -1,5 +1,7 @@ use std::error::Error; -use std::os::unix::net::UnixListener; +use std::io; +use std::os::unix::net::SocketAddr; +use std::os::unix::net::{UnixListener, UnixStream}; pub enum PacketThreadCommand { UpdateConfig(UpdateConfigCommand), @@ -8,8 +10,36 @@ pub enum PacketThreadCommand { pub struct UpdateConfigCommand {} +pub struct CommandClient { + stream: UnixStream, + addr: SocketAddr, + current_command: CommandType +} +pub enum CommandType { + WaitingForCommand, + Read, + Write +} + pub fn command_handler(listener: UnixListener) -> Result<(), Box> { listener.set_nonblocking(true)?; + let mut clients: Vec = vec![]; + + match listener.accept() { + Ok((stream, addr)) => { + clients.push(CommandClient { stream, addr, current_command: CommandType::WaitingForCommand }) + }, + Err(e) if matches!(e.kind(), io::ErrorKind::WouldBlock) => { + // ignore. + }, + Err(e) => { + // okay, real error, knock it up + return Err(e.into()) + } + } + + // uh lets play ignorey ignorey for now + Ok(()) } \ No newline at end of file diff --git a/quicktap-cli/src/main.rs b/quicktap-cli/src/main.rs index d3911e7..9d025d7 100644 --- a/quicktap-cli/src/main.rs +++ b/quicktap-cli/src/main.rs @@ -100,6 +100,7 @@ fn main() { } }; + match tx.send(PacketThreadCommand::Stop) { Ok(_) => (), Err(e) => { diff --git a/quicktap-cli/src/pproc.rs b/quicktap-cli/src/pproc.rs index 7f363c6..34fda97 100644 --- a/quicktap-cli/src/pproc.rs +++ b/quicktap-cli/src/pproc.rs @@ -37,6 +37,9 @@ pub fn packet_thread(mut device: TunDevice, rx: Receiver) - if let Some(p) = packet { info!("recv_packet_on_tun {:?}", p.header); + + // parse and route + } } } \ No newline at end of file diff --git a/quicktap/src/drivers/error.rs b/quicktap/src/drivers/error.rs new file mode 100644 index 0000000..46804be --- /dev/null +++ b/quicktap/src/drivers/error.rs @@ -0,0 +1,20 @@ +//! Defines the error type for the glue layer +use std::error::Error; +use std::fmt::{Display, Formatter}; + +#[derive(Debug)] +#[allow(clippy::module_name_repetitions)] +/// Represents an error in the driver (glue) portion of quicktap +pub enum DriverError { + /// An invalid packet type has been received on the interface, and quicktap does not know how to process it + /// This is raised in the PREENTRY stage of packet processing + InvalidPacketTypeRecvOnInterface +} +impl Error for DriverError {} +impl Display for DriverError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidPacketTypeRecvOnInterface => write!(f, "Invalid packet type received on interface") + } + } +} \ No newline at end of file diff --git a/quicktap/src/drivers/linux.rs b/quicktap/src/drivers/linux.rs index 111ae53..aeb789d 100644 --- a/quicktap/src/drivers/linux.rs +++ b/quicktap/src/drivers/linux.rs @@ -71,3 +71,4 @@ impl GenericDriver for TunDevice { Ok(()) } } + diff --git a/quicktap/src/drivers/mod.rs b/quicktap/src/drivers/mod.rs index 93e0349..addf8d2 100644 --- a/quicktap/src/drivers/mod.rs +++ b/quicktap/src/drivers/mod.rs @@ -4,4 +4,7 @@ #[path = "linux.rs"] pub mod tun; // Tun/tap drivers for Linux -pub mod tungeneric; \ No newline at end of file +pub mod tungeneric; + +pub mod udp_listener; +pub mod error; \ No newline at end of file diff --git a/quicktap/src/drivers/udp_listener.rs b/quicktap/src/drivers/udp_listener.rs new file mode 100644 index 0000000..388763a --- /dev/null +++ b/quicktap/src/drivers/udp_listener.rs @@ -0,0 +1,82 @@ +//! The logic for the WireGuard UDP socket. + +use std::error::Error; +use std::io; +use std::net::UdpSocket; +use crate::drivers::error::DriverError; +use crate::stack::entry::PacketEntryStage; +use crate::stack::PacketProcessingStage; + +/// The maximum amount of bytes that can be taken up by a Handshake Initiation packet. +pub const MSG1_MAX_BYTES: usize = 148; +/// The maximum amount of bytes that can be taken up by a Handshake Response packet. +pub const MSG2_MAX_BYTES: usize = 92; +/// The maximum amount of bytes that can be taken up by a data packet. +pub const MSGDATA_MAX_BYTES: usize = 65567; +/// The maximum amount of bytes that can be taken up by a cookie reply packet. +pub const MSGCOOKIE_MAX_BYTES: usize = 64; + +/// Represents the result of a socket processing round +pub enum SocketRoundResult { + /// Represents that an io-specific error was received + IoError(io::Error), + /// Represents that a non-io-specific error was received + GenericAnyError(Box) +} + +/// Run a socket update round on the (nonblocking!) UDP socket provided. Checks for any packets that may be present and send them off to get processed. +/// **Warning!** This only runs *one* update round - therefore only one packet will be processed if any are present. +/// If you're writing a frontend, this is probably not what you want. Use `qtap_socket_rounds_until_empty` instead. +/// # Errors +/// This function will error if a socket read fails or the processing of the packet fails. See `PacketEntryStage::process_packet()` for more details on packet processing. +pub fn qtap_socket_round(socket: &mut UdpSocket) -> Result<(), SocketRoundResult> { + let mut packet_id = [0u8; 1]; + match socket.peek(&mut packet_id) { + Ok(_) => (), + Err(e) => return Err(SocketRoundResult::IoError(e)) + }; // Intentionally peek into a 1-byte vector. This will NOT remove the datagram from the queue. + // We WANT to discard the rest of the packet; right now we only care about the first byte so we know how much memory we need to allocate for the buffer. + // The packet type tells us this. + + let buf_size = match packet_id[0] { + 1 => MSG1_MAX_BYTES, + 2 => MSG2_MAX_BYTES, + 4 => MSGDATA_MAX_BYTES, + 3 => MSGCOOKIE_MAX_BYTES, + _ => return Err(SocketRoundResult::GenericAnyError(DriverError::InvalidPacketTypeRecvOnInterface.into())) + }; + + let mut packet_buf = vec![0u8; buf_size]; + let (read_bytes, from) = match socket.recv_from(&mut packet_buf) { + Ok(r) => r, + Err(e) => return Err(SocketRoundResult::IoError(e)) + }; + + let packet = packet_buf[..read_bytes].to_vec(); + + match PacketEntryStage::process_packet(packet, &from) { + Ok(_) => Ok(()), + Err(e) => Err(SocketRoundResult::GenericAnyError(e)) + } +} + +/// Run several socket update rounds on the (nonblocking!) UDP socket provided. Will continually call `qtap_socket_round` in a loop until `io::WouldBlock` is returned. +/// **Warning!** This runs as many socket rounds are needed to clear the UDP datagram queue. If there is a long queue, this function will take a very long time. +/// If you only want to run one update, use `qtap_socket_round` instead. +/// # Errors +/// See `qtap_socket_round` +pub fn qtap_socket_rounds_until_empty(socket: &mut UdpSocket) -> Result<(), Box> { + loop { + match qtap_socket_round(socket) { + Ok(_) => (), + Err(e) => { + match e { + SocketRoundResult::IoError(e) if matches!(e.kind(), io::ErrorKind::WouldBlock) => break, + SocketRoundResult::GenericAnyError(e) => return Err(e), + SocketRoundResult::IoError(e) => return Err(e.into()) + } + } + } + } + Ok(()) +} \ No newline at end of file diff --git a/quicktap/src/lib.rs b/quicktap/src/lib.rs index f2acf2a..07ed6c0 100644 --- a/quicktap/src/lib.rs +++ b/quicktap/src/lib.rs @@ -16,6 +16,7 @@ pub use cidr; pub mod drivers; pub mod qcrypto; pub mod noise; +pub mod stack; /// Gets the compile-time versioning information for the engine build. pub const fn version() -> &'static str { diff --git a/quicktap/src/noise/mod.rs b/quicktap/src/noise/mod.rs index 4a489be..e4578f0 100644 --- a/quicktap/src/noise/mod.rs +++ b/quicktap/src/noise/mod.rs @@ -4,4 +4,5 @@ pub mod error; pub mod data; #[cfg(test)] -pub mod tests; \ No newline at end of file +pub mod tests; +pub mod rfc6479; \ No newline at end of file diff --git a/quicktap/src/noise/rfc6479/mod.rs b/quicktap/src/noise/rfc6479/mod.rs new file mode 100644 index 0000000..17e6879 --- /dev/null +++ b/quicktap/src/noise/rfc6479/mod.rs @@ -0,0 +1,76 @@ +//! A Rust implementation of the anti-replay algorithm described in [RFC 6479](https://rfc-editor.org/rfc6479). + +const SIZE_OF_INTEGER: usize = std::mem::size_of::() * 8; +const BITMAP_LEN: usize = 1024 / SIZE_OF_INTEGER; +const BITMAP_INDEX_MASK: u64 = BITMAP_LEN as u64 - 1; +const REDUNDANT_BIT_SHIFTS: u64 = 5; +const REDUNDANT_BITS: u64 = 1 << REDUNDANT_BIT_SHIFTS; +const BITMAP_LOC_MASK: u64 = REDUNDANT_BITS - 1; +const WINDOW_SIZE: u64 = 512; + +#[derive(Default)] +/// An implementation of the RFC6479 anti-replay algorithm +pub struct ShiftWindow { + replaywin_lastseq: u64, + replaywin_bitmap: [usize; BITMAP_LEN], +} + +impl ShiftWindow { + /// Create a new ShiftWindow with default values + pub fn new() -> Self { + Self::default() + } + + /// Check if a given sequence value is okay given the current state of the shift window + pub const fn check_replay_window(&self, seq: u64) -> bool { + // first == 0 or wrapped + if seq == 0 { + return false; + } + + // larger is always good + if seq > self.replaywin_lastseq { + return true; + } + + // too old + if (seq + WINDOW_SIZE) < self.replaywin_lastseq { + return false; + } + + let bit_location = seq & BITMAP_LOC_MASK; + let index = (seq >> REDUNDANT_BIT_SHIFTS) & BITMAP_INDEX_MASK; + + self.replaywin_bitmap[index as usize] & (1 << bit_location) == 0 + } + + /// Update the internal state of the replay window to mark the given sequence number as used and disallow future use of it. + pub fn update_replay_window(&mut self, seq: u64) { + if !self.check_replay_window(seq) { + return; + } + + let index = seq >> REDUNDANT_BIT_SHIFTS; + + if seq > self.replaywin_lastseq { + let index_cur = self.replaywin_lastseq >> REDUNDANT_BIT_SHIFTS; + let mut diff = index - index_cur; + + if diff > BITMAP_LEN as u64 { + diff = BITMAP_LEN as u64; + } + + for id in 0..diff { + let iindex = (id + index_cur + 1) & BITMAP_INDEX_MASK; + self.replaywin_bitmap[iindex as usize] = 0; + } + + self.replaywin_lastseq = seq; + } + + let index = index & BITMAP_INDEX_MASK; + let bit_location = seq & BITMAP_LOC_MASK; + + self.replaywin_bitmap[index as usize] |= 1 << bit_location; + } +} \ No newline at end of file diff --git a/quicktap/src/stack/entry.rs b/quicktap/src/stack/entry.rs new file mode 100644 index 0000000..569ef10 --- /dev/null +++ b/quicktap/src/stack/entry.rs @@ -0,0 +1,14 @@ +//! The ENTRY stage of the packet processing stack. Provides a `PacketEntryStage` which implements `PacketProcessingStage`. + +use std::error::Error; +use std::net::SocketAddr; +use crate::stack::{PacketProcessingStage}; + +/// The ENTRY stage of the packet processing stack +pub struct PacketEntryStage {} + +impl PacketProcessingStage for PacketEntryStage { + fn process_packet(pkt: Vec, from: &SocketAddr) -> Result<(), Box> { + Ok(()) + } +} \ No newline at end of file diff --git a/quicktap/src/stack/mod.rs b/quicktap/src/stack/mod.rs new file mode 100644 index 0000000..f7839f6 --- /dev/null +++ b/quicktap/src/stack/mod.rs @@ -0,0 +1,39 @@ +//! quicktap's packet processing stack +//! Packet processing is done in several *stages*: +//! - ENTRY +//! - CRYPT +//! - ROUTING +//! - PROCESSING +//! - EXIT +//! # UDP Packet Flow +//! For a packet received on the UDP socket, the following route is taken through the processing stack: +//! ```text +//! UDP Socket -> ENTRY -> CRYPT -> (drop) <-----+ +//! | | ^ | +//! | | | | +//! | +-----> ROUTING -> CRYPT -> EXIT +//! | v +//! +---> PROCESSING -> (end flow) +//! ``` +//! # TUN Packet Flow +//! For a packet received on the TUN interface, the following route is taken through the processing stack: +//! ```text +//! TUN Interface -> ENTRY -> ROUTING -> CRYPT -> EXIT +//! | | +//! v | +//! (drop) <----+ +//! ``` + +use std::error::Error; +use std::net::SocketAddr; + +pub mod entry; + +/// The methods and functions that need to be implemented by a stage in the packet processor +pub trait PacketProcessingStage { + /// Process a raw datagram. This should go down the WHOLE routing chain - assume previous steps have already done. + /// For example, if you are the CRYPT layer, and are processing a tun packet, assume that the ENTRY and ROUTING stack stages have already been completed and you are being given the result of that processing. + /// # Errors + /// This function will error if an error occurs + fn process_packet(pkt: Vec, from: &SocketAddr) -> Result<(), Box> where Self: Sized; +} \ No newline at end of file