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 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)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)?;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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()?;
|
||||||
|
|
||||||
|
|||||||
@@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user