docs(wireguard-broker): add docs and examples

This commit is contained in:
David Niehues
2024-12-16 16:27:47 +01:00
parent dd0db53e8b
commit c78a9cb777
11 changed files with 751 additions and 6 deletions

View File

@@ -1,3 +1,52 @@
//! Asynchronous WireGuard PSK broker client using mio for non-blocking I/O.
//!
//! This module provides a client implementation that communicates with a WireGuard broker
//! through Unix domain sockets using non-blocking I/O operations. It's designed to be used
//! in event-driven applications using the mio event framework.
//!
//! # Examples
//!
//! ```no_run
//! # use mio::net::UnixStream;
//! # use rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient;
//! # use rosenpass_wireguard_broker::{WireGuardBroker, WireguardBrokerMio};
//! # use mio::{Events, Interest, Poll, Token};
//! # use rosenpass_secret_memory::{Public, Secret};
//! # use rosenpass_wireguard_broker::api::config::NetworkBrokerConfig;
//! # use rosenpass_wireguard_broker::SerializedBrokerConfig;
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let socket = UnixStream::connect("/path/to/broker.sock")?;
//! let mut client = MioBrokerClient::new(socket);
//!
//! // Set up mio polling
//! let mut poll = Poll::new()?;
//! let mut events = Events::with_capacity(128);
//! client.register(&poll.registry(), Token(0))?;
//!
//! // Prepare PSK configuration
//! let network_config = NetworkBrokerConfig {
//! iface: "wg0",
//! peer_id: &Public::zero(), // Replace with actual peer ID
//! psk: &Secret::zero(), // Replace with actual PSK
//! };
//!
//! // Convert to serialized format and send
//! let config: SerializedBrokerConfig = network_config.into();
//! client.set_psk(config)?;
//!
//! // Process responses in event loop
//! loop {
//! poll.poll(&mut events, None)?;
//! for event in &events {
//! if event.token() == Token(0) {
//! client.process_poll()?;
//! }
//! }
//! }
//! # Ok(())
//! # }
//! ```
use anyhow::{bail, Context};
use 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()?;

View File

@@ -1,3 +1,35 @@
//! Native Unix implementation of the WireGuard PSK broker using the `wg` command-line tool.
//!
//! This module provides an implementation that works on Unix systems by executing the `wg`
//! command-line tool to set pre-shared keys. It requires the `wg` tool to be installed and
//! accessible in the system PATH.
//!
//! # Examples
//!
//! ```no_run
//! use rosenpass_secret_memory::{Public, Secret};
//! use rosenpass_wireguard_broker::brokers::native_unix::{NativeUnixBroker, NativeUnixBrokerConfigBase};
//! use rosenpass_wireguard_broker::{WireGuardBroker, WireguardBrokerCfg, WG_KEY_LEN, WG_PEER_LEN};
//!
//! # fn main() -> Result<(), anyhow::Error> {
//! // Create a broker instance
//! let mut broker = NativeUnixBroker::new();
//!
//! // Create configuration
//! let config = NativeUnixBrokerConfigBase {
//! interface: "wg0".to_string(),
//! peer_id: Public::zero(), // Replace with actual peer ID
//! extra_params: Vec::new(),
//! };
//!
//! // Set PSK using the broker
//! let psk = Secret::<WG_KEY_LEN>::zero(); // Replace with actual PSK
//! let serialized_config = config.create_config(&psk);
//! broker.set_psk(serialized_config)?;
//! # Ok(())
//! # }
//! ```
use std::fmt::Debug;
use std::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>,
}

View File

@@ -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;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! 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(())
//! # }
//! ```
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")]
@@ -55,11 +85,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
///
/// ```no_run
/// 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 })