docs(wireguard-broker): add docs and examples (#550)

This commit is contained in:
Paul Spooren
2024-12-19 09:51:51 +01:00
committed by GitHub
11 changed files with 800 additions and 8 deletions

View File

@@ -1,3 +1,41 @@
//! Client implementation for the WireGuard broker protocol.
//!
//! This module provides a client implementation that communicates with a WireGuard broker server
//! using a binary protocol. The client handles serialization and deserialization of messages,
//! error handling, and the core interaction flow.
//!
//! # Examples
//!
//! ```
//! use rosenpass_wireguard_broker::api::client::{BrokerClient, BrokerClientIo};
//! #[derive(Debug)]
//! struct MyIo;
//!
//! impl BrokerClientIo for MyIo {
//! type SendError = std::io::Error;
//! type RecvError = std::io::Error;
//!
//! fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError> {
//! // Implement sending logic
//! Ok(())
//! }
//!
//! fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError> {
//! // Implement receiving logic
//! Ok(None)
//! }
//! }
//!
//! // Create client with custom IO implementation
//! let mut client = BrokerClient::new(MyIo);
//! assert!(client.poll_response().unwrap().is_none());
//! ```
//!
//! # Protocol
//!
//! The client implements a simple request-response protocol for setting WireGuard pre-shared keys.
//! Messages are serialized using a binary format defined in the [`crate::api::msgs`] module.
use std::{borrow::BorrowMut, fmt::Debug}; use std::{borrow::BorrowMut, fmt::Debug};
use crate::{ use crate::{
@@ -13,10 +51,13 @@ use super::{
msgs::{Envelope, SetPskResponse}, msgs::{Envelope, SetPskResponse},
}; };
/// Error type for polling responses from the broker server.
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)] #[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerClientPollResponseError<RecvError> { pub enum BrokerClientPollResponseError<RecvError> {
/// An IO error occurred while receiving the response
#[error(transparent)] #[error(transparent)]
IoError(RecvError), IoError(RecvError),
/// The received message was invalid or malformed
#[error("Invalid message.")] #[error("Invalid message.")]
InvalidMessage, InvalidMessage,
} }
@@ -28,34 +69,52 @@ impl<RecvError> From<msgs::InvalidMessageTypeError> for BrokerClientPollResponse
} }
} }
/// Helper function that wraps a receive error into a `BrokerClientPollResponseError::IoError`
fn io_poller<RecvError>(e: RecvError) -> BrokerClientPollResponseError<RecvError> { fn io_poller<RecvError>(e: RecvError) -> BrokerClientPollResponseError<RecvError> {
BrokerClientPollResponseError::<RecvError>::IoError(e) BrokerClientPollResponseError::<RecvError>::IoError(e)
} }
/// Helper function that returns a `BrokerClientPollResponseError::InvalidMessage` error
fn invalid_msg_poller<RecvError>() -> BrokerClientPollResponseError<RecvError> { fn invalid_msg_poller<RecvError>() -> BrokerClientPollResponseError<RecvError> {
BrokerClientPollResponseError::<RecvError>::InvalidMessage BrokerClientPollResponseError::<RecvError>::InvalidMessage
} }
/// Error type for setting pre-shared keys through the broker client.
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)] #[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerClientSetPskError<SendError> { pub enum BrokerClientSetPskError<SendError> {
/// Error encoding or decoding the message
#[error("Error with encoding/decoding message")] #[error("Error with encoding/decoding message")]
MsgError, MsgError,
/// Error in the broker configuration
#[error("Network Broker Config error: {0}")] #[error("Network Broker Config error: {0}")]
BrokerError(NetworkBrokerConfigErr), BrokerError(NetworkBrokerConfigErr),
/// IO error while sending the request
#[error(transparent)] #[error(transparent)]
IoError(SendError), IoError(SendError),
/// Interface name exceeds maximum length
#[error("Interface name out of bounds")] #[error("Interface name out of bounds")]
IfaceOutOfBounds, IfaceOutOfBounds,
} }
/// Trait defining the IO operations required by the broker client.
///
/// Implementors must provide methods for sending and receiving binary messages.
pub trait BrokerClientIo { pub trait BrokerClientIo {
/// Error type returned by send operations
type SendError; type SendError;
/// Error type returned by receive operations
type RecvError; type RecvError;
/// Send a binary message
fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError>; fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError>;
/// Receive a binary message, returning None if no message is available
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError>; fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError>;
} }
/// Client for interacting with a WireGuard broker server.
///
/// The client handles the protocol-level communication with the server,
/// including message serialization and response handling.
#[derive(Debug)] #[derive(Debug)]
pub struct BrokerClient<Io> pub struct BrokerClient<Io>
where where
@@ -68,18 +127,37 @@ impl<Io> BrokerClient<Io>
where where
Io: BrokerClientIo + Debug, Io: BrokerClientIo + Debug,
{ {
/// Creates a new `BrokerClient` with the given IO implementation.
pub fn new(io: Io) -> Self { pub fn new(io: Io) -> Self {
Self { io } Self { io }
} }
/// Returns a reference to the underlying IO implementation.
pub fn io(&self) -> &Io { pub fn io(&self) -> &Io {
&self.io &self.io
} }
/// Returns a mutable reference to the underlying IO implementation.
pub fn io_mut(&mut self) -> &mut Io { pub fn io_mut(&mut self) -> &mut Io {
&mut self.io &mut self.io
} }
/// Polls for a response from the broker server.
///
/// This method attempts to receive and parse a SetPsk response message from the server.
/// If no message is available, returns `Ok(None)`. If a message is received, it is
/// parsed and validated before being returned as `Ok(Some(result))`.
///
/// # Returns
/// - `Ok(Some(result))` if a valid response was received
/// - `Ok(None)` if no message was available
/// - `Err(BrokerClientPollResponseError)` if an error occurred during receiving or parsing
///
/// # Errors
/// Returns an error if:
/// - An IO error occurs while receiving the message
/// - The received message is invalid or malformed
/// - The message type is incorrect
pub fn poll_response( pub fn poll_response(
&mut self, &mut self,
) -> Result<Option<msgs::SetPskResult>, BrokerClientPollResponseError<Io::RecvError>> { ) -> Result<Option<msgs::SetPskResult>, BrokerClientPollResponseError<Io::RecvError>> {
@@ -147,3 +225,112 @@ where
Ok(()) Ok(())
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use msgs::{MsgType, SetPskError, SetPskResponseReturnCode};
// Mock IO implementation for testing
#[derive(Debug)]
struct MockIo {
recv_data: Vec<u8>,
}
impl MockIo {
fn new() -> Self {
Self {
recv_data: Vec::new(),
}
}
fn set_recv_data(&mut self, data: Option<Vec<u8>>) {
self.recv_data = data.unwrap_or_default();
}
}
impl BrokerClientIo for MockIo {
type SendError = std::io::Error;
type RecvError = std::io::Error;
fn send_msg(&mut self, _buf: &[u8]) -> Result<(), Self::SendError> {
Ok(())
}
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError> {
if self.recv_data.is_empty() {
Ok(None)
} else {
Ok(Some(&self.recv_data))
}
}
}
fn create_response_msg(return_code: u8) -> Vec<u8> {
let mut msg = vec![
MsgType::SetPsk as u8, // msg_type
0,
0,
0, // reserved bytes
];
msg.push(return_code); // return_code
msg
}
#[test]
fn test_poll_response_no_message() {
let io = MockIo::new();
let mut client = BrokerClient::new(io);
assert_eq!(client.poll_response().unwrap(), None);
}
#[test]
fn test_poll_response_success() {
let mut io = MockIo::new();
io.set_recv_data(Some(create_response_msg(
SetPskResponseReturnCode::Success as u8,
)));
let mut client = BrokerClient::new(io);
assert_eq!(client.poll_response().unwrap(), Some(Ok(())));
}
#[test]
fn test_poll_response_no_such_peer() {
let mut io = MockIo::new();
io.set_recv_data(Some(create_response_msg(
SetPskResponseReturnCode::NoSuchPeer as u8,
)));
let mut client = BrokerClient::new(io);
assert_eq!(
client.poll_response().unwrap(),
Some(Err(SetPskError::NoSuchPeer))
);
}
#[test]
fn test_poll_response_invalid_message_type() {
let mut io = MockIo::new();
io.set_recv_data(Some(vec![0xFF, 0, 0, 0, 0])); // Invalid message type
let mut client = BrokerClient::new(io);
assert!(matches!(
client.poll_response(),
Err(BrokerClientPollResponseError::InvalidMessage)
));
}
#[test]
fn test_poll_response_invalid_return_code() {
let mut io = MockIo::new();
io.set_recv_data(Some(create_response_msg(0xFF))); // Invalid return code
let mut client = BrokerClient::new(io);
assert!(matches!(
client.poll_response(),
Err(BrokerClientPollResponseError::InvalidMessage)
));
}
}

View File

@@ -1,3 +1,7 @@
//! This module provides [NetworkBrokerConfig] for configuring a
//! [BrokerServer](crate::api::server::BrokerServer) and tooling to serialize and deserialize these
//! configurations.
use crate::{SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN}; use crate::{SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
use derive_builder::Builder; use derive_builder::Builder;
use rosenpass_secret_memory::{Public, Secret}; use rosenpass_secret_memory::{Public, Secret};
@@ -5,13 +9,19 @@ use rosenpass_secret_memory::{Public, Secret};
#[derive(Builder, Debug)] #[derive(Builder, Debug)]
#[builder(pattern = "mutable")] #[builder(pattern = "mutable")]
//TODO: Use generics for iface, add additional params //TODO: Use generics for iface, add additional params
/// Specifies a configuration for a [BrokerServer](crate::api::server::BrokerServer).
pub struct NetworkBrokerConfig<'a> { pub struct NetworkBrokerConfig<'a> {
/// The interface for the [BrokerServer](crate::api::server::BrokerServer).
pub iface: &'a str, pub iface: &'a str,
/// The peer identifier for the [BrokerServer](crate::api::server::BrokerServer).
pub peer_id: &'a Public<WG_PEER_LEN>, pub peer_id: &'a Public<WG_PEER_LEN>,
/// The pre-shared key for the [BrokerServer](crate::api::server::BrokerServer) and the
/// interface.
pub psk: &'a Secret<WG_KEY_LEN>, pub psk: &'a Secret<WG_KEY_LEN>,
} }
impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> { impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> {
/// Transforms a [NetworkBrokerConfig] into a [SerializedBrokerConfig] meant for serialization.
fn from(src: NetworkBrokerConfig<'a>) -> SerializedBrokerConfig<'a> { fn from(src: NetworkBrokerConfig<'a>) -> SerializedBrokerConfig<'a> {
Self { Self {
interface: src.iface.as_bytes(), interface: src.iface.as_bytes(),
@@ -22,15 +32,23 @@ impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> {
} }
} }
/// Error variants that can occur when loading a [NetworkBrokerConfig] from a
/// [SerializedBrokerConfig].
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)] #[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum NetworkBrokerConfigErr { pub enum NetworkBrokerConfigErr {
/// Error indicating that the interface specification could not be read correctly.
#[error("Interface")] #[error("Interface")]
Interface, Interface, // TODO, give this a better name.
} }
impl<'a> TryFrom<SerializedBrokerConfig<'a>> for NetworkBrokerConfig<'a> { impl<'a> TryFrom<SerializedBrokerConfig<'a>> for NetworkBrokerConfig<'a> {
type Error = NetworkBrokerConfigErr; type Error = NetworkBrokerConfigErr;
/// Tries to load a [NetworkBrokerConfig] from a [SerializedBrokerConfig].
///
/// # Errors
/// Returns a [NetworkBrokerConfigErr::Interface]-error when the interface description
/// can not be parsed correctly.
fn try_from(value: SerializedBrokerConfig<'a>) -> Result<Self, Self::Error> { fn try_from(value: SerializedBrokerConfig<'a>) -> Result<Self, Self::Error> {
let iface = let iface =
std::str::from_utf8(value.interface).map_err(|_| NetworkBrokerConfigErr::Interface)?; std::str::from_utf8(value.interface).map_err(|_| NetworkBrokerConfigErr::Interface)?;

View File

@@ -1,3 +1,9 @@
//! This module implements the binary WireGuard broker protocol in the form of the [client::BrokerClient]
//! and the [server::BrokerServer].
//!
//! Specifically, The protocol enables the client to tell the server to set a pre-shared key for a
//! wireguard interface.
pub mod client; pub mod client;
pub mod config; pub mod config;
pub mod msgs; pub mod msgs;

View File

@@ -1,11 +1,19 @@
//! This module defines message formats for messages in the Wireguard Broker protocol as well as
//! helper structures like errors and conversion functions.
use std::str::{from_utf8, Utf8Error}; use std::str::{from_utf8, Utf8Error};
use zerocopy::{AsBytes, FromBytes, FromZeroes}; use zerocopy::{AsBytes, FromBytes, FromZeroes};
/// The number of bytes reserved for overhead when packaging data.
pub const ENVELOPE_OVERHEAD: usize = 1 + 3; pub const ENVELOPE_OVERHEAD: usize = 1 + 3;
/// The buffer size for request messages.
pub const REQUEST_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 32 + 32 + 1 + 255; pub const REQUEST_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 32 + 32 + 1 + 255;
/// The buffer size for responses.
pub const RESPONSE_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 1; pub const RESPONSE_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 1;
/// Envelope for messages being passed around.
#[repr(packed)] #[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)] #[derive(AsBytes, FromBytes, FromZeroes)]
pub struct Envelope<M: AsBytes + FromBytes> { pub struct Envelope<M: AsBytes + FromBytes> {
@@ -17,25 +25,43 @@ pub struct Envelope<M: AsBytes + FromBytes> {
pub payload: M, pub payload: M,
} }
/// Message format for requests to set a pre-shared key.
/// # Example
///
#[repr(packed)] #[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)] #[derive(AsBytes, FromBytes, FromZeroes)]
pub struct SetPskRequest { pub struct SetPskRequest {
/// The pre-shared key.
pub psk: [u8; 32], pub psk: [u8; 32],
/// The identifier of the peer.
pub peer_id: [u8; 32], pub peer_id: [u8; 32],
/// The size for the interface
pub iface_size: u8, // TODO: We should have variable length strings in lenses pub iface_size: u8, // TODO: We should have variable length strings in lenses
/// The buffer for the interface.
pub iface_buf: [u8; 255], pub iface_buf: [u8; 255],
} }
impl SetPskRequest { impl SetPskRequest {
/// Gets the interface specification as byte slice.
pub fn iface_bin(&self) -> &[u8] { pub fn iface_bin(&self) -> &[u8] {
let len = self.iface_size as usize; let len = self.iface_size as usize;
&self.iface_buf[..len] &self.iface_buf[..len]
} }
/// Gets the interface specification as a `&str`.
///
/// # Errors
/// Returns a [Utf8Error] if the interface specification isn't utf8 encoded.
pub fn iface(&self) -> Result<&str, Utf8Error> { pub fn iface(&self) -> Result<&str, Utf8Error> {
from_utf8(self.iface_bin()) from_utf8(self.iface_bin())
} }
/// Sets the interface specification to `iface`. No check is made whether `iface` is correctly
/// encoded as utf8.
///
/// # Result
/// Returns [None] if `iface` is longer than 255 bytes. Otherwise, it returns
/// [Some(())](Some).
pub fn set_iface_bin(&mut self, iface: &[u8]) -> Option<()> { pub fn set_iface_bin(&mut self, iface: &[u8]) -> Option<()> {
(iface.len() < 256).then_some(())?; // Assert iface.len() < 256 (iface.len() < 256).then_some(())?; // Assert iface.len() < 256
@@ -47,17 +73,24 @@ impl SetPskRequest {
Some(()) Some(())
} }
/// Sets the interface specification to `iface`.
///
/// # Result
/// Returns [None] if `iface` is longer than 255 bytes. Otherwise, it returns
/// [Some(())](Some).
pub fn set_iface(&mut self, iface: &str) -> Option<()> { pub fn set_iface(&mut self, iface: &str) -> Option<()> {
self.set_iface_bin(iface.as_bytes()) self.set_iface_bin(iface.as_bytes())
} }
} }
/// Message format for response to the set pre-shared key operation.
#[repr(packed)] #[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)] #[derive(AsBytes, FromBytes, FromZeroes)]
pub struct SetPskResponse { pub struct SetPskResponse {
pub return_code: u8, pub return_code: u8,
} }
/// Error type for the errors that can occur when setting a pre-shared key.
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)] #[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum SetPskError { pub enum SetPskError {
#[error("The wireguard pre-shared-key assignment broker experienced an internal error.")] #[error("The wireguard pre-shared-key assignment broker experienced an internal error.")]
@@ -70,6 +103,12 @@ pub enum SetPskError {
pub type SetPskResult = Result<(), SetPskError>; pub type SetPskResult = Result<(), SetPskError>;
/// The return codes and their meanings for the set psk response operation.
///
/// [SetPskResponseReturnCode] is represented by by a single `u8` as required by the protocol.
///
/// # Example
/// See [SetPskResponseReturnCode::try_from] for an example.
#[repr(u8)] #[repr(u8)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum SetPskResponseReturnCode { pub enum SetPskResponseReturnCode {
@@ -85,6 +124,19 @@ pub struct InvalidSetPskResponseError;
impl TryFrom<u8> for SetPskResponseReturnCode { impl TryFrom<u8> for SetPskResponseReturnCode {
type Error = InvalidSetPskResponseError; type Error = InvalidSetPskResponseError;
/// Parse a [u8] as a [MsgType].
///
/// # Example
/// ```
/// # use rosenpass_wireguard_broker::api::msgs::{InvalidSetPskResponseError, SetPskResponseReturnCode};
/// let return_code: u8 = 0x00; // Usually specifically set or comes out of a message.
/// let res = SetPskResponseReturnCode::try_from(return_code);
/// assert!(res.is_ok());
/// assert_eq!(res.unwrap(), SetPskResponseReturnCode::Success);
/// # Ok::<(), InvalidSetPskResponseError>(())
/// ```
/// # Errors
/// Returns a [InvalidSetPskResponseError] if `value` does not correspond to a known return code.
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
use SetPskResponseReturnCode::*; use SetPskResponseReturnCode::*;
match value { match value {
@@ -98,6 +150,9 @@ impl TryFrom<u8> for SetPskResponseReturnCode {
} }
impl From<SetPskResponseReturnCode> for SetPskResult { impl From<SetPskResponseReturnCode> for SetPskResult {
/// A [SetPskResult] can directly be deduced from a [SetPskResponseReturnCode].
/// An [Ok] type is only returned if `value` is [SetPskResponseReturnCode::Success].
/// Otherwise, an appropriate variant of [SetPskError] will be returned.
fn from(value: SetPskResponseReturnCode) -> Self { fn from(value: SetPskResponseReturnCode) -> Self {
use SetPskError as E; use SetPskError as E;
use SetPskResponseReturnCode as C; use SetPskResponseReturnCode as C;
@@ -111,6 +166,7 @@ impl From<SetPskResponseReturnCode> for SetPskResult {
} }
impl From<SetPskResult> for SetPskResponseReturnCode { impl From<SetPskResult> for SetPskResponseReturnCode {
/// A [SetPskResponseReturnCode] can directly be deduced from a [SetPskResult].
fn from(value: SetPskResult) -> Self { fn from(value: SetPskResult) -> Self {
use SetPskError as E; use SetPskError as E;
use SetPskResponseReturnCode as C; use SetPskResponseReturnCode as C;
@@ -123,18 +179,45 @@ impl From<SetPskResult> for SetPskResponseReturnCode {
} }
} }
/// The types of messages supported by this crate. At the time of writing, this is only
/// the message to set a pre-shared key.
///
/// [MsgType] is represented by a single `u8` as required by the protocol.
///
/// # Example
/// It is usually used like this:
/// ```
/// # use rosenpass_wireguard_broker::api::msgs::{InvalidMessageTypeError, MsgType};
/// let typ: u8 = 0x01; // Usually specifically set or comes out of a message.
/// let typ = MsgType::try_from(typ)?;
/// let MsgType::SetPsk = typ; // Assert type.
/// # Ok::<(), InvalidMessageTypeError>(())
/// ```
#[repr(u8)] #[repr(u8)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum MsgType { pub enum MsgType {
SetPsk = 0x01, SetPsk = 0x01,
} }
/// Error indicating that an invalid [MsgType] was used.
/// This error is returned by [MsgType::try_from].
#[derive(Eq, PartialEq, Debug, Clone)] #[derive(Eq, PartialEq, Debug, Clone)]
pub struct InvalidMessageTypeError; pub struct InvalidMessageTypeError;
impl TryFrom<u8> for MsgType { impl TryFrom<u8> for MsgType {
type Error = InvalidMessageTypeError; type Error = InvalidMessageTypeError;
/// Parse a [u8] as a [MsgType].
///
/// # Example
/// ```rust
/// use rosenpass_wireguard_broker::api::msgs::MsgType;
/// let msg_type = MsgType::try_from(0x01);
/// assert!(msg_type.is_ok());
/// assert_eq!(msg_type.unwrap(), MsgType::SetPsk);
/// ```
/// # Errors
/// Returns an [InvalidMessageTypeError] if `value` does not correspond to a valid [MsgType].
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
match value { match value {
0x01 => Ok(MsgType::SetPsk), 0x01 => Ok(MsgType::SetPsk),

View File

@@ -1,3 +1,10 @@
//! Server implementation for the WireGuard broker protocol.
//!
//! This module provides a server implementation that communicates with WireGuard broker clients
//! using a binary protocol. The server handles serialization and deserialization of messages,
//! error handling, and the core interaction flow.
//! Specifically, it handles requests to set a pre-shared key for a wireguard interface.
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
use rosenpass_secret_memory::{Public, Secret}; use rosenpass_secret_memory::{Public, Secret};
@@ -7,12 +14,16 @@ use crate::WireGuardBroker;
use super::config::{NetworkBrokerConfigBuilder, NetworkBrokerConfigErr}; use super::config::{NetworkBrokerConfigBuilder, NetworkBrokerConfigErr};
/// Error variants for the [BrokerServer].
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)] #[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerServerError { pub enum BrokerServerError {
/// Indicates that an unknown request type was encountered.
#[error("No such request type: {}", .0)] #[error("No such request type: {}", .0)]
NoSuchRequestType(u8), NoSuchRequestType(u8),
/// Indicates that an invalid message was sent.
#[error("Invalid message received.")] #[error("Invalid message received.")]
InvalidMessage, InvalidMessage,
/// Indicates an error when configuration the network broker.
#[error("Network Broker Config error: {0}")] #[error("Network Broker Config error: {0}")]
BrokerError(NetworkBrokerConfigErr), BrokerError(NetworkBrokerConfigErr),
} }
@@ -24,11 +35,18 @@ impl From<msgs::InvalidMessageTypeError> for BrokerServerError {
} }
} }
/// The broker server. It requires an inner [WireGuardBroker] and an error type such
/// that the [msgs::SetPskError] implements [From] for the error type.
/// # Type Parameters
/// - `Err`: The used error type. Must be chosen such that [msgs::SetPskError] implements
/// [`From<Err>`](From)
///- `Inner`: A [WireGuardBroker]-type parametrized with `Err`.
pub struct BrokerServer<Err, Inner> pub struct BrokerServer<Err, Inner>
where where
Inner: WireGuardBroker<Error = Err>, Inner: WireGuardBroker<Error = Err>,
msgs::SetPskError: From<Err>, msgs::SetPskError: From<Err>,
{ {
/// The inner [WireGuardBroker].
inner: Inner, inner: Inner,
} }
@@ -38,10 +56,17 @@ where
msgs::SetPskError: From<Err>, msgs::SetPskError: From<Err>,
Err: std::fmt::Debug, Err: std::fmt::Debug,
{ {
/// Creates a new [BrokerServer] from a [WireGuardBroker].
pub fn new(inner: Inner) -> Self { pub fn new(inner: Inner) -> Self {
Self { inner } Self { inner }
} }
/// Processes a message (at the moment only setting the pre-shared key is supported)
/// and takes the appropriate actions.
///
/// # Errors
/// - [BrokerServerError::InvalidMessage] if the message is not properly formatted or refers to
/// an unsupported message type.
pub fn handle_message( pub fn handle_message(
&mut self, &mut self,
req: &[u8], req: &[u8],
@@ -53,22 +78,28 @@ where
let typ = msgs::MsgType::try_from(*typ)?; let typ = msgs::MsgType::try_from(*typ)?;
let msgs::MsgType::SetPsk = typ; // Assert type let msgs::MsgType::SetPsk = typ; // Assert type
let req = zerocopy::Ref::<&[u8], Envelope<SetPskRequest>>::new(req) let req =
.ok_or(BrokerServerError::InvalidMessage)?; zerocopy::Ref::<&[u8], Envelope<SetPskRequest>>::new(req).ok_or(InvalidMessage)?;
let mut res = zerocopy::Ref::<&mut [u8], Envelope<SetPskResponse>>::new(res) let mut res =
.ok_or(BrokerServerError::InvalidMessage)?; zerocopy::Ref::<&mut [u8], Envelope<SetPskResponse>>::new(res).ok_or(InvalidMessage)?;
res.msg_type = msgs::MsgType::SetPsk as u8; res.msg_type = msgs::MsgType::SetPsk as u8;
self.handle_set_psk(&req.payload, &mut res.payload)?; self.handle_set_psk(&req.payload, &mut res.payload)?;
Ok(res.bytes().len()) Ok(res.bytes().len())
} }
/// Sets the pre-shared key for the interface identified in `req` to the pre-shared key
/// specified in `req`.
///
/// # Errors
/// - [InvalidMessage](BrokerServerError::InvalidMessage) if the `iface` specified in `req` is
/// longer than 255 bytes or not correctly encoded in utf8.
fn handle_set_psk( fn handle_set_psk(
&mut self, &mut self,
req: &SetPskRequest, req: &SetPskRequest,
res: &mut SetPskResponse, res: &mut SetPskResponse,
) -> Result<(), BrokerServerError> { ) -> Result<(), BrokerServerError> {
// Using unwrap here since lenses can not return fixed-size arrays // Using unwrap here since lenses can not return fixed-size arrays.
// TODO: Slices should give access to fixed size arrays // TODO: Slices should give access to fixed size arrays
let peer_id = Public::from_slice(&req.peer_id); let peer_id = Public::from_slice(&req.peer_id);
let psk = Secret::from_slice(&req.psk); let psk = Secret::from_slice(&req.psk);
@@ -95,3 +126,60 @@ where
Ok(()) Ok(())
} }
} }
// We can only include this test if this feature is enabled, because otherwise
// brokers::netlink::SetPskError is not defined.
#[cfg(all(feature = "experiment_api", target_os = "linux"))]
#[cfg(test)]
mod tests {
use crate::api::msgs;
use crate::api::msgs::{Envelope, SetPskRequest};
use crate::api::server::BrokerServer;
use crate::brokers::netlink::SetPskError;
use crate::{SerializedBrokerConfig, WireGuardBroker};
use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
use zerocopy::AsBytes;
#[derive(Debug, Clone)]
struct MockWireGuardBroker {
psk: Secret<32>,
}
impl WireGuardBroker for MockWireGuardBroker {
type Error = SetPskError;
fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> Result<(), Self::Error> {
self.psk = config.psk.clone();
Ok(())
}
}
#[test]
fn test_broker_server() {
secret_policy_use_only_malloc_secrets();
let mock_broker = MockWireGuardBroker {
psk: Secret::zero(),
};
let mut broker_server = BrokerServer::new(mock_broker);
let mut iface_buf: [u8; 255] = [0; 255];
// These are the utf encoded bytes of the string "wg0".
iface_buf[0] = 119;
iface_buf[1] = 103;
iface_buf[2] = 48;
let psk_req = SetPskRequest {
psk: [0; 32],
peer_id: [0; 32],
iface_size: 3,
iface_buf,
};
let req: Envelope<SetPskRequest> = Envelope {
msg_type: 0x01, // The only valid value.
reserved: [0, 0, 0],
payload: psk_req,
};
let mut res: [u8; msgs::RESPONSE_MSG_BUFFER_SIZE] = [0; msgs::RESPONSE_MSG_BUFFER_SIZE];
broker_server
.handle_message(req.as_bytes(), &mut res)
.unwrap();
}
}

View File

@@ -1,3 +1,12 @@
//! This module defines the Unix socket broker that interacts with the Linux-specific
//! WireGuard broker through a privileged process.
//!
//! It manages communication using length-prefixed
//! messages that are read from standard-input.
//! On each input message the process responds through its standard-output
//!
//! The functionality is only supported on Linux systems.
fn main() { fn main() {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
linux::main().unwrap(); linux::main().unwrap();
@@ -8,20 +17,33 @@ fn main() {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub mod linux { pub mod linux {
//! Linux-specific implementation for the broker that communicates with the WireGuard broker.
use std::io::{stdin, stdout, Read, Write}; use std::io::{stdin, stdout, Read, Write};
use rosenpass_wireguard_broker::api::msgs; use rosenpass_wireguard_broker::api::msgs;
use rosenpass_wireguard_broker::api::server::BrokerServer; use rosenpass_wireguard_broker::api::server::BrokerServer;
use rosenpass_wireguard_broker::brokers::netlink as wg; use rosenpass_wireguard_broker::brokers::netlink as wg;
/// Represents errors that can occur during WireGuard broker operations
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum BrokerAppError { pub enum BrokerAppError {
/// Wraps standard I/O errors that may occur during broker operations
#[error(transparent)] #[error(transparent)]
IoError(#[from] std::io::Error), IoError(#[from] std::io::Error),
/// Wraps WireGuard connection errors
#[error(transparent)] #[error(transparent)]
WgConnectError(#[from] wg::ConnectError), WgConnectError(#[from] wg::ConnectError),
/// Wraps errors that occur when setting WireGuard Pre-Shared Keys (PSK)
#[error(transparent)] #[error(transparent)]
WgSetPskError(#[from] wg::SetPskError), WgSetPskError(#[from] wg::SetPskError),
/// Indicates that a received message exceeds the maximum allowed size
///
/// # Arguments
/// * `u64` - The size of the oversized message in bytes
#[error("Oversized message {}; something about the request is fatally wrong", .0)] #[error("Oversized message {}; something about the request is fatally wrong", .0)]
OversizedMessage(u64), OversizedMessage(u64),
} }

View File

@@ -1,3 +1,6 @@
//! Provides an asynchronous Unix socket handler for managing connections between clients
//! and privileged WireGuard broker processes.
use std::process::Stdio; use std::process::Stdio;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
@@ -12,6 +15,7 @@ use clap::{ArgGroup, Parser};
use rosenpass_util::fd::claim_fd; use rosenpass_util::fd::claim_fd;
use rosenpass_wireguard_broker::api::msgs; use rosenpass_wireguard_broker::api::msgs;
/// Command-line arguments for configuring the socket handler
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
#[clap(group( #[clap(group(
@@ -45,11 +49,13 @@ struct Args {
command: Vec<String>, command: Vec<String>,
} }
/// Represents a request to the broker with a channel for receiving the response
struct BrokerRequest { struct BrokerRequest {
reply_to: oneshot::Sender<BrokerResponse>, reply_to: oneshot::Sender<BrokerResponse>,
request: Vec<u8>, request: Vec<u8>,
} }
/// Contains the broker's response data
struct BrokerResponse { struct BrokerResponse {
response: Vec<u8>, response: Vec<u8>,
} }
@@ -87,6 +93,7 @@ async fn main() -> Result<()> {
} }
} }
/// Manages communication with the privileged broker process
async fn direct_broker_process( async fn direct_broker_process(
mut queue: mpsc::Receiver<BrokerRequest>, mut queue: mpsc::Receiver<BrokerRequest>,
cmd: Vec<String>, cmd: Vec<String>,
@@ -131,6 +138,7 @@ async fn direct_broker_process(
} }
} }
/// Accepts and handles incoming client connections
async fn listen_for_clients(queue: mpsc::Sender<BrokerRequest>, sock: UnixListener) -> Result<()> { async fn listen_for_clients(queue: mpsc::Sender<BrokerRequest>, sock: UnixListener) -> Result<()> {
loop { loop {
let (stream, _addr) = sock.accept().await?; let (stream, _addr) = sock.accept().await?;
@@ -145,6 +153,7 @@ async fn listen_for_clients(queue: mpsc::Sender<BrokerRequest>, sock: UnixListen
// NOTE: If loop can ever terminate we need to join the spawned tasks // NOTE: If loop can ever terminate we need to join the spawned tasks
} }
/// Handles individual client connections and message processing
async fn on_accept(queue: mpsc::Sender<BrokerRequest>, mut stream: UnixStream) -> Result<()> { async fn on_accept(queue: mpsc::Sender<BrokerRequest>, mut stream: UnixStream) -> Result<()> {
let mut req_buf = Vec::new(); let mut req_buf = Vec::new();

View File

@@ -1,3 +1,52 @@
//! Asynchronous WireGuard PSK broker client using mio for non-blocking I/O.
//!
//! This module provides a client implementation that communicates with a WireGuard broker
//! through Unix domain sockets using non-blocking I/O operations. It's designed to be used
//! in event-driven applications using the mio event framework.
//!
//! # Examples
//!
//! ```no_run
//! # use mio::net::UnixStream;
//! # use rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient;
//! # use rosenpass_wireguard_broker::{WireGuardBroker, WireguardBrokerMio};
//! # use mio::{Events, Interest, Poll, Token};
//! # use rosenpass_secret_memory::{Public, Secret};
//! # use rosenpass_wireguard_broker::api::config::NetworkBrokerConfig;
//! # use rosenpass_wireguard_broker::SerializedBrokerConfig;
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let socket = UnixStream::connect("/path/to/broker.sock")?;
//! let mut client = MioBrokerClient::new(socket);
//!
//! // Set up mio polling
//! let mut poll = Poll::new()?;
//! let mut events = Events::with_capacity(128);
//! client.register(&poll.registry(), Token(0))?;
//!
//! // Prepare PSK configuration
//! let network_config = NetworkBrokerConfig {
//! iface: "wg0",
//! peer_id: &Public::zero(), // Replace with actual peer ID
//! psk: &Secret::zero(), // Replace with actual PSK
//! };
//!
//! // Convert to serialized format and send
//! let config: SerializedBrokerConfig = network_config.into();
//! client.set_psk(config)?;
//!
//! // Process responses in event loop
//! loop {
//! poll.poll(&mut events, None)?;
//! for event in &events {
//! if event.token() == Token(0) {
//! client.process_poll()?;
//! }
//! }
//! }
//! # Ok(())
//! # }
//! ```
use anyhow::{bail, Context}; use anyhow::{bail, Context};
use mio::Interest; use mio::Interest;
use rosenpass_secret_memory::Secret; use rosenpass_secret_memory::Secret;
@@ -13,12 +62,44 @@ use crate::api::client::{
}; };
use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerMio}; use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerMio};
/// WireGuard broker client using mio for non-blocking I/O operations.
///
/// This client communicates with a WireGuard broker through a Unix domain socket,
/// using length-prefixed messages for communication. It supports both the basic
/// `WireGuardBroker` operations and non-blocking I/O through the
/// `WireguardBrokerMio` trait.
///
/// # Examples
///
/// ```no_run
/// use mio::net::UnixStream;
/// use rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient;
/// use rosenpass_wireguard_broker::{WireGuardBroker, SerializedBrokerConfig};
/// use rosenpass_secret_memory::{Public, Secret};
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let socket = UnixStream::connect("/path/to/broker.sock")?;
/// let mut client = MioBrokerClient::new(socket);
///
/// // Set a PSK
/// let config = SerializedBrokerConfig {
/// interface: "wg0".as_bytes(),
/// peer_id: &Public::zero(), // Replace with actual peer ID
/// psk: &Secret::zero(), // Replace with actual PSK
/// additional_params: &[],
/// };
///
/// client.set_psk(config)?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)] #[derive(Debug)]
pub struct MioBrokerClient { pub struct MioBrokerClient {
inner: BrokerClient<MioBrokerClientIo>, inner: BrokerClient<MioBrokerClientIo>,
mio_token: Option<mio::Token>, mio_token: Option<mio::Token>,
} }
/// A buffer wrapper that provides secure memory for sensitive data.
#[derive(Debug)] #[derive(Debug)]
struct SecretBuffer<const N: usize>(pub Secret<N>); struct SecretBuffer<const N: usize>(pub Secret<N>);
@@ -43,6 +124,10 @@ impl<const N: usize> BorrowMut<[u8]> for SecretBuffer<N> {
type ReadBuffer = LengthPrefixDecoder<SecretBuffer<4096>>; type ReadBuffer = LengthPrefixDecoder<SecretBuffer<4096>>;
type WriteBuffer = LengthPrefixEncoder<SecretBuffer<4096>>; type WriteBuffer = LengthPrefixEncoder<SecretBuffer<4096>>;
/// I/O implementation for the broker client using non-blocking operations.
///
/// This type handles the low-level details of sending and receiving length-prefixed
/// messages over a Unix domain socket.
#[derive(Debug)] #[derive(Debug)]
struct MioBrokerClientIo { struct MioBrokerClientIo {
socket: mio::net::UnixStream, socket: mio::net::UnixStream,
@@ -51,6 +136,10 @@ struct MioBrokerClientIo {
} }
impl MioBrokerClient { impl MioBrokerClient {
/// Creates a new client from a Unix domain socket.
///
/// The socket should be connected to a WireGuard broker server that speaks
/// the same protocol.
pub fn new(socket: mio::net::UnixStream) -> Self { pub fn new(socket: mio::net::UnixStream) -> Self {
let read_buffer = LengthPrefixDecoder::new(SecretBuffer::new()); let read_buffer = LengthPrefixDecoder::new(SecretBuffer::new());
let write_buffer = LengthPrefixEncoder::from_buffer(SecretBuffer::new()); let write_buffer = LengthPrefixEncoder::from_buffer(SecretBuffer::new());
@@ -66,6 +155,10 @@ impl MioBrokerClient {
} }
} }
/// Polls for and processes any pending responses from the broker.
///
/// This method should be called when the socket becomes readable according
/// to mio events.
fn poll(&mut self) -> anyhow::Result<()> { fn poll(&mut self) -> anyhow::Result<()> {
self.inner.io_mut().flush()?; self.inner.io_mut().flush()?;

View File

@@ -1,3 +1,35 @@
//! Native Unix implementation of the WireGuard PSK broker using the `wg` command-line tool.
//!
//! This module provides an implementation that works on Unix systems by executing the `wg`
//! command-line tool to set pre-shared keys. It requires the `wg` tool to be installed and
//! accessible in the system PATH.
//!
//! # Examples
//!
//! ```no_run
//! use rosenpass_secret_memory::{Public, Secret};
//! use rosenpass_wireguard_broker::brokers::native_unix::{NativeUnixBroker, NativeUnixBrokerConfigBase};
//! use rosenpass_wireguard_broker::{WireGuardBroker, WireguardBrokerCfg, WG_KEY_LEN, WG_PEER_LEN};
//!
//! # fn main() -> Result<(), anyhow::Error> {
//! // Create a broker instance
//! let mut broker = NativeUnixBroker::new();
//!
//! // Create configuration
//! let config = NativeUnixBrokerConfigBase {
//! interface: "wg0".to_string(),
//! peer_id: Public::zero(), // Replace with actual peer ID
//! extra_params: Vec::new(),
//! };
//!
//! // Set PSK using the broker
//! let psk = Secret::<WG_KEY_LEN>::zero(); // Replace with actual PSK
//! let serialized_config = config.create_config(&psk);
//! broker.set_psk(serialized_config)?;
//! # Ok(())
//! # }
//! ```
use std::fmt::Debug; use std::fmt::Debug;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::thread; use std::thread;
@@ -12,9 +44,21 @@ use rosenpass_util::{b64::B64Display, file::StoreValueB64Writer};
use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerCfg, WireguardBrokerMio}; use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerCfg, WireguardBrokerMio};
use crate::{WG_KEY_LEN, WG_PEER_LEN}; use crate::{WG_KEY_LEN, WG_PEER_LEN};
/// Maximum size of a base64-encoded WireGuard key in bytes
const MAX_B64_KEY_SIZE: usize = WG_KEY_LEN * 5 / 3; const MAX_B64_KEY_SIZE: usize = WG_KEY_LEN * 5 / 3;
/// Maximum size of a base64-encoded WireGuard peer ID in bytes
const MAX_B64_PEER_ID_SIZE: usize = WG_PEER_LEN * 5 / 3; const MAX_B64_PEER_ID_SIZE: usize = WG_PEER_LEN * 5 / 3;
/// A WireGuard broker implementation that uses the native `wg` command-line tool.
///
/// This broker executes the `wg` command to set pre-shared keys. It supports both synchronous
/// operations through the `WireGuardBroker` trait and asynchronous operations through the
/// `WireguardBrokerMio` trait.
///
/// # Requirements
///
/// - The `wg` command-line tool must be installed and in the system PATH
/// - The user running the broker must have sufficient permissions to execute `wg` commands
#[derive(Debug)] #[derive(Debug)]
pub struct NativeUnixBroker { pub struct NativeUnixBroker {
mio_token: Option<mio::Token>, mio_token: Option<mio::Token>,
@@ -110,16 +154,63 @@ impl WireguardBrokerMio for NativeUnixBroker {
} }
} }
/// Base configuration for the native Unix WireGuard broker.
///
/// This configuration type is used to store persistent broker settings and create
/// serialized configurations for individual PSK operations.
///
/// # Examples
///
/// ```
/// use rosenpass_wireguard_broker::brokers::native_unix::NativeUnixBrokerConfigBase;
/// use rosenpass_secret_memory::Public;
/// use rosenpass_wireguard_broker::WG_PEER_LEN;
///
/// let config = NativeUnixBrokerConfigBase {
/// interface: "wg0".to_string(),
/// peer_id: Public::zero(),
/// extra_params: Vec::new(),
/// };
/// ```
#[derive(Debug, Builder)] #[derive(Debug, Builder)]
#[builder(pattern = "mutable")] #[builder(pattern = "mutable")]
pub struct NativeUnixBrokerConfigBase { pub struct NativeUnixBrokerConfigBase {
/// Name of the WireGuard interface (e.g., "wg0")
pub interface: String, pub interface: String,
/// Public key of the peer
pub peer_id: Public<WG_PEER_LEN>, pub peer_id: Public<WG_PEER_LEN>,
/// Additional parameters to pass to the wg command
#[builder(private)] #[builder(private)]
pub extra_params: Vec<u8>, pub extra_params: Vec<u8>,
} }
impl NativeUnixBrokerConfigBaseBuilder { impl NativeUnixBrokerConfigBaseBuilder {
/// Sets the peer ID from a base64-encoded string.
///
/// # Arguments
///
/// * `peer_id` - Base64-encoded peer public key
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use rosenpass_wireguard_broker::brokers::native_unix::{NativeUnixBrokerConfigBaseBuilder};
/// let mut peer_cfg = NativeUnixBrokerConfigBaseBuilder::default();
/// // set peer id to [48;32] encoded as base64
/// peer_cfg.peer_id_b64("MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=")?;
/// peer_cfg.interface("wg0".to_string());
/// peer_cfg.extra_params_ser(&vec![])?;
/// let peer_cfg = peer_cfg.build()?;
/// assert_eq!(peer_cfg.peer_id.value, [48u8;32]);
///
/// let error = NativeUnixBrokerConfigBaseBuilder::default()
/// .peer_id_b64("MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA") // invalid base64 encoding
/// .err().unwrap();
/// assert_eq!(error.to_string(), "Failed to parse peer id b64");
/// # Ok(())
/// # }
/// ```
pub fn peer_id_b64( pub fn peer_id_b64(
&mut self, &mut self,
peer_id: &str, peer_id: &str,
@@ -133,6 +224,29 @@ impl NativeUnixBrokerConfigBaseBuilder {
Ok(self.peer_id(peer_id_b64)) Ok(self.peer_id(peer_id_b64))
} }
/// Sets additional parameters for the wg command.
///
/// Note: This function cannot fail as `Vec<String>` is always serializable.
///
/// # Examples
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use rosenpass_wireguard_broker::brokers::native_unix::NativeUnixBrokerConfigBaseBuilder;
///
/// let mut peer_cfg = NativeUnixBrokerConfigBaseBuilder::default();
/// // Set typical wireguard parameters
/// peer_cfg.interface("wg0".to_string());
/// peer_cfg.peer_id_b64("Zm9v")?;
/// peer_cfg.extra_params_ser(&vec![
/// "persistent-keepalive".to_string(),
/// "25".to_string(),
/// "allowed-ips".to_string(),
/// "10.0.0.2/32".to_string(),
/// ])?;
/// let peer_cfg = peer_cfg.build()?;
/// # Ok(())
/// # }
/// ```
pub fn extra_params_ser( pub fn extra_params_ser(
&mut self, &mut self,
extra_params: &Vec<String>, extra_params: &Vec<String>,
@@ -157,12 +271,17 @@ impl WireguardBrokerCfg for NativeUnixBrokerConfigBase {
} }
} }
/// Runtime configuration for a single PSK operation.
#[derive(Debug, Builder)] #[derive(Debug, Builder)]
#[builder(pattern = "mutable")] #[builder(pattern = "mutable")]
pub struct NativeUnixBrokerConfig<'a> { pub struct NativeUnixBrokerConfig<'a> {
/// WireGuard interface name
pub interface: &'a str, pub interface: &'a str,
/// Public key of the peer
pub peer_id: &'a Public<WG_PEER_LEN>, pub peer_id: &'a Public<WG_PEER_LEN>,
/// Pre-shared key to set
pub psk: &'a Secret<WG_KEY_LEN>, pub psk: &'a Secret<WG_KEY_LEN>,
/// Additional wg command parameters
pub extra_params: Vec<String>, pub extra_params: Vec<String>,
} }

View File

@@ -1,4 +1,31 @@
#![cfg(target_os = "linux")] #![cfg(target_os = "linux")]
//! Linux-specific WireGuard PSK broker implementation using netlink.
//!
//! This module provides direct kernel communication through netlink sockets for managing
//! WireGuard pre-shared keys. It's more efficient than the command-line implementation
//! but only available on Linux systems.
//!
//! # Examples
//!
//! ```no_run
//! use rosenpass_secret_memory::{Public, Secret};
//! use rosenpass_wireguard_broker::{WireGuardBroker, SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
//! use rosenpass_wireguard_broker::brokers::netlink::NetlinkWireGuardBroker;
//! # use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
//! # secret_policy_use_only_malloc_secrets();
//!
//! let mut broker = NetlinkWireGuardBroker::new()?;
//!
//! let config = SerializedBrokerConfig {
//! interface: "wg0".as_bytes(),
//! peer_id: &Public::zero(), // Replace with actual peer ID
//! psk: &Secret::zero(), // Replace with actual PSK
//! additional_params: &[],
//! };
//!
//! broker.set_psk(config)?;
//! # Ok::<(), anyhow::Error>(())
//! ```
use std::fmt::Debug; use std::fmt::Debug;
@@ -8,12 +35,14 @@ use crate::api::config::NetworkBrokerConfig;
use crate::api::msgs; use crate::api::msgs;
use crate::{SerializedBrokerConfig, WireGuardBroker}; use crate::{SerializedBrokerConfig, WireGuardBroker};
/// Error that can occur when connecting to the WireGuard netlink interface.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum ConnectError { pub enum ConnectError {
#[error(transparent)] #[error(transparent)]
ConnectError(#[from] wg::err::ConnectError), ConnectError(#[from] wg::err::ConnectError),
} }
/// Errors that can occur during netlink operations.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum NetlinkError { pub enum NetlinkError {
#[error(transparent)] #[error(transparent)]
@@ -22,6 +51,7 @@ pub enum NetlinkError {
GetDevice(#[from] wg::err::GetDeviceError), GetDevice(#[from] wg::err::GetDeviceError),
} }
/// Errors that can occur when setting a pre-shared key.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum SetPskError { pub enum SetPskError {
#[error("The indicated wireguard interface does not exist")] #[error("The indicated wireguard interface does not exist")]
@@ -32,18 +62,41 @@ pub enum SetPskError {
NetlinkError(#[from] NetlinkError), NetlinkError(#[from] NetlinkError),
} }
/// # Example
/// ```
/// # use wireguard_uapi::err::NlError;
/// # use wireguard_uapi::linux::err::SetDeviceError;
/// use rosenpass_wireguard_broker::brokers::netlink::SetPskError;
/// let set_device_error: SetDeviceError = SetDeviceError::NlError(NlError::Msg("test-error".to_string()));
/// let set_psk_error: SetPskError = set_device_error.into();
/// ```
impl From<wg::err::SetDeviceError> for SetPskError { impl From<wg::err::SetDeviceError> for SetPskError {
fn from(err: wg::err::SetDeviceError) -> Self { fn from(err: wg::err::SetDeviceError) -> Self {
NetlinkError::from(err).into() NetlinkError::from(err).into()
} }
} }
/// # Example
/// ```
/// # use wireguard_uapi::err::NlError;
/// # use wireguard_uapi::linux::err::GetDeviceError;
/// # use rosenpass_wireguard_broker::brokers::netlink::SetPskError;
/// let get_device_error: GetDeviceError = GetDeviceError::NlError(NlError::Msg("test-error".to_string()));
/// let set_psk_error: SetPskError = get_device_error.into();
/// ```
impl From<wg::err::GetDeviceError> for SetPskError { impl From<wg::err::GetDeviceError> for SetPskError {
fn from(err: wg::err::GetDeviceError) -> Self { fn from(err: wg::err::GetDeviceError) -> Self {
NetlinkError::from(err).into() NetlinkError::from(err).into()
} }
} }
/// # Example
/// ```
/// use rosenpass_wireguard_broker::api::msgs::SetPskError as SetPskMsgsError;
/// use rosenpass_wireguard_broker::brokers::netlink::SetPskError as SetPskNetlinkError;
/// let set_psk_nlink_error: SetPskNetlinkError = SetPskNetlinkError::NoSuchInterface;
/// let set_psk_msgs_error = SetPskMsgsError::from(set_psk_nlink_error);
/// ```
use msgs::SetPskError as SetPskMsgsError; use msgs::SetPskError as SetPskMsgsError;
use SetPskError as SetPskNetlinkError; use SetPskError as SetPskNetlinkError;
impl From<SetPskNetlinkError> for SetPskMsgsError { impl From<SetPskNetlinkError> for SetPskMsgsError {
@@ -55,11 +108,33 @@ impl From<SetPskNetlinkError> for SetPskMsgsError {
} }
} }
/// WireGuard broker implementation using Linux netlink sockets.
///
/// This implementation communicates directly with the kernel through netlink sockets,
/// providing better performance than command-line based implementations.
///
/// # Examples
///
/// ```
/// use rosenpass_wireguard_broker::brokers::netlink::NetlinkWireGuardBroker;
/// use rosenpass_wireguard_broker::WireGuardBroker;
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let mut broker = NetlinkWireGuardBroker::new()?;
/// # Ok(())
/// # }
/// ```
///
/// # Platform Support
///
/// This implementation is only available on Linux systems and requires appropriate
/// permissions to use netlink sockets.
pub struct NetlinkWireGuardBroker { pub struct NetlinkWireGuardBroker {
sock: wg::WgSocket, sock: wg::WgSocket,
} }
impl NetlinkWireGuardBroker { impl NetlinkWireGuardBroker {
/// Opens a netlink socket to the WireGuard kernel module
/// and returns a new netlink-based WireGuard broker.
pub fn new() -> Result<Self, ConnectError> { pub fn new() -> Result<Self, ConnectError> {
let sock = wg::WgSocket::connect()?; let sock = wg::WgSocket::connect()?;
Ok(Self { sock }) Ok(Self { sock })
@@ -112,3 +187,27 @@ impl WireGuardBroker for NetlinkWireGuardBroker {
Ok(()) Ok(())
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Public, Secret};
#[test]
fn smoke_test() -> Result<(), Box<dyn std::error::Error>> {
secret_policy_use_only_malloc_secrets();
let result = NetlinkWireGuardBroker::new();
assert!(result.is_ok());
let mut broker = result.unwrap();
let peer_id = Public::zero();
let psk = Secret::zero();
let config: SerializedBrokerConfig = NetworkBrokerConfig {
iface: "wg0",
peer_id: &peer_id,
psk: &psk,
}
.into();
let result = broker.set_psk(config);
assert!(result.is_err());
Ok(())
}
}

View File

@@ -1,38 +1,106 @@
//! A broker interface for managing WireGuard pre-shared keys (PSK).
//!
//! This crate provides traits and implementations for interacting with WireGuard interfaces
//! to set pre-shared keys for peers. It supports different backend implementations including:
//! - Native Unix command-line interface
//! - Linux netlink interface
//! - Custom Unix socket protocol
//!
//! # Examples
//!
//! ```no_run
//! # use rosenpass_secret_memory::{Public, Secret};
//! # use rosenpass_wireguard_broker::{WireGuardBroker, SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
//! # use std::error::Error;
//!
//! # fn main() -> Result<(), Box<dyn Error>> {
//! # rosenpass_secret_memory::policy::secret_policy_try_use_memfd_secrets();
//! # let interface = "wg0";
//! # let peer_id = Public::<WG_PEER_LEN>::zero();
//! # let psk = Secret::<WG_KEY_LEN>::zero();
//!
//! // Create a native Unix broker
//! let mut broker = rosenpass_wireguard_broker::brokers::native_unix::NativeUnixBroker::new();
//!
//! // Configure and set PSK
//! let config = SerializedBrokerConfig {
//! interface: interface.as_bytes(),
//! peer_id: &peer_id,
//! psk: &psk,
//! additional_params: &[],
//! };
//!
//! broker.set_psk(config)?;
//! # Ok(())
//! # }
//! ```
use rosenpass_secret_memory::{Public, Secret}; use rosenpass_secret_memory::{Public, Secret};
use std::fmt::Debug; use std::fmt::Debug;
/// Length of a WireGuard key in bytes
pub const WG_KEY_LEN: usize = 32; pub const WG_KEY_LEN: usize = 32;
/// Length of a WireGuard peer ID in bytes
pub const WG_PEER_LEN: usize = 32; pub const WG_PEER_LEN: usize = 32;
/// Core trait for WireGuard PSK brokers.
///
/// This trait defines the basic interface for setting pre-shared keys (PSK) on WireGuard interfaces.
/// Implementations handle the actual communication with WireGuard, whether through command-line tools,
/// netlink, or other mechanisms.
pub trait WireGuardBroker: Debug { pub trait WireGuardBroker: Debug {
/// The error type returned by broker operations
type Error; type Error;
/// Set a pre-shared key for a WireGuard peer
fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> Result<(), Self::Error>; fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> Result<(), Self::Error>;
} }
/// Configuration trait for WireGuard PSK brokers.
///
/// This trait allows creation of broker configurations from a PSK and implementation-specific
/// configuration data.
pub trait WireguardBrokerCfg: Debug { pub trait WireguardBrokerCfg: Debug {
/// Creates a serialized broker configuration from this config and a specific PSK
fn create_config<'a>(&'a self, psk: &'a Secret<WG_KEY_LEN>) -> SerializedBrokerConfig<'a>; fn create_config<'a>(&'a self, psk: &'a Secret<WG_KEY_LEN>) -> SerializedBrokerConfig<'a>;
} }
/// Serialized configuration for WireGuard PSK operations.
#[derive(Debug)] #[derive(Debug)]
pub struct SerializedBrokerConfig<'a> { pub struct SerializedBrokerConfig<'a> {
/// The WireGuard interface name as UTF-8 bytes
pub interface: &'a [u8], pub interface: &'a [u8],
/// The public key of the peer
pub peer_id: &'a Public<WG_PEER_LEN>, pub peer_id: &'a Public<WG_PEER_LEN>,
/// The pre-shared key to set
pub psk: &'a Secret<WG_KEY_LEN>, pub psk: &'a Secret<WG_KEY_LEN>,
/// Additional implementation-specific parameters
pub additional_params: &'a [u8], pub additional_params: &'a [u8],
} }
/// Extension trait for mio integration with WireGuard brokers.
///
/// This trait extends the basic `WireGuardBroker` functionality with asynchronous I/O
/// operations using the mio event framework.
pub trait WireguardBrokerMio: WireGuardBroker { pub trait WireguardBrokerMio: WireGuardBroker {
/// The error type for mio operations
type MioError; type MioError;
/// Register interested events for mio::Registry
/// Register the broker with a mio Registry for event notifications
fn register( fn register(
&mut self, &mut self,
registry: &mio::Registry, registry: &mio::Registry,
token: mio::Token, token: mio::Token,
) -> Result<(), Self::MioError>; ) -> Result<(), Self::MioError>;
/// Get the mio token associated with this broker, if any
fn mio_token(&self) -> Option<mio::Token>; fn mio_token(&self) -> Option<mio::Token>;
/// Run after a mio::poll operation /// Process events after a mio poll operation
fn process_poll(&mut self) -> Result<(), Self::MioError>; fn process_poll(&mut self) -> Result<(), Self::MioError>;
/// Unregister the broker from a mio Registry
fn unregister(&mut self, registry: &mio::Registry) -> Result<(), Self::MioError>; fn unregister(&mut self, registry: &mio::Registry) -> Result<(), Self::MioError>;
} }