use std::io;
use futures_util::{SinkExt, StreamExt};
use tokio::sync::broadcast;
use tokio_util::codec::Framed;
use crate::auth::{AuthRetryMode, AuthType};
use crate::client_deny::ClientDeny;
use crate::codec::OvpnCodec;
use crate::command::{OvpnCommand, RemoteEntryRange};
use crate::kill_target::KillTarget;
use crate::message::{Notification, OvpnMessage};
use crate::need_ok::NeedOkResponse;
use crate::parsed_response::{self, LoadStats, StateEntry};
use crate::proxy_action::ProxyAction;
use crate::redacted::Redacted;
use crate::remote_action::RemoteAction;
use crate::signal::Signal;
use crate::status::{self, ClientStatistics, StatusResponse};
use crate::status_format::StatusFormat;
use crate::stream_mode::StreamMode;
use crate::version_info::VersionInfo;
#[derive(Debug, thiserror::Error)]
pub enum ClientError {
#[error("transport error: {0}")]
Io(#[from] io::Error),
#[error("connection closed while awaiting response")]
ConnectionClosed,
#[error("server error: {0}")]
ServerError(String),
#[error("unexpected response: {0:?}")]
UnexpectedResponse(OvpnMessage),
#[error("response parse error: {0}")]
ParseResponse(#[from] parsed_response::ParseResponseError),
#[error("status parse error: {0}")]
ParseStatus(#[from] status::ParseStatusError),
}
pub struct ManagementClient<T> {
framed: Framed<T, OvpnCodec>,
notification_tx: broadcast::Sender<Notification>,
}
impl<T> ManagementClient<T>
where
T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
{
pub fn new(
framed: Framed<T, OvpnCodec>,
notification_tx: broadcast::Sender<Notification>,
) -> Self {
Self {
framed,
notification_tx,
}
}
pub fn into_framed(self) -> Framed<T, OvpnCodec> {
self.framed
}
async fn recv_response(&mut self) -> Result<OvpnMessage, ClientError> {
loop {
let msg = self
.framed
.next()
.await
.ok_or(ClientError::ConnectionClosed)??;
match msg {
OvpnMessage::Notification(notification) => {
self.notification_tx
.send(notification)
.inspect_err(|error| {
tracing::debug!(%error, "no notification subscribers");
})
.ok();
}
other => return Ok(other),
}
}
}
async fn send_and_recv(&mut self, cmd: OvpnCommand) -> Result<OvpnMessage, ClientError> {
self.framed.send(cmd).await?;
self.recv_response().await
}
async fn send_expect_success(&mut self, cmd: OvpnCommand) -> Result<String, ClientError> {
match self.send_and_recv(cmd).await? {
OvpnMessage::Success(payload) => Ok(payload),
OvpnMessage::Error(msg) => Err(ClientError::ServerError(msg)),
other => Err(ClientError::UnexpectedResponse(other)),
}
}
async fn send_expect_multi_line(
&mut self,
cmd: OvpnCommand,
) -> Result<Vec<String>, ClientError> {
match self.send_and_recv(cmd).await? {
OvpnMessage::MultiLine(lines) => Ok(lines),
OvpnMessage::Error(msg) => Err(ClientError::ServerError(msg)),
other => Err(ClientError::UnexpectedResponse(other)),
}
}
async fn send_expect_ok(&mut self, cmd: OvpnCommand) -> Result<(), ClientError> {
self.send_expect_success(cmd).await?;
Ok(())
}
async fn send_stream_command(
&mut self,
mode: StreamMode,
cmd: OvpnCommand,
) -> Result<Option<Vec<String>>, ClientError> {
if mode.returns_history() {
Ok(Some(self.send_expect_multi_line(cmd).await?))
} else {
self.send_expect_ok(cmd).await?;
Ok(None)
}
}
pub async fn status_raw(&mut self, format: StatusFormat) -> Result<Vec<String>, ClientError> {
self.send_expect_multi_line(OvpnCommand::Status(format))
.await
}
pub async fn status(&mut self, format: StatusFormat) -> Result<StatusResponse, ClientError> {
let lines = self.status_raw(format).await?;
Ok(status::parse_status(&lines)?)
}
pub async fn client_statistics(
&mut self,
format: StatusFormat,
) -> Result<ClientStatistics, ClientError> {
let lines = self.status_raw(format).await?;
Ok(status::parse_client_statistics(&lines)?)
}
pub async fn state(&mut self) -> Result<Vec<StateEntry>, ClientError> {
let lines = self.send_expect_multi_line(OvpnCommand::State).await?;
Ok(parsed_response::parse_state_history(&lines)?)
}
pub async fn current_state(&mut self) -> Result<StateEntry, ClientError> {
let lines = self.send_expect_multi_line(OvpnCommand::State).await?;
Ok(parsed_response::parse_current_state(&lines)?)
}
pub async fn state_stream(
&mut self,
mode: StreamMode,
) -> Result<Option<Vec<StateEntry>>, ClientError> {
match self
.send_stream_command(mode, OvpnCommand::StateStream(mode))
.await?
{
Some(lines) => Ok(Some(parsed_response::parse_state_history(&lines)?)),
None => Ok(None),
}
}
pub async fn version(&mut self) -> Result<VersionInfo, ClientError> {
let lines = self.send_expect_multi_line(OvpnCommand::Version).await?;
Ok(parsed_response::parse_version(&lines))
}
pub async fn set_version(&mut self, version: u32) -> Result<(), ClientError> {
let cmd = OvpnCommand::SetVersion(version);
if version < 4 {
self.framed.send(cmd).await?;
Ok(())
} else {
self.send_expect_ok(cmd).await
}
}
pub async fn pid(&mut self) -> Result<u32, ClientError> {
let payload = self.send_expect_success(OvpnCommand::Pid).await?;
Ok(parsed_response::parse_pid(&payload)?)
}
pub async fn help(&mut self) -> Result<Vec<String>, ClientError> {
self.send_expect_multi_line(OvpnCommand::Help).await
}
pub async fn verb(&mut self, level: Option<u8>) -> Result<String, ClientError> {
self.send_expect_success(OvpnCommand::Verb(level)).await
}
pub async fn mute(&mut self, threshold: Option<u32>) -> Result<String, ClientError> {
self.send_expect_success(OvpnCommand::Mute(threshold)).await
}
pub async fn net(&mut self) -> Result<Vec<String>, ClientError> {
self.send_expect_multi_line(OvpnCommand::Net).await
}
pub async fn log(&mut self, mode: StreamMode) -> Result<Option<Vec<String>>, ClientError> {
self.send_stream_command(mode, OvpnCommand::Log(mode)).await
}
pub async fn echo(&mut self, mode: StreamMode) -> Result<Option<Vec<String>>, ClientError> {
self.send_stream_command(mode, OvpnCommand::Echo(mode))
.await
}
pub async fn bytecount(&mut self, interval: u32) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::ByteCount(interval)).await
}
pub async fn signal(&mut self, signal: Signal) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::Signal(signal)).await
}
pub async fn kill(&mut self, target: KillTarget) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::Kill(target)).await
}
pub async fn hold_query(&mut self) -> Result<bool, ClientError> {
let payload = self.send_expect_success(OvpnCommand::HoldQuery).await?;
Ok(parsed_response::parse_hold(&payload)?)
}
pub async fn hold_on(&mut self) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::HoldOn).await
}
pub async fn hold_off(&mut self) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::HoldOff).await
}
pub async fn hold_release(&mut self) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::HoldRelease).await
}
pub async fn username(
&mut self,
auth_type: AuthType,
value: impl Into<String>,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::Username {
auth_type,
value: Redacted::new(value.into()),
})
.await
}
pub async fn password(
&mut self,
auth_type: AuthType,
value: impl Into<String>,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::Password {
auth_type,
value: Redacted::new(value.into()),
})
.await
}
pub async fn auth_retry(&mut self, mode: AuthRetryMode) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::AuthRetry(mode)).await
}
pub async fn forget_passwords(&mut self) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::ForgetPasswords).await
}
pub async fn challenge_response(
&mut self,
state_id: impl Into<String>,
response: impl Into<String>,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::ChallengeResponse {
state_id: state_id.into(),
response: Redacted::new(response.into()),
})
.await
}
pub async fn static_challenge_response(
&mut self,
password_b64: impl Into<String>,
response_b64: impl Into<String>,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::StaticChallengeResponse {
password_b64: Redacted::new(password_b64.into()),
response_b64: Redacted::new(response_b64.into()),
})
.await
}
pub async fn cr_response(&mut self, response: impl Into<String>) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::CrResponse {
response: Redacted::new(response.into()),
})
.await
}
pub async fn need_ok(
&mut self,
name: impl Into<String>,
response: NeedOkResponse,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::NeedOk {
name: name.into(),
response,
})
.await
}
pub async fn need_str(
&mut self,
name: impl Into<String>,
value: impl Into<String>,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::NeedStr {
name: name.into(),
value: value.into(),
})
.await
}
pub async fn pkcs11_id_count(&mut self) -> Result<String, ClientError> {
self.send_expect_success(OvpnCommand::Pkcs11IdCount).await
}
pub async fn pkcs11_id_get(&mut self, index: u32) -> Result<String, ClientError> {
self.send_expect_success(OvpnCommand::Pkcs11IdGet(index))
.await
}
pub async fn rsa_sig(&mut self, base64_lines: Vec<String>) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::RsaSig { base64_lines })
.await
}
pub async fn pk_sig(&mut self, base64_lines: Vec<String>) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::PkSig { base64_lines })
.await
}
pub async fn certificate(&mut self, pem_lines: Vec<String>) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::Certificate { pem_lines })
.await
}
pub async fn client_auth(
&mut self,
cid: u64,
kid: u64,
config_lines: Vec<String>,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::ClientAuth {
cid,
kid,
config_lines,
})
.await
}
pub async fn client_auth_nt(&mut self, cid: u64, kid: u64) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::ClientAuthNt { cid, kid })
.await
}
pub async fn client_deny(&mut self, deny: ClientDeny) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::ClientDeny(deny)).await
}
pub async fn client_kill(
&mut self,
cid: u64,
message: Option<String>,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::ClientKill { cid, message })
.await
}
pub async fn client_pending_auth(
&mut self,
cid: u64,
kid: u64,
extra: impl Into<String>,
timeout: u32,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::ClientPendingAuth {
cid,
kid,
extra: extra.into(),
timeout,
})
.await
}
pub async fn remote(&mut self, action: RemoteAction) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::Remote(action)).await
}
pub async fn proxy(&mut self, action: ProxyAction) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::Proxy(action)).await
}
pub async fn load_stats(&mut self) -> Result<LoadStats, ClientError> {
let payload = self.send_expect_success(OvpnCommand::LoadStats).await?;
Ok(parsed_response::parse_load_stats(&payload)?)
}
pub async fn env_filter(&mut self, level: u32) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::EnvFilter(level)).await
}
pub async fn remote_entry_count(&mut self) -> Result<Vec<String>, ClientError> {
self.send_expect_multi_line(OvpnCommand::RemoteEntryCount)
.await
}
pub async fn remote_entry_get(
&mut self,
range: RemoteEntryRange,
) -> Result<Vec<String>, ClientError> {
self.send_expect_multi_line(OvpnCommand::RemoteEntryGet(range))
.await
}
pub async fn push_update_broad(
&mut self,
options: impl Into<String>,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::PushUpdateBroad {
options: options.into(),
})
.await
}
pub async fn push_update_cid(
&mut self,
cid: u64,
options: impl Into<String>,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::PushUpdateCid {
cid,
options: options.into(),
})
.await
}
pub async fn management_password(
&mut self,
password: impl Into<String>,
) -> Result<(), ClientError> {
self.send_expect_ok(OvpnCommand::ManagementPassword(Redacted::new(
password.into(),
)))
.await
}
pub async fn exit(mut self) -> Result<(), ClientError> {
self.framed.send(OvpnCommand::Exit).await?;
Ok(())
}
pub async fn raw(&mut self, command: impl Into<String>) -> Result<String, ClientError> {
self.send_expect_success(OvpnCommand::Raw(command.into()))
.await
}
pub async fn raw_multi_line(
&mut self,
command: impl Into<String>,
) -> Result<Vec<String>, ClientError> {
self.send_expect_multi_line(OvpnCommand::RawMultiLine(command.into()))
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::io::{AsyncReadExt, AsyncWriteExt, DuplexStream};
fn mock_client() -> (
ManagementClient<DuplexStream>,
broadcast::Sender<Notification>,
DuplexStream,
) {
let (client_stream, server_stream) = tokio::io::duplex(4096);
let framed = Framed::new(client_stream, OvpnCodec::new());
let (notification_tx, _) = broadcast::channel(64);
let client = ManagementClient::new(framed, notification_tx.clone());
(client, notification_tx, server_stream)
}
async fn server_respond(server: &mut DuplexStream, lines: &[&str]) {
for line in lines {
server.write_all(line.as_bytes()).await.unwrap();
server.write_all(b"\r\n").await.unwrap();
}
}
#[tokio::test]
async fn pid_returns_parsed_value() {
let (mut client, _notif, mut server) = mock_client();
let handle = tokio::spawn(async move {
let mut buf = vec![0u8; 64];
let _n = server.read(&mut buf).await.unwrap();
server_respond(&mut server, &["SUCCESS: pid=42"]).await;
server
});
let pid = client.pid().await.unwrap();
assert_eq!(pid, 42);
handle.await.unwrap();
}
#[tokio::test]
async fn notifications_forwarded_during_command() {
let (mut client, notif_tx, mut server) = mock_client();
let mut rx = notif_tx.subscribe();
let handle = tokio::spawn(async move {
let mut buf = vec![0u8; 64];
let _n = server.read(&mut buf).await.unwrap();
server_respond(&mut server, &[">BYTECOUNT:1024,2048", "SUCCESS: pid=99"]).await;
server
});
let pid = client.pid().await.unwrap();
assert_eq!(pid, 99);
let notif = rx.try_recv().unwrap();
assert!(
matches!(
notif,
Notification::ByteCount {
bytes_in: 1024,
bytes_out: 2048
}
),
"expected ByteCount, got {notif:?}"
);
handle.await.unwrap();
}
#[tokio::test]
async fn server_error_maps_to_client_error() {
let (mut client, _notif, mut server) = mock_client();
let handle = tokio::spawn(async move {
let mut buf = vec![0u8; 64];
let _n = server.read(&mut buf).await.unwrap();
server_respond(&mut server, &["ERROR: command not allowed"]).await;
server
});
let err = client.hold_release().await.unwrap_err();
assert!(
matches!(&err, ClientError::ServerError(msg) if msg == "command not allowed"),
"expected ServerError, got {err:?}"
);
handle.await.unwrap();
}
#[tokio::test]
async fn version_returns_parsed_info() {
let (mut client, _notif, mut server) = mock_client();
let handle = tokio::spawn(async move {
let mut buf = vec![0u8; 64];
let _n = server.read(&mut buf).await.unwrap();
server_respond(
&mut server,
&[
"OpenVPN Version: OpenVPN 2.6.9 x86_64-pc-linux-gnu",
"Management Interface Version: 5",
"END",
],
)
.await;
server
});
let info = client.version().await.unwrap();
assert_eq!(info.management_version(), Some(5));
assert!(info.openvpn_version_line().unwrap().contains("2.6.9"));
handle.await.unwrap();
}
#[tokio::test]
async fn connection_closed_returns_error() {
let (mut client, _notif, server) = mock_client();
drop(server);
let err = client.pid().await.unwrap_err();
assert!(
matches!(&err, ClientError::ConnectionClosed | ClientError::Io(_)),
"expected connection error, got {err:?}"
);
}
#[tokio::test]
async fn multiple_notification_subscribers() {
let (mut client, notif_tx, mut server) = mock_client();
let mut rx1 = notif_tx.subscribe();
let mut rx2 = notif_tx.subscribe();
let handle = tokio::spawn(async move {
let mut buf = vec![0u8; 64];
let _n = server.read(&mut buf).await.unwrap();
server_respond(
&mut server,
&[">HOLD:Waiting for hold release:5", "SUCCESS: pid=1"],
)
.await;
server
});
let pid = client.pid().await.unwrap();
assert_eq!(pid, 1);
let n1 = rx1.try_recv().unwrap();
let n2 = rx2.try_recv().unwrap();
assert!(matches!(n1, Notification::Hold { .. }));
assert!(matches!(n2, Notification::Hold { .. }));
handle.await.unwrap();
}
#[tokio::test]
async fn load_stats_parsed() {
let (mut client, _notif, mut server) = mock_client();
let handle = tokio::spawn(async move {
let mut buf = vec![0u8; 64];
let _n = server.read(&mut buf).await.unwrap();
server_respond(
&mut server,
&["SUCCESS: nclients=3,bytesin=100000,bytesout=50000"],
)
.await;
server
});
let stats = client.load_stats().await.unwrap();
assert_eq!(stats.nclients, 3);
assert_eq!(stats.bytesin, 100_000);
assert_eq!(stats.bytesout, 50_000);
handle.await.unwrap();
}
#[tokio::test]
async fn hold_query_parsed() {
let (mut client, _notif, mut server) = mock_client();
let handle = tokio::spawn(async move {
let mut buf = vec![0u8; 64];
let _n = server.read(&mut buf).await.unwrap();
server_respond(&mut server, &["SUCCESS: hold=1"]).await;
server
});
assert!(client.hold_query().await.unwrap());
handle.await.unwrap();
}
}