diff options
| author | David Lönnhager <david.l@mullvad.net> | 2020-05-25 18:41:13 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2020-06-02 11:19:49 +0200 |
| commit | 4f4d75749bfd61b61ebd8f28e020e305c1b389da (patch) | |
| tree | ae47f4c76bc09cd20c0e05bb51ff37b6de2809b8 /talpid-core/src | |
| parent | bae528b847e13faf0ea745e29c8d119687241e5c (diff) | |
| download | mullvadvpn-4f4d75749bfd61b61ebd8f28e020e305c1b389da.tar.xz mullvadvpn-4f4d75749bfd61b61ebd8f28e020e305c1b389da.zip | |
Use gRPC for OpenVPN plugin IPC
Diffstat (limited to 'talpid-core/src')
| -rw-r--r-- | talpid-core/src/tunnel/openvpn.rs | 208 |
1 files changed, 158 insertions, 50 deletions
diff --git a/talpid-core/src/tunnel/openvpn.rs b/talpid-core/src/tunnel/openvpn.rs index b1e35041a0..391d1f75f5 100644 --- a/talpid-core/src/tunnel/openvpn.rs +++ b/talpid-core/src/tunnel/openvpn.rs @@ -20,8 +20,8 @@ use std::{ thread, time::Duration, }; -use talpid_ipc; use talpid_types::net::openvpn; +use tokio02::task; #[cfg(target_os = "linux")] use which; @@ -33,13 +33,17 @@ pub type Result<T> = std::result::Result<T, Error>; #[derive(err_derive::Error, Debug)] #[error(no_from)] pub enum Error { + /// Failed to initialize the tokio runtime. + #[error(display = "Failed to initialize the tokio runtime")] + RuntimeError(#[error(source)] io::Error), + /// Unable to start, wait for or kill the OpenVPN process. #[error(display = "Error in OpenVPN process management: {}", _0)] ChildProcessError(&'static str, #[error(source)] io::Error), - /// Unable to start or manage the IPC server listening for events from OpenVPN. - #[error(display = "Unable to start or manage the event dispatcher IPC server")] - EventDispatcherError(#[error(source)] talpid_ipc::Error), + /// Unable to start the IPC server. + #[error(display = "Unable to start the event dispatcher IPC server")] + EventDispatcherError(#[error(source)] event_server::Error), /// The OpenVPN event dispatcher exited unexpectedly #[error(display = "The OpenVPN event dispatcher exited unexpectedly")] @@ -121,13 +125,16 @@ const OPENVPN_BIN_FILENAME: &str = "openvpn.exe"; pub struct OpenVpnMonitor<C: OpenVpnBuilder = OpenVpnCommand> { child: Arc<C::ProcessHandle>, proxy_monitor: Option<Box<dyn ProxyMonitor>>, - event_dispatcher: Option<talpid_ipc::IpcServer>, log_path: Option<PathBuf>, closed: Arc<AtomicBool>, /// Keep the `TempFile` for the user-pass file in the struct, so it's removed on drop. _user_pass_file: mktemp::TempFile, /// Keep the 'TempFile' for the proxy user-pass file in the struct, so it's removed on drop. _proxy_auth_file: Option<mktemp::TempFile>, + + runtime: tokio02::runtime::Runtime, + event_server_abort_tx: triggered::Trigger, + server_join_handle: Option<task::JoinHandle<std::result::Result<(), event_server::Error>>>, } impl OpenVpnMonitor<OpenVpnCommand> { @@ -223,11 +230,39 @@ impl<C: OpenVpnBuilder + 'static> OpenVpnMonitor<C> { where L: Fn(openvpn_plugin::EventType, HashMap<String, String>) + Send + Sync + 'static, { - let event_dispatcher = - event_server::start(on_event).map_err(Error::EventDispatcherError)?; + let uuid = uuid::Uuid::new_v4().to_string(); + let ipc_path = if cfg!(windows) { + format!("//./pipe/talpid-openvpn-{}", uuid) + } else { + format!("/tmp/talpid-openvpn-{}", uuid) + }; + + let (event_server_abort_tx, event_server_abort_rx) = triggered::trigger(); + + let mut runtime = tokio02::runtime::Builder::new() + .threaded_scheduler() + .core_threads(1) + .enable_all() + .build() + .map_err(Error::RuntimeError)?; + + let (start_tx, start_rx) = mpsc::channel(); + let server_join_handle = runtime.spawn(event_server::start( + ipc_path.clone(), + start_tx, + on_event, + event_server_abort_rx, + )); + if let Err(_) = start_rx.recv() { + return Err(runtime + .block_on(server_join_handle) + .expect("Failed to resolve quit handle future") + .map_err(Error::EventDispatcherError) + .unwrap_err()); + } let child = cmd - .plugin(plugin_path, vec![event_dispatcher.path().to_owned()]) + .plugin(plugin_path, vec![ipc_path]) .log(log_path.as_ref().map(|p| p.as_path())) .start() .map_err(|e| Error::ChildProcessError("Failed to start", e))?; @@ -235,11 +270,14 @@ impl<C: OpenVpnBuilder + 'static> OpenVpnMonitor<C> { Ok(OpenVpnMonitor { child: Arc::new(child), proxy_monitor, - event_dispatcher: Some(event_dispatcher), log_path, closed: Arc::new(AtomicBool::new(false)), _user_pass_file: user_pass_file, _proxy_auth_file: proxy_auth_file, + + runtime, + event_server_abort_tx, + server_join_handle: Some(server_join_handle), }) } @@ -332,20 +370,25 @@ impl<C: OpenVpnBuilder + 'static> OpenVpnMonitor<C> { let child_wait_handle = self.child.clone(); let closed_handle = self.closed.clone(); let child_close_handle = self.close_handle(); - let event_dispatcher = self.event_dispatcher.take().expect("No event_dispatcher"); - let dispatcher_handle = event_dispatcher.close_handle(); let (child_tx, rx) = mpsc::channel(); let dispatcher_tx = child_tx.clone(); + let event_server_abort_tx = self.event_server_abort_tx.clone(); + thread::spawn(move || { let result = child_wait_handle.wait(); let closed = closed_handle.load(Ordering::SeqCst); child_tx.send(WaitResult::Child(result, closed)).unwrap(); - dispatcher_handle.close(); + event_server_abort_tx.trigger(); }); - thread::spawn(move || { - event_dispatcher.wait(); + + let server_join_handle = self + .server_join_handle + .take() + .expect("No event server quit handle"); + self.runtime.spawn(async move { + let _ = server_join_handle.await; dispatcher_tx.send(WaitResult::EventDispatcher).unwrap(); let _ = child_close_handle.close(); }); @@ -569,55 +612,120 @@ impl ProcessHandle for OpenVpnProcHandle { mod event_server { - use jsonrpc_core::{Error, IoHandler, MetaIoHandler}; - use jsonrpc_macros::build_rpc_trait; - use std::collections::HashMap; - use talpid_ipc; - use uuid; + use futures::stream::TryStreamExt; + use parity_tokio_ipc::{Endpoint as IpcEndpoint, SecurityAttributes}; + use std::{ + collections::HashMap, + pin::Pin, + task::{Context, Poll}, + }; + use tokio02::io::{AsyncRead, AsyncWrite}; + use tonic::{ + self, + transport::{server::Connected, Server}, + Request, Response, + }; - /// Construct and start the IPC server with the given event listener callback. - pub fn start<L>(on_event: L) -> std::result::Result<talpid_ipc::IpcServer, talpid_ipc::Error> - where - L: Fn(openvpn_plugin::EventType, HashMap<String, String>) + Send + Sync + 'static, - { - let uuid = uuid::Uuid::new_v4().to_string(); - let ipc_path = if cfg!(windows) { - format!("//./pipe/talpid-openvpn-{}", uuid) - } else { - format!("/tmp/talpid-openvpn-{}", uuid) - }; - let rpc = OpenVpnEventApiImpl { on_event }; - let mut io = IoHandler::new(); - io.extend_with(rpc.to_delegate()); - let meta_io: MetaIoHandler<()> = MetaIoHandler::from(io); - talpid_ipc::IpcServer::start(meta_io, &ipc_path) + mod proto { + tonic::include_proto!("talpid_openvpn_plugin"); } + use proto::{ + openvpn_event_proxy_server::{OpenvpnEventProxy, OpenvpnEventProxyServer}, + Empty, EventType, + }; - build_rpc_trait! { - pub trait OpenVpnEventApi { + #[derive(err_derive::Error, Debug)] + pub enum Error { + /// Failure to set up the IPC server. + #[error(display = "Failed to create pipe or Unix socket")] + StartServer(#[error(source)] std::io::Error), - #[rpc(name = "openvpn_event")] - fn openvpn_event(&self, openvpn_plugin::EventType, HashMap<String, String>) - -> Result<(), Error>; - } + /// An error occurred while the server was running. + #[error(display = "Tonic error")] + TonicError(#[error(source)] tonic::transport::Error), } - struct OpenVpnEventApiImpl<L> { + /// Implements a gRPC service used to process events sent to by OpenVPN. + #[derive(Debug)] + pub struct OpenvpnEventProxyImpl<L> { on_event: L, } - impl<L> OpenVpnEventApi for OpenVpnEventApiImpl<L> + #[tonic::async_trait] + impl<L> OpenvpnEventProxy for OpenvpnEventProxyImpl<L> where L: Fn(openvpn_plugin::EventType, HashMap<String, String>) + Send + Sync + 'static, { - fn openvpn_event( + async fn event( &self, - event: openvpn_plugin::EventType, - env: HashMap<String, String>, - ) -> Result<(), Error> { - log::trace!("OpenVPN event {:?}", event); - (self.on_event)(event, env); - Ok(()) + request: Request<EventType>, + ) -> std::result::Result<Response<Empty>, tonic::Status> { + log::info!("OpenVPN event {:?}", request); + + let request = request.into_inner(); + + let event_type = openvpn_plugin::EventType::try_from(request.event) + .ok_or(tonic::Status::invalid_argument("Unknown event type"))?; + + (self.on_event)(event_type, request.env); + + Ok(Response::new(Empty {})) + } + } + + pub async fn start<L>( + ipc_path: String, + server_start_tx: std::sync::mpsc::Sender<()>, + on_event: L, + abort_rx: triggered::Listener, + ) -> std::result::Result<(), Error> + where + L: Fn(openvpn_plugin::EventType, HashMap<String, String>) + Send + Sync + 'static, + { + let mut endpoint = IpcEndpoint::new(ipc_path.clone()); + endpoint.set_security_attributes(SecurityAttributes::allow_everyone_create().unwrap()); + let incoming = endpoint.incoming().map_err(Error::StartServer)?; + let _ = server_start_tx.send(()); + + let server = OpenvpnEventProxyImpl { on_event }; + + Server::builder() + .add_service(OpenvpnEventProxyServer::new(server)) + .serve_with_incoming_shutdown(incoming.map_ok(StreamBox), abort_rx) + .await + .map_err(Error::TonicError) + } + + #[derive(Debug)] + pub struct StreamBox<T: AsyncRead + AsyncWrite>(pub T); + impl<T: AsyncRead + AsyncWrite> Connected for StreamBox<T> {} + impl<T: AsyncRead + AsyncWrite + Unpin> AsyncRead for StreamBox<T> { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll<std::io::Result<usize>> { + Pin::new(&mut self.0).poll_read(cx, buf) + } + } + impl<T: AsyncRead + AsyncWrite + Unpin> AsyncWrite for StreamBox<T> { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll<std::io::Result<usize>> { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> { + Pin::new(&mut self.0).poll_flush(cx) + } + + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll<std::io::Result<()>> { + Pin::new(&mut self.0).poll_shutdown(cx) } } } |
