feat(rosenpass): Add wireguard-broker interface in AppServer (#303)

Dynamically dispatch WireguardBrokerMio trait in AppServer. Also allows for mio event registration and poll processing, logic from dev/broker-architecture branch

Co-authored-by: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com>
Co-authored-by: Karolin Varner <karo@cupdev.net>
This commit is contained in:
Prabhpreet Dua
2024-05-20 18:12:42 +05:30
committed by GitHub
parent ae7577c641
commit c1abfbfd14
26 changed files with 693 additions and 177 deletions

View File

@@ -1,11 +1,17 @@
use std::borrow::BorrowMut;
use std::{borrow::BorrowMut, fmt::Debug};
use crate::{
api::msgs::{self, REQUEST_MSG_BUFFER_SIZE},
WireGuardBroker,
api::{
config::NetworkBrokerConfig,
msgs::{self, REQUEST_MSG_BUFFER_SIZE},
},
SerializedBrokerConfig, WireGuardBroker,
};
use super::msgs::{Envelope, SetPskResponse};
use super::{
config::NetworkBrokerConfigErr,
msgs::{Envelope, SetPskResponse},
};
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerClientPollResponseError<RecvError> {
@@ -34,6 +40,8 @@ fn invalid_msg_poller<RecvError>() -> BrokerClientPollResponseError<RecvError> {
pub enum BrokerClientSetPskError<SendError> {
#[error("Error with encoding/decoding message")]
MsgError,
#[error("Network Broker Config error: {0}")]
BrokerError(NetworkBrokerConfigErr),
#[error(transparent)]
IoError(SendError),
#[error("Interface name out of bounds")]
@@ -51,14 +59,14 @@ pub trait BrokerClientIo {
#[derive(Debug)]
pub struct BrokerClient<Io>
where
Io: BrokerClientIo,
Io: BrokerClientIo + Debug,
{
io: Io,
}
impl<Io> BrokerClient<Io>
where
Io: BrokerClientIo,
Io: BrokerClientIo + Debug,
{
pub fn new(io: Io) -> Self {
Self { io }
@@ -99,16 +107,14 @@ where
impl<Io> WireGuardBroker for BrokerClient<Io>
where
Io: BrokerClientIo,
Io: BrokerClientIo + Debug,
{
type Error = BrokerClientSetPskError<Io::SendError>;
fn set_psk(
&mut self,
iface: &str,
peer_id: [u8; 32],
psk: [u8; 32],
) -> Result<(), Self::Error> {
fn set_psk(&mut self, config: SerializedBrokerConfig) -> Result<(), Self::Error> {
let config: Result<NetworkBrokerConfig, NetworkBrokerConfigErr> = config.try_into();
let config = config.map_err(|e| BrokerClientSetPskError::BrokerError(e))?;
use BrokerClientSetPskError::*;
const BUF_SIZE: usize = REQUEST_MSG_BUFFER_SIZE;
@@ -126,9 +132,10 @@ where
let req = &mut req.payload;
// Populate payload
req.peer_id.copy_from_slice(&peer_id);
req.psk.copy_from_slice(&psk);
req.set_iface(iface).ok_or(IfaceOutOfBounds)?;
req.peer_id.copy_from_slice(&config.peer_id.value);
req.psk.copy_from_slice(config.psk.secret());
req.set_iface(config.iface.as_ref())
.ok_or(IfaceOutOfBounds)?;
}
// Send message

View File

@@ -0,0 +1,43 @@
use crate::{SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
use derive_builder::Builder;
use rosenpass_secret_memory::{Public, Secret};
#[derive(Builder)]
#[builder(pattern = "mutable")]
//TODO: Use generics for iface, add additional params
pub struct NetworkBrokerConfig<'a> {
pub iface: &'a str,
pub peer_id: &'a Public<WG_PEER_LEN>,
pub psk: &'a Secret<WG_KEY_LEN>,
}
impl<'a> Into<SerializedBrokerConfig<'a>> for NetworkBrokerConfig<'a> {
fn into(self) -> SerializedBrokerConfig<'a> {
SerializedBrokerConfig {
interface: self.iface.as_bytes(),
peer_id: self.peer_id,
psk: self.psk,
additional_params: &[],
}
}
}
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum NetworkBrokerConfigErr {
#[error("Interface")]
Interface,
}
impl<'a> TryFrom<SerializedBrokerConfig<'a>> for NetworkBrokerConfig<'a> {
type Error = NetworkBrokerConfigErr;
fn try_from(value: SerializedBrokerConfig<'a>) -> Result<Self, Self::Error> {
let iface =
std::str::from_utf8(value.interface).map_err(|_| NetworkBrokerConfigErr::Interface)?;
Ok(Self {
iface,
peer_id: value.peer_id,
psk: value.psk,
})
}
}

View File

@@ -1,4 +1,4 @@
pub mod client;
pub mod mio_client;
pub mod config;
pub mod msgs;
pub mod server;

View File

@@ -1,15 +1,21 @@
use std::borrow::BorrowMut;
use std::result::Result;
use rosenpass_secret_memory::{Public, Secret};
use crate::api::msgs::{self, Envelope, SetPskRequest, SetPskResponse};
use crate::WireGuardBroker;
use super::config::{NetworkBrokerConfigBuilder, NetworkBrokerConfigErr};
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerServerError {
#[error("No such request type: {}", .0)]
NoSuchRequestType(u8),
#[error("Invalid message received.")]
InvalidMessage,
#[error("Network Broker Config error: {0}")]
BrokerError(NetworkBrokerConfigErr),
}
impl From<msgs::InvalidMessageTypeError> for BrokerServerError {
@@ -21,16 +27,16 @@ impl From<msgs::InvalidMessageTypeError> for BrokerServerError {
pub struct BrokerServer<Err, Inner>
where
msgs::SetPskError: From<Err>,
Inner: WireGuardBroker<Error = Err>,
msgs::SetPskError: From<Err>,
{
inner: Inner,
}
impl<Err, Inner> BrokerServer<Err, Inner>
where
msgs::SetPskError: From<Err>,
Inner: WireGuardBroker<Error = Err>,
msgs::SetPskError: From<Err>,
{
pub fn new(inner: Inner) -> Self {
Self { inner }
@@ -64,12 +70,20 @@ where
) -> Result<(), BrokerServerError> {
// Using unwrap here since lenses can not return fixed-size arrays
// TODO: Slices should give access to fixed size arrays
let r: Result<(), Err> = self.inner.borrow_mut().set_psk(
req.iface()
.map_err(|_e| BrokerServerError::InvalidMessage)?,
req.peer_id.try_into().unwrap(),
req.psk.try_into().unwrap(),
);
let peer_id = Public::from_slice(&req.peer_id);
let psk = Secret::from_slice(&req.psk);
let interface = req
.iface()
.map_err(|_e| BrokerServerError::InvalidMessage)?;
let config = NetworkBrokerConfigBuilder::default()
.peer_id(&peer_id)
.psk(&psk)
.iface(interface)
.build()
.unwrap();
let r: Result<(), Err> = self.inner.borrow_mut().set_psk(config.into());
let r: msgs::SetPskResult = r.map_err(|e| e.into());
let r: msgs::SetPskResponseReturnCode = r.into();
res.return_code = r as u8;

View File

@@ -3,7 +3,7 @@ use std::result::Result;
use rosenpass_wireguard_broker::api::msgs;
use rosenpass_wireguard_broker::api::server::BrokerServer;
use rosenpass_wireguard_broker::netlink as wg;
use rosenpass_wireguard_broker::brokers::netlink as wg;
#[derive(thiserror::Error, Debug)]
pub enum BrokerAppError {

View File

@@ -1,14 +1,14 @@
use anyhow::{bail, ensure};
use mio::Interest;
use std::collections::VecDeque;
use std::io::{ErrorKind, Read, Write};
use anyhow::{bail, ensure};
use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerMio};
use crate::WireGuardBroker;
use super::client::{
use crate::api::client::{
BrokerClient, BrokerClientIo, BrokerClientPollResponseError, BrokerClientSetPskError,
};
use super::msgs::{self, RESPONSE_MSG_BUFFER_SIZE};
use crate::api::msgs::{self, RESPONSE_MSG_BUFFER_SIZE};
#[derive(Debug)]
pub struct MioBrokerClient {
@@ -47,7 +47,7 @@ impl MioBrokerClient {
Self { inner }
}
pub fn poll(&mut self) -> anyhow::Result<Option<msgs::SetPskResult>> {
fn poll(&mut self) -> anyhow::Result<Option<msgs::SetPskResult>> {
self.inner.io_mut().flush()?;
// This sucks
@@ -68,18 +68,46 @@ impl MioBrokerClient {
impl WireGuardBroker for MioBrokerClient {
type Error = anyhow::Error;
fn set_psk(&mut self, iface: &str, peer_id: [u8; 32], psk: [u8; 32]) -> anyhow::Result<()> {
fn set_psk<'a>(&mut self, config: SerializedBrokerConfig<'a>) -> anyhow::Result<()> {
use BrokerClientSetPskError::*;
let e = self.inner.set_psk(iface, peer_id, psk);
let e = self.inner.set_psk(config);
match e {
Ok(()) => Ok(()),
Err(IoError(e)) => Err(e),
Err(IfaceOutOfBounds) => bail!("Interface name size is out of bounds."),
Err(MsgError) => bail!("Error with encoding/decoding message."),
Err(BrokerError(e)) => bail!("Broker error: {:?}", e),
}
}
}
impl WireguardBrokerMio for MioBrokerClient {
type MioError = anyhow::Error;
fn register(
&mut self,
registry: &mio::Registry,
token: mio::Token,
) -> Result<(), Self::MioError> {
registry.register(
&mut self.inner.io_mut().socket,
token,
Interest::READABLE | Interest::WRITABLE,
)?;
Ok(())
}
fn process_poll(&mut self) -> Result<(), Self::MioError> {
self.poll()?;
Ok(())
}
fn unregister(&mut self, registry: &mio::Registry) -> Result<(), Self::MioError> {
registry.deregister(&mut self.inner.io_mut().socket)?;
Ok(())
}
}
impl BrokerClientIo for MioBrokerClientIo {
type SendError = anyhow::Error;
type RecvError = anyhow::Error;

View File

@@ -0,0 +1,6 @@
#[cfg(feature = "enable_broker_api")]
pub mod mio_client;
#[cfg(feature = "enable_broker_api")]
pub mod netlink;
pub mod native_unix;

View File

@@ -0,0 +1,177 @@
use std::fmt::Debug;
use std::process::{Command, Stdio};
use std::thread;
use derive_builder::Builder;
use log::{debug, error};
use postcard::{from_bytes, to_allocvec};
use rosenpass_secret_memory::{Public, Secret};
use rosenpass_util::b64::b64_decode;
use rosenpass_util::{b64::B64Display, file::StoreValueB64Writer};
use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerCfg, WireguardBrokerMio};
use crate::{WG_KEY_LEN, WG_PEER_LEN};
const MAX_B64_KEY_SIZE: usize = WG_KEY_LEN * 5 / 3;
const MAX_B64_PEER_ID_SIZE: usize = WG_PEER_LEN * 5 / 3;
#[derive(Debug)]
pub struct NativeUnixBroker {}
impl Default for NativeUnixBroker {
fn default() -> Self {
Self::new()
}
}
impl NativeUnixBroker {
pub fn new() -> Self {
Self {}
}
}
impl WireGuardBroker for NativeUnixBroker {
type Error = anyhow::Error;
fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> Result<(), Self::Error> {
let config: NativeUnixBrokerConfig = config.try_into()?;
let peer_id = format!("{}", config.peer_id.fmt_b64::<MAX_B64_PEER_ID_SIZE>());
let mut child = match Command::new("wg")
.arg("set")
.arg(config.interface)
.arg("peer")
.arg(peer_id)
.arg("preshared-key")
.arg("/dev/stdin")
.stdin(Stdio::piped())
.args(config.extra_params)
.spawn()
{
Ok(x) => x,
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
anyhow::bail!("Could not find wg command");
} else {
return Err(anyhow::Error::new(e));
}
}
};
if let Err(e) = config
.psk
.store_b64_writer::<MAX_B64_KEY_SIZE, _>(child.stdin.take().unwrap())
{
error!("could not write psk to wg: {:?}", e);
}
thread::spawn(move || {
let status = child.wait();
if let Ok(status) = status {
if status.success() {
debug!("successfully passed psk to wg")
} else {
error!("could not pass psk to wg {:?}", status)
}
} else {
error!("wait failed: {:?}", status)
}
});
Ok(())
}
}
impl WireguardBrokerMio for NativeUnixBroker {
type MioError = anyhow::Error;
fn register(
&mut self,
_registry: &mio::Registry,
_token: mio::Token,
) -> Result<(), Self::MioError> {
Ok(())
}
fn process_poll(&mut self) -> Result<(), Self::MioError> {
Ok(())
}
fn unregister(&mut self, _registry: &mio::Registry) -> Result<(), Self::MioError> {
Ok(())
}
}
#[derive(Debug, Builder)]
#[builder(pattern = "mutable")]
pub struct NativeUnixBrokerConfigBase {
pub interface: String,
pub peer_id: Public<WG_PEER_LEN>,
#[builder(private)]
pub extra_params: Vec<u8>,
}
impl NativeUnixBrokerConfigBaseBuilder {
pub fn peer_id_b64(
&mut self,
peer_id: &str,
) -> Result<&mut Self, NativeUnixBrokerConfigBaseBuilderError> {
let mut peer_id_b64 = Public::<WG_PEER_LEN>::zero();
b64_decode(peer_id.as_bytes(), &mut peer_id_b64.value).map_err(|_e| {
NativeUnixBrokerConfigBaseBuilderError::ValidationError(
"Failed to parse peer id b64".to_string(),
)
})?;
Ok(self.peer_id(peer_id_b64))
}
pub fn extra_params_ser(
&mut self,
extra_params: &Vec<String>,
) -> Result<&mut Self, NativeUnixBrokerConfigBuilderError> {
let params = to_allocvec(extra_params).map_err(|_e| {
NativeUnixBrokerConfigBuilderError::ValidationError(
"Failed to parse extra params".to_string(),
)
})?;
Ok(self.extra_params(params))
}
}
impl WireguardBrokerCfg for NativeUnixBrokerConfigBase {
fn create_config<'a>(&'a self, psk: &'a Secret<WG_KEY_LEN>) -> SerializedBrokerConfig<'a> {
SerializedBrokerConfig {
interface: self.interface.as_bytes(),
peer_id: &self.peer_id,
psk,
additional_params: &self.extra_params,
}
}
}
#[derive(Debug, Builder)]
#[builder(pattern = "mutable")]
pub struct NativeUnixBrokerConfig<'a> {
pub interface: &'a str,
pub peer_id: &'a Public<WG_PEER_LEN>,
pub psk: &'a Secret<WG_KEY_LEN>,
pub extra_params: Vec<String>,
}
impl<'a> TryFrom<SerializedBrokerConfig<'a>> for NativeUnixBrokerConfig<'a> {
type Error = anyhow::Error;
fn try_from(value: SerializedBrokerConfig<'a>) -> Result<Self, Self::Error> {
let iface = std::str::from_utf8(value.interface)
.map_err(|_| anyhow::Error::msg("Interface UTF8 decoding error"))?;
let extra_params: Vec<String> =
from_bytes(value.additional_params).map_err(anyhow::Error::new)?;
Ok(Self {
interface: iface,
peer_id: value.peer_id,
psk: value.psk,
extra_params,
})
}
}

View File

@@ -1,7 +1,10 @@
use std::fmt::Debug;
use wireguard_uapi::linux as wg;
use crate::api::config::NetworkBrokerConfig;
use crate::api::msgs;
use crate::WireGuardBroker;
use crate::{SerializedBrokerConfig, WireGuardBroker};
#[derive(thiserror::Error, Debug)]
pub enum ConnectError {
@@ -61,39 +64,45 @@ impl NetlinkWireGuardBroker {
}
}
impl Debug for NetlinkWireGuardBroker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
//TODO: Add useful info in Debug
f.debug_struct("NetlinkWireGuardBroker").finish()
}
}
impl WireGuardBroker for NetlinkWireGuardBroker {
type Error = SetPskError;
fn set_psk(
&mut self,
interface: &str,
peer_id: [u8; 32],
psk: [u8; 32],
) -> Result<(), Self::Error> {
fn set_psk(&mut self, config: SerializedBrokerConfig) -> Result<(), Self::Error> {
let config: NetworkBrokerConfig = config
.try_into()
.map_err(|e| SetPskError::NoSuchInterface)?;
// Ensure that the peer exists by querying the device configuration
// TODO: Use InvalidInterfaceError
let state = self
.sock
.get_device(wg::DeviceInterface::from_name(interface.to_owned()))?;
.get_device(wg::DeviceInterface::from_name(config.iface))?;
if state
.peers
.iter()
.find(|p| &p.public_key == &peer_id)
.find(|p| &p.public_key == &config.peer_id.value)
.is_none()
{
return Err(SetPskError::NoSuchPeer);
}
// Peer update description
let mut set_peer = wireguard_uapi::set::Peer::from_public_key(&peer_id);
let mut set_peer = wireguard_uapi::set::Peer::from_public_key(&config.peer_id);
set_peer
.flags
.push(wireguard_uapi::linux::set::WgPeerF::UpdateOnly);
set_peer.preshared_key = Some(&psk);
set_peer.preshared_key = Some(&config.psk.secret());
// Device update description
let mut set_dev = wireguard_uapi::set::Device::from_ifname(interface.to_owned());
let mut set_dev = wireguard_uapi::set::Device::from_ifname(config.iface);
set_dev.peers.push(set_peer);
self.sock.set_device(set_dev)?;

View File

@@ -1,19 +1,40 @@
#[cfg(feature = "enable_broker")]
use std::result::Result;
use rosenpass_secret_memory::{Public, Secret};
use std::{fmt::Debug, result::Result};
#[cfg(feature = "enable_broker")]
pub trait WireGuardBroker {
pub const WG_KEY_LEN: usize = 32;
pub const WG_PEER_LEN: usize = 32;
pub trait WireGuardBroker: Debug {
type Error;
fn set_psk(
&mut self,
interface: &str,
peer_id: [u8; 32],
psk: [u8; 32],
) -> Result<(), Self::Error>;
fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> Result<(), Self::Error>;
}
#[cfg(feature = "enable_broker")]
pub trait WireguardBrokerCfg: Debug {
fn create_config<'a>(&'a self, psk: &'a Secret<WG_KEY_LEN>) -> SerializedBrokerConfig<'a>;
}
#[derive(Debug)]
pub struct SerializedBrokerConfig<'a> {
pub interface: &'a [u8],
pub peer_id: &'a Public<WG_PEER_LEN>,
pub psk: &'a Secret<WG_KEY_LEN>,
pub additional_params: &'a [u8],
}
pub trait WireguardBrokerMio: WireGuardBroker {
type MioError;
/// Register interested events for mio::Registry
fn register(
&mut self,
registry: &mio::Registry,
token: mio::Token,
) -> Result<(), Self::MioError>;
/// Run after a mio::poll operation
fn process_poll(&mut self) -> Result<(), Self::MioError>;
fn unregister(&mut self, registry: &mio::Registry) -> Result<(), Self::MioError>;
}
#[cfg(feature = "enable_broker_api")]
pub mod api;
#[cfg(feature = "enable_broker")]
pub mod netlink;
pub mod brokers;