mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-05 20:40:02 -08:00
docs(wireguard-broker): add docs and examples (#550)
This commit is contained in:
@@ -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 crate::{
|
||||
@@ -13,10 +51,13 @@ use super::{
|
||||
msgs::{Envelope, SetPskResponse},
|
||||
};
|
||||
|
||||
/// Error type for polling responses from the broker server.
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BrokerClientPollResponseError<RecvError> {
|
||||
/// An IO error occurred while receiving the response
|
||||
#[error(transparent)]
|
||||
IoError(RecvError),
|
||||
/// The received message was invalid or malformed
|
||||
#[error("Invalid message.")]
|
||||
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> {
|
||||
BrokerClientPollResponseError::<RecvError>::IoError(e)
|
||||
}
|
||||
|
||||
/// Helper function that returns a `BrokerClientPollResponseError::InvalidMessage` error
|
||||
fn invalid_msg_poller<RecvError>() -> BrokerClientPollResponseError<RecvError> {
|
||||
BrokerClientPollResponseError::<RecvError>::InvalidMessage
|
||||
}
|
||||
|
||||
/// Error type for setting pre-shared keys through the broker client.
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BrokerClientSetPskError<SendError> {
|
||||
/// Error encoding or decoding the message
|
||||
#[error("Error with encoding/decoding message")]
|
||||
MsgError,
|
||||
/// Error in the broker configuration
|
||||
#[error("Network Broker Config error: {0}")]
|
||||
BrokerError(NetworkBrokerConfigErr),
|
||||
/// IO error while sending the request
|
||||
#[error(transparent)]
|
||||
IoError(SendError),
|
||||
/// Interface name exceeds maximum length
|
||||
#[error("Interface name out of bounds")]
|
||||
IfaceOutOfBounds,
|
||||
}
|
||||
|
||||
/// Trait defining the IO operations required by the broker client.
|
||||
///
|
||||
/// Implementors must provide methods for sending and receiving binary messages.
|
||||
pub trait BrokerClientIo {
|
||||
/// Error type returned by send operations
|
||||
type SendError;
|
||||
/// Error type returned by receive operations
|
||||
type RecvError;
|
||||
|
||||
/// Send a binary message
|
||||
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>;
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub struct BrokerClient<Io>
|
||||
where
|
||||
@@ -68,18 +127,37 @@ impl<Io> BrokerClient<Io>
|
||||
where
|
||||
Io: BrokerClientIo + Debug,
|
||||
{
|
||||
/// Creates a new `BrokerClient` with the given IO implementation.
|
||||
pub fn new(io: Io) -> Self {
|
||||
Self { io }
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying IO implementation.
|
||||
pub fn io(&self) -> &Io {
|
||||
&self.io
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying IO implementation.
|
||||
pub fn io_mut(&mut self) -> &mut 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(
|
||||
&mut self,
|
||||
) -> Result<Option<msgs::SetPskResult>, BrokerClientPollResponseError<Io::RecvError>> {
|
||||
@@ -147,3 +225,112 @@ where
|
||||
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)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 derive_builder::Builder;
|
||||
use rosenpass_secret_memory::{Public, Secret};
|
||||
@@ -5,13 +9,19 @@ use rosenpass_secret_memory::{Public, Secret};
|
||||
#[derive(Builder, Debug)]
|
||||
#[builder(pattern = "mutable")]
|
||||
//TODO: Use generics for iface, add additional params
|
||||
/// Specifies a configuration for a [BrokerServer](crate::api::server::BrokerServer).
|
||||
pub struct NetworkBrokerConfig<'a> {
|
||||
/// The interface for the [BrokerServer](crate::api::server::BrokerServer).
|
||||
pub iface: &'a str,
|
||||
/// The peer identifier for the [BrokerServer](crate::api::server::BrokerServer).
|
||||
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>,
|
||||
}
|
||||
|
||||
impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> {
|
||||
/// Transforms a [NetworkBrokerConfig] into a [SerializedBrokerConfig] meant for serialization.
|
||||
fn from(src: NetworkBrokerConfig<'a>) -> SerializedBrokerConfig<'a> {
|
||||
Self {
|
||||
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)]
|
||||
pub enum NetworkBrokerConfigErr {
|
||||
/// Error indicating that the interface specification could not be read correctly.
|
||||
#[error("Interface")]
|
||||
Interface,
|
||||
Interface, // TODO, give this a better name.
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<SerializedBrokerConfig<'a>> for NetworkBrokerConfig<'a> {
|
||||
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> {
|
||||
let iface =
|
||||
std::str::from_utf8(value.interface).map_err(|_| NetworkBrokerConfigErr::Interface)?;
|
||||
|
||||
@@ -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 config;
|
||||
pub mod msgs;
|
||||
|
||||
@@ -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 zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
|
||||
/// The number of bytes reserved for overhead when packaging data.
|
||||
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;
|
||||
/// The buffer size for responses.
|
||||
pub const RESPONSE_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 1;
|
||||
|
||||
/// Envelope for messages being passed around.
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
@@ -17,25 +25,43 @@ pub struct Envelope<M: AsBytes + FromBytes> {
|
||||
pub payload: M,
|
||||
}
|
||||
|
||||
/// Message format for requests to set a pre-shared key.
|
||||
/// # Example
|
||||
///
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct SetPskRequest {
|
||||
/// The pre-shared key.
|
||||
pub psk: [u8; 32],
|
||||
/// The identifier of the peer.
|
||||
pub peer_id: [u8; 32],
|
||||
/// The size for the interface
|
||||
pub iface_size: u8, // TODO: We should have variable length strings in lenses
|
||||
/// The buffer for the interface.
|
||||
pub iface_buf: [u8; 255],
|
||||
}
|
||||
|
||||
impl SetPskRequest {
|
||||
/// Gets the interface specification as byte slice.
|
||||
pub fn iface_bin(&self) -> &[u8] {
|
||||
let len = self.iface_size as usize;
|
||||
&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> {
|
||||
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<()> {
|
||||
(iface.len() < 256).then_some(())?; // Assert iface.len() < 256
|
||||
|
||||
@@ -47,17 +73,24 @@ impl SetPskRequest {
|
||||
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<()> {
|
||||
self.set_iface_bin(iface.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
/// Message format for response to the set pre-shared key operation.
|
||||
#[repr(packed)]
|
||||
#[derive(AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct SetPskResponse {
|
||||
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)]
|
||||
pub enum SetPskError {
|
||||
#[error("The wireguard pre-shared-key assignment broker experienced an internal error.")]
|
||||
@@ -70,6 +103,12 @@ pub enum 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)]
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum SetPskResponseReturnCode {
|
||||
@@ -85,6 +124,19 @@ pub struct InvalidSetPskResponseError;
|
||||
impl TryFrom<u8> for SetPskResponseReturnCode {
|
||||
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> {
|
||||
use SetPskResponseReturnCode::*;
|
||||
match value {
|
||||
@@ -98,6 +150,9 @@ impl TryFrom<u8> for SetPskResponseReturnCode {
|
||||
}
|
||||
|
||||
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 {
|
||||
use SetPskError as E;
|
||||
use SetPskResponseReturnCode as C;
|
||||
@@ -111,6 +166,7 @@ impl From<SetPskResponseReturnCode> for SetPskResult {
|
||||
}
|
||||
|
||||
impl From<SetPskResult> for SetPskResponseReturnCode {
|
||||
/// A [SetPskResponseReturnCode] can directly be deduced from a [SetPskResult].
|
||||
fn from(value: SetPskResult) -> Self {
|
||||
use SetPskError as E;
|
||||
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)]
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||
pub enum MsgType {
|
||||
SetPsk = 0x01,
|
||||
}
|
||||
|
||||
/// Error indicating that an invalid [MsgType] was used.
|
||||
/// This error is returned by [MsgType::try_from].
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
pub struct InvalidMessageTypeError;
|
||||
|
||||
impl TryFrom<u8> for MsgType {
|
||||
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> {
|
||||
match value {
|
||||
0x01 => Ok(MsgType::SetPsk),
|
||||
|
||||
@@ -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 rosenpass_secret_memory::{Public, Secret};
|
||||
@@ -7,12 +14,16 @@ use crate::WireGuardBroker;
|
||||
|
||||
use super::config::{NetworkBrokerConfigBuilder, NetworkBrokerConfigErr};
|
||||
|
||||
/// Error variants for the [BrokerServer].
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum BrokerServerError {
|
||||
/// Indicates that an unknown request type was encountered.
|
||||
#[error("No such request type: {}", .0)]
|
||||
NoSuchRequestType(u8),
|
||||
/// Indicates that an invalid message was sent.
|
||||
#[error("Invalid message received.")]
|
||||
InvalidMessage,
|
||||
/// Indicates an error when configuration the network broker.
|
||||
#[error("Network Broker Config error: {0}")]
|
||||
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>
|
||||
where
|
||||
Inner: WireGuardBroker<Error = Err>,
|
||||
msgs::SetPskError: From<Err>,
|
||||
{
|
||||
/// The inner [WireGuardBroker].
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
@@ -38,10 +56,17 @@ where
|
||||
msgs::SetPskError: From<Err>,
|
||||
Err: std::fmt::Debug,
|
||||
{
|
||||
/// Creates a new [BrokerServer] from a [WireGuardBroker].
|
||||
pub fn new(inner: Inner) -> Self {
|
||||
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(
|
||||
&mut self,
|
||||
req: &[u8],
|
||||
@@ -53,22 +78,28 @@ where
|
||||
let typ = msgs::MsgType::try_from(*typ)?;
|
||||
let msgs::MsgType::SetPsk = typ; // Assert type
|
||||
|
||||
let req = zerocopy::Ref::<&[u8], Envelope<SetPskRequest>>::new(req)
|
||||
.ok_or(BrokerServerError::InvalidMessage)?;
|
||||
let mut res = zerocopy::Ref::<&mut [u8], Envelope<SetPskResponse>>::new(res)
|
||||
.ok_or(BrokerServerError::InvalidMessage)?;
|
||||
let req =
|
||||
zerocopy::Ref::<&[u8], Envelope<SetPskRequest>>::new(req).ok_or(InvalidMessage)?;
|
||||
let mut res =
|
||||
zerocopy::Ref::<&mut [u8], Envelope<SetPskResponse>>::new(res).ok_or(InvalidMessage)?;
|
||||
res.msg_type = msgs::MsgType::SetPsk as u8;
|
||||
self.handle_set_psk(&req.payload, &mut res.payload)?;
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
req: &SetPskRequest,
|
||||
res: &mut SetPskResponse,
|
||||
) -> 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
|
||||
let peer_id = Public::from_slice(&req.peer_id);
|
||||
let psk = Secret::from_slice(&req.psk);
|
||||
@@ -95,3 +126,60 @@ where
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
#[cfg(target_os = "linux")]
|
||||
linux::main().unwrap();
|
||||
@@ -8,20 +17,33 @@ fn main() {
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux {
|
||||
//! Linux-specific implementation for the broker that communicates with the WireGuard broker.
|
||||
|
||||
use std::io::{stdin, stdout, Read, Write};
|
||||
|
||||
use rosenpass_wireguard_broker::api::msgs;
|
||||
use rosenpass_wireguard_broker::api::server::BrokerServer;
|
||||
use rosenpass_wireguard_broker::brokers::netlink as wg;
|
||||
|
||||
/// Represents errors that can occur during WireGuard broker operations
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum BrokerAppError {
|
||||
/// Wraps standard I/O errors that may occur during broker operations
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
/// Wraps WireGuard connection errors
|
||||
#[error(transparent)]
|
||||
WgConnectError(#[from] wg::ConnectError),
|
||||
|
||||
/// Wraps errors that occur when setting WireGuard Pre-Shared Keys (PSK)
|
||||
#[error(transparent)]
|
||||
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)]
|
||||
OversizedMessage(u64),
|
||||
}
|
||||
|
||||
@@ -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 tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
@@ -12,6 +15,7 @@ use clap::{ArgGroup, Parser};
|
||||
use rosenpass_util::fd::claim_fd;
|
||||
use rosenpass_wireguard_broker::api::msgs;
|
||||
|
||||
/// Command-line arguments for configuring the socket handler
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[clap(group(
|
||||
@@ -45,11 +49,13 @@ struct Args {
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
/// Represents a request to the broker with a channel for receiving the response
|
||||
struct BrokerRequest {
|
||||
reply_to: oneshot::Sender<BrokerResponse>,
|
||||
request: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Contains the broker's response data
|
||||
struct BrokerResponse {
|
||||
response: Vec<u8>,
|
||||
}
|
||||
@@ -87,6 +93,7 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages communication with the privileged broker process
|
||||
async fn direct_broker_process(
|
||||
mut queue: mpsc::Receiver<BrokerRequest>,
|
||||
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<()> {
|
||||
loop {
|
||||
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
|
||||
}
|
||||
|
||||
/// Handles individual client connections and message processing
|
||||
async fn on_accept(queue: mpsc::Sender<BrokerRequest>, mut stream: UnixStream) -> Result<()> {
|
||||
let mut req_buf = Vec::new();
|
||||
|
||||
|
||||
@@ -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 mio::Interest;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
@@ -13,12 +62,44 @@ use crate::api::client::{
|
||||
};
|
||||
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)]
|
||||
pub struct MioBrokerClient {
|
||||
inner: BrokerClient<MioBrokerClientIo>,
|
||||
mio_token: Option<mio::Token>,
|
||||
}
|
||||
|
||||
/// A buffer wrapper that provides secure memory for sensitive data.
|
||||
#[derive(Debug)]
|
||||
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 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)]
|
||||
struct MioBrokerClientIo {
|
||||
socket: mio::net::UnixStream,
|
||||
@@ -51,6 +136,10 @@ struct MioBrokerClientIo {
|
||||
}
|
||||
|
||||
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 {
|
||||
let read_buffer = LengthPrefixDecoder::new(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<()> {
|
||||
self.inner.io_mut().flush()?;
|
||||
|
||||
|
||||
@@ -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::process::{Command, Stdio};
|
||||
use std::thread;
|
||||
@@ -12,9 +44,21 @@ use rosenpass_util::{b64::B64Display, file::StoreValueB64Writer};
|
||||
use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerCfg, WireguardBrokerMio};
|
||||
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;
|
||||
/// Maximum size of a base64-encoded WireGuard peer ID in bytes
|
||||
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)]
|
||||
pub struct NativeUnixBroker {
|
||||
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)]
|
||||
#[builder(pattern = "mutable")]
|
||||
pub struct NativeUnixBrokerConfigBase {
|
||||
/// Name of the WireGuard interface (e.g., "wg0")
|
||||
pub interface: String,
|
||||
/// Public key of the peer
|
||||
pub peer_id: Public<WG_PEER_LEN>,
|
||||
/// Additional parameters to pass to the wg command
|
||||
#[builder(private)]
|
||||
pub extra_params: Vec<u8>,
|
||||
}
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
peer_id: &str,
|
||||
@@ -133,6 +224,29 @@ impl NativeUnixBrokerConfigBaseBuilder {
|
||||
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(
|
||||
&mut self,
|
||||
extra_params: &Vec<String>,
|
||||
@@ -157,12 +271,17 @@ impl WireguardBrokerCfg for NativeUnixBrokerConfigBase {
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime configuration for a single PSK operation.
|
||||
#[derive(Debug, Builder)]
|
||||
#[builder(pattern = "mutable")]
|
||||
pub struct NativeUnixBrokerConfig<'a> {
|
||||
/// WireGuard interface name
|
||||
pub interface: &'a str,
|
||||
/// Public key of the peer
|
||||
pub peer_id: &'a Public<WG_PEER_LEN>,
|
||||
/// Pre-shared key to set
|
||||
pub psk: &'a Secret<WG_KEY_LEN>,
|
||||
/// Additional wg command parameters
|
||||
pub extra_params: Vec<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
#![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;
|
||||
|
||||
@@ -8,12 +35,14 @@ use crate::api::config::NetworkBrokerConfig;
|
||||
use crate::api::msgs;
|
||||
use crate::{SerializedBrokerConfig, WireGuardBroker};
|
||||
|
||||
/// Error that can occur when connecting to the WireGuard netlink interface.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ConnectError {
|
||||
#[error(transparent)]
|
||||
ConnectError(#[from] wg::err::ConnectError),
|
||||
}
|
||||
|
||||
/// Errors that can occur during netlink operations.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum NetlinkError {
|
||||
#[error(transparent)]
|
||||
@@ -22,6 +51,7 @@ pub enum NetlinkError {
|
||||
GetDevice(#[from] wg::err::GetDeviceError),
|
||||
}
|
||||
|
||||
/// Errors that can occur when setting a pre-shared key.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum SetPskError {
|
||||
#[error("The indicated wireguard interface does not exist")]
|
||||
@@ -32,18 +62,41 @@ pub enum SetPskError {
|
||||
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 {
|
||||
fn from(err: wg::err::SetDeviceError) -> Self {
|
||||
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 {
|
||||
fn from(err: wg::err::GetDeviceError) -> Self {
|
||||
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 SetPskError as SetPskNetlinkError;
|
||||
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 {
|
||||
sock: wg::WgSocket,
|
||||
}
|
||||
|
||||
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> {
|
||||
let sock = wg::WgSocket::connect()?;
|
||||
Ok(Self { sock })
|
||||
@@ -112,3 +187,27 @@ impl WireGuardBroker for NetlinkWireGuardBroker {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 std::fmt::Debug;
|
||||
|
||||
/// Length of a WireGuard key in bytes
|
||||
pub const WG_KEY_LEN: usize = 32;
|
||||
|
||||
/// Length of a WireGuard peer ID in bytes
|
||||
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 {
|
||||
/// The error type returned by broker operations
|
||||
type Error;
|
||||
|
||||
/// Set a pre-shared key for a WireGuard peer
|
||||
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 {
|
||||
/// 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>;
|
||||
}
|
||||
|
||||
/// Serialized configuration for WireGuard PSK operations.
|
||||
#[derive(Debug)]
|
||||
pub struct SerializedBrokerConfig<'a> {
|
||||
/// The WireGuard interface name as UTF-8 bytes
|
||||
pub interface: &'a [u8],
|
||||
/// The public key of the peer
|
||||
pub peer_id: &'a Public<WG_PEER_LEN>,
|
||||
/// The pre-shared key to set
|
||||
pub psk: &'a Secret<WG_KEY_LEN>,
|
||||
/// Additional implementation-specific parameters
|
||||
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 {
|
||||
/// The error type for mio operations
|
||||
type MioError;
|
||||
/// Register interested events for mio::Registry
|
||||
|
||||
/// Register the broker with a mio Registry for event notifications
|
||||
fn register(
|
||||
&mut self,
|
||||
registry: &mio::Registry,
|
||||
token: mio::Token,
|
||||
) -> Result<(), Self::MioError>;
|
||||
|
||||
/// Get the mio token associated with this broker, if any
|
||||
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>;
|
||||
|
||||
/// Unregister the broker from a mio Registry
|
||||
fn unregister(&mut self, registry: &mio::Registry) -> Result<(), Self::MioError>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user