diff --git a/rust/connlib/clients/shared/src/eventloop.rs b/rust/connlib/clients/shared/src/eventloop.rs index 36a55b266..22a405a32 100644 --- a/rust/connlib/clients/shared/src/eventloop.rs +++ b/rust/connlib/clients/shared/src/eventloop.rs @@ -4,6 +4,7 @@ use connlib_model::ResourceId; use firezone_tunnel::messages::{client::*, *}; use firezone_tunnel::ClientTunnel; use phoenix_channel::{ErrorReply, OutboundRequestId, PhoenixChannel, PublicKeyParam}; +use std::time::Instant; use std::{ collections::{BTreeMap, BTreeSet}, io, @@ -59,12 +60,12 @@ where match self.rx.poll_recv(cx) { Poll::Ready(Some(Command::Stop)) | Poll::Ready(None) => return Poll::Ready(Ok(())), Poll::Ready(Some(Command::SetDns(dns))) => { - self.tunnel.set_new_dns(dns); + self.tunnel.state_mut().update_system_resolvers(dns); continue; } Poll::Ready(Some(Command::SetDisabledResources(resources))) => { - self.tunnel.set_disabled_resources(resources); + self.tunnel.state_mut().set_disabled_resources(resources); continue; } Poll::Ready(Some(Command::SetTun(tun))) => { @@ -226,15 +227,18 @@ where fn handle_portal_inbound_message(&mut self, msg: IngressMessages) { match msg { - IngressMessages::ConfigChanged(config) => { - self.tunnel.set_new_interface_config(config.interface) - } + IngressMessages::ConfigChanged(config) => self + .tunnel + .state_mut() + .update_interface_config(config.interface), IngressMessages::IceCandidates(GatewayIceCandidates { gateway_id, candidates, }) => { for candidate in candidates { - self.tunnel.add_ice_candidate(gateway_id, candidate) + self.tunnel + .state_mut() + .add_ice_candidate(gateway_id, candidate, Instant::now()) } } IngressMessages::Init(InitClient { @@ -242,28 +246,40 @@ where resources, relays, }) => { - self.tunnel.set_new_interface_config(interface); - self.tunnel.set_resources(resources); - self.tunnel.update_relays(BTreeSet::default(), relays); + let state = self.tunnel.state_mut(); + + state.update_interface_config(interface); + state.set_resources(resources); + state.update_relays( + BTreeSet::default(), + firezone_tunnel::turn(&relays), + Instant::now(), + ); } IngressMessages::ResourceCreatedOrUpdated(resource) => { - self.tunnel.add_resource(resource); + self.tunnel.state_mut().add_resource(resource); } IngressMessages::ResourceDeleted(resource) => { - self.tunnel.remove_resource(resource); + self.tunnel.state_mut().remove_resource(resource); } IngressMessages::RelaysPresence(RelaysPresence { disconnected_ids, connected, - }) => self - .tunnel - .update_relays(BTreeSet::from_iter(disconnected_ids), connected), + }) => self.tunnel.state_mut().update_relays( + BTreeSet::from_iter(disconnected_ids), + firezone_tunnel::turn(&connected), + Instant::now(), + ), IngressMessages::InvalidateIceCandidates(GatewayIceCandidates { gateway_id, candidates, }) => { for candidate in candidates { - self.tunnel.remove_ice_candidate(gateway_id, candidate) + self.tunnel.state_mut().remove_ice_candidate( + gateway_id, + candidate, + Instant::now(), + ) } } } @@ -278,10 +294,11 @@ where resource_id, .. }) => { - if let Err(e) = self.tunnel.received_offer_response( - resource_id, + if let Err(e) = self.tunnel.state_mut().accept_answer( ice_parameters, + resource_id, gateway_public_key.0.into(), + Instant::now(), ) { tracing::warn!("Failed to accept connection: {e}"); } @@ -307,10 +324,12 @@ where return; } - match self - .tunnel - .on_routing_details(resource_id, gateway_id, site_id) - { + match self.tunnel.state_mut().on_routing_details( + resource_id, + gateway_id, + site_id, + Instant::now(), + ) { Ok(()) => {} Err(e) => { tracing::warn!("Failed to request new connection: {e}"); @@ -334,7 +353,9 @@ where tracing::debug!(resource_id = %offline_resource, "Resource is offline"); - self.tunnel.set_resource_offline(offline_resource); + self.tunnel + .state_mut() + .set_resource_offline(offline_resource); } ErrorReply::Disabled => { diff --git a/rust/connlib/tunnel/src/client.rs b/rust/connlib/tunnel/src/client.rs index edc2858a4..9f6a5bd65 100644 --- a/rust/connlib/tunnel/src/client.rs +++ b/rust/connlib/tunnel/src/client.rs @@ -6,11 +6,8 @@ pub(crate) use resource::{CidrResource, Resource}; pub(crate) use resource::{DnsResource, InternetResource}; use crate::dns::StubResolver; -use crate::messages::client::ResourceDescription; use crate::messages::ResolveRequest; -use crate::messages::{ - Answer, DnsServer, Interface as InterfaceConfig, IpDnsServer, Key, Offer, Relay, -}; +use crate::messages::{DnsServer, Interface as InterfaceConfig, IpDnsServer, Key, Offer}; use crate::peer_store::PeerStore; use crate::{dns, TunConfig}; use anyhow::Context; @@ -24,8 +21,8 @@ use ip_packet::{IpPacket, UdpSlice}; use itertools::Itertools; use crate::peer::GatewayOnClient; -use crate::utils::{earliest, turn}; -use crate::{ClientEvent, ClientTunnel, Tun}; +use crate::utils::earliest; +use crate::ClientEvent; use domain::base::{Message, MessageBuilder}; use lru::LruCache; use secrecy::{ExposeSecret as _, Secret}; @@ -76,132 +73,6 @@ const IDS_EXPIRE: std::time::Duration = std::time::Duration::from_secs(60); /// We only store [`GatewayId`]s so the memory footprint is negligible. const MAX_REMEMBERED_GATEWAYS: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(100) }; -impl ClientTunnel { - pub fn set_resources(&mut self, resources: Vec) { - self.role_state.set_resources( - resources - .into_iter() - .filter_map(Resource::from_description) - .collect(), - ); - - self.role_state - .buffered_events - .push_back(ClientEvent::ResourcesChanged { - resources: self.role_state.resources(), - }); - } - - pub fn set_disabled_resources(&mut self, new_disabled_resources: BTreeSet) { - self.role_state - .set_disabled_resource(new_disabled_resources); - } - - pub fn set_tun(&mut self, tun: Box) { - self.io.set_tun(tun); - } - - pub fn update_relays(&mut self, to_remove: BTreeSet, to_add: Vec) { - self.role_state - .update_relays(to_remove, turn(&to_add), Instant::now()) - } - - /// Adds a the given resource to the tunnel. - pub fn add_resource(&mut self, resource: ResourceDescription) { - let Some(resource) = Resource::from_description(resource) else { - return; - }; - - self.role_state.add_resource(resource); - - self.role_state - .buffered_events - .push_back(ClientEvent::ResourcesChanged { - resources: self.role_state.resources(), - }); - } - - pub fn remove_resource(&mut self, id: ResourceId) { - self.role_state.remove_resource(id); - - self.role_state - .buffered_events - .push_back(ClientEvent::ResourcesChanged { - resources: self.role_state.resources(), - }); - } - - /// Updates the system's dns - pub fn set_new_dns(&mut self, new_dns: Vec) { - // We store the sentinel dns both in the config and in the system's resolvers - // but when we calculate the dns mapping, those are ignored. - self.role_state.update_system_resolvers(new_dns); - } - - #[tracing::instrument(level = "trace", skip(self))] - pub fn set_new_interface_config(&mut self, config: InterfaceConfig) { - self.role_state.update_interface_config(config); - } - - pub fn cleanup_connection(&mut self, id: ResourceId) { - self.role_state.on_connection_failed(id); - } - - pub fn set_resource_offline(&mut self, id: ResourceId) { - self.role_state.set_resource_offline(id); - - self.role_state.on_connection_failed(id); - - self.role_state - .buffered_events - .push_back(ClientEvent::ResourcesChanged { - resources: self.role_state.resources(), - }); - } - - pub fn add_ice_candidate(&mut self, conn_id: GatewayId, ice_candidate: String) { - self.role_state - .add_ice_candidate(conn_id, ice_candidate, Instant::now()); - } - - pub fn remove_ice_candidate(&mut self, conn_id: GatewayId, ice_candidate: String) { - self.role_state - .remove_ice_candidate(conn_id, ice_candidate, Instant::now()); - } - - pub fn on_routing_details( - &mut self, - resource_id: ResourceId, - gateway_id: GatewayId, - site_id: SiteId, - ) -> anyhow::Result<()> { - self.role_state - .on_routing_details(resource_id, gateway_id, site_id, Instant::now()) - } - - #[expect(deprecated, reason = "Will be deleted together with deprecated API")] - pub fn received_offer_response( - &mut self, - resource_id: ResourceId, - answer: Answer, - gateway_public_key: PublicKey, - ) -> anyhow::Result<()> { - self.role_state.accept_answer( - snownet::Answer { - credentials: snownet::Credentials { - username: answer.username, - password: answer.password, - }, - }, - resource_id, - gateway_public_key, - Instant::now(), - )?; - - Ok(()) - } -} - /// A sans-IO implementation of a Client's functionality. /// /// Internally, this composes a [`snownet::ClientNode`] with firezone's policy engine around resources. @@ -350,7 +221,7 @@ impl ClientState { ResourceStatus::Unknown } - fn set_resource_offline(&mut self, id: ResourceId) { + pub fn set_resource_offline(&mut self, id: ResourceId) { let Some(resource) = self.resources_by_id.get(&id).cloned() else { return; }; @@ -358,6 +229,9 @@ impl ClientState { for Site { id, .. } in resource.sites() { self.sites_status.insert(*id, ResourceStatus::Offline); } + + self.on_connection_failed(id); + self.emit_resources_changed(); } pub(crate) fn public_key(&self) -> PublicKey { @@ -593,11 +467,13 @@ impl ClientState { #[expect(deprecated, reason = "Will be deleted together with deprecated API")] pub fn accept_answer( &mut self, - answer: snownet::Answer, + answer: impl Into, resource_id: ResourceId, gateway: PublicKey, now: Instant, ) -> anyhow::Result<()> { + let answer = answer.into(); + debug_assert!(!self.awaiting_connection_details.contains_key(&resource_id)); let gateway_id = self @@ -846,7 +722,7 @@ impl ClientState { self.mangled_dns_queries.clear(); } - pub fn set_disabled_resource(&mut self, new_disabled_resources: BTreeSet) { + pub fn set_disabled_resources(&mut self, new_disabled_resources: BTreeSet) { let current_disabled_resources = self.disabled_resources.clone(); // We set disabled_resources before anything else so that add_resource knows what resources are enabled right now. @@ -918,7 +794,7 @@ impl ClientState { .or(self.internet_resource) } - pub(crate) fn update_system_resolvers(&mut self, new_dns: Vec) { + pub fn update_system_resolvers(&mut self, new_dns: Vec) { tracing::debug!(servers = ?new_dns, "Received system-defined DNS servers"); self.system_resolvers = new_dns; @@ -926,7 +802,7 @@ impl ClientState { self.update_dns_mapping() } - pub(crate) fn update_interface_config(&mut self, config: InterfaceConfig) { + pub fn update_interface_config(&mut self, config: InterfaceConfig) { tracing::trace!(upstream_dns = ?config.upstream_dns, ipv4 = %config.ipv4, ipv6 = %config.ipv6, "Received interface configuration from portal"); match self.tun_config.as_mut() { @@ -1077,10 +953,7 @@ impl ClientState { } if resources_changed { - self.buffered_events - .push_back(ClientEvent::ResourcesChanged { - resources: self.resources(), - }); + self.emit_resources_changed() } for (conn_id, candidates) in added_ice_candidates.into_iter() { @@ -1142,7 +1015,15 @@ impl ClientState { /// /// TODO: Add a test that asserts the above. /// That is tricky because we need to assert on state deleted by [`ClientState::remove_resource`] and check that it did in fact not get deleted. - pub(crate) fn set_resources(&mut self, new_resources: Vec) { + pub fn set_resources(&mut self, new_resources: Vec) + where + R: TryInto, + { + let new_resources = new_resources + .into_iter() + .filter_map(|r| r.try_into().inspect_err(|e| tracing::debug!("{e}")).ok()) + .collect::>(); + let current_resource_ids = self .resources_by_id .keys() @@ -1163,9 +1044,18 @@ impl ClientState { } self.maybe_update_tun_routes(); + self.emit_resources_changed(); } - pub(crate) fn add_resource(&mut self, new_resource: Resource) { + pub fn add_resource(&mut self, new_resource: impl TryInto) { + let new_resource = match new_resource.try_into() { + Ok(r) => r, + Err(e) => { + tracing::debug!("{e}"); + return; + } + }; + if let Some(resource) = self.resources_by_id.get(&new_resource.id()) { if resource.has_different_address(&new_resource) { self.remove_resource(resource.id()); @@ -1205,13 +1095,28 @@ impl ClientState { tracing::info!(%name, address, %sites, "Activating resource"); self.maybe_update_tun_routes(); + self.emit_resources_changed(); } #[tracing::instrument(level = "debug", skip_all, fields(?id))] - pub(crate) fn remove_resource(&mut self, id: ResourceId) { + pub fn remove_resource(&mut self, id: ResourceId) { self.disable_resource(id); self.resources_by_id.remove(&id); self.maybe_update_tun_routes(); + self.emit_resources_changed(); + } + + /// Emit a [`ClientEvent::ResourcesChanged`] event. + /// + /// Each instance of this event contains the latest state of the resources. + /// To not spam clients with multiple updates, we remove all prior instances of that event. + fn emit_resources_changed(&mut self) { + self.buffered_events + .retain(|e| !matches!(e, ClientEvent::ResourcesChanged { .. })); + self.buffered_events + .push_back(ClientEvent::ResourcesChanged { + resources: self.resources(), + }); } fn disable_resource(&mut self, id: ResourceId) { diff --git a/rust/connlib/tunnel/src/client/resource.rs b/rust/connlib/tunnel/src/client/resource.rs index b5446cbec..ec679a713 100644 --- a/rust/connlib/tunnel/src/client/resource.rs +++ b/rust/connlib/tunnel/src/client/resource.rs @@ -140,6 +140,18 @@ impl Resource { } } +impl TryFrom for Resource { + type Error = UnknownResourceType; + + fn try_from(value: ResourceDescription) -> Result { + Self::from_description(value).ok_or(UnknownResourceType) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Unknown resource type")] +pub struct UnknownResourceType; + impl CidrResource { pub fn from_description(resource: ResourceDescriptionCidr) -> Self { Self { diff --git a/rust/connlib/tunnel/src/gateway.rs b/rust/connlib/tunnel/src/gateway.rs index 71b50c6fd..3a94d3b81 100644 --- a/rust/connlib/tunnel/src/gateway.rs +++ b/rust/connlib/tunnel/src/gateway.rs @@ -1,21 +1,19 @@ use crate::messages::ResolveRequest; -use crate::messages::{gateway::ResourceDescription, Answer, Key, Offer}; +use crate::messages::{gateway::ResourceDescription, Answer}; use crate::peer::ClientOnGateway; use crate::peer_store::PeerStore; use crate::utils::earliest; -use crate::{GatewayEvent, GatewayTunnel}; +use crate::GatewayEvent; use anyhow::Context; use boringtun::x25519::PublicKey; use chrono::{DateTime, Utc}; use connlib_model::{ClientId, DomainName, RelayId, ResourceId}; use ip_network::{Ipv4Network, Ipv6Network}; use ip_packet::IpPacket; -use secrecy::{ExposeSecret as _, Secret}; use snownet::{EncryptBuffer, RelaySocket, ServerNode}; use std::collections::{BTreeMap, BTreeSet, VecDeque}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::time::{Duration, Instant}; -use tun::Tun; pub const IPV4_PEERS: Ipv4Network = match Ipv4Network::new(Ipv4Addr::new(100, 64, 0, 0), 11) { Ok(n) => n, @@ -29,106 +27,6 @@ pub const IPV6_PEERS: Ipv6Network = const EXPIRE_RESOURCES_INTERVAL: Duration = Duration::from_secs(1); -impl GatewayTunnel { - pub fn set_tun(&mut self, tun: Box) { - self.io.set_tun(tun); - } - - /// Accept a connection request from a client. - #[expect(deprecated, reason = "Will be deleted together with deprecated API")] - pub fn accept( - &mut self, - client_id: ClientId, - key: Secret, - offer: Offer, - client: PublicKey, - ) -> Answer { - self.role_state.accept( - client_id, - snownet::Offer { - session_key: key.expose_secret().0.into(), - credentials: snownet::Credentials { - username: offer.username, - password: offer.password, - }, - }, - client, - Instant::now(), - ) - } - - pub fn cleanup_connection(&mut self, id: &ClientId) { - self.role_state.peers.remove(id); - } - - pub fn allow_access( - &mut self, - client: ClientId, - ipv4: Ipv4Addr, - ipv6: Ipv6Addr, - dns_resource_nat: Option, - expires_at: Option>, - resource: ResourceDescription, - ) -> anyhow::Result<()> { - let resource_id = resource.id(); - - self.role_state - .allow_access(client, ipv4, ipv6, expires_at, resource); - - if let Some(entry) = dns_resource_nat { - self.role_state.create_dns_resource_nat_entry( - client, - resource_id, - entry, - Instant::now(), - )?; - } - - Ok(()) - } - - pub fn refresh_translation( - &mut self, - client: ClientId, - resource_id: ResourceId, - name: DomainName, - resolved_ips: Vec, - ) { - self.role_state - .refresh_translation(client, resource_id, name, resolved_ips, Instant::now()) - } - - pub fn update_resource(&mut self, resource: ResourceDescription) { - for peer in self.role_state.peers.iter_mut() { - peer.update_resource(&resource); - } - } - - #[tracing::instrument(level = "debug", skip_all, fields(%resource, %client))] - pub fn remove_access(&mut self, client: &ClientId, resource: &ResourceId) { - let Some(peer) = self.role_state.peers.get_mut(client) else { - return; - }; - - peer.remove_resource(resource); - if peer.is_emptied() { - self.role_state.peers.remove(client); - } - - tracing::debug!("Access removed"); - } - - pub fn add_ice_candidate(&mut self, conn_id: ClientId, ice_candidate: String) { - self.role_state - .add_ice_candidate(conn_id, ice_candidate, Instant::now()); - } - - pub fn remove_ice_candidate(&mut self, conn_id: ClientId, ice_candidate: String) { - self.role_state - .remove_ice_candidate(conn_id, ice_candidate, Instant::now()); - } -} - /// A SANS-IO implementation of a gateway's functionality. /// /// Internally, this composes a [`snownet::ServerNode`] with firezone's policy engine around resources. @@ -247,6 +145,10 @@ impl GatewayState { Some(packet) } + pub fn cleanup_connection(&mut self, id: &ClientId) { + self.peers.remove(id); + } + pub fn add_ice_candidate(&mut self, conn_id: ClientId, ice_candidate: String, now: Instant) { self.node.add_remote_candidate(conn_id, ice_candidate, now); } @@ -256,6 +158,26 @@ impl GatewayState { .remove_remote_candidate(conn_id, ice_candidate, now); } + #[tracing::instrument(level = "debug", skip_all, fields(%resource, %client))] + pub fn remove_access(&mut self, client: &ClientId, resource: &ResourceId) { + let Some(peer) = self.peers.get_mut(client) else { + return; + }; + + peer.remove_resource(resource); + if peer.is_emptied() { + self.peers.remove(client); + } + + tracing::debug!("Access removed"); + } + + pub fn update_resource(&mut self, resource: ResourceDescription) { + for peer in self.peers.iter_mut() { + peer.update_resource(&resource); + } + } + /// Accept a connection request from a client. #[expect(deprecated, reason = "Will be deleted together with deprecated API")] pub fn accept( @@ -290,6 +212,7 @@ impl GatewayState { }; } + #[expect(clippy::too_many_arguments)] // It is a deprecated API, we don't care. pub fn allow_access( &mut self, client: ClientId, @@ -297,7 +220,9 @@ impl GatewayState { ipv6: Ipv6Addr, expires_at: Option>, resource: ResourceDescription, - ) { + dns_resource_nat: Option, + now: Instant, + ) -> anyhow::Result<()> { let peer = self .peers .entry(client) @@ -308,25 +233,19 @@ impl GatewayState { self.peers.add_ip(&client, &ipv6.into()); tracing::info!(%client, resource = %resource.id(), expires = ?expires_at.map(|e| e.to_rfc3339()), "Allowing access to resource"); - } - pub fn create_dns_resource_nat_entry( - &mut self, - client: ClientId, - resource: ResourceId, - entry: DnsResourceNatEntry, - now: Instant, - ) -> anyhow::Result<()> { - self.peers - .get_mut(&client) - .context("Unknown peer")? - .assign_translations( - entry.domain, - resource, - &entry.resolved_ips, - entry.proxy_ips, - now, - )?; + if let Some(entry) = dns_resource_nat { + self.peers + .get_mut(&client) + .context("Unknown peer")? + .assign_translations( + entry.domain, + resource.id(), + &entry.resolved_ips, + entry.proxy_ips, + now, + )?; + } Ok(()) } diff --git a/rust/connlib/tunnel/src/lib.rs b/rust/connlib/tunnel/src/lib.rs index 350717a9b..c0ee8d1e6 100644 --- a/rust/connlib/tunnel/src/lib.rs +++ b/rust/connlib/tunnel/src/lib.rs @@ -12,6 +12,7 @@ use connlib_model::{ use io::Io; use ip_network::{Ipv4Network, Ipv6Network}; use ip_packet::MAX_DATAGRAM_PAYLOAD; +use snownet::EncryptBuffer; use socket_factory::{SocketFactory, TcpSocket, UdpSocket}; use std::{ collections::{BTreeMap, BTreeSet}, @@ -22,7 +23,6 @@ use std::{ time::Instant, }; use tun::Tun; -use utils::turn; mod client; mod device_channel; @@ -56,7 +56,7 @@ pub type ClientTunnel = Tunnel; pub use client::ClientState; pub use gateway::{DnsResourceNatEntry, GatewayState, IPV4_PEERS, IPV6_PEERS}; -use snownet::EncryptBuffer; +pub use utils::turn; /// [`Tunnel`] glues together connlib's [`Io`] component and the respective (pure) state of a client or gateway. /// @@ -78,6 +78,16 @@ pub struct Tunnel { encrypt_buf: EncryptBuffer, } +impl Tunnel { + pub fn state_mut(&mut self) -> &mut TRoleState { + &mut self.role_state + } + + pub fn set_tun(&mut self, tun: Box) { + self.io.set_tun(tun); + } +} + impl ClientTunnel { pub fn new( tcp_socket_factory: Arc>, diff --git a/rust/connlib/tunnel/src/messages.rs b/rust/connlib/tunnel/src/messages.rs index fde3af2ee..1856dcdec 100644 --- a/rust/connlib/tunnel/src/messages.rs +++ b/rust/connlib/tunnel/src/messages.rs @@ -4,6 +4,7 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use chrono::{serde::ts_seconds, DateTime, Utc}; use connlib_model::{GatewayId, RelayId, ResourceId}; use ip_network::IpNetwork; +use secrecy::{ExposeSecret, Secret}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -101,12 +102,38 @@ pub struct Answer { pub password: String, } +#[expect(deprecated)] +impl From for snownet::Answer { + fn from(val: Answer) -> Self { + snownet::Answer { + credentials: snownet::Credentials { + username: val.username, + password: val.password, + }, + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct Offer { pub username: String, pub password: String, } +#[expect(deprecated)] +impl Offer { + // Not a very clean API but it is deprecated anyway. + pub fn into_snownet_offer(self, key: Secret) -> snownet::Offer { + snownet::Offer { + session_key: Secret::new(key.expose_secret().0), + credentials: snownet::Credentials { + username: self.username, + password: self.password, + }, + } + } +} + #[derive(Debug, Deserialize, Serialize, Clone, Hash, PartialEq, Eq)] pub struct DomainResponse { pub domain: DomainName, diff --git a/rust/connlib/tunnel/src/tests/sut.rs b/rust/connlib/tunnel/src/tests/sut.rs index 335bdfc50..661779f52 100644 --- a/rust/connlib/tunnel/src/tests/sut.rs +++ b/rust/connlib/tunnel/src/tests/sut.rs @@ -132,7 +132,7 @@ impl TunnelTest { } Transition::DisableResources(resources) => state .client - .exec_mut(|c| c.sut.set_disabled_resource(resources)), + .exec_mut(|c| c.sut.set_disabled_resources(resources)), Transition::SendICMPPacketToNonResourceIp { src, dst, @@ -661,23 +661,17 @@ impl TunnelTest { let resource = portal.map_client_resource_to_gateway_resource(resource_id); gateway.exec_mut(|g| { - g.sut.allow_access( - self.client.inner().id, - self.client.inner().sut.tunnel_ip4().unwrap(), - self.client.inner().sut.tunnel_ip6().unwrap(), - None, - resource.clone(), - ); - if let Some(entry) = maybe_entry { - g.sut - .create_dns_resource_nat_entry( - self.client.inner().id, - resource.id(), - entry, - now, - ) - .unwrap() - }; + g.sut + .allow_access( + self.client.inner().id, + self.client.inner().sut.tunnel_ip4().unwrap(), + self.client.inner().sut.tunnel_ip6().unwrap(), + None, + resource.clone(), + maybe_entry, + now, + ) + .unwrap(); }); } ClientEvent::ResourcesChanged { .. } => { @@ -734,23 +728,17 @@ impl TunnelTest { self.client.inner().sut.public_key(), now, ); - g.sut.allow_access( - self.client.inner().id, - self.client.inner().sut.tunnel_ip4().unwrap(), - self.client.inner().sut.tunnel_ip6().unwrap(), - None, - resource.clone(), - ); - if let Some(entry) = maybe_entry { - g.sut - .create_dns_resource_nat_entry( - self.client.inner().id, - resource.id(), - entry, - now, - ) - .unwrap() - }; + g.sut + .allow_access( + self.client.inner().id, + self.client.inner().sut.tunnel_ip4().unwrap(), + self.client.inner().sut.tunnel_ip6().unwrap(), + None, + resource.clone(), + maybe_entry, + now, + ) + .unwrap(); anyhow::Ok(answer) }) diff --git a/rust/gateway/src/eventloop.rs b/rust/gateway/src/eventloop.rs index ab46ed1dc..619de1d26 100644 --- a/rust/gateway/src/eventloop.rs +++ b/rust/gateway/src/eventloop.rs @@ -20,7 +20,7 @@ use std::convert::Infallible; use std::io; use std::net::IpAddr; use std::task::{Context, Poll}; -use std::time::Duration; +use std::time::{Duration, Instant}; pub const PHOENIX_TOPIC: &str = "gateway"; @@ -197,7 +197,9 @@ impl Eventloop { .. } => { for candidate in candidates { - self.tunnel.add_ice_candidate(client_id, candidate); + self.tunnel + .state_mut() + .add_ice_candidate(client_id, candidate, Instant::now()); } } phoenix_channel::Event::InboundMessage { @@ -209,7 +211,11 @@ impl Eventloop { .. } => { for candidate in candidates { - self.tunnel.remove_ice_candidate(client_id, candidate); + self.tunnel.state_mut().remove_ice_candidate( + client_id, + candidate, + Instant::now(), + ); } } phoenix_channel::Event::InboundMessage { @@ -220,7 +226,9 @@ impl Eventloop { }), .. } => { - self.tunnel.remove_access(&client_id, &resource_id); + self.tunnel + .state_mut() + .remove_access(&client_id, &resource_id); } phoenix_channel::Event::InboundMessage { msg: @@ -229,14 +237,20 @@ impl Eventloop { connected, }), .. - } => self - .tunnel - .update_relays(BTreeSet::from_iter(disconnected_ids), connected), + } => self.tunnel.state_mut().update_relays( + BTreeSet::from_iter(disconnected_ids), + firezone_tunnel::turn(&connected), + Instant::now(), + ), phoenix_channel::Event::InboundMessage { msg: IngressMessages::Init(init), .. } => { - self.tunnel.update_relays(BTreeSet::default(), init.relays); + self.tunnel.state_mut().update_relays( + BTreeSet::default(), + firezone_tunnel::turn(&init.relays), + Instant::now(), + ); // FIXME(tech-debt): Currently, the `Tunnel` creates the TUN device as part of `set_interface`. // For the gateway, it doesn't do anything else so in an ideal world, we would cause the side-effect out here and just pass an opaque `Device` to the `Tunnel`. @@ -249,7 +263,9 @@ impl Eventloop { msg: IngressMessages::ResourceUpdated(resource_description), .. } => { - self.tunnel.update_resource(resource_description); + self.tunnel + .state_mut() + .update_resource(resource_description); } phoenix_channel::Event::ErrorResponse { topic, req_id, res } => { tracing::warn!(%topic, %req_id, "Request failed: {res:?}"); @@ -272,27 +288,31 @@ impl Eventloop { .inspect_err(|e| tracing::debug!(client = %req.client.id, reference = %req.reference, "DNS resolution timed out as part of connection request: {e}")) .unwrap_or_default(); - let answer = self.tunnel.accept( + let answer = self.tunnel.state_mut().accept( req.client.id, - req.client.peer.preshared_key, - req.client.payload.ice_parameters, + req.client + .payload + .ice_parameters + .into_snownet_offer(req.client.peer.preshared_key), PublicKey::from(req.client.peer.public_key.0), + Instant::now(), ); - if let Err(e) = self.tunnel.allow_access( + if let Err(e) = self.tunnel.state_mut().allow_access( req.client.id, req.client.peer.ipv4, req.client.peer.ipv6, + req.expires_at, + req.resource, req.client .payload .domain .map(|r| DnsResourceNatEntry::new(r, addresses)), - req.expires_at, - req.resource, + Instant::now(), ) { let client = req.client.id; - self.tunnel.cleanup_connection(&client); + self.tunnel.state_mut().cleanup_connection(&client); tracing::debug!(%client, "Connection request failed: {e:#}"); return; } @@ -314,13 +334,14 @@ impl Eventloop { .inspect_err(|e| tracing::debug!(client = %req.client_id, reference = %req.reference, "DNS resolution timed out as part of allow access request: {e}")) .unwrap_or_default(); - if let Err(e) = self.tunnel.allow_access( + if let Err(e) = self.tunnel.state_mut().allow_access( req.client_id, req.client_ipv4, req.client_ipv6, - req.payload.map(|r| DnsResourceNatEntry::new(r, addresses)), req.expires_at, req.resource, + req.payload.map(|r| DnsResourceNatEntry::new(r, addresses)), + Instant::now(), ) { tracing::warn!(client = %req.client_id, "Allow access request failed: {e:#}"); }; @@ -337,8 +358,13 @@ impl Eventloop { .inspect_err(|e| tracing::debug!(%conn_id, "DNS resolution timed out as part of allow access request: {e}")) .unwrap_or_default(); - self.tunnel - .refresh_translation(conn_id, resource_id, name, addresses); + self.tunnel.state_mut().refresh_translation( + conn_id, + resource_id, + name, + addresses, + Instant::now(), + ); } }