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:
Thomas Eizinger
2024-10-15 12:05:35 +11:00
committed by GitHub
parent b1e631dd00
commit dbe618c080
8 changed files with 254 additions and 346 deletions

View File

@@ -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 => {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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(())
}

View File

@@ -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>>,

View File

@@ -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,

View File

@@ -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)
})

View File

@@ -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(),
);
}
}