diff --git a/burrow/src/daemon/instance.rs b/burrow/src/daemon/instance.rs index fdcd95f..1eb0629 100644 --- a/burrow/src/daemon/instance.rs +++ b/burrow/src/daemon/instance.rs @@ -71,20 +71,19 @@ impl DaemonRPCServer { self.network_update_chan.0.send(()).map_err(proc_err) } - async fn resolve_tunnel(&self) -> Result, RspStatus> { + async fn resolve_tunnel(&self) -> Result { let conn = self.get_connection()?; let networks = list_networks(&conn).map_err(proc_err)?; ResolvedTunnel::from_networks(&networks).map_err(proc_err) } async fn current_tunnel_configuration(&self) -> Result { - match self.resolve_tunnel().await? { - Some(config) => { - let config = config.server_config().map_err(proc_err)?; - Ok(configuration_rsp(config)) - } - None => Ok(empty_configuration_rsp()), - } + let config = self + .resolve_tunnel() + .await? + .server_config() + .map_err(proc_err)?; + Ok(configuration_rsp(config)) } async fn stop_active_tunnel(&self) -> Result { @@ -114,10 +113,6 @@ impl DaemonRPCServer { async fn reconcile_runtime(&self) -> Result<(), RspStatus> { let desired = self.resolve_tunnel().await?; - let Some(desired) = desired else { - let _ = self.stop_active_tunnel().await?; - return Ok(()); - }; let needs_restart = { let guard = self.active_tunnel.read().await; guard @@ -163,10 +158,7 @@ impl Tunnel for DaemonRPCServer { } async fn tunnel_start(&self, _request: Request) -> Result, RspStatus> { - let desired = self - .resolve_tunnel() - .await? - .ok_or_else(|| RspStatus::failed_precondition("no stored network configured"))?; + let desired = self.resolve_tunnel().await?; let already_running = { let guard = self.active_tunnel.read().await; guard @@ -285,13 +277,6 @@ fn configuration_rsp(config: ServerConfig) -> TunnelConfigurationResponse { } } -fn empty_configuration_rsp() -> TunnelConfigurationResponse { - TunnelConfigurationResponse { - mtu: 1500, - addresses: Vec::new(), - } -} - fn status_rsp(state: RunState) -> TunnelStatusResponse { TunnelStatusResponse { state: state.to_rpc().into(), diff --git a/burrow/src/daemon/mod.rs b/burrow/src/daemon/mod.rs index f5ad7d3..8fe3d41 100644 --- a/burrow/src/daemon/mod.rs +++ b/burrow/src/daemon/mod.rs @@ -72,7 +72,7 @@ mod tests { client::BurrowClient, grpc_defs::{ Empty, Network, NetworkListResponse, NetworkReorderRequest, NetworkType, - TunnelConfigurationResponse, + TunnelConfigurationResponse, TunnelStatusResponse, }, }; @@ -111,6 +111,11 @@ mod tests { .network_list(Empty {}) .await? .into_inner(); + let mut status_stream = client + .tunnel_client + .tunnel_status(Empty {}) + .await? + .into_inner(); let initial_config = next_configuration(&mut config_stream).await?; assert!(initial_config.addresses.is_empty()); @@ -119,12 +124,27 @@ mod tests { let initial_networks = next_networks(&mut network_stream).await?; assert!(initial_networks.network.is_empty()); - let start_err = client - .tunnel_client - .tunnel_start(Empty {}) - .await - .expect_err("starting without a stored network should fail"); - assert_eq!(start_err.code(), tonic::Code::FailedPrecondition); + let initial_status = next_status(&mut status_stream).await?; + assert_eq!( + initial_status.state(), + crate::daemon::rpc::grpc_defs::State::Stopped + ); + + client.tunnel_client.tunnel_start(Empty {}).await?; + + let passthrough_status = next_status(&mut status_stream).await?; + assert_eq!( + passthrough_status.state(), + crate::daemon::rpc::grpc_defs::State::Running + ); + + client.tunnel_client.tunnel_stop(Empty {}).await?; + + let stopped_status = next_status(&mut status_stream).await?; + assert_eq!( + stopped_status.state(), + crate::daemon::rpc::grpc_defs::State::Stopped + ); client .networks_client @@ -246,6 +266,14 @@ Endpoint = wg.burrow.rs:51820 .ok_or_else(|| anyhow!("network stream ended unexpectedly")) } + async fn next_status( + stream: &mut tonic::Streaming, + ) -> Result { + timeout(Duration::from_secs(5), stream.message()) + .await?? + .ok_or_else(|| anyhow!("status stream ended unexpectedly")) + } + fn network_ids(response: &NetworkListResponse) -> Vec<(i32, NetworkType)> { response .network diff --git a/burrow/src/daemon/runtime.rs b/burrow/src/daemon/runtime.rs index 31c0b0a..7fea964 100644 --- a/burrow/src/daemon/runtime.rs +++ b/burrow/src/daemon/runtime.rs @@ -15,6 +15,7 @@ use crate::{ #[derive(Clone, Debug, PartialEq, Eq)] pub enum RuntimeIdentity { + Passthrough, Network { id: i32, network_type: NetworkType, @@ -24,6 +25,9 @@ pub enum RuntimeIdentity { #[derive(Clone, Debug)] pub enum ResolvedTunnel { + Passthrough { + identity: RuntimeIdentity, + }, WireGuard { identity: RuntimeIdentity, config: Config, @@ -35,9 +39,11 @@ pub enum ResolvedTunnel { } impl ResolvedTunnel { - pub fn from_networks(networks: &[Network]) -> Result> { + pub fn from_networks(networks: &[Network]) -> Result { let Some(network) = networks.first() else { - return Ok(None); + return Ok(Self::Passthrough { + identity: RuntimeIdentity::Passthrough, + }); }; let identity = RuntimeIdentity::Network { @@ -51,23 +57,30 @@ impl ResolvedTunnel { let payload = String::from_utf8(network.payload.clone()) .context("wireguard payload must be valid UTF-8")?; let config = Config::from_content_fmt(&payload, "ini")?; - Ok(Some(Self::WireGuard { identity, config })) + Ok(Self::WireGuard { identity, config }) } NetworkType::HackClub => { let config = HackClubNetworkConfig::from_payload(&network.payload)?; - Ok(Some(Self::HackClub { identity, config })) + Ok(Self::HackClub { identity, config }) } } } pub fn identity(&self) -> &RuntimeIdentity { match self { - Self::WireGuard { identity, .. } | Self::HackClub { identity, .. } => identity, + Self::Passthrough { identity } + | Self::WireGuard { identity, .. } + | Self::HackClub { identity, .. } => identity, } } pub fn server_config(&self) -> Result { match self { + Self::Passthrough { .. } => Ok(ServerConfig { + address: Vec::new(), + name: None, + mtu: Some(1500), + }), Self::WireGuard { config, .. } => ServerConfig::try_from(config), Self::HackClub { config, .. } => Ok(ServerConfig { address: config.local_addresses.clone(), @@ -82,6 +95,7 @@ impl ResolvedTunnel { tun_interface: Arc>>, ) -> Result { match self { + Self::Passthrough { identity } => Ok(ActiveTunnel::Passthrough { identity }), Self::WireGuard { identity, config } => { let tun = TunOptions::new().open()?; tun_interface.write().await.replace(tun); @@ -118,6 +132,9 @@ impl ResolvedTunnel { } pub enum ActiveTunnel { + Passthrough { + identity: RuntimeIdentity, + }, WireGuard { identity: RuntimeIdentity, interface: Arc>, @@ -132,12 +149,15 @@ pub enum ActiveTunnel { impl ActiveTunnel { pub fn identity(&self) -> &RuntimeIdentity { match self { - Self::WireGuard { identity, .. } | Self::HackClub { identity, .. } => identity, + Self::Passthrough { identity } + | Self::WireGuard { identity, .. } + | Self::HackClub { identity, .. } => identity, } } pub async fn shutdown(self, tun_interface: &Arc>>) -> Result<()> { match self { + Self::Passthrough { .. } => Ok(()), Self::WireGuard { interface, task, .. } => { interface.read().await.remove_tun().await; let task_result = task.await; @@ -174,7 +194,12 @@ mod tests { use super::*; #[test] - fn no_networks_resolves_to_no_tunnel() { - assert!(ResolvedTunnel::from_networks(&[]).unwrap().is_none()); + fn no_networks_resolve_to_passthrough() { + let resolved = ResolvedTunnel::from_networks(&[]).unwrap(); + assert_eq!(resolved.identity(), &RuntimeIdentity::Passthrough); + assert_eq!( + resolved.server_config().unwrap().address, + Vec::::new() + ); } }