mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
refactor(connlib): expose &mut TRoleState for direct access (#7026)
Currently, we have a lot of stupid code to forward data from the
`{Client,Gateway}Tunnel` interface to `{Client,Gateway}State`. Recent
refactorings such as #6919 made it possible to get rid of this
forwarding layer by directly exposing `&mut TRoleState`.
To maintain some type-privacy, several functions are made generic to
accept `impl Into` or `impl TryInto`.
This commit is contained in:
@@ -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 => {
|
||||
|
||||
@@ -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<ResourceDescription>) {
|
||||
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<ResourceId>) {
|
||||
self.role_state
|
||||
.set_disabled_resource(new_disabled_resources);
|
||||
}
|
||||
|
||||
pub fn set_tun(&mut self, tun: Box<dyn Tun>) {
|
||||
self.io.set_tun(tun);
|
||||
}
|
||||
|
||||
pub fn update_relays(&mut self, to_remove: BTreeSet<RelayId>, to_add: Vec<Relay>) {
|
||||
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<IpAddr>) {
|
||||
// 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<snownet::Answer>,
|
||||
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<ResourceId>) {
|
||||
pub fn set_disabled_resources(&mut self, new_disabled_resources: BTreeSet<ResourceId>) {
|
||||
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<IpAddr>) {
|
||||
pub fn update_system_resolvers(&mut self, new_dns: Vec<IpAddr>) {
|
||||
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<Resource>) {
|
||||
pub fn set_resources<R>(&mut self, new_resources: Vec<R>)
|
||||
where
|
||||
R: TryInto<Resource, Error: std::error::Error>,
|
||||
{
|
||||
let new_resources = new_resources
|
||||
.into_iter()
|
||||
.filter_map(|r| r.try_into().inspect_err(|e| tracing::debug!("{e}")).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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<Resource, Error: std::error::Error>) {
|
||||
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) {
|
||||
|
||||
@@ -140,6 +140,18 @@ impl Resource {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ResourceDescription> for Resource {
|
||||
type Error = UnknownResourceType;
|
||||
|
||||
fn try_from(value: ResourceDescription) -> Result<Self, Self::Error> {
|
||||
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 {
|
||||
|
||||
@@ -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<dyn Tun>) {
|
||||
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<Key>,
|
||||
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<DnsResourceNatEntry>,
|
||||
expires_at: Option<DateTime<Utc>>,
|
||||
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<IpAddr>,
|
||||
) {
|
||||
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<DateTime<Utc>>,
|
||||
resource: ResourceDescription,
|
||||
) {
|
||||
dns_resource_nat: Option<DnsResourceNatEntry>,
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -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<ClientState>;
|
||||
|
||||
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<TRoleState> {
|
||||
encrypt_buf: EncryptBuffer,
|
||||
}
|
||||
|
||||
impl<TRoleState> Tunnel<TRoleState> {
|
||||
pub fn state_mut(&mut self) -> &mut TRoleState {
|
||||
&mut self.role_state
|
||||
}
|
||||
|
||||
pub fn set_tun(&mut self, tun: Box<dyn Tun>) {
|
||||
self.io.set_tun(tun);
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientTunnel {
|
||||
pub fn new(
|
||||
tcp_socket_factory: Arc<dyn SocketFactory<TcpSocket>>,
|
||||
|
||||
@@ -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<Answer> 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<Key>) -> 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,
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user