openvpn3_rs/helpers/
session.rs

1//! Provides an interface to communicate with the OpenVPN 3 sessions D-Bus API.
2
3use crate::{
4    proxy::sessions_node::{AttentionRequiredStream, LogStream, StatusChangeStream},
5    sessions_node::{
6        constants::{ClientAttentionGroup, ClientAttentionType},
7        result::{Statistics, Status, UserInputQueueTypeGroup},
8    },
9    Error, Result, SessionsNodeProxy,
10};
11
12use zbus::{
13    zvariant::{ObjectPath, OwnedObjectPath, OwnedValue},
14    CacheProperties, Connection,
15};
16
17/// OpenVPN 3 Session
18#[derive(Clone, Debug)]
19pub struct Session<'a> {
20    pub(crate) proxy: SessionsNodeProxy<'a>,
21}
22
23impl<'a> Session<'a> {
24    const DBUS_INTERFACE: &'static str = "net.openvpn.v3.sessions";
25
26    /// Constructs a new [Session] that represents a single OpenVPN 3 VPN session through the D-Bus API.
27    pub(crate) async fn new(
28        conn: Connection,
29        session_path: OwnedObjectPath,
30    ) -> Result<Session<'a>> {
31        let proxy = SessionsNodeProxy::builder(&conn)
32            .destination(Self::DBUS_INTERFACE)?
33            .path(session_path.clone())?
34            .cache_properties(CacheProperties::No)
35            .build()
36            .await?;
37
38        Ok(Self { proxy })
39    }
40
41    /// Get a reference to the underlying proxy's object path.
42    pub fn path(&'a self) -> &ObjectPath {
43        self.proxy.path()
44    }
45
46    pub async fn ready(&'a self) -> Result<()> {
47        Ok(self.proxy.ready().await.map_err(|err| {
48            let err_str = err.to_string();
49
50            if err_str.contains("net.openvpn.v3.sessions.error: Backend VPN process is not ready") {
51                Error::BackendNotReady
52            } else if err_str.contains("net.openvpn.v3.error.ready: Missing user credentials") {
53                Error::MissingUserCredentials
54            } else {
55                Error::Zbus(err)
56            }
57        })?)
58    }
59
60    /// Start the connection process.
61    pub async fn connect(&'a self) -> Result<()> {
62        Ok(self.proxy.connect().await?)
63    }
64
65    /// Pause an active connection.
66    pub async fn pause(&'a self, reason: &str) -> Result<()> {
67        Ok(self.proxy.pause(reason).await?)
68    }
69
70    /// Resume a paused connection.
71    pub async fn resume(&'a self) -> Result<()> {
72        Ok(self.proxy.resume().await?)
73    }
74
75    /// Disconnect and reconnect.
76    pub async fn restart(&'a self) -> Result<()> {
77        Ok(self.proxy.restart().await?)
78    }
79
80    /// Disconnect and remove the VPN session.
81    pub async fn disconnect(&'a self) -> Result<()> {
82        Ok(self.proxy.disconnect().await?)
83    }
84
85    /// Get the last processed [StatusChange] signal.
86    pub async fn status(&'a self) -> Result<Status> {
87        Ok(self.proxy.status().await?)
88    }
89
90    /// Get tunnel statistics.
91    pub async fn statistics(&'a self) -> Result<Statistics> {
92        Ok(self.proxy.statistics().await?)
93    }
94
95    /// Get a property value from the underlying D-Bus proxy.
96    pub async fn get_property<T>(&'a self, property_name: &str) -> Result<T>
97    where
98        T: TryFrom<OwnedValue>,
99        T::Error: Into<zbus::Error>,
100    {
101        Ok(self.proxy.get_property(property_name).await?)
102    }
103
104    pub async fn user_input_queue_get_type_group(&'a self) -> Result<Vec<UserInputQueueTypeGroup>> {
105        Ok(self.proxy.user_input_queue_get_type_group().await?)
106    }
107
108    pub async fn user_input_queue_check(
109        &'a self,
110        qtype: ClientAttentionType,
111        qgroup: ClientAttentionGroup,
112    ) -> Result<Vec<u32>> {
113        Ok(self.proxy.user_input_queue_check(qtype, qgroup).await?)
114    }
115
116    /// Fetch a [UserInputSlot] which represents a request for user input and which can be used to provide input to the backend.
117    ///
118    /// # Arguments
119    ///
120    /// * `qtype` - Queue type of the user input slot.
121    /// * `qgroup` - Queue group of the user input slot.
122    /// * `qid` - Queue ID of the user input slot to retrieve.
123    ///
124    /// # Returns
125    ///
126    /// A [UserInputSlot] which provides information on what input to query for.
127    pub async fn user_input_queue_fetch(
128        &'a self,
129        qtype: ClientAttentionType,
130        qgroup: ClientAttentionGroup,
131        qid: u32,
132    ) -> Result<UserInputSlot<'a>> {
133        Ok(UserInputSlot::new(&self.proxy, qtype, qgroup, qid).await?)
134    }
135
136    /// Fetch all required user inputs.
137    ///
138    /// # Returns
139    ///
140    /// An array of [UserInputSlot] instances which represent single requests for input and can be used to provide input to the backend.
141    pub async fn fetch_user_input_slots(&'a self) -> Result<Vec<UserInputSlot>> {
142        let mut slots = Vec::new();
143
144        for (qtype, qgroup) in self.user_input_queue_get_type_group().await? {
145            for qid in self.user_input_queue_check(qtype, qgroup).await? {
146                slots.push(UserInputSlot::new(&self.proxy, qtype, qgroup, qid).await?)
147            }
148        }
149
150        Ok(slots)
151    }
152
153    /// Get a [AttentionRequiredStream] for this VPN session.
154    pub async fn attention_required_stream(&self) -> Result<AttentionRequiredStream<'a>> {
155        Ok(self.proxy.receive_attention_required().await?)
156    }
157
158    /// Get a [StatusChangeStream] for this VPN session.
159    pub async fn status_change_stream(&self) -> Result<StatusChangeStream<'a>> {
160        Ok(self.proxy.receive_status_change().await?)
161    }
162
163    /// Get a [LogStream] for this VPN session.
164    ///
165    /// This should be called after the backend process is ready
166    pub async fn log_stream(&self) -> Result<LogStream<'a>> {
167        self.proxy.log_forward(true).await?;
168        Ok(self.proxy.receive_log().await?)
169    }
170}
171
172/// User Input Slot
173///
174/// Represents a single request for user input by the backend VPN process.
175pub struct UserInputSlot<'a> {
176    proxy: &'a SessionsNodeProxy<'a>,
177    qtype: ClientAttentionType,
178    qgroup: ClientAttentionGroup,
179    qid: u32,
180    variable_name: String,
181    label: String,
182    mask: bool,
183}
184
185impl<'a> UserInputSlot<'a> {
186    /// Construct a [UserInputSlot]
187    ///
188    /// # Arguments
189    ///
190    /// * `proxy` - [SessionsNodeProxy] object for the related VPN session.
191    /// * `qtype` - [ClientAttentionType] of the request.
192    /// * `qgroup` - [ClientAttentionGroup] of the request.
193    /// * `qid` - Unique ID for this request.
194    pub async fn new(
195        proxy: &'a SessionsNodeProxy<'_>,
196        qtype: ClientAttentionType,
197        qgroup: ClientAttentionGroup,
198        qid: u32,
199    ) -> Result<UserInputSlot<'a>> {
200        let qslot = proxy.user_input_queue_fetch(qtype, qgroup, qid).await?;
201
202        // Sanity check
203        if qtype != qslot.0 || qgroup != qslot.1 || qid != qslot.2 {
204            return Err(Error::UserInputSlotMismatch);
205        }
206
207        Ok(Self {
208            proxy,
209            qtype,
210            qgroup,
211            qid,
212            variable_name: qslot.3,
213            label: qslot.4,
214            mask: qslot.5,
215        })
216    }
217
218    /// Provide input to the backend for this request.
219    ///
220    /// # Arguments
221    ///
222    /// * `value` - Input value.
223    pub async fn provide_input(&'a self, value: &str) -> Result<()> {
224        Ok(self
225            .proxy
226            .user_input_provide(self.qtype, self.qgroup, self.qid, value)
227            .await?)
228    }
229
230    /// A tuple consisting of this request's [ClientAttentionType] and [ClientAttentionGroup].
231    pub fn type_group(&'a self) -> UserInputQueueTypeGroup {
232        (self.qtype, self.qgroup)
233    }
234
235    /// Internal variable name.
236    pub fn variable_name(&'a self) -> &str {
237        &self.variable_name
238    }
239
240    /// A description to present to the user.
241    pub fn label(&'a self) -> &str {
242        &self.label
243    }
244
245    /// Should the user's input be masked/hidden?
246    pub fn input_mask(&'a self) -> bool {
247        self.mask
248    }
249}