From cca02dc8d195c23895fbd3d9cc2265c5b4a4c255 Mon Sep 17 00:00:00 2001 From: David Niehues Date: Wed, 11 Dec 2024 11:23:31 +0100 Subject: [PATCH 1/2] add documentation for the rp crate --- rp/src/cli.rs | 30 +++++++++++++++++++++++++++++ rp/src/exchange.rs | 48 ++++++++++++++++++++++++++++++++++++++++++---- rp/src/key.rs | 16 ++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/rp/src/cli.rs b/rp/src/cli.rs index f7371a63..26742e97 100644 --- a/rp/src/cli.rs +++ b/rp/src/cli.rs @@ -3,6 +3,11 @@ use std::{iter::Peekable, net::SocketAddr}; use crate::exchange::{ExchangeOptions, ExchangePeer}; +/// The different commands supported by the `rp` binary. +/// [GenKey](crate::cli::Command::GenKey), [PubKey](crate::cli::Command::PubKey), +/// [Exchange](crate::cli::Command::Exchange) and +/// [ExchangeConfig](crate::cli::Command::ExchangeConfig) +/// contain information specific to the respective command. pub enum Command { GenKey { private_keys_dir: PathBuf, @@ -18,6 +23,10 @@ pub enum Command { Help, } +/// The different command types supported by the `rp` binary. +/// This enum is exclusively used in [fatal] and when calling [fatal] and is therefore +/// limited to the command types that can fail. E.g., the help command can not fail and is therefore +/// not part of the [CommandType]-enum. enum CommandType { GenKey, PubKey, @@ -25,12 +34,24 @@ enum CommandType { ExchangeConfig, } +/// This structure captures the result of parsing the arguments to the `rp` binary. +/// A new [Cli] is created by calling [Cli::parse] with the appropriate arguments. #[derive(Default)] pub struct Cli { + /// whether the output should be verbose. pub verbose: bool, + /// the command specified by the given arguments. pub command: Option, } +/// Processes a fatal error when parsing cli arguments. +/// It *always* returns an [Err(String)], where such that the contained [String] explains +/// the parsing error, including the provided `note`. +/// +/// # Generic Parameters +/// the generic parameter `T` is given to make the [Result]-type compatible with the respective +/// return type of the calling function. +/// fn fatal(note: &str, command: Option) -> Result { match command { Some(command) => match command { @@ -44,6 +65,9 @@ fn fatal(note: &str, command: Option) -> Result { } impl ExchangePeer { + /// Parses peer parameters given to the `rp` binary in the context of an `exchange` operation. + /// It returns a result with either [ExchangePeer] that contains the parameters of the peer + /// or an error describing why the arguments could not be parsed. pub fn parse(args: &mut &mut Peekable>) -> Result { let mut peer = ExchangePeer::default(); @@ -126,6 +150,9 @@ impl ExchangePeer { } impl ExchangeOptions { + /// Parses the arguments given to the `rp` binary *if the `exchange` operation is given*. + /// It returns a result with either [ExchangeOptions] that contains the result of parsing the + /// arguments or an error describing why the arguments could not be parsed. pub fn parse(mut args: &mut Peekable>) -> Result { let mut options = ExchangeOptions::default(); @@ -191,6 +218,9 @@ impl ExchangeOptions { } impl Cli { + /// Parses the arguments given to the `rp` binary. It returns a result with either + /// a [Cli] that contains the result of parsing the arguments or an error describing + /// why the arguments could not be parsed. pub fn parse(mut args: Peekable>) -> Result { let mut cli = Cli::default(); diff --git a/rp/src/exchange.rs b/rp/src/exchange.rs index 757ed66e..28e985d6 100644 --- a/rp/src/exchange.rs +++ b/rp/src/exchange.rs @@ -11,21 +11,35 @@ use anyhow::Result; #[cfg(any(target_os = "linux", target_os = "freebsd"))] use crate::key::WG_B64_LEN; +/// Used to define a peer for the rosenpass connection that consists of +/// a directory for storing public keys and optionally an IP address and port of the endpoint, +/// for how long the connection should be kept alive and a list of allowed IPs for the peer. #[derive(Default, Deserialize)] pub struct ExchangePeer { + /// Directory where public keys are stored pub public_keys_dir: PathBuf, + /// The IP address of the endpoint pub endpoint: Option, + /// For how long to keep the connection alive pub persistent_keepalive: Option, + /// The IPs that are allowed for this peer. pub allowed_ips: Option, } +/// Options for the exchange operation of the `rp` binary. #[derive(Default, Deserialize)] pub struct ExchangeOptions { + /// Whether the cli output should be verbose. pub verbose: bool, + /// path to the directory where private keys are stored. pub private_keys_dir: PathBuf, + /// The link rosenpass should run as. If None is given [exchange] will use `"rosenpass0"` instead. pub dev: Option, + /// The IP-address rosenpass should run under pub ip: Option, + /// The IP-address and port that the rosenpass [AppServer](rosenpass::app_server::AppServer) should use. pub listen: Option, + /// Other peers a connection should be initialized to pub peers: Vec, } @@ -48,8 +62,11 @@ mod netlink { use netlink_packet_wireguard::nlas::WgDeviceAttrs; use rtnetlink::Handle; + /// Creates a netlink named `link_name` and changes the state to up. It returns the index + /// of the interface in the list of interfaces as the result or an error if any of the + ///operations of creating the link or changing its state to up fails. pub async fn link_create_and_up(rtnetlink: &Handle, link_name: String) -> Result { - // add the link + // add the link, equivalent to `ip link add type wireguard` rtnetlink .link() .add() @@ -57,7 +74,8 @@ mod netlink { .execute() .await?; - // retrieve the link to be able to up it + // retrieve the link to be able to up it, equivalent to `ip link show` and then + // using the link shown that is identified by `link_name`. let link = rtnetlink .link() .get() @@ -69,7 +87,7 @@ mod netlink { .0 .unwrap()?; - // up the link + // up the link, equivalent to `ip link set dev up` rtnetlink .link() .set(link.header.index) @@ -80,12 +98,16 @@ mod netlink { Ok(link.header.index) } + /// Deletes a link using rtnetlink. The link is specified using its index in the list of links. pub async fn link_cleanup(rtnetlink: &Handle, index: u32) -> Result<()> { rtnetlink.link().del(index).execute().await?; Ok(()) } + /// Deletes a link using rtnetlink. The link is specified using its index in the list of links. + /// In contrast to [link_cleanup], this function create a new socket connection to netlink and + /// *ignores errors* that occur during deletion. pub async fn link_cleanup_standalone(index: u32) -> Result<()> { let (connection, rtnetlink, _) = rtnetlink::new_connection()?; tokio::spawn(connection); @@ -138,6 +160,9 @@ mod netlink { } } +/// A wrapper for a list of cleanup handlers that can be used in an asynchronous context +/// to clean up after the usage of rosenpass or if the `rp` binary is interrupted with ctrl+c +/// or a `SIGINT` signal in general. #[derive(Clone)] #[cfg(any(target_os = "linux", target_os = "freebsd"))] struct CleanupHandlers( @@ -146,19 +171,27 @@ struct CleanupHandlers( #[cfg(any(target_os = "linux", target_os = "freebsd"))] impl CleanupHandlers { + /// Creates a new list of [CleanupHandlers]. fn new() -> Self { CleanupHandlers(Arc::new(::futures::lock::Mutex::new(vec![]))) } + /// Enqueues a new cleanup handler in the form of a [Future]. async fn enqueue(&self, handler: Pin> + Send>>) { self.0.lock().await.push(Box::pin(handler)) } + /// Runs all cleanup handlers. Following the documentation of [futures::future::try_join_all]: + /// If any cleanup handler returns an error then all other cleanup handlers will be canceled and + /// an error will be returned immediately. If all cleanup handlers complete successfully, + /// however, then the returned future will succeed with a Vec of all the successful results. async fn run(self) -> Result, Error> { futures::future::try_join_all(self.0.lock().await.deref_mut()).await } } +/// Sets up the rosenpass link and wireguard and configures both with the configuration specified by +/// `options`. #[cfg(any(target_os = "linux", target_os = "freebsd"))] pub async fn exchange(options: ExchangeOptions) -> Result<()> { use std::fs; @@ -182,6 +215,8 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { let link_name = options.dev.clone().unwrap_or("rosenpass0".to_string()); let link_index = netlink::link_create_and_up(&rtnetlink, link_name.clone()).await?; + // set up a list of (initiallc empty) cleanup handlers that are to be run if + // ctrl-c is hit or generally a `SIGINT` signal is received and always in the end. let cleanup_handlers = CleanupHandlers::new(); let final_cleanup_handlers = (&cleanup_handlers).clone(); @@ -198,6 +233,8 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { .expect("Failed to clean up"); })?; + // run `ip address add dev ` and enqueue + // `ip address del dev ` as a cleanup if let Some(ip) = options.ip { let dev = options.dev.clone().unwrap_or("rosenpass0".to_string()); Command::new("ip") @@ -244,6 +281,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { netlink::wg_set(&mut genetlink, link_index, attr).await?; + // set up the rosenpass AppServer let pqsk = options.private_keys_dir.join("pqsk"); let pqpk = options.private_keys_dir.join("pqpk"); @@ -271,6 +309,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { anyhow::Error::msg(format!("NativeUnixBrokerConfigBaseBuilderError: {:?}", e)) } + // configure everything per peer for peer in options.peers { let wgpk = peer.public_keys_dir.join("wgpk"); let pqpk = peer.public_keys_dir.join("pqpk"); @@ -318,7 +357,8 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { peer.endpoint.map(|x| x.to_string()), )?; - // Configure routes + // Configure routes, equivalent to `ip route replace dev ` and set up + // the cleanup as `ip route del `. if let Some(allowed_ips) = peer.allowed_ips { Command::new("ip") .arg("route") diff --git a/rp/src/key.rs b/rp/src/key.rs index 0ffdd42f..4f98bb84 100644 --- a/rp/src/key.rs +++ b/rp/src/key.rs @@ -14,6 +14,7 @@ use rosenpass_cipher_traits::Kem; use rosenpass_ciphers::kem::StaticKem; use rosenpass_secret_memory::{file::StoreSecret as _, Public, Secret}; +/// The length of wireguard keys as a length in base 64 encoding. pub const WG_B64_LEN: usize = 32 * 5 / 3; #[cfg(not(target_family = "unix"))] @@ -24,6 +25,14 @@ pub fn genkey(_: &Path) -> Result<()> { )) } +/// Generates a new symmetric keys for wireguard and asymmetric keys for rosenpass +/// in the provided `private_keys_dir`. +/// +/// It checks whether the directory `private_keys_dir` points to exists and creates it otherwise. +/// If it exists, it ensures that the permission is set to 0700 and aborts otherwise. If the +/// directory is newly created, the appropriate permissions are set. +/// +/// Already existing keys are not overwritten. #[cfg(target_family = "unix")] pub fn genkey(private_keys_dir: &Path) -> Result<()> { if private_keys_dir.exists() { @@ -70,6 +79,11 @@ pub fn genkey(private_keys_dir: &Path) -> Result<()> { Ok(()) } +/// Creates a new directory under `public_keys_dir` and stores the public keys for rosenpass and for +/// wireguard that correspond to the private keys in `private_keys_dir` in `public_keys_dir`. +/// +/// If `public_keys_dir` already exists, the wireguard private key or the rosenpass public key +/// are not present in `private_keys_dir`, an error is returned. pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> { if public_keys_dir.exists() { return Err(anyhow!("Directory {:?} already exists", public_keys_dir)); @@ -90,9 +104,11 @@ pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> { Public::from_slice(public.as_bytes()) }; + // store the wireguard public key wgpk.store_b64::(public_wgpk)?; wgpk.zeroize(); + // copy the pq-public key to the public directory fs::copy(private_pqpk, public_pqpk)?; Ok(()) From 8805ef7c38d9f54f4536da7acbb9b32ecf45f631 Mon Sep 17 00:00:00 2001 From: David Niehues Date: Thu, 12 Dec 2024 17:27:01 +0100 Subject: [PATCH 2/2] style: Ensure inline comments start upper case and end with a dot, and fix some overlong lines. --- rp/src/cli.rs | 4 ++-- rp/src/exchange.rs | 34 ++++++++++++++++++---------------- rp/src/key.rs | 4 ++-- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/rp/src/cli.rs b/rp/src/cli.rs index 26742e97..97fce509 100644 --- a/rp/src/cli.rs +++ b/rp/src/cli.rs @@ -38,9 +38,9 @@ enum CommandType { /// A new [Cli] is created by calling [Cli::parse] with the appropriate arguments. #[derive(Default)] pub struct Cli { - /// whether the output should be verbose. + /// Whether the output should be verbose. pub verbose: bool, - /// the command specified by the given arguments. + /// The command specified by the given arguments. pub command: Option, } diff --git a/rp/src/exchange.rs b/rp/src/exchange.rs index 28e985d6..6deb9ba0 100644 --- a/rp/src/exchange.rs +++ b/rp/src/exchange.rs @@ -33,11 +33,13 @@ pub struct ExchangeOptions { pub verbose: bool, /// path to the directory where private keys are stored. pub private_keys_dir: PathBuf, - /// The link rosenpass should run as. If None is given [exchange] will use `"rosenpass0"` instead. + /// The link rosenpass should run as. If None is given [exchange] will use `"rosenpass0"` + /// instead. pub dev: Option, - /// The IP-address rosenpass should run under + /// The IP-address rosenpass should run under. pub ip: Option, - /// The IP-address and port that the rosenpass [AppServer](rosenpass::app_server::AppServer) should use. + /// The IP-address and port that the rosenpass [AppServer](rosenpass::app_server::AppServer) + /// should use. pub listen: Option, /// Other peers a connection should be initialized to pub peers: Vec, @@ -64,9 +66,9 @@ mod netlink { /// Creates a netlink named `link_name` and changes the state to up. It returns the index /// of the interface in the list of interfaces as the result or an error if any of the - ///operations of creating the link or changing its state to up fails. + /// operations of creating the link or changing its state to up fails. pub async fn link_create_and_up(rtnetlink: &Handle, link_name: String) -> Result { - // add the link, equivalent to `ip link add type wireguard` + // Add the link, equivalent to `ip link add type wireguard`. rtnetlink .link() .add() @@ -74,7 +76,7 @@ mod netlink { .execute() .await?; - // retrieve the link to be able to up it, equivalent to `ip link show` and then + // Retrieve the link to be able to up it, equivalent to `ip link show` and then // using the link shown that is identified by `link_name`. let link = rtnetlink .link() @@ -87,7 +89,7 @@ mod netlink { .0 .unwrap()?; - // up the link, equivalent to `ip link set dev up` + // Up the link, equivalent to `ip link set dev up`. rtnetlink .link() .set(link.header.index) @@ -133,7 +135,7 @@ mod netlink { use netlink_packet_generic::GenlMessage; use netlink_packet_wireguard::{Wireguard, WireguardCmd}; - // Scope our `set` command to only the device of the specified index + // Scope our `set` command to only the device of the specified index. attr.insert(0, WgDeviceAttrs::IfIndex(index)); // Construct the WireGuard-specific netlink packet @@ -142,12 +144,12 @@ mod netlink { nlas: attr, }; - // Construct final message + // Construct final message. let genl = GenlMessage::from_payload(wgc); let mut nlmsg = NetlinkMessage::from(genl); nlmsg.header.flags = NLM_F_REQUEST | NLM_F_ACK; - // Send and wait for the ACK or error + // Send and wait for the ACK or error. let (res, _) = genetlink.request(nlmsg).await?.into_future().await; if let Some(res) = res { let res = res?; @@ -215,7 +217,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { let link_name = options.dev.clone().unwrap_or("rosenpass0".to_string()); let link_index = netlink::link_create_and_up(&rtnetlink, link_name.clone()).await?; - // set up a list of (initiallc empty) cleanup handlers that are to be run if + // Set up a list of (initiallc empty) cleanup handlers that are to be run if // ctrl-c is hit or generally a `SIGINT` signal is received and always in the end. let cleanup_handlers = CleanupHandlers::new(); let final_cleanup_handlers = (&cleanup_handlers).clone(); @@ -233,8 +235,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { .expect("Failed to clean up"); })?; - // run `ip address add dev ` and enqueue - // `ip address del dev ` as a cleanup + // Run `ip address add dev ` and enqueue `ip address del dev ` as a cleanup. if let Some(ip) = options.ip { let dev = options.dev.clone().unwrap_or("rosenpass0".to_string()); Command::new("ip") @@ -260,7 +261,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { .await; } - // Deploy the classic wireguard private key + // Deploy the classic wireguard private key. let (connection, mut genetlink, _) = genetlink::new_connection()?; tokio::spawn(connection); @@ -309,7 +310,7 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { anyhow::Error::msg(format!("NativeUnixBrokerConfigBaseBuilderError: {:?}", e)) } - // configure everything per peer + // Configure everything per peer. for peer in options.peers { let wgpk = peer.public_keys_dir.join("wgpk"); let pqpk = peer.public_keys_dir.join("pqpk"); @@ -389,7 +390,8 @@ pub async fn exchange(options: ExchangeOptions) -> Result<()> { match out { Ok(_) => Ok(()), Err(e) => { - // Check if the returned error is actually EINTR, in which case, the run actually succeeded. + // Check if the returned error is actually EINTR, in which case, the run actually + // succeeded. let is_ok = if let Some(e) = e.root_cause().downcast_ref::() { matches!(e.kind(), std::io::ErrorKind::Interrupted) } else { diff --git a/rp/src/key.rs b/rp/src/key.rs index 4f98bb84..f061d058 100644 --- a/rp/src/key.rs +++ b/rp/src/key.rs @@ -104,11 +104,11 @@ pub fn pubkey(private_keys_dir: &Path, public_keys_dir: &Path) -> Result<()> { Public::from_slice(public.as_bytes()) }; - // store the wireguard public key + // Store the wireguard public key. wgpk.store_b64::(public_wgpk)?; wgpk.zeroize(); - // copy the pq-public key to the public directory + // Copy the pq-public key to the public directory. fs::copy(private_pqpk, public_pqpk)?; Ok(())